Skip to content

Conversation

@jaypipes
Copy link
Collaborator

@jaypipes jaypipes commented Mar 10, 2021

adds code generation for field comparisons

Adds new code generation functions to pkg/generate/code that output Go
code that compares fields of various types and calls delta.Add() if a
difference is detected.

For fields with underlying scalar types, the generated code looks like
this:

if *a.ko.Spec.Name != *b.ko.Spec.Name) {
        delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name)
}

For non-scalar types, generated code recurses over the fields and/or
calls specialized comparison functions in the runtime library's
pkg/compare module. Here is an example of a complex underlying field
type and what the generated code might look like:

if ackcompare.HasNilDifference(a.ko.Spec.CreateBucketConfiguration, b.ko.Spec.CreateBucketConfiguration) {
        delta.Add("Spec.CreateBucketConfiguration", a.ko.Spec.CreateBucketConfiguration, b.ko.Spec.CreateBucketConfiguration)
} else if a.ko.Spec.CreateBucketConfiguration != nil && b.ko.Spec.CreateBucketConfiguration != nil {
        if ackcompare.HasNilDifference(a.ko.Spec.CreateBucketConfiguration.LocationConstraint, b.ko.Spec.CreateBucketConfiguration.LocationConstraint) {
                delta.Add("Spec.CreateBucketConfiguration.LocationConstraint", a.ko.Spec.CreateBucketConfiguration.LocationConstraint, b.ko.Spec.CreateBucketConfiguration.LocationConstraint)
        } else if a.ko.Spec.CreateBucketConfiguration.LocationConstraint != nil && b.ko.Spec.CreateBucketConfiguration.LocationConstraint {
                if *a.ko.Spec.CreateBucketConfiguration.LocationConstraint != *b.ko.Spec.CreateBucketConfiguration.LocationConstraint {
                        delta.Add("Spec.CreateBucketConfiguration.LocationConstraint", a.ko.Spec.CreateBucketConfiguration.LocationConstraint, b.ko.Spec.CreateBucketConfiguration.LocationConstraint)
                }
        }
}

Adds plumbing to the pkg/resource/depscriptor.go.tpl template that
implements the Delta() method on the
github.com/aws-controllers-k8s/runtime/pkg/types.AWSResourceDescriptor
interface-implementing resource struct.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Copy link
Contributor

@vijtrip2 vijtrip2 left a comment

Choose a reason for hiding this comment

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

LGTM!

Copy link
Member

@a-hilaly a-hilaly left a comment

Choose a reason for hiding this comment

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

C'est genial @jaypipes! ça se rapproche!
I left few comments and questions

Comment on lines -65 to -73
opts := []cmp.Option{cmpopts.EquateEmpty()}
{{- if .CRD.CompareIgnoredFields }}
opts = append(opts, cmpopts.IgnoreFields(*ac.ko,
{{- range $fieldPath := .CRD.CompareIgnoredFields }}
{{ printf "%q" $fieldPath }},
{{- end }}
))
{{- end }}
return cmp.Equal(ac.ko, bc.ko, opts...)
Copy link
Member

Choose a reason for hiding this comment

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

the modules github.com/google/go-cmp/cmp and github.com/google/go-cmp/cmp/cmpopts are no longer used we can remove them from the template (line 8,9)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

👍 great catch

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed.

package {{ .CRD.Names.Snake }}

import (
ackcompare "github.com/aws/aws-controllers-k8s/pkg/compare"
Copy link
Member

Choose a reason for hiding this comment

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

s/github.com/aws/aws-controllers-k8s/ github.com/aws-controllers-k8s/runtime/

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yikes, great catch.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed.

// Delta returns an `ackcompare.Delta` object containing the difference between
// one `AWSResource` and another.
func (d *resourceDescriptor) Delta(a, b acktypes.AWSResource) *ackcompare.Delta {
return newResourceDelta(a, b)
Copy link
Member

Choose a reason for hiding this comment

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

The generated code doesn't compile since a type is an interface. I'm not very sure, but i think we can use newResourceDelta(a.(*resource), b.(*resource)) (thinking about type assertion panics..)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed.

Comment on lines -6 to 11
latest *resource,
diffReporter *ackcompare.Reporter,
delta *ackcompare.Delta,
) (*resource, error) {
{{ $customMethod := .CRD.GetCustomImplementation .CRD.Ops.Update }}
{{ if $customMethod }}
customResp, customRespErr := rm.{{ $customMethod }}(ctx, desired, latest, diffReporter)
customResp, customRespErr := rm.{{ $customMethod }}(ctx, desired, latest, delta)
if customResp != nil || customRespErr != nil {
Copy link
Member

Choose a reason for hiding this comment

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

We should do the same for the other update methods in manager.go.tpl, sdk_update_set_attributes.go.tpl, sdk_update_not_implemented.go.tpl and sdk_update_custom.go.tpl

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed.

delta.Add("", a, b)
return delta
}
{{ GoCodeCompare .CRD "delta" "a.ko" "b.ko" 1}}
Copy link
Member

Choose a reason for hiding this comment

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

We need a "GoCodeCompare" implementation in pkg/generate/ack/controller.go e.g:

"GoCodeCompare": func(r *ackmodel.CRD, deltaVarName, firstResVarName string, secondResVarName string, indentLevel int) string {
	return code.CompareResource(r.Config(), r, deltaVarName, firstResVarName, secondResVarName, indentLevel)
},

Copy link
Member

Choose a reason for hiding this comment

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

also in pkg/generate/ack/controller.go "delta.go.tpl" should be added the targets list

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed.

if ackcompare.HasNilDifference(a.ko.Spec.ACL, b.ko.Spec.ACL) {
delta.Add("Spec.ACL", a.ko.Spec.ACL, b.ko.Spec.ACL)
} else {
if *a.ko.Spec.ACL != *b.ko.Spec.ACL {
Copy link
Member

Choose a reason for hiding this comment

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

While testing the s3 controller (generated with the new compare methods) i observed few runtime panics, and the reason is that we don't handle cases where both of a.ko.Spec.* and a.ko.Spec.* are nil pointers. Calling "valueof" of a nil pointer causes panic.

Copy link
Member

@a-hilaly a-hilaly Mar 10, 2021

Choose a reason for hiding this comment

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

quick fix (Probably there is a better way..): a-hilaly@f787376 :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Another excellent catch, @a-hilaly! I went with a route that generated:

} else if a.ko.Spec.Name != nil && b.ko.Spec.Name != nil {

instead of an additional indent-level if statement.

Copy link
Member

Choose a reason for hiding this comment

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

Great! 👍

IsIgnored bool `json:"is_ignored"`
// NilEqualsEmptyString indicates a nil string pointer and empty string
// should be considered equal for the purposes of comparison
NilEqualsEmptyString bool `json:"nil_equals_empty_string"`
Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering if we should rename this field to NilEqualsZeroValue? i'm thinking about cases where we want to equate 0 and nil, 0.0 and nil, false and nil ... etc

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

awesome point.

@jaypipes jaypipes changed the title adds code generation for field comparisons DO NOT MERGE: adds code generation for field comparisons Mar 11, 2021
Adds new code generation functions to `pkg/generate/code` that output Go
code that compares fields of various types and calls `delta.Add()` if a
difference is detected.

For fields with underlying scalar types, the generated code looks like
this:

```go
if *a.ko.Spec.Name != *b.ko.Spec.Name) {
        delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name)
}
```

For non-scalar types, generated code recurses over the fields and/or
calls specialized comparison functions in the runtime library's
`pkg/compare` module. Here is an example of a complex underlying field
type and what the generated code might look like:

```go
if ackcompare.HasNilDifference(a.ko.Spec.CreateBucketConfiguration, b.ko.Spec.CreateBucketConfiguration) {
        delta.Add("Spec.CreateBucketConfiguration", a.ko.Spec.CreateBucketConfiguration, b.ko.Spec.CreateBucketConfiguration)
} else {
        if ackcompare.HasNilDifference(a.ko.Spec.CreateBucketConfiguration.LocationConstraint, b.ko.Spec.CreateBucketConfiguration.LocationConstraint) {
                delta.Add("Spec.CreateBucketConfiguration.LocationConstraint", a.ko.Spec.CreateBucketConfiguration.LocationConstraint, b.ko.Spec.CreateBucketConfiguration.LocationConstraint)
        } else {
                if *a.ko.Spec.CreateBucketConfiguration.LocationConstraint != *b.ko.Spec.CreateBucketConfiguration.LocationConstraint {
                        delta.Add("Spec.CreateBucketConfiguration.LocationConstraint", a.ko.Spec.CreateBucketConfiguration.LocationConstraint, b.ko.Spec.CreateBucketConfiguration.LocationConstraint)
                }
        }
}
```
@jaypipes jaypipes changed the title DO NOT MERGE: adds code generation for field comparisons adds code generation for field comparisons Mar 11, 2021
Adds plumbing to the `pkg/resource/depscriptor.go.tpl` template that
implements the `Delta()` method on the
`github.com/aws-controllers-k8s/runtime/pkg/types.AWSResourceDescriptor`
interface-implementing `resource` struct.

Also adds a test for the compare field ignore functionality.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants