Skip to content

Commit 7e7730b

Browse files
committed
WIP - deal with diff Go types
Adds a new SetFieldConfig struct to the code generator config that allows teams to instruct the code generator to handle the SetResource generator specially for a particular `pkg/model.OpType`. This is useful for at least a couple use cases: 1. Different Go types in input and output shapes When the Go type of a field in the Create Input shape differs from the Go type of the same-named field in another operation's Output shape, the code generator doesn't know how to handle this situation and ends up outputting invalid Go code. An example of this behaviour is in the RDS controller's DBInstance resource. The DBSecurityGroups field in the Create Input shape is of type `[]*string`, but the same field in the Create, ReadMany and Update output shapes is of type `[]*DBSecurityGroupMembership`. The `DBSecurityGroupMembership` struct has two fields, `DBSecurityGroupName` and `Status`. The only part of that struct that is relevant to the `Spec.DBSecurityGroups` field is the `DBSecurityGroupName` field, which corresponds to the string identifier of the DB security group provided by the user in the `CreateDBInstanceRequest.DBSecurityGroups` field. The code generator ends up calling `pkg/generate/code.SetResource` for the DBInstance resource, gets to the `Spec.DBSecurityGroups` field and just doesn't know how to handle the different Go types and so ends up outputting nothing, which breaks compilation and has caused us to manually comment out or fix the generated code. operation. See: https://github.com/aws-controllers-k8s/rds-controller/blob/dc3271fa7b455fc99c3531ce372ea221c9f5b8e7/pkg/resource/db_instance/sdk.go#L853-L864 In the RDS DBInstance DBSecurityGroups case, we'd add the following to the RDS generator.yaml file to instruct the code generator how to transform the response from the DescribeDBInstances Output shape for setting the value of `Spec.DBSecurityGroups` from the set of `DBSecurityGroupMembership.DBSecurityGroupName` values: ```yaml resources: DBInstance: fields: DBSecurityGroups: set: - on: READ_MANY from: DBSecurityGroupName ``` 2. Ignoring fields in output shapes containing stale data For some service APIs, the returned value for a field in the Output shape of an Update operation contains the *originally-set* value instead of the value of the field that was set in the Update operation itself. An example of this situation is the ElastiCache ModifyReplicationGroup API call. If you pass some value for the LogDeliveryConfiguration field in the Input shape (which comes from the Spec.LogDeliveryConfiguration field of the ReplicationGroup resource), the value that is returned in the Output shape's LogDeliveryConfiguration is actually the *old* (stale) value for the field. The reason for this return of stale data is because the log delivery configuration is asynchronously mutated. For these cases, we want to be able to tell the code generator to ignore these fields when outputting code for the `pkg/generate/code.SetResource` function, but only for the Update operation. In this case, we'd add the following to the ElastiCache generator.yaml file: ```yaml resources: ReplicationGroup: fields: LogDeliveryConfiguration: set: - on: UPDATE ignore: true ``` Signed-off-by: Jay Pipes <[email protected]>
1 parent 385779a commit 7e7730b

File tree

4 files changed

+137
-0
lines changed

4 files changed

+137
-0
lines changed

pkg/generate/code/set_resource.go

