diff --git a/pkg/config/field.go b/pkg/config/field.go index 3479be3bc..1450ddc97 100644 --- a/pkg/config/field.go +++ b/pkg/config/field.go @@ -410,7 +410,8 @@ type ReferencesConfig struct { // which contains generator.yaml ServiceName string `json:"service_name,omitempty"` // Resource mentions the K8s resource which is read to resolve the - // reference + // reference. It is used as the reference field name prefix when the + // field name consists of an identifier suffix only. Resource string `json:"resource"` // SkipResourceStateValidations if true, skips state validations performed during // ResolveReferences step, that ensure the referenced resource exists in AWS and is synced. diff --git a/pkg/model/field.go b/pkg/model/field.go index 18dec547a..d180d10f0 100644 --- a/pkg/model/field.go +++ b/pkg/model/field.go @@ -402,10 +402,7 @@ func (f *Field) GetReferenceFieldName() (names.Names, error) { } } if refNamePrefix == "" { - return names.Names{}, fmt.Errorf( - "reference field name cannot be just an identifier suffix (id/arn/name) — field: %s", - f.Names.Original, - ) + refNamePrefix = f.FieldConfig.References.Resource } refName := refNamePrefix // If the shape of corresponding field is a list, singularize the refNamePrefix diff --git a/pkg/model/field_test.go b/pkg/model/field_test.go index e2c1b948e..1ea50c879 100644 --- a/pkg/model/field_test.go +++ b/pkg/model/field_test.go @@ -254,27 +254,34 @@ func TestGetReferenceFieldName(t *testing.T) { fieldName string expectedReferenceFieldName string shapeRef *api.ShapeRef + references *ackgenconfig.ReferencesConfig }{ - {"ClusterName", "ClusterRef", &stringShape}, - {"ClusterNames", "ClusterRefs", &listShape}, - {"ClusterARN", "ClusterRef", &stringShape}, - {"ClusterARNs", "ClusterRefs", &listShape}, - {"ClusterID", "ClusterRef", &stringShape}, - {"ClusterId", "ClusterRef", &stringShape}, - {"ClusterIds", "ClusterRefs", &listShape}, - {"ClusterIDs", "ClusterRefs", &listShape}, - {"Cluster", "ClusterRef", &stringShape}, - {"Clusters", "ClusterRefs", &listShape}, + {"ClusterName", "ClusterRef", &stringShape, nil}, + {"ClusterNames", "ClusterRefs", &listShape, nil}, + {"ClusterARN", "ClusterRef", &stringShape, nil}, + {"ClusterARNs", "ClusterRefs", &listShape, nil}, + {"ClusterID", "ClusterRef", &stringShape, nil}, + {"ClusterId", "ClusterRef", &stringShape, nil}, + {"ClusterIds", "ClusterRefs", &listShape, nil}, + {"ClusterIDs", "ClusterRefs", &listShape, nil}, + {"Cluster", "ClusterRef", &stringShape, nil}, + {"Clusters", "ClusterRefs", &listShape, nil}, // When the resource name indicates plural but it is singular. Ex: DHCPOptions - {"Clusters", "ClustersRef", &stringShape}, - {"BlueDeploymentId", "BlueDeploymentRef", &stringShape}, - {"GreenDeploymentId", "GreenDeploymentRef", &stringShape}, + {"Clusters", "ClustersRef", &stringShape, nil}, + {"BlueDeploymentId", "BlueDeploymentRef", &stringShape, nil}, + {"GreenDeploymentId", "GreenDeploymentRef", &stringShape, nil}, + {"Name", "ClusterRef", &stringShape, &ackgenconfig.ReferencesConfig{Resource: "Cluster"}}, + {"Names", "ClusterRefs", &listShape, &ackgenconfig.ReferencesConfig{Resource: "Cluster"}}, + {"Names", "ClusterRefs", &listShape, &ackgenconfig.ReferencesConfig{Resource: "Clusters"}}, + // The resource name is only used when the field name does not contain a prefix itself. + {"ClusterName", "ClusterRef", &stringShape, &ackgenconfig.ReferencesConfig{Resource: "Other"}}, } for _, tc := range testCases { f := model.Field{} f.ShapeRef = tc.shapeRef f.Names = names.New(tc.fieldName) + f.FieldConfig = &ackgenconfig.FieldConfig{References: tc.references} refNames, err := f.GetReferenceFieldName() assert.NoError(err) referenceFieldName := refNames.Camel diff --git a/pkg/model/model.go b/pkg/model/model.go index 47d643f5b..8b6f0c2e9 100644 --- a/pkg/model/model.go +++ b/pkg/model/model.go @@ -848,7 +848,9 @@ func (m *Model) processNestedFieldTypeDefs( } } if field.FieldConfig.References != nil { - if err := updateTypeDefAttributeWithReference(crd, fieldPath, tdefs); err != nil { + if err := updateTypeDefAttributeWithReference( + crd, fieldPath, tdefs, field.FieldConfig, + ); err != nil { return fmt.Errorf("resource %q, field %q: %w", crd.Names.Original, fieldPath, err) } } @@ -1012,13 +1014,18 @@ func setTypeDefAttributeImmutable(crd *CRD, fieldPath string, tdefs []*TypeDef) // 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 { +func updateTypeDefAttributeWithReference( + crd *CRD, + fieldPath string, + tdefs []*TypeDef, + fieldConfig *ackgenconfig.FieldConfig, +) error { parentFieldTypeDef, fieldAttr, err := getAttributeFromPath(crd, fieldPath, tdefs) if err != nil { return err } if fieldAttr != nil && parentFieldTypeDef != nil { - if err := addReferenceAttribute(parentFieldTypeDef, fieldAttr); err != nil { + if err := addReferenceAttribute(parentFieldTypeDef, fieldAttr, fieldConfig); err != nil { return err } } @@ -1027,13 +1034,18 @@ func updateTypeDefAttributeWithReference(crd *CRD, fieldPath string, tdefs []*Ty // addReferenceAttribute creates a corresponding reference attribute for // "attr" attribute and adds it to "td" TypeDef -func addReferenceAttribute(td *TypeDef, attr *Attr) error { +func addReferenceAttribute( + td *TypeDef, + attr *Attr, + fieldConfig *ackgenconfig.FieldConfig, +) error { // Create a custom "model.Field" to generate ReferenceFieldName and reuse // the existing method for generating top-level reference fields fieldShapeRef := awssdkmodel.ShapeRef{Shape: attr.Shape} field := &Field{ - Names: attr.Names, - ShapeRef: &fieldShapeRef, + Names: attr.Names, + ShapeRef: &fieldShapeRef, + FieldConfig: fieldConfig, } refAttrName, err := field.GetReferenceFieldName() if err != nil { diff --git a/pkg/model/model_wafv2_test.go b/pkg/model/model_wafv2_test.go index 1db342802..7a2ecd310 100644 --- a/pkg/model/model_wafv2_test.go +++ b/pkg/model/model_wafv2_test.go @@ -79,3 +79,51 @@ func TestWAFv2_SharedShape_CustomNestedFields(t *testing.T) { "%s.Rules.Statement.AndStatement should be string type", crd.name) } } + +// TestWAFv2_NestedReferences verifies that references configured on nested +// ARN fields generate the expected reference field names for RuleGroup and +// WebACL. +func TestWAFv2_NestedReferences(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewModelForService(t, "wafv2") + + crds, err := g.GetCRDs() + require.NoError(err) + + testCases := []struct { + resource string + fieldPath string + refName string + }{ + { + resource: "RuleGroup", + fieldPath: "Rules.Statement.IPSetReferenceStatement.ARN", + refName: "IPSetRef", + }, + { + resource: "WebACL", + fieldPath: "Rules.Statement.IPSetReferenceStatement.ARN", + refName: "IPSetRef", + }, + { + resource: "WebACL", + fieldPath: "Rules.Statement.RuleGroupReferenceStatement.ARN", + refName: "RuleGroupRef", + }, + } + + for _, tc := range testCases { + crd := getCRDByName(tc.resource, crds) + require.NotNil(crd, "CRD %s not found", tc.resource) + + field := crd.Fields[tc.fieldPath] + require.NotNil(field, "field %s.%s not found", tc.resource, tc.fieldPath) + assert.True(field.HasReference(), "field %s.%s should have a reference", tc.resource, tc.fieldPath) + + refName, err := field.GetReferenceFieldName() + require.NoError(err) + assert.Equal(tc.refName, refName.Camel) + } +} diff --git a/pkg/testdata/models/apis/wafv2/0000-00-00/generator.yaml b/pkg/testdata/models/apis/wafv2/0000-00-00/generator.yaml index feaf7d347..f4e10117a 100644 --- a/pkg/testdata/models/apis/wafv2/0000-00-00/generator.yaml +++ b/pkg/testdata/models/apis/wafv2/0000-00-00/generator.yaml @@ -21,11 +21,18 @@ empty_shapes: - RateLimitHTTPMethod - NoneAction operations: + GetIPSet: + output_wrapper_field_path: IPSet GetRuleGroup: output_wrapper_field_path: RuleGroup GetWebACL: output_wrapper_field_path: WebACL resources: + IPSet: + fields: + Name: + is_primary_key: true + is_immutable: true RuleGroup: fields: Name: @@ -51,6 +58,10 @@ resources: type: string set: - ignore: "all" + Rules.Statement.IPSetReferenceStatement.ARN: + references: + resource: IPSet + path: Status.ACKResourceMetadata.ARN WebACL: fields: Name: @@ -76,3 +87,11 @@ resources: type: string set: - ignore: "all" + Rules.Statement.IPSetReferenceStatement.ARN: + references: + resource: IPSet + path: Status.ACKResourceMetadata.ARN + Rules.Statement.RuleGroupReferenceStatement.ARN: + references: + resource: RuleGroup + path: Status.ACKResourceMetadata.ARN