Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Direct-code APIKeys Key #1177

Merged

Conversation

justinsb
Copy link
Collaborator

Building on #1168, taking the APIKeys Key terraform resource and experimenting with working directly with the underlying APIs.

@justinsb
Copy link
Collaborator Author

cc @cheftako ... it's the "APIKeysKey: Create direct controller" commit that I think is interesting. I actually don't love the version of the mapping that is on this branch, I have another one I'm going to swap in, but wanted to give you a sneak peek!

@justinsb justinsb force-pushed the handcode_apikeys_apikey branch from b64442b to e11642a Compare February 1, 2024 18:06
@justinsb justinsb force-pushed the handcode_apikeys_apikey branch from e11642a to ae7f29c Compare February 7, 2024 17:10
@@ -0,0 +1,59 @@
package httpmux
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Header? copyright et al

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch ... hopefully our tests will also yell at me about that!

forwardResponseOptions := mux.GetForwardResponseOptions()

// GET /{prefix}/operations/{name}
if err := mux.HandlePath("GET", "/"+prefix+"/operations/{name}", func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for string concatenation over sprintf?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My personal style is to use concatenation when it is just concatenation of a reasonable number of strings (go doesn't have string auto-conversion, so it has to be strings). When there's a int -> string involved, then I find %d much more readable than strconv.Itoa etc


func (r *protoResolver) FindMessageByURL(url string) (protoreflect.MessageType, error) {
if strings.HasPrefix(url, "type.googleapis.com/google.") {
s := "type.googleapis.com/mockgcp." + strings.TrimPrefix(url, "type.googleapis.com/google.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the intent for swapping google. for mockgcp. (when we later swap it back) to ensure that we don't accidentally send to a live system during the test? I think it would be good to add a comment about how/why we temporarily make this change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a comment. This is a workaround for proto oddities, where you can't load two proto messages with the same proto name but different go packages. (Now I think about it, that's probably precisely to support Any, which is what we're dealing with here!)


obj := &pb.TagKey{}
if err := s.storage.Get(ctx, fqn, obj); err != nil {
if apierrors.IsNotFound(err) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With #1221 I don't think we need this special handling here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call (and in fact it messes things up now, because we no longer return a 404!)

@@ -0,0 +1,296 @@
// Copyright 2022 Google LLC
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2022?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will update!

}, nil
}

func ValueOf[T any](p *T) T {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be moved to a library file rather then being kept in a specific controller?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory yes... depending on how we feel about the . import of the mappings we could put it there...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to a utils.go file, I also think we can group services by package. So it might be repeated 100x, but not 1000x.

return false, nil
}

req := &pb.GetKeyRequest{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make more sense to add things like keyID != "" check into the fullyQualifiedName() method and add an error return?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My 2c: fullyQualifiedName isn't that complicated, and if we had to check errors we'd be tempted not to use it.

func (a *adapter) Delete(ctx context.Context) error {
// TODO: Delete via status selfLink?
req := &pb.DeleteKeyRequest{
Name: a.fullyQualifiedName(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per above I realize we would have to populate this outside but it would centralize/catch errors to do with the name,

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a call sequence that is part of the (evolving) contract, I'll document the contract as-is.

}
op, err := a.gcp.DeleteKey(ctx, req)
if err != nil {
return fmt.Errorf("deleting key: %w", err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we return an error if the object matching the key has already been deleted?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I don't think we should return an error, but I do now return a bool to tell the difference between "I really deleted this" and "The object was already gone"

return err
}

op, err := a.gcp.CreateKey(ctx, req)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we return an error if the key already exists?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean like a special error? Maybe ... we don't have a test for this with the mocks (though we should!). We might have test coverage for this in the "older" tests, my preference would be to beef up our new tests as there are too many silent exceptions in the "old" tests.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this should actually be unlikely now, only happening if someone else is racing with us:

By contract we called Find immediately previously and did not find the object. If someone else concurrently created the object, I don't think we can do any better than to return an error from the reconciler, retry (with backoff), and then hopefully Find will find the object next time.


updateMask := &fieldmaskpb.FieldMask{}

// TODO: I think we can do this with a helper
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do have a helper now in another PR (somewhere!)

// }

if len(updateMask.Paths) == 0 {
// TODO: Log/warn/error?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd warn for now and we can adjust as we get more experience.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!


_, err := a.gcp.UpdateKey(ctx, req)
if err != nil {
return nil, err
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems racy as there should have been a get before this but what if now the resource does not exist?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we treat it like any other not-entirely-expected error, we return the error from the controller and allow it to re-reconcile.

}

// HasHTTPCode returns true if the given error is an HTTP response with the given code.
func HasHTTPCode(err error, code int) bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like another library function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - moved to a per-package utils.go file for now

@justinsb justinsb force-pushed the handcode_apikeys_apikey branch 3 times, most recently from bbecd6e to 9e5dc43 Compare February 14, 2024 01:19
@justinsb justinsb changed the title WIP: Handcode APIKeys Key Handcode APIKeys Key Feb 14, 2024
@justinsb
Copy link
Collaborator Author

As this is now behind a featureflag, removing WIP :-)

@justinsb justinsb force-pushed the handcode_apikeys_apikey branch from 9e5dc43 to 1cb7002 Compare February 14, 2024 23:25
Copy link
Collaborator

@acpana acpana left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this 🥇 ;

left some non blocking comments

Comment on lines +22 to +29
// UserProjectOverride provides the option to use the resource project for preconditions, quota, and billing,
// instead of the project the credentials belong to; false by default
UserProjectOverride bool

// BillingProject is the project used by the TF provider and DCL client to determine preconditions,
// quota, and billing if UserProjectOverride is set to true. If this field is empty,
// but UserProjectOverride is set to true, resource project will be used.
BillingProject string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is not net new but could we get away with just specifying BillingProject to mean the project override? It doesn't seem like there's a mode where UserProjectOverride == false && BillingProject != "" (unless I am misunderstanding)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would make sense but let's not conflate it into this PR. Do you want to open a PR / issue so we don't lose the idea?

return nil, fmt.Errorf("unable to determine resourceID")
}

location := "global"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this need to be a configurable option down the road as part of the adapter?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this resource, it appears - apikeys are always global, though the location is in the URL. It's an odd one (which is likely why it wasn't implemented previously)

I'll add a comment!

pkg/controller/direct/apikeys/apikeyskey_controller.go Outdated Show resolved Hide resolved
Comment on lines +71 to +72
// var logger = log.Log.WithName(controllerName)

r := DirectReconciler{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we throw a named logger in with the DirectReconciler that can be used downstream?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we need a logger, we should use log := klog.FromContext(ctx) or similar. The "named logger" idea predates context, and the percolation of context through the logging ecosystem. I think controller-runtime already populates the context logger with the Kind & NamespacedName

Comment on lines +300 to +304
resource, err := toK8sResource(policy)
if err != nil {
return false, fmt.Errorf("error converting to k8s resource while handling unresolvable dependencies event: %w", err)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this may be an existing pattern but all these handleX funcs do the same work here of calling toK8sResource; it may be worth pulling it one level higher ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is because directbase_controller is currently a fairly direct mapping of the terraform controller. In about 3 PRs we will switch it to stop doing this k8s.Resource dance, and just work with unstructured.Unstructured. I'll add a comment & TODO!

Comment on lines 33 to 50
// TODO: Do we want to normalize?
// return FieldID(strings.ToLower(id))
return FieldID(id)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 let's normalize

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the comment. It's more a question of whether we want to normalize this here :-)

So far, we haven't need to normalize, we use the json field as the identifier for the field, and our CRD and the API JSON matches exactly. We might get into more trouble with e.g. JSON fields that end in "ID"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall thoughts for the mappings package

  • sanity check me but it makes sense to roll our own translation logic because there's not something readily available off the shelf right? at least I couldn't find a go library for this,
  • if not now, when the shape settles down, could we add some unit tests for all the parsing?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on unit test coverage.

I think detailed comments will also be helpful on exported variables and types.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sanity check me but it makes sense to roll our own translation logic because there's not something readily available off the shelf right? at least I couldn't find a go library for this,

Right, I couldn't find one. Also our use case is greatly simplified because we generated the CRD structure and field names to be 1:1 with the API (albeit indirectly). So we don't need a full library, and actually the validation suggestions are more important to us.

if not now, when the shape settles down, could we add some unit tests for all the parsing?

Yes! There is some unit-test level coverage in this PR (apikeyskey_test.go). We'll also get pretty strict coverage in our integration tests once we turn on http validation. I also propose we add tests whenever we find a bug, though personally I prefer higher-level integration tests because they tend to bitrot less (though it can be harder to construct an integration test!)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think detailed comments will also be helpful on exported variables and types.

Done!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a cheap skeleton smoke test we can put in place for this reconciler?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think our best bet is to plug it into our integration tests. When running against mockgcp they are cheap. When running against mockkubeapiserver they are very cheap.

Copy link
Collaborator

@maqiuyujoyce maqiuyujoyce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the implementation of the SDK-based resource, @justinsb ! I have some design related questions:

  1. Is apikeyskey_controller.go fully hand-coded? Looks like it's a bit different from the design doc where automation/boilerplate template are mentioned to simplify the process. (Or did I misunderstand when automation will be used?) How much automation do you think we can introduce into the implementation?
  2. Will contributors use the test failures to drive the implementation of the customized controller? Or do we expect contributors to fully understand both the KCC customized controller framework + the GCP resource API before they can write the customized controllers?
  3. There doesn't seem to be a new CRD generated, is it because there is already a APIKeysKey CRD? In the apikeyskey_controller.go, it looks like not all the fields in the existing CRD are there, then do we plan to still use the same CRD to talk to either controllers (the customized one and the TF-based one)? For new CRDs, I guess we still need to add a different CRD generation logic for them, right?
  4. Have you tried to run an integration test (CRUD+no change) locally for this resource? I assume we will reuse existing test cases for this resource once we do continuous testing?

In addition, it looks like there are still some missing pieces (in the adaptor functions, field mappings, etc) to make it work E2E, so I guess it is still a WIP feature? As it doesn't really touch much existing logic, have it merged should not be a big issue, though could you clean up the commented out code before you merge the code?

// Fix our mockgcp hack
// Fix our mockgcp hack:
// The protobuf libraries get upset if we have two proto message types
// with the same proto path, but different go paths.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for self-education purpose:

  1. Do we own the two proto message types? Or just one of them?
  2. How is the proto path computed?
  3. Is it because we have a copy of the same proto message type in the protobuf lib to implement the mock and caused the potential conflict?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it because we have a copy of the same proto message type in the protobuf lib to implement the mock and caused the potential conflict?

Right.

How is the proto path computed?

It's defined in the proto file.

Do we own the two proto message types? Or just one of them?

One is in the built-in proto library. One is the one we used to generate grpc-gateway. Now I'm wondering if we can use the existing types with grpc-gateway. But I'll look at that in a separate PR if that's OK, this isn't something we're introducing in this PR.

UserAgent: gcp.KCCUserAgent,
}
dclOptions := clientconfig.Options{}
dclOptions.UserProjectOverride = config.UserProjectOverride
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for self-education purpose: Why do you move the assignment of subfields in the struct outside of the initialization of the struct? It looks like that you've done the same for some of struct initializations but not all.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it's now a nested field, so the struct initialization approach is now less elegant.

// See the License for the specific language governing permissions and
// limitations under the License.

package apikeys
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be a generic util file for all the controllers (in the future), or do we plan to intentionally limit the scope of the code to be within the customized controller package only?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBD. We're probably going to refactor so we group reconcilers by service, so we would have 100 copies instead of 1000 copies. But that's still a lot.

The thing we're trying to avoid is a utils package until we understand what we actually need in it, otherwise the utils package inevitably becomes incoherent over time.


func (a *adapter) buildCreateRequest(ctx context.Context) (*pb.CreateKeyRequest, error) {
// You can configure only the `display_name`, `restrictions`, and
// `annotations` fields.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like annotations field is ignored in KeyMapping - does it not impact the ability to build create requests?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what terraform gives us. Our next step is to go to infinity and beyond... or at least add some missing fields.

config controller.Config
}

var keyMapping = NewMapping(&pb.Key{}, &krm.APIKeysKey{},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we'll need to go through all the top-level fields and their subfields to configure the key mappings, right? Do we plan to automate the key mappings generation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Validation already helps. Duet is surprisingly helpful out of the box, sort of magically. But this is one area that is ripe for experimentation!

"sigs.k8s.io/controller-runtime/pkg/source"
)

// Add creates a new IAM Policy Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the comment needs to be updated as it is not a IAM Policy controller?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fixed. I copied the IAM Policy Controller as the starting point!

return false, r.handleUpdateFailed(ctx, u, fmt.Errorf("error creating: %w", err))
}
} else {
if _, err = adapter.Update(ctx); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we don't do a diff for the entire spec but only check the fields Update() function cares about?
Not sure if it is applicable to this resource, but overall the immutability checks will be necessary for invalid changes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added TODO

return false, r.handleUpdateFailed(ctx, u, fmt.Errorf("error updating: %w", err))
}
}
if isAPIServerUpdateRequired(u) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kinda doubt this is still necessary as we always want to handle UpToDate (and you changed it to return true anyways).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a TODO, I think we need it back! But we don't have failing tests yet.

type structTypeMapping struct {
scope *Mapping

inType *reflectType
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some comments for each field? It's a bit hard to understand how they should be used properly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point - done!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on unit test coverage.

I think detailed comments will also be helpful on exported variables and types.

@justinsb
Copy link
Collaborator Author

Is apikeyskey_controller.go fully hand-coded? Looks like it's a bit different from the design doc where automation/boilerplate template are mentioned to simplify the process. (Or did I misunderstand when automation will be used?) How much automation do you think we can introduce into the implementation?

Yes, it's hand coded, though Duet and validation are already steering the show. We can likely use @acpana 's linter work to make inline suggestions from validation. I'm pretty sure we can introduce a lot of automation, I'm not sure it's a bottleneck even today.

Will contributors use the test failures to drive the implementation of the customized controller? Or do we expect contributors to fully understand both the KCC customized controller framework + the GCP resource API before they can write the customized controllers?

Yes, we should be very test driven. And yes, contributors should understand the GCP API and at least understand controllers. Once we have state-into-spec absent, I think our controllers are pretty normal controllers.

There doesn't seem to be a new CRD generated, is it because there is already a APIKeysKey CRD? In the apikeyskey_controller.go, it looks like not all the fields in the existing CRD are there, then do we plan to still use the same CRD to talk to either controllers (the customized one and the TF-based one)? For new CRDs, I guess we still need to add a different CRD generation logic for them, right?

Right, I started with one where the CRD existed already. Future steps are (1) to add a field that isn't in terraform and (2)

Have you tried to run an integration test (CRUD+no change) locally for this resource? I assume we will reuse existing test cases for this resource once we do continuous testing?

Yes, and right. We were pretty close on the HTTP level. The biggest challenge is that terraform is very chatty (particularly extra reads of the GCP resource!), I wasn't sure if we should add some spurious API calls just to keep the golden http output closer. @gemmahou is driving our http request/response testing.

@justinsb justinsb force-pushed the handcode_apikeys_apikey branch from ed86e42 to f589efd Compare February 19, 2024 00:12
@justinsb
Copy link
Collaborator Author

To see the changes across the rebase: git range-diff f589efd^^^^...f589efd 1cb7002^^^^...1cb7002 ; I've attached them
diff.txt

if err := pb.RegisterApiKeysHandler(ctx, mux, conn); err != nil {
mux, err := httpmux.NewServeMux(ctx, conn,
pb.RegisterApiKeysHandler,
s.operations.RegisterOperationsHandler("v2"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is v2 because https://cloud.google.com/api-keys/docs/reference/rest#rest-resource:-v2.keys is v2. If we plan on this being a template it might be good to document that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's the path-prefix that the API implements in front of operation.name. I'll a comment ...

It will actually become a true template in a few PRs :-)

op, err := a.gcp.DeleteKey(ctx, req)
if err != nil {
if IsNotFound(err) {
return false, nil
Copy link
Collaborator

@cheftako cheftako Feb 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is false actually the correct return value? If the goal of delete is for resource to not exist, then it being not found seems to indicate a success. Also if we return false on non existence then it would seems likely to trigger an endless series of retries...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comment-pointers to the interface, where it is documented as returning (true, nil) when we really deleted it, and (false, nil) when we found the object was not found.

We don't actually use that first value today, but I figured we might post different events and that I didn't want to have to go through and manually update each IsNotFound block etc. That said, we don't need it today...

)

func TestBuildCreate(t *testing.T) {
ctx := context.TODO()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless we believe that TestBuildCreate will add a context parameter this should probably be just context.Background()

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced with ctx := test.WithContext(context.Background(), t)

@justinsb justinsb force-pushed the handcode_apikeys_apikey branch from 9c84ab5 to ff50835 Compare February 22, 2024 02:04
@@ -0,0 +1,394 @@
// Copyright 2022 Google LLC
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know what the linter is complaining about here? Is is the year in the copyright? If so why not in any of the other new files? Does it dislike the redundancy of something prefixed with directbase in a directbase directory? I think this is then transitively causing other lint failures.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rebased and fixed up the lint issues. I think the core issue was that one of the intermediate changes renamed the name of one of the error values (NotFoundError -> ErrNotFound)

func (d DeferredJSON) String() string {
b, err := json.Marshal(d.O)
if err != nil {
return fmt.Sprintf("<error: %v>", err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little worried that by deferring marshalling we have essentially hidden this error. Wondering if we should at least log this case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this should be called as klog.Infof("foo: %v", debug.JSON(obj)). It will print the object as json. If json conversion fails, the error message is printed instead of the json object. So we do log it :-)

ctx, cancel := context.WithTimeout(context.TODO(), timeoutPeriod)
defer cancel()
logger.Info("starting wait with timeout on resource's reference", "timeout", timeoutPeriod)
if err := watcher.WaitForResourceToBeReady(ctx, refNN, refGVK); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing this under a weighted semaphore seems like it may cause (?exacerbate) ordering problems? Holding a semaphore, even a weighted semaphore and then blocking indefinitely just seems to be asking for problems. Are we sure this isn't a premature optimization? I've seen KAS and KCM ticking overly fairly happily with hundreds of thousands of goroutines. I'd like to be convinced we actually need to solve this issue. If we do, then I wonder if this is the best way to do it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So good news / bad news ... directbase_controller started with a copy of one of our existing go controllers (iampolicy_controller.go, IIRC). I don't think I ever looked at this code, it wasn't in the critical path for the direct controllers yet :-) I agree with you that we can do better, we actually have the logic to do so in our current experimental/composite directory (TrackingDynamicClient).

Do you mind filing a github issue so we don't lose this problem, but I don't think we want to solve it in this PR.

// Like traditional assertions, assertions are runtime checks that
// will panic the program, but we only run those checks if assertion
// checks are enabled.
var AssertEnabled bool = true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not convinced that bool is the correct type here. I'm betting we end up wanting an enumeration for things like testing only assertion and production assertions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, bool is just all I needed right now!

@justinsb justinsb force-pushed the handcode_apikeys_apikey branch from 7421db7 to 674913a Compare February 27, 2024 03:28
We want some operations support
This enables us to merge the code without activating it yet, and test
it against the old code.

Current syntax is:
`export KCC_USE_DIRECT_RECONCILERS=APIKeysKey,<kind2>`

However I think we should consider this syntax provisional and subject
to change.
This should help implementors choose the right prefix value.
@justinsb justinsb force-pushed the handcode_apikeys_apikey branch from 674913a to 76adc4d Compare February 28, 2024 13:49
@justinsb justinsb changed the title Handcode APIKeys Key Direct-code APIKeys Key Feb 28, 2024
}
return schemaUpdater, nil
default:
klog.Warningf("requested direct reconciler for %v, but it is not supported", gvk.GroupKind())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This at a minimum needs to be an Error. Worried that we could fall through to an alternate reconciler. Worried about that reconciler then doing something we don't want with the customers objects.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, good point, I'll take a look!

case reflect.Int64:
return "int64"
case reflect.Map:
return "map[todo]todo"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming this won't actually work. If true would it be better to fail here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're outputing a proposed line to copy and paste into their go code. We could output "map[todo]todo /* TODO: replace with real types" :-) The problem with failing is that we don't give the caller anything to start from!

@cheftako
Copy link
Collaborator

/lgtm
/approve

@google-oss-prow google-oss-prow bot added the lgtm label Feb 29, 2024
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: cheftako

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@google-oss-prow google-oss-prow bot merged commit ef47c38 into GoogleCloudPlatform:master Feb 29, 2024
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants