diff --git a/pkg/generate/ack/controller.go b/pkg/generate/ack/controller.go index 53d4f4c7..d31759ed 100644 --- a/pkg/generate/ack/controller.go +++ b/pkg/generate/ack/controller.go @@ -161,6 +161,9 @@ var ( "CheckNilFieldPath": func(f *ackmodel.Field, sourceVarName string) string { return code.CheckNilFieldPath(f, sourceVarName) }, + "CheckNilReferencesPath": func(f *ackmodel.Field, sourceVarName string) string { + return code.CheckNilReferencesPath(f, sourceVarName) + }, } ) diff --git a/pkg/generate/code/check.go b/pkg/generate/code/check.go index 48694f04..28a8b33e 100644 --- a/pkg/generate/code/check.go +++ b/pkg/generate/code/check.go @@ -216,3 +216,29 @@ func CheckNilFieldPath(field *model.Field, sourceVarName string) string { } return strings.TrimPrefix(out, " || ") } + +// CheckNilReferencesPath returns the condition statement for Nil check +// on the path in ReferencesConfig. This nil check on the reference path is +// useful to avoid nil pointer panics when accessing the referenced value. +// +// This function only outputs the logical condition and not the "if" block +// so that the output can be reused in many templates, where +// logic inside "if" block can be different. +// +// Example Output for ReferencesConfig path "Status.ACKResourceMetadata.ARN", +// and sourceVarName "obj" is +// "obj.Status.ACKResourceMetadata == nil || obj.Status.ACKResourceMetadata.ARN == nil" +func CheckNilReferencesPath(field *model.Field, sourceVarName string) string { + out := "" + if field.HasReference() { + refPath := fieldpath.FromString(field.FieldConfig.References.Path) + // Remove the front from reference path because "Spec" or "Status" being + // an struct cannot be added in nil check + fieldNamePrefix := "." + refPath.PopFront() + for refPath.Size() > 0 { + fieldNamePrefix = fmt.Sprintf("%s.%s", fieldNamePrefix, refPath.PopFront()) + out += fmt.Sprintf(" || %s%s == nil", sourceVarName, fieldNamePrefix) + } + } + return strings.TrimPrefix(out, " || ") +} diff --git a/pkg/generate/code/check_test.go b/pkg/generate/code/check_test.go index c42346a6..b959d180 100644 --- a/pkg/generate/code/check_test.go +++ b/pkg/generate/code/check_test.go @@ -17,6 +17,8 @@ import ( "strings" "testing" + "github.com/aws-controllers-k8s/code-generator/pkg/config" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -155,3 +157,25 @@ func TestCheckNilFieldPath(t *testing.T) { "ko.Spec.JWTConfiguration == nil || ko.Spec.JWTConfiguration.Issuer == nil", code.CheckNilFieldPath(&field, "ko.Spec")) } + +func TestCheckNilReferencesPath(t *testing.T) { + field := model.Field{} + // Empty ReferencesPath + referenceFieldConfig := config.ReferencesConfig{Path: ""} + fieldConfig := config.FieldConfig{References: &referenceFieldConfig} + field.FieldConfig = &fieldConfig + assert.Equal(t, "", code.CheckNilReferencesPath(&field, "obj")) + // Non nested ReferencesPath + referenceFieldConfig.Path = "Status" + assert.Equal(t, "", code.CheckNilReferencesPath(&field, "obj")) + // Nested ReferencesPath + referenceFieldConfig.Path = "Status.ACKResourceMetadata" + assert.Equal(t, + "obj.Status.ACKResourceMetadata == nil", + code.CheckNilReferencesPath(&field, "obj")) + // Multi Level Nested ReferencesPath + referenceFieldConfig.Path = "Status.ACKResourceMetadata.ARN" + assert.Equal(t, + "obj.Status.ACKResourceMetadata == nil || obj.Status.ACKResourceMetadata.ARN == nil", + code.CheckNilReferencesPath(&field, "obj")) +} diff --git a/templates/cmd/controller/main.go.tpl b/templates/cmd/controller/main.go.tpl index b7390c2a..47bc7e7a 100644 --- a/templates/cmd/controller/main.go.tpl +++ b/templates/cmd/controller/main.go.tpl @@ -16,9 +16,6 @@ import ( ctrlrt "sigs.k8s.io/controller-runtime" ctrlrtmetrics "sigs.k8s.io/controller-runtime/pkg/metrics" svcsdk "github.com/aws/aws-sdk-go/service/{{ .ServicePackageName }}" - - svcresource "github.com/aws-controllers-k8s/{{ .ServicePackageName }}-controller/pkg/resource" - svctypes "github.com/aws-controllers-k8s/{{ .ServicePackageName }}-controller/apis/{{ .APIVersion }}" {{- /* Import the go types from service controllers whose resources are referenced in this service controller. If these referenced types are not added to scheme, this service controller will not be able to read resources across service controller. */ -}} @@ -29,6 +26,9 @@ resources across service controller. */ -}} {{ $referencedServiceName }}apitypes "github.com/aws-controllers-k8s/{{ $referencedServiceName }}-controller/apis/{{ $apiVersion }}" {{- end }} {{- end }} + + svcresource "github.com/aws-controllers-k8s/{{ .ServicePackageName }}-controller/pkg/resource" + svctypes "github.com/aws-controllers-k8s/{{ .ServicePackageName }}-controller/apis/{{ .APIVersion }}" {{/* TODO(a-hilaly): import apis/* packages to register webhooks */}} {{range $crdName := .SnakeCasedCRDNames }}_ "github.com/aws-controllers-k8s/{{ $servicePackageName }}-controller/pkg/resource/{{ $crdName }}" {{end}} diff --git a/templates/pkg/resource/references_read_referenced_resource.go.tpl b/templates/pkg/resource/references_read_referenced_resource.go.tpl index cd3d4ccf..e1cab181 100644 --- a/templates/pkg/resource/references_read_referenced_resource.go.tpl +++ b/templates/pkg/resource/references_read_referenced_resource.go.tpl @@ -42,10 +42,13 @@ Where field is of type 'Field' from aws-controllers-k8s/code-generator/pkg/model "{{ .FieldConfig.References.Resource }}", namespace, *arr.Name) } - if obj.{{ .FieldConfig.References.Path }} == nil { + {{ $nilCheck := CheckNilReferencesPath . "obj" -}} + {{ if not (eq $nilCheck "") -}} + if {{ $nilCheck }} { return ackerr.ResourceReferenceMissingTargetFieldFor( "{{ .FieldConfig.References.Resource }}", namespace, *arr.Name, "{{ .FieldConfig.References.Path }}") } + {{- end -}} {{- end -}} \ No newline at end of file