+18
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,15 @@ func SetResource(
217217
targetAdaptedVarName += cfg.PrefixConfig.StatusField
218218
}
219219
targetMemberShapeRef = f.ShapeRef
220+
221+
// We may have some special instructions for how to handle setting the
222+
// field value...
223+
setCfg := f.GetSetterConfig(opType)
224+
225+
if setCfg != nil && setCfg.Ignore {
226+
continue
227+
}
228+
220229
// fieldVarName is the name of the variable that is used for temporary
221230
// storage of complex member field values
222231
//
@@ -500,6 +509,15 @@ func setResourceReadMany(
500509
}
501510
targetAdaptedVarName += cfg.PrefixConfig.StatusField
502511
}
512+
513+
// We may have some special instructions for how to handle setting the
514+
// field value...
515+
setCfg := f.GetSetterConfig(model.OpTypeList)
516+
517+
if setCfg != nil && setCfg.Ignore {
518+
continue
519+
}
520+
503521
targetMemberShapeRef = f.ShapeRef
504522
out += fmt.Sprintf(
505523
"%s\tif %s != nil {\n", indent, sourceAdaptedVarName,

pkg/generate/code/set_sdk.go

+1
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,7 @@ func setSDKForSlice(
11361136
targetShape.MemberRef.Shape,
11371137
indentLevel+1,
11381138
)
1139+
11391140
// f0elem = *f0iter
11401141
//
11411142
// or

pkg/generate/config/field.go

+104
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,106 @@ type SourceFieldConfig struct {
105105
Path string `json:"path"`
106106
}
107107

108+
// SetFieldConfig instructs the code generator how to handle setting the value
109+
// of the field from an Output shape in the API response.
110+
//
111+
// These instructions are necessary when the Go type for Input and Output
112+
// fields is different.
113+
//
114+
// For example, consider the `DBSecurityGroups` field for an RDS `DBInstance`
115+
// resource.
116+
//
117+
// The Create operation's Input shape's `DBSecurityGroups` field has a Go type
118+
// of `[]*string` [0] because the user is expected to provide a list of
119+
// DBSecurityGroup identifiers when creating the DBInstance.
120+
//
121+
// However, the Output shape for both the Create and ReadOne operation uses a
122+
// different Go type for the `DBSecurityGroups` field. For these Output shapes,
123+
// the Go type is `[]*DBSecurityGroupMembership` [1]. The
124+
// `DBSecurityGroupMembership` struct contains the DBSecurityGroup's name and
125+
// "status".
126+
//
127+
// The challenge that the ACK code generator has is to figure out how to take
128+
// the Go type of the Output shape and process it into the Go type of the Input
129+
// shape.
130+
//
131+
// In other words, for the `DBInstance.Spec.DBSecurityGroups` field discussed
132+
// above, we need to have the code generator produce the following Go code in
133+
// the SetResource generator:
134+
//
135+
// ```go
136+
// if resp.DBInstance.DBSecurityGroups != nil {
137+
// f17 := []*string{}
138+
// for _, f17iter := range resp.DBInstance.DBSecurityGroups {
139+
// var f17elem string
140+
// f17elem = *f17iter.DBSecurityGroupName
141+
// f17 = append(f17, &f17elem)
142+
// }
143+
// ko.Spec.DBSecurityGroupNames = f17
144+
// } else {
145+
// ko.Spec.DBSecurityGroupNames = nil
146+
// }
147+
// ```
148+
//
149+
// [0] https://github.com/aws/aws-sdk-go/blob/0a01aef9caf16d869c7340e729080205760dc2a2/models/apis/rds/2014-10-31/api-2.json#L2985
150+
// [1] https://github.com/aws/aws-sdk-go/blob/0a01aef9caf16d869c7340e729080205760dc2a2/models/apis/rds/2014-10-31/api-2.json#L3815
151+
type SetFieldConfig struct {
152+
// On is the `pkg/model.OpType` whose Output shape will be transformed by
153+
// this config. If empty, this set field config applies to all operations
154+
On *string `json:on,omitempty`
155+
// MemberAccessPath tells the code generator to output Go code that sets
156+
// the value of a variable containing the target resource field with the
157+
// contents of a member field in the source struct.
158+
//
159+
// Consider the `DBInstance.Spec.DBSecurityGroups` field discussed above.
160+
//
161+
// If we have the following generator.yaml config:
162+
//
163+
// ```yaml
164+
// resources:
165+
// DBInstance:
166+
// fields:
167+
// DBSecurityGroups:
168+
// set:
169+
// - on: CREATE
170+
// from: DBSecurityGroupName
171+
// - on: READ_ONE
172+
// from: DBSecurityGroupName
173+
// ```
174+
//
175+
// That will instruct the code generator to output this Go code when
176+
// processing the `*DBSecurityGroupMembership` struct elements of the
177+
// Output shape's DBSecurityGroups field:
178+
//
179+
// ```go
180+
// f17elem = *f17iter.DBSecurityGroupName
181+
// ```
182+
From *string `json:"from,omitempty"`
183+
// Ignore instructs the code generator to ignore this field in the Output
184+
// shape when setting the value of the resource's field in the Spec. This
185+
// is useful when we know that, for example, the returned value of field in
186+
// an Output shape contains stale data, such as when the ElastiCache
187+
// ModifyReplicationGroup API's Output response shape contains the
188+
// originally-set value for the LogDeliveryConfiguration field that was
189+
// updated in the Input shape.
190+
//
191+
// See: https://github.com/aws-controllers-k8s/elasticache-controller/pull/59/
192+
//
193+
// In the case of ElastiCache, we might have the following generator.yaml
194+
// config:
195+
//
196+
// ```yaml
197+
// resources:
198+
// ReplicationGroup:
199+
// fields:
200+
// LogDeliveryConfiguration:
201+
// set:
202+
// - on: UPDATE
203+
// ignore: true
204+
// ```
205+
Ignore bool `json:"ignore,omitempty"`
206+
}
207+
108208
// CompareFieldConfig informs the code generator how to compare two values of a
109209
// field
110210
type CompareFieldConfig struct {
@@ -196,6 +296,10 @@ type FieldConfig struct {
196296
// Compare instructs the code generator how to produce code that compares
197297
// the value of the field in two resources
198298
Compare *CompareFieldConfig `json:"compare,omitempty"`
299+
// Set contains instructions for the code generator how to deal with
300+
// fields where the Go type of the same-named fields in an Output shape is
301+
// different from the Go type of the Input shape.
302+
Set []*SetFieldConfig `json:"set"`
199303
// Print instructs the code generator how to generate comment markers that
200304
// influence hows field are printed in `kubectl get` response. If this field
201305
// is not nil, it will be added to the columns of `kubectl get`.

pkg/model/field.go

+14
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,20 @@ func (f *Field) IsRequired() bool {
6363
return util.InStrings(f.Names.ModelOriginal, f.CRD.Ops.Create.InputRef.Shape.Required)
6464
}
6565

66+
// GetSetterConfig returns the SetFieldConfig object associated with this field
67+
// and a supplied operation type, or nil if none exists.
68+
func (f *Field) GetSetterConfig(opType OpType) *ackgenconfig.SetFieldConfig {
69+
if f.FieldConfig == nil {
70+
return nil
71+
}
72+
for _, setCfg := range f.FieldConfig.Set {
73+
if setCfg.On == nil || OpTypeFromString(*setCfg.On) == opType {
74+
return setCfg
75+
}
76+
}
77+
return nil
78+
}
79+
6680
// ParentFieldPath takes a field path and returns the field path of the
6781
// containing "parent" field. For example, if the field path
6882
// `Users..Credentials.Login` is passed in, this function returns

0 commit comments

Comments
 (0)