diff --git a/pkg/config/field.go b/pkg/config/field.go index 48e3b259..48886f7a 100644 --- a/pkg/config/field.go +++ b/pkg/config/field.go @@ -384,6 +384,13 @@ type ReferencesConfig struct { Path string `json:"path"` } +// CELRule represents a single CEL (Common Expression Language) validation rule +// to be emitted as a +kubebuilder:validation:XValidation marker. +type CELRule struct { + Rule string `json:"rule"` + Message *string `json:"message,omitempty"` +} + // FieldConfig contains instructions to the code generator about how // to interpret the value of an Attribute and how to map it to a CRD's Spec or // Status field @@ -492,6 +499,9 @@ type FieldConfig struct { // // (See https://github.com/aws-controllers-k8s/pkg/blob/main/names/names.go) GoTag *string `json:"go_tag,omitempty"` + // CustomCELRules contains CEL validation rules emitted as + // +kubebuilder:validation:XValidation markers on this field. + CustomCELRules []CELRule `json:"custom_cel_rules,omitempty"` } // GetFieldConfigs returns all FieldConfigs for a given resource as a map. diff --git a/pkg/config/field_test.go b/pkg/config/field_test.go new file mode 100644 index 00000000..25830a05 --- /dev/null +++ b/pkg/config/field_test.go @@ -0,0 +1,64 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sigs.k8s.io/yaml" +) + +func TestCELRule_Parsing(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + yamlStr := ` +resources: + MyResource: + custom_cel_rules: + - rule: "has(self.foo)" + message: "foo is required" + - rule: "self.bar > 0" + fields: + MyField: + custom_cel_rules: + - rule: "self.matches('^[a-z]+')" + message: "must be lowercase" +` + cfg, err := New("", Config{}) + require.Nil(err) + err = yaml.UnmarshalStrict([]byte(yamlStr), &cfg) + require.Nil(err) + + resConfig, ok := cfg.Resources["MyResource"] + require.True(ok) + + // Resource-level rules + require.Len(resConfig.CustomCELRules, 2) + assert.Equal("has(self.foo)", resConfig.CustomCELRules[0].Rule) + require.NotNil(resConfig.CustomCELRules[0].Message) + assert.Equal("foo is required", *resConfig.CustomCELRules[0].Message) + assert.Equal("self.bar > 0", resConfig.CustomCELRules[1].Rule) + assert.Nil(resConfig.CustomCELRules[1].Message) // no message key + + // Field-level rules + fieldConfig, ok := resConfig.Fields["MyField"] + require.True(ok) + require.Len(fieldConfig.CustomCELRules, 1) + assert.Equal("self.matches('^[a-z]+')", fieldConfig.CustomCELRules[0].Rule) + require.NotNil(fieldConfig.CustomCELRules[0].Message) + assert.Equal("must be lowercase", *fieldConfig.CustomCELRules[0].Message) +} diff --git a/pkg/config/resource.go b/pkg/config/resource.go index 4e63fdbf..a3ad0b2d 100644 --- a/pkg/config/resource.go +++ b/pkg/config/resource.go @@ -122,6 +122,10 @@ type ResourceConfig struct { // SDK implementation details that are auto-filled by the SDK middleware // when nil and should not be exposed in the CRD. IgnoreIdempotencyToken bool `json:"ignore_idempotency_token,omitempty"` + // CustomCELRules contains CEL validation rules emitted as + // +kubebuilder:validation:XValidation markers on the Spec struct, + // enabling cross-field validation. + CustomCELRules []CELRule `json:"custom_cel_rules,omitempty"` } // TagConfig instructs the code generator on how to generate functions that diff --git a/pkg/generate/ack/apis.go b/pkg/generate/ack/apis.go index e471a95a..b4dc71cc 100644 --- a/pkg/generate/ack/apis.go +++ b/pkg/generate/ack/apis.go @@ -40,6 +40,12 @@ var ( apisCopyPaths = []string{} apisFuncMap = ttpl.FuncMap{ "Join": strings.Join, + "Deref": func(s *string) string { + if s == nil { + return "" + } + return *s + }, } ) diff --git a/pkg/generate/ack/cel_test.go b/pkg/generate/ack/cel_test.go new file mode 100644 index 00000000..f5d204dc --- /dev/null +++ b/pkg/generate/ack/cel_test.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package ack_test + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ackgenerate "github.com/aws-controllers-k8s/code-generator/pkg/generate/ack" + "github.com/aws-controllers-k8s/code-generator/pkg/testutil" +) + +// TestCELOnTypeDef verifies that custom_cel_rules configured on a +// nested field are rendered as +kubebuilder:validation:XValidation markers in +// the generated types.go (via the type_def template include). +func TestCELOnTypeDef(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewModelForServiceWithOptions(t, "apigatewayv2", &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-with-cel-rules.yaml", + }) + + ts, err := ackgenerate.APIs(g, []string{testutil.TemplatesBasePath(t)}) + require.NoError(err) + require.NoError(ts.Execute()) + + typesGo, ok := ts.Executed()["types.go"] + require.True(ok, "types.go not found in executed templates") + + output := typesGo.String() + assert.True( + strings.Contains(output, `// +kubebuilder:validation:XValidation:rule="self.startsWith('https://')",message="Issuer must be an HTTPS URL"`), + "expected XValidation marker for Issuer CEL rule in types.go", + ) +} + +// TestCELOnSpec verifies that custom_cel_rules configured at the +// resource level are rendered as +kubebuilder:validation:XValidation markers +// on the generated CRD Spec struct (via the crd.go template). +func TestCELOnSpec(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewModelForService(t, "route53") + + ts, err := ackgenerate.APIs(g, []string{testutil.TemplatesBasePath(t)}) + require.NoError(err) + require.NoError(ts.Execute()) + + hostedZoneGo, ok := ts.Executed()["hosted_zone.go"] + require.True(ok, "hosted_zone.go not found in executed templates") + + output := hostedZoneGo.String() + assert.True( + strings.Contains(output, `// +kubebuilder:validation:XValidation:rule="!has(self.hostedZoneConfig) || !self.hostedZoneConfig.privateZone || has(self.vpc)",message="spec.vpc is required for private hosted zones"`), + "expected XValidation marker for first HostedZone CEL rule", + ) + assert.True( + strings.Contains(output, `// +kubebuilder:validation:XValidation:rule="size(self.name) > 0"`), + "expected XValidation marker for second HostedZone CEL rule (no message)", + ) +} + +// TestCELOnField verifies that custom_cel_rules configured on a +// top-level spec field are rendered as +kubebuilder:validation:XValidation +// markers on the individual field in the generated CRD Spec struct. +func TestCELOnField(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewModelForService(t, "route53") + + ts, err := ackgenerate.APIs(g, []string{testutil.TemplatesBasePath(t)}) + require.NoError(err) + require.NoError(ts.Execute()) + + recordSetGo, ok := ts.Executed()["record_set.go"] + require.True(ok, "record_set.go not found in executed templates") + + output := recordSetGo.String() + assert.True( + strings.Contains(output, `// +kubebuilder:validation:XValidation:rule="self.endsWith('.')",message="DNS name must end with a dot"`), + "expected XValidation marker for Name field CEL rule in record_set.go", + ) +} diff --git a/pkg/model/attr.go b/pkg/model/attr.go index 701322f7..5ab555c2 100644 --- a/pkg/model/attr.go +++ b/pkg/model/attr.go @@ -17,15 +17,17 @@ import ( "fmt" awssdkmodel "github.com/aws-controllers-k8s/code-generator/pkg/api" + ackgenconfig "github.com/aws-controllers-k8s/code-generator/pkg/config" "github.com/aws-controllers-k8s/pkg/names" ) type Attr struct { - Names names.Names - GoType string - Shape *awssdkmodel.Shape - GoTag string - IsImmutable bool + Names names.Names + GoType string + Shape *awssdkmodel.Shape + GoTag string + IsImmutable bool + CustomCELRules []ackgenconfig.CELRule } func NewAttr( diff --git a/pkg/model/crd.go b/pkg/model/crd.go index f523b933..f07018fa 100644 --- a/pkg/model/crd.go +++ b/pkg/model/crd.go @@ -422,6 +422,16 @@ func (r *CRD) IsARNPrimaryKey() bool { return resGenConfig.IsARNPrimaryKey } +// CustomCELRules returns the custom CEL validation rules configured for this +// resource's Spec struct, or nil if none are configured. +func (r *CRD) CustomCELRules() []ackgenconfig.CELRule { + resGenConfig := r.cfg.GetResourceConfig(r.Names.Original) + if resGenConfig == nil { + return nil + } + return resGenConfig.CustomCELRules +} + // GetPrimaryKeyField returns the field designated as the primary key, nil if // none are specified or an error if multiple are designated. func (r *CRD) GetPrimaryKeyField() (*Field, error) { diff --git a/pkg/model/field.go b/pkg/model/field.go index bd9879f2..c613b12b 100644 --- a/pkg/model/field.go +++ b/pkg/model/field.go @@ -209,6 +209,15 @@ func (f *Field) IsImmutable() bool { return false } +// CustomCELRules returns the custom CEL validation rules configured for +// this field, or nil if none are configured. +func (f *Field) CustomCELRules() []ackgenconfig.CELRule { + if f.FieldConfig != nil { + return f.FieldConfig.CustomCELRules + } + return nil +} + // GetSetterConfig returns the SetFieldConfig object associated with this field // and a supplied operation type, or nil if none exists. func (f *Field) GetSetterConfig(opType OpType) *ackgenconfig.SetFieldConfig { diff --git a/pkg/model/model.go b/pkg/model/model.go index ba90bc0c..b3a77655 100644 --- a/pkg/model/model.go +++ b/pkg/model/model.go @@ -761,6 +761,11 @@ func (m *Model) processNestedFieldTypeDefs( return fmt.Errorf("resource %q, field %q: %w", crd.Names.Original, fieldPath, err) } } + if len(field.CustomCELRules()) > 0 { + if err := setTypeDefAttributeCELRules(crd, fieldPath, field.CustomCELRules(), tdefs); err != nil { + return fmt.Errorf("resource %q, field %q: %w", crd.Names.Original, fieldPath, err) + } + } } } return nil @@ -909,6 +914,19 @@ func setTypeDefAttributeImmutable(crd *CRD, fieldPath string, tdefs []*TypeDef) return nil } +// setTypeDefAttributeCELRules sets the CustomCELRules on the Attr corresponding +// to the nested field at fieldPath. +func setTypeDefAttributeCELRules(crd *CRD, fieldPath string, rules []ackgenconfig.CELRule, tdefs []*TypeDef) error { + _, fieldAttr, err := getAttributeFromPath(crd, fieldPath, tdefs) + if err != nil { + return err + } + if fieldAttr != nil { + fieldAttr.CustomCELRules = rules + } + return nil +} + // updateTypeDefAttributeWithReference adds a new AWSResourceReference attribute // for the corresponding attribute represented by fieldPath of nested field func updateTypeDefAttributeWithReference(crd *CRD, fieldPath string, tdefs []*TypeDef) error { diff --git a/pkg/model/model_apigwv2_test.go b/pkg/model/model_apigwv2_test.go index 58831b11..637b02b2 100644 --- a/pkg/model/model_apigwv2_test.go +++ b/pkg/model/model_apigwv2_test.go @@ -251,6 +251,35 @@ func TestAPIGatewayV2_WithReference(t *testing.T) { assert.Equal(2, len(referencedServiceNames)) } +func TestAPIGatewayV2_CustomCELRules_NestedField(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + g := testutil.NewModelForServiceWithOptions(t, "apigatewayv2", &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-with-cel-rules.yaml", + }) + + tds, err := g.GetTypeDefs() + require.Nil(err) + require.NotNil(tds) + + var tdef *model.TypeDef + for _, td := range tds { + if td != nil && strings.EqualFold(td.Names.Original, "jwtConfiguration") { + tdef = td + break + } + } + require.NotNil(tdef, "JWTConfiguration TypeDef must exist — check SDK shape name casing if nil") + + issuerAttr, ok := tdef.Attrs["Issuer"] + require.True(ok, "Issuer attr must exist in JWTConfiguration TypeDef") + require.Len(issuerAttr.CustomCELRules, 1) + assert.Equal("self.startsWith('https://')", issuerAttr.CustomCELRules[0].Rule) + require.NotNil(issuerAttr.CustomCELRules[0].Message) + assert.Equal("Issuer must be an HTTPS URL", *issuerAttr.CustomCELRules[0].Message) +} + func TestAPIGatewayV2_WithNestedReference(t *testing.T) { _ = assert.New(t) require := require.New(t) diff --git a/pkg/model/model_route53_test.go b/pkg/model/model_route53_test.go index 56133d6b..69f49e8d 100644 --- a/pkg/model/model_route53_test.go +++ b/pkg/model/model_route53_test.go @@ -83,4 +83,40 @@ func TestRoute53_RecordSet(t *testing.T) { "SubmittedAt", } assert.Equal(expStatusFieldCamel, attrCamelNames(statusFields)) + + // A resource without custom_cel_rules configured returns nil + assert.Nil(crd.CustomCELRules()) +} + +func TestRoute53_CELRules(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + m := testutil.NewModelForService(t, "route53") + + crds, err := m.GetCRDs() + require.Nil(err) + + // Test resource-level CEL rules on HostedZone + crd := getCRDByName("HostedZone", crds) + require.NotNil(crd) + + require.Len(crd.CustomCELRules(), 2) + assert.Equal("!has(self.hostedZoneConfig) || !self.hostedZoneConfig.privateZone || has(self.vpc)", crd.CustomCELRules()[0].Rule) + assert.NotNil(crd.CustomCELRules()[0].Message) + assert.Equal("spec.vpc is required for private hosted zones", *crd.CustomCELRules()[0].Message) + assert.Equal("size(self.name) > 0", crd.CustomCELRules()[1].Rule) + assert.Nil(crd.CustomCELRules()[1].Message) + + // Test field-level CEL rules on RecordSet.Name + recordSetCRD := getCRDByName("RecordSet", crds) + require.NotNil(recordSetCRD) + + nameField := recordSetCRD.SpecFields["Name"] + require.NotNil(nameField) + + require.Len(nameField.CustomCELRules(), 1) + assert.Equal("self.endsWith('.')", nameField.CustomCELRules()[0].Rule) + assert.NotNil(nameField.CustomCELRules()[0].Message) + assert.Equal("DNS name must end with a dot", *nameField.CustomCELRules()[0].Message) } diff --git a/pkg/testdata/models/apis/apigatewayv2/0000-00-00/generator-with-cel-rules.yaml b/pkg/testdata/models/apis/apigatewayv2/0000-00-00/generator-with-cel-rules.yaml new file mode 100644 index 00000000..8c4a71d9 --- /dev/null +++ b/pkg/testdata/models/apis/apigatewayv2/0000-00-00/generator-with-cel-rules.yaml @@ -0,0 +1,20 @@ +resources: + Authorizer: + fields: + JwtConfiguration.Issuer: + custom_cel_rules: + - rule: "self.startsWith('https://')" + message: "Issuer must be an HTTPS URL" +ignore: + resource_names: + - Api + - ApiMapping + - Deployment + - DomainName + - Integration + - IntegrationResponse + - Model + - Route + - RouteResponse + - Stage + - VpcLink diff --git a/pkg/testdata/models/apis/route53/0000-00-00/generator.yaml b/pkg/testdata/models/apis/route53/0000-00-00/generator.yaml index 91ccce5f..33335a59 100644 --- a/pkg/testdata/models/apis/route53/0000-00-00/generator.yaml +++ b/pkg/testdata/models/apis/route53/0000-00-00/generator.yaml @@ -18,6 +18,11 @@ operations: resource_name: RecordSet resources: + HostedZone: + custom_cel_rules: + - rule: "!has(self.hostedZoneConfig) || !self.hostedZoneConfig.privateZone || has(self.vpc)" + message: "spec.vpc is required for private hosted zones" + - rule: "size(self.name) > 0" RecordSet: fields: AliasTarget: @@ -61,6 +66,9 @@ resources: operation: ListResourceRecordSets path: ResourceRecordSets.Name is_immutable: true + custom_cel_rules: + - rule: "self.endsWith('.')" + message: "DNS name must end with a dot" # Changing this value after a CR has been created could result in orphaned record sets RecordType: from: diff --git a/pkg/testutil/schema_helper.go b/pkg/testutil/schema_helper.go index 7fe9fec0..c669aaa0 100644 --- a/pkg/testutil/schema_helper.go +++ b/pkg/testutil/schema_helper.go @@ -51,6 +51,35 @@ func (o *TestingModelOptions) SetDefaults() { } } +// repoRootFromWD returns the repository root directory by looking for "generate" +// or "model" in the working directory path (both live directly under pkg/). +// Returns ("", false) if the root cannot be determined. +func repoRootFromWD(wd string) (string, bool) { + pathParts := strings.Split(wd, "/") + for x, pathPart := range pathParts { + if pathPart == "generate" || pathPart == "model" { + // x-1 points to "pkg"; everything before that is the repo root + return filepath.Join("/", filepath.Join(pathParts[0:x-1]...)), true + } + } + return "", false +} + +// TemplatesBasePath returns the absolute path to the templates/ directory at +// the root of the repository. +func TemplatesBasePath(t *testing.T) string { + t.Helper() + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + root, ok := repoRootFromWD(wd) + if !ok { + t.Fatalf("could not determine templates/ path from working directory %q", wd) + } + return filepath.Join(root, "templates") +} + // NewModelForService returns a new *ackmodel.Model used for testing purposes. func NewModelForService(t *testing.T, servicePackageName string) *ackmodel.Model { return NewModelForServiceWithOptions(t, servicePackageName, &TestingModelOptions{}) @@ -58,25 +87,21 @@ func NewModelForService(t *testing.T, servicePackageName string) *ackmodel.Model // NewModelForServiceWithOptions returns a new *ackmodel.Model used for testing purposes. func NewModelForServiceWithOptions(t *testing.T, servicePackageName string, options *TestingModelOptions) *ackmodel.Model { - path, err := os.Getwd() + wd, err := os.Getwd() if err != nil { t.Fatal(err) } // We have subdirectories in pkg/generate and pkg/model that rely on the testdata // in pkg/generate. This code simply detects if we're running from one of // those subdirectories and if so, rebuilds the path to the API model files - // in pkg/generate/testdata - pathParts := strings.Split(path, "/") - for x, pathPart := range pathParts { - if pathPart == "generate" || pathPart == "model" { - path = filepath.Join(pathParts[0:x]...) - path = filepath.Join("/", path, "testdata") - break - } + // in pkg/testdata + testdataPath := wd + if root, ok := repoRootFromWD(wd); ok { + testdataPath = filepath.Join(root, "pkg", "testdata") } options.SetDefaults() - generatorConfigPath := filepath.Join(path, "models", "apis", servicePackageName, options.ServiceAPIVersion, options.GeneratorConfigFile) + generatorConfigPath := filepath.Join(testdataPath, "models", "apis", servicePackageName, options.ServiceAPIVersion, options.GeneratorConfigFile) if _, err := os.Stat(generatorConfigPath); os.IsNotExist(err) { t.Fatalf("Could not find generator file %q", generatorConfigPath) } @@ -84,7 +109,7 @@ func NewModelForServiceWithOptions(t *testing.T, servicePackageName string, opti if err != nil { t.Fatal(err) } - sdkHelper := acksdk.NewHelper(path, cfg) + sdkHelper := acksdk.NewHelper(testdataPath, cfg) sdkHelper.WithAPIVersion(options.ServiceAPIVersion) sdkAPI, err := sdkHelper.API(servicePackageName) if err != nil { @@ -93,7 +118,7 @@ func NewModelForServiceWithOptions(t *testing.T, servicePackageName string, opti docConfigPath := "" if options.DocumentationConfigFile != "" { - docConfigPath = filepath.Join(path, "models", "apis", servicePackageName, options.ServiceAPIVersion, options.DocumentationConfigFile) + docConfigPath = filepath.Join(testdataPath, "models", "apis", servicePackageName, options.ServiceAPIVersion, options.DocumentationConfigFile) } docCfg, err := ackgenconfig.NewDocumentationConfig(docConfigPath) if err != nil { diff --git a/templates/apis/crd.go.tpl b/templates/apis/crd.go.tpl index 67343b42..8347bbdd 100644 --- a/templates/apis/crd.go.tpl +++ b/templates/apis/crd.go.tpl @@ -15,6 +15,9 @@ import ( ) {{ .CRD.Documentation }} +{{- range $rule := .CRD.CustomCELRules }} +// +kubebuilder:validation:XValidation:rule="{{ $rule.Rule }}"{{- if $rule.Message }},message="{{ $rule.Message | Deref }}"{{- end }} +{{- end }} type {{ .CRD.Kind }}Spec struct { {{ range $fieldName := .CRD.SpecFieldNames }} {{- $field := (index $.CRD.SpecFields $fieldName) }} @@ -26,6 +29,10 @@ type {{ .CRD.Kind }}Spec struct { // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable once set" {{ end -}} +{{- range $rule := $field.CustomCELRules }} + // +kubebuilder:validation:XValidation:rule="{{ $rule.Rule }}"{{- if $rule.Message }},message="{{ $rule.Message | Deref }}"{{- end }} +{{ end -}} + {{- if and ($field.IsRequired) (not $field.HasReference) -}} // +kubebuilder:validation:Required {{ end -}} diff --git a/templates/apis/type_def.go.tpl b/templates/apis/type_def.go.tpl index 84032aeb..e412635f 100644 --- a/templates/apis/type_def.go.tpl +++ b/templates/apis/type_def.go.tpl @@ -11,6 +11,9 @@ type {{ .Names.Camel }} struct { {{- if $attr.IsImmutable }} // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable once set" {{- end }} + {{- range $rule := $attr.CustomCELRules }} + // +kubebuilder:validation:XValidation:rule="{{ $rule.Rule }}"{{- if $rule.Message }},message="{{ $rule.Message | Deref }}"{{- end }} + {{- end }} {{ $attr.Names.Camel }} {{ $attr.GoType }} {{ $attr.GetGoTag }} {{- end }} } diff --git a/templates/pkg/resource/delta.go.tpl b/templates/pkg/resource/delta.go.tpl index 832ec85e..93f9d43f 100644 --- a/templates/pkg/resource/delta.go.tpl +++ b/templates/pkg/resource/delta.go.tpl @@ -14,6 +14,7 @@ import ( var ( _ = &bytes.Buffer{} _ = &acktags.Tags{} + _ = equality.Semantic ) // newResourceDelta returns a new `ackcompare.Delta` used to compare two diff --git a/templates/pkg/resource/resource.go.tpl b/templates/pkg/resource/resource.go.tpl index 02b89ec4..bb8c70b1 100644 --- a/templates/pkg/resource/resource.go.tpl +++ b/templates/pkg/resource/resource.go.tpl @@ -3,6 +3,8 @@ package {{ .CRD.Names.Snake }} import ( + "fmt" + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" ackerrors "github.com/aws-controllers-k8s/runtime/pkg/errors" @@ -16,6 +18,8 @@ import ( // Hack to avoid import errors during build... var ( _ = &ackerrors.MissingNameIdentifier + _ = fmt.Sprintf + _ = aws.String ) // resource implements the `aws-controller-k8s/runtime/pkg/types.AWSResource` diff --git a/templates/pkg/resource/sdk.go.tpl b/templates/pkg/resource/sdk.go.tpl index 25bf7e2e..8c70048c 100644 --- a/templates/pkg/resource/sdk.go.tpl +++ b/templates/pkg/resource/sdk.go.tpl @@ -39,6 +39,7 @@ var ( _ = fmt.Sprintf("") _ = &ackrequeue.NoRequeue{} _ = &aws.Config{} + _ = math.MaxInt32 ) // sdkFind returns SDK-specific information about a supplied resource diff --git a/templates/pkg/resource/tags.go.tpl b/templates/pkg/resource/tags.go.tpl index d715f44b..98e8d6b6 100644 --- a/templates/pkg/resource/tags.go.tpl +++ b/templates/pkg/resource/tags.go.tpl @@ -3,6 +3,9 @@ package {{ .CRD.Names.Snake }} import( + "slices" + "strings" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" svcapitypes "github.com/aws-controllers-k8s/{{ .ControllerName }}-controller/apis/{{ .APIVersion }}"