Skip to content

Commit 3974a2a

Browse files
Replace primary_identifier_field_name with is_primary_key (#190)
Description of changes: We have had quite a few issues with the logic of `SetResourceIdentifiers` struggling to find the correct identifier field name or matching it to the corresponding spec or status field. The current implementation solves the first of these issues by pointing directly to the identifier member in the input shape, but the logic to link this back to a spec or status field is still flawed. Rather than pointing to a member in the input shape and trying to infer the spec or status field, this new implementation directly denotes a field on a resource as the "primary key". The `SetResourceIdentifiers` logic can now directly find the target field and create the corresponding setter. The override logic for ARNs has now been moved to a `is_arn_primary_key` on the `ResourceConfig` object, since we cannot access the ARN as a `FieldConfig`. This is a **backwards compatibility breaking change** as all existing `primary_identifier_field_name` configurations need to be refactored into `is_primary_key` fields on the respective resource's field. For example: ```yaml operations: DescribeDBInstances: primary_identifier_field_name: DBInstanceIdentifier DescribeDBSubnetGroups: primary_identifier_field_name: DBSubnetGroupName ``` becomes: ```yaml resources: DBInstance: fields: DBInstanceIdentifier: is_primary_key: true DBSubnetGroup: fields: Name: is_primary_key: true ``` By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent c6e8ce2 commit 3974a2a

File tree

9 files changed

+111
-51
lines changed

9 files changed

+111
-51
lines changed

pkg/generate/code/common.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,6 @@ func FindPrimaryIdentifierFieldNames(
131131
) (crField string, shapeField string) {
132132
shape := op.InputRef.Shape
133133

134-
// Attempt to fetch the primary identifier override from the configuration
135-
opConfig, ok := cfg.Operations[op.Name]
136-
if ok {
137-
shapeField = opConfig.PrimaryIdentifierFieldName
138-
}
139-
140134
if shapeField == "" {
141135
// For ReadOne, search for a direct identifier
142136
if op == r.Ops.ReadOne {
@@ -151,8 +145,8 @@ func FindPrimaryIdentifierFieldNames(
151145
default:
152146
panic("Found multiple possible primary identifiers for " +
153147
r.Names.Original + ". Set " +
154-
"`primary_identifier_field_name` for the " + op.Name +
155-
" operation in the generator config.")
148+
"`is_primary_key` for the primary field in the " +
149+
r.Names.Camel + " resource.")
156150
}
157151
} else {
158152
// For ReadMany, search for pluralized identifiers
@@ -162,12 +156,12 @@ func FindPrimaryIdentifierFieldNames(
162156
// Require override if still can't find any identifiers
163157
if shapeField == "" {
164158
panic("Could not find primary identifier for " + r.Names.Original +
165-
". Set `primary_identifier_field_name` for the " + op.Name +
166-
" operation in the generator config.")
159+
". Set `is_primary_key` for the primary field in the " +
160+
r.Names.Camel + " resource.")
167161
}
168162
}
169163

170-
if r.IsPrimaryARNField(shapeField) || shapeField == PrimaryIdentifierARNOverride {
164+
if r.IsPrimaryARNField(shapeField) {
171165
return "", PrimaryIdentifierARNOverride
172166
}
173167

pkg/generate/code/set_resource.go

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -788,12 +788,14 @@ func SetResourceGetAttributes(
788788
// that an ARN, ID or Name) and any other "additional keys" required for the AWS
789789
// service to uniquely identify the object.
790790
//
791-
// The method will attempt to use the `ReadOne` operation, if present, otherwise
792-
// will fall back to using `ReadMany`. If it detects the operation uses an ARN
793-
// to identify the resource it will read it from the metadata status field.
794-
// Otherwise it will use any fields with a matching name in the operation,
795-
// pulling from spec or status - requiring that exactly one of those fields is
796-
// marked as the "primary" identifier.
791+
// The method will attempt to look for the field denoted with a value of true
792+
// for `is_primary_key`, or will use the ARN if the resource has a value of true
793+
// for `is_arn_primary_key`. Otherwise, the method will attempt to use the
794+
// `ReadOne` operation, if present, falling back to using `ReadMany`.
795+
// If it detects the operation uses an ARN to identify the resource it will read
796+
// it from the metadata status field. Otherwise it will use any field with a
797+
// name that matches the primary identifier from the operation, pulling from
798+
// top-level spec or status fields.
797799
//
798800
// An example of code with no additional keys:
799801
//
@@ -873,21 +875,42 @@ func SetResourceIdentifiers(
873875
primaryKeyConditionalOut := "\n"
874876
primaryKeyConditionalOut += identifierNameOrIDGuardConstructor(sourceVarName, indentLevel)
875877

876-
primaryCRField, primaryShapeField := FindPrimaryIdentifierFieldNames(cfg, r, op)
877-
if primaryShapeField == PrimaryIdentifierARNOverride {
878-
// if r.ko.Status.ACKResourceMetadata == nil {
879-
// r.ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{}
880-
// }
881-
// r.ko.Status.ACKResourceMetadata.ARN = identifier.ARN
882-
arnOut := "\n"
883-
arnOut += ackResourceMetadataGuardConstructor(fmt.Sprintf("%s.Status", targetVarName), indentLevel)
884-
arnOut += fmt.Sprintf(
885-
"%s%s.Status.ACKResourceMetadata.ARN = %s.ARN\n",
886-
indent, targetVarName, sourceVarName,
887-
)
878+
// if r.ko.Status.ACKResourceMetadata == nil {
879+
// r.ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{}
880+
// }
881+
// r.ko.Status.ACKResourceMetadata.ARN = identifier.ARN
882+
arnOut := "\n"
883+
arnOut += ackResourceMetadataGuardConstructor(fmt.Sprintf("%s.Status", targetVarName), indentLevel)
884+
arnOut += fmt.Sprintf(
885+
"%s%s.Status.ACKResourceMetadata.ARN = %s.ARN\n",
886+
indent, targetVarName, sourceVarName,
887+
)
888888

889+
// Check if the CRD defines the primary keys
890+
if r.IsARNPrimaryKey() {
889891
return arnOut
890892
}
893+
primaryField, err := r.GetPrimaryKeyField()
894+
if err != nil {
895+
panic(err)
896+
}
897+
898+
var primaryCRField, primaryShapeField string
899+
isPrimarySet := primaryField != nil
900+
if isPrimarySet {
901+
memberPath, _ := findFieldInCR(cfg, r, primaryField.Names.Original)
902+
targetVarPath := fmt.Sprintf("%s%s", targetVarName, memberPath)
903+
primaryKeyOut += setResourceIdentifierPrimaryIdentifier(cfg, r,
904+
primaryField,
905+
targetVarPath,
906+
sourceVarName,
907+
indentLevel)
908+
} else {
909+
primaryCRField, primaryShapeField = FindPrimaryIdentifierFieldNames(cfg, r, op)
910+
if primaryShapeField == PrimaryIdentifierARNOverride {
911+
return arnOut
912+
}
913+
}
891914

892915
paginatorFieldLookup := []string{
893916
"NextToken",
@@ -924,6 +947,11 @@ func SetResourceIdentifiers(
924947
op.Name, memberName,
925948
)
926949

950+
// Check to see if we've already set the field as the primary identifier
951+
if isPrimarySet && renamedName == primaryField.Names.Camel {
952+
continue
953+
}
954+
927955
isPrimaryIdentifier := memberName == primaryShapeField
928956

929957
searchField := ""
@@ -934,7 +962,7 @@ func SetResourceIdentifiers(
934962
}
935963

936964
memberPath, targetField := findFieldInCR(cfg, r, searchField)
937-
if targetField == nil {
965+
if targetField == nil || (isPrimarySet && targetField == primaryField) {
938966
continue
939967
}
940968

pkg/generate/config/field.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,11 @@ type FieldConfig struct {
170170
// Required indicates whether this field is a required member or not.
171171
// This field is used to configure '+kubebuilder:validation:Required' on API object's members.
172172
IsRequired *bool `json:"is_required,omitempty"`
173-
// IsName indicates the field represents the name/string identifier field
174-
// for the resource. This allows the generator config to override the
175-
// default behaviour of considering a field called "Name" or
173+
// IsPrimaryKey indicates the field represents the primary name/string
174+
// identifier field for the resource. This allows the generator config to
175+
// override the default behaviour of considering a field called "Name" or
176176
// "{Resource}Name" or "{Resource}Id" as the "name field" for the resource.
177-
IsName bool `json:"is_name"`
177+
IsPrimaryKey bool `json:"is_primary_key"`
178178
// IsOwnerAccountID indicates the field contains the AWS Account ID
179179
// that owns the resource. This is a special field that we direct to
180180
// storage in the common `Status.ACKResourceMetadata.OwnerAccountID` field.

pkg/generate/config/operation.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818

1919
awssdkmodel "github.com/aws/aws-sdk-go/private/model/api"
2020

21-
"github.com/aws-controllers-k8s/code-generator/pkg/util"
21+
"github.com/aws-controllers-k8s/code-generator/pkg/util"
2222
)
2323

2424
// StringArray is a type that can be represented in JSON as *either* a string
@@ -45,10 +45,6 @@ type OperationConfig struct {
4545
// An example of this is `Put...` or `Register...` API operations not being correctly classified as `Create` op type
4646
// OperationType []string `json:"operation_type"`
4747
OperationType StringArray `json:"operation_type"`
48-
// PrimaryIdentifierFieldName provides the name of the field that should be
49-
// interpreted as the "primary" identifier field. This field will be used as
50-
// the primary field for resource adoption.
51-
PrimaryIdentifierFieldName string `json:"primary_identifier_field_name,omitempty"`
5248
}
5349

5450
// IsIgnoredOperation returns true if Operation Name is configured to be ignored

pkg/generate/config/resource.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ type ResourceConfig struct {
8282
// Print contains instructions for the code generator to generate kubebuilder printcolumns
8383
// marker comments.
8484
Print *PrintConfig `json:"print,omitempty"`
85+
// IsARNPrimaryKey determines whether the CRD uses the ARN as the primary
86+
// identifier in the ReadOne operations.
87+
IsARNPrimaryKey bool `json:"is_arn_primary_key"`
8588
}
8689

8790
// HooksConfig instructs the code generator how to inject custom callback hooks

pkg/model/crd.go

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,46 @@ func (r *CRD) HasImmutableFieldChanges() bool {
353353
return false
354354
}
355355

356+
// IsARNPrimaryKey returns true if the CRD uses its ARN as its primary key in
357+
// ReadOne calls.
358+
func (r *CRD) IsARNPrimaryKey() bool {
359+
if r.cfg == nil {
360+
return false
361+
}
362+
resGenConfig, found := r.cfg.Resources[r.Names.Original]
363+
if !found {
364+
return false
365+
}
366+
return resGenConfig.IsARNPrimaryKey
367+
}
368+
369+
// GetPrimaryKeyField returns the field designated as the primary key, nil if
370+
// none are specified or an error if multiple are designated.
371+
func (r *CRD) GetPrimaryKeyField() (*Field, error) {
372+
fConfigs := r.cfg.ResourceFields(r.Names.Original)
373+
374+
var primaryField *Field
375+
for fieldName, fieldConfig := range fConfigs {
376+
if !fieldConfig.IsPrimaryKey {
377+
continue
378+
}
379+
380+
// Multiple primary fields
381+
if primaryField != nil {
382+
return nil, fmt.Errorf("multiple fields are marked with is_primary_key")
383+
}
384+
385+
fieldNames := names.New(fieldName)
386+
fPath := fieldNames.Camel
387+
var notFound bool
388+
primaryField, notFound = r.Fields[fPath]
389+
if !notFound {
390+
return nil, fmt.Errorf("what the frick")
391+
}
392+
}
393+
return primaryField, nil
394+
}
395+
356396
// SetOutputCustomMethodName returns custom set output operation as *string for
357397
// given operation on custom resource, if specified in generator config
358398
func (r *CRD) SetOutputCustomMethodName(
@@ -536,14 +576,12 @@ func (r *CRD) GetCustomCheckRequiredFieldsMissingMethod(
536576

537577
// SpecIdentifierField returns the name of the "Name" or string identifier field
538578
// in the Spec.
539-
// This method does not guarantee that the identifier field returned is the
540-
// primary identifier used in any of the `Read*` operations.
541579
func (r *CRD) SpecIdentifierField() *string {
542580
if r.cfg != nil {
543581
rConfig, found := r.cfg.Resources[r.Names.Original]
544582
if found {
545583
for fName, fConfig := range rConfig.Fields {
546-
if fConfig.IsName {
584+
if fConfig.IsPrimaryKey {
547585
return &fName
548586
}
549587
}

pkg/testdata/models/apis/ecr/0000-00-00/generator-with-field-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ resources:
22
Repository:
33
fields:
44
Name:
5-
is_name: true
5+
is_primary_key: true
66
exceptions:
77
errors:
88
404:

pkg/testdata/models/apis/rds/0000-00-00/generator.yaml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
ignore:
22
shape_names:
33
- DBSecurityGroupMembershipList
4-
operations:
5-
DescribeDBInstances:
6-
primary_identifier_field_name: DBInstanceIdentifier
7-
DescribeDBSubnetGroups:
8-
primary_identifier_field_name: DBSubnetGroupName
94
resources:
5+
DBInstance:
6+
fields:
7+
DBInstanceIdentifier:
8+
is_primary_key: true
109
DBSubnetGroup:
10+
fields:
11+
Name:
12+
is_primary_key: true
1113
renames:
1214
operations:
1315
DescribeDBSubnetGroups:

pkg/testdata/models/apis/sagemaker/0000-00-00/generator.yaml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ resources:
2222
Endpoint:
2323
reconcile:
2424
requeue_on_success_seconds: 10
25-
operations:
26-
DescribeModelPackage:
27-
primary_identifier_field_name: ARN
25+
ModelPackage:
26+
is_arn_primary_key: true
2827
ignore:
2928
resource_names:
3029
- Algorithm

0 commit comments

Comments
 (0)