Skip to content

Commit af5f01e

Browse files
Create custom list and map fields (#222)
Issue #, if available: aws-controllers-k8s/community#881 Description of changes: Supports creating custom list and map fields where the member shapes of those fields already exist in the SDK. Rather than supplying a `from` in the field config, you can now supply a `custom_field` property which requires either `list_of` or `map_of` with the name of an API shape as the value. An example of generating a list (for an S3 Bucket): ```yaml resources: Bucket: fields: AnalyticsConfigurations: custom_field: list_of: AnalyticsConfiguration ``` Produces the following: ```golang AnalyticsConfigurations []*AnalyticsConfiguration `json:"analyticsConfigurations,omitempty"` ``` An example of generating a map (for an S3 Bucket): ```yaml resources: Bucket: fields: AnalyticsConfigurations: custom_field: map_of: AnalyticsConfiguration ``` Produces the following: ```golang AnalyticsConfigurations map[string]*AnalyticsConfiguration `json:"analyticsConfigurations,omitempty"` ``` By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 210b64d commit af5f01e

File tree

13 files changed

+504
-59
lines changed

13 files changed

+504
-59
lines changed

cmd/ack-generate/command/common.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ func loadModel(svcAlias string, apiVersion string) (*ackmodel.Model, error) {
237237
modelName = svcAlias
238238
}
239239

240-
sdkHelper := acksdk.NewHelper(sdkDir)
240+
sdkHelper := acksdk.NewHelper(sdkDir, cfg)
241241
sdkAPI, err := sdkHelper.API(modelName)
242242
if err != nil {
243243
retryModelName, err := FallBackFindServiceID(sdkDir, svcAlias)

cmd/ack-generate/command/crossplane.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func generateCrossplane(_ *cobra.Command, args []string) error {
7070
if err != nil {
7171
return err
7272
}
73-
sdkHelper := acksdk.NewHelper(sdkDir)
73+
sdkHelper := acksdk.NewHelper(sdkDir, cfg)
7474
sdkHelper.APIGroupSuffix = "aws.crossplane.io"
7575
sdkAPI, err := sdkHelper.API(svcAlias)
7676
if err != nil {

pkg/generate/config/config.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,38 @@ type PrefixConfig struct {
7474
StatusField string `json:"status_field,omitempty"`
7575
}
7676

77+
// GetCustomListFieldMembers finds all of the custom list fields that need to
78+
// be generated as defined in the generator config.
79+
func (c *Config) GetCustomListFieldMembers() []string {
80+
members := []string{}
81+
82+
for _, resource := range c.Resources {
83+
for _, field := range resource.Fields {
84+
if field.CustomField != nil && field.CustomField.ListOf != "" {
85+
members = append(members, field.CustomField.ListOf)
86+
}
87+
}
88+
}
89+
90+
return members
91+
}
92+
93+
// GetCustomMapFieldMembers finds all of the custom map fields that need to be
94+
// generated as defined in the generator config.
95+
func (c *Config) GetCustomMapFieldMembers() []string {
96+
members := []string{}
97+
98+
for _, resource := range c.Resources {
99+
for _, field := range resource.Fields {
100+
if field.CustomField != nil && field.CustomField.MapOf != "" {
101+
members = append(members, field.CustomField.MapOf)
102+
}
103+
}
104+
}
105+
106+
return members
107+
}
108+
77109
// ResourceContainsSecret returns true if any of the fields in any resource are
78110
// defined as secrets.
79111
func (c *Config) ResourceContainsSecret() bool {

pkg/generate/config/field.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,18 @@ type PrintFieldConfig struct {
136136
Index int `json:"index"`
137137
}
138138

139+
// CustomField instructs the code generator to create a new list or map field
140+
// type using a shape that exists in the SDK.
141+
type CustomFieldConfig struct {
142+
// ListOf provides the name of the SDK shape which will become the
143+
// member of a custom slice field.
144+
ListOf string `json:"list_of,omitempty"`
145+
// MapOf provides the name of the SDK shape which will become the value
146+
// shape for a custom map field. All maps will have `string` as their key
147+
// type.
148+
MapOf string `json:"map_of,omitempty"`
149+
}
150+
139151
// LateInitializeConfig contains instructions for how to handle the
140152
// retrieval and setting of server-side defaulted fields.
141153
// NOTE: Currently the members of this have no effect on late initialization of fields.
@@ -193,6 +205,9 @@ type FieldConfig struct {
193205
// From instructs the code generator that the value of the field should
194206
// be retrieved from the specified operation and member path
195207
From *SourceFieldConfig `json:"from,omitempty"`
208+
// CustomField instructs the code generator to create a new field that does
209+
// not exist in the SDK.
210+
CustomField *CustomFieldConfig `json:"custom_field,omitempty"`
196211
// Compare instructs the code generator how to produce code that compares
197212
// the value of the field in two resources
198213
Compare *CompareFieldConfig `json:"compare,omitempty"`

pkg/model/model.go

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -137,25 +137,45 @@ func (m *Model) GetCRDs() ([]*CRD, error) {
137137
// It's a Status field...
138138
continue
139139
}
140-
if fieldConfig.From == nil {
141-
// Isn't an additional Spec field...
142-
continue
143-
}
144-
from := fieldConfig.From
145-
memberShapeRef, found := m.SDKAPI.GetInputShapeRef(
146-
from.Operation, from.Path,
147-
)
148-
if found {
149-
memberNames := names.New(targetFieldName)
150-
crd.AddSpecField(memberNames, memberShapeRef)
151-
} else {
152-
// This is a compile-time failure, just bomb out...
153-
msg := fmt.Sprintf(
154-
"unknown additional Spec field with Op: %s and Path: %s",
140+
141+
var found bool
142+
var memberShapeRef *awssdkmodel.ShapeRef
143+
144+
if fieldConfig.From != nil {
145+
from := fieldConfig.From
146+
memberShapeRef, found = m.SDKAPI.GetInputShapeRef(
155147
from.Operation, from.Path,
156148
)
157-
panic(msg)
149+
if !found {
150+
// This is a compile-time failure, just bomb out...
151+
msg := fmt.Sprintf(
152+
"unknown additional Spec field with Op: %s and Path: %s",
153+
from.Operation, from.Path,
154+
)
155+
panic(msg)
156+
}
157+
} else if fieldConfig.CustomField != nil {
158+
customField := fieldConfig.CustomField
159+
if customField.ListOf != "" {
160+
memberShapeRef = m.SDKAPI.GetCustomShapeRef(customField.ListOf)
161+
} else {
162+
memberShapeRef = m.SDKAPI.GetCustomShapeRef(customField.MapOf)
163+
}
164+
if memberShapeRef == nil {
165+
// This is a compile-time failure, just bomb out...
166+
msg := fmt.Sprintf(
167+
"unknown additional Spec field with custom field %+v",
168+
customField,
169+
)
170+
panic(msg)
171+
}
172+
} else {
173+
// Spec field is not well defined
174+
continue
158175
}
176+
177+
memberNames := names.New(targetFieldName)
178+
crd.AddSpecField(memberNames, memberShapeRef)
159179
}
160180

161181
// Now process the fields that will go into the Status struct. We want
@@ -209,25 +229,45 @@ func (m *Model) GetCRDs() ([]*CRD, error) {
209229
// It's a Spec field...
210230
continue
211231
}
212-
if fieldConfig.From == nil {
213-
// Isn't an additional Status field...
214-
continue
215-
}
216-
from := fieldConfig.From
217-
memberShapeRef, found := m.SDKAPI.GetOutputShapeRef(
218-
from.Operation, from.Path,
219-
)
220-
if found {
221-
memberNames := names.New(targetFieldName)
222-
crd.AddStatusField(memberNames, memberShapeRef)
223-
} else {
224-
// This is a compile-time failure, just bomb out...
225-
msg := fmt.Sprintf(
226-
"unknown additional Status field with Op: %s and Path: %s",
232+
233+
var found bool
234+
var memberShapeRef *awssdkmodel.ShapeRef
235+
236+
if fieldConfig.From != nil {
237+
from := fieldConfig.From
238+
memberShapeRef, found = m.SDKAPI.GetOutputShapeRef(
227239
from.Operation, from.Path,
228240
)
229-
panic(msg)
241+
if !found {
242+
// This is a compile-time failure, just bomb out...
243+
msg := fmt.Sprintf(
244+
"unknown additional Status field with Op: %s and Path: %s",
245+
from.Operation, from.Path,
246+
)
247+
panic(msg)
248+
}
249+
} else if fieldConfig.CustomField != nil {
250+
customField := fieldConfig.CustomField
251+
if customField.ListOf != "" {
252+
memberShapeRef = m.SDKAPI.GetCustomShapeRef(customField.ListOf)
253+
} else {
254+
memberShapeRef = m.SDKAPI.GetCustomShapeRef(customField.MapOf)
255+
}
256+
if memberShapeRef == nil {
257+
// This is a compile-time failure, just bomb out...
258+
msg := fmt.Sprintf(
259+
"unknown additional Status field with custom field %+v",
260+
customField,
261+
)
262+
panic(msg)
263+
}
264+
} else {
265+
// Status field is not well defined
266+
continue
230267
}
268+
269+
memberNames := names.New(targetFieldName)
270+
crd.AddStatusField(memberNames, memberShapeRef)
231271
}
232272

233273
crds = append(crds, crd)

pkg/model/multiversion/manager.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@ func NewAPIVersionManager(
7272
return nil, fmt.Errorf("cannot read sdk git repository: %v", err)
7373
}
7474

75-
SDKAPIHelper := acksdk.NewHelper(sdkCacheDir)
76-
7775
// create model for each non-deprecated api version
7876
models := map[string]*ackmodel.Model{}
7977
for _, version := range metadata.APIVersions {
@@ -90,23 +88,24 @@ func NewAPIVersionManager(
9088
return nil, fmt.Errorf("could not find API info for API version %s", version.APIVersion)
9189
}
9290

93-
err = SDKAPIHelper.WithSDKVersion(apiInfo.AWSSDKVersion)
91+
cfg, err := ackgenconfig.New(apiInfo.GeneratorConfigPath, defaultConfig)
9492
if err != nil {
9593
return nil, err
9694
}
9795

98-
cfg, err := ackgenconfig.New(apiInfo.GeneratorConfigPath, defaultConfig)
96+
sdkAPIHelper := acksdk.NewHelper(sdkCacheDir, cfg)
97+
err = sdkAPIHelper.WithSDKVersion(apiInfo.AWSSDKVersion)
9998
if err != nil {
10099
return nil, err
101100
}
102101

103-
SDKAPI, err := SDKAPIHelper.API(servicePackageName)
102+
sdkAPI, err := sdkAPIHelper.API(servicePackageName)
104103
if err != nil {
105104
return nil, err
106105
}
107106

108107
i, err := ackmodel.New(
109-
SDKAPI,
108+
sdkAPI,
110109
servicePackageName,
111110
version.APIVersion,
112111
cfg,

pkg/model/sdk_api.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const (
3434
type SDKAPI struct {
3535
API *awssdkmodel.API
3636
APIGroupSuffix string
37+
CustomShapes []*CustomShape
3738
// A map of operation type and resource name to
3839
// aws-sdk-go/private/model/api.Operation structs
3940
opMap *OperationMap
@@ -75,6 +76,36 @@ func (a *SDKAPI) GetOperationMap(cfg *ackgenconfig.Config) *OperationMap {
7576
return &opMap
7677
}
7778

79+
// GetCustomShapeRef finds a ShapeRef for a custom shape using either its member
80+
// or its value shape name.
81+
func (a *SDKAPI) GetCustomShapeRef(shapeName string) *awssdkmodel.ShapeRef {
82+
customList := a.getCustomListRef(shapeName)
83+
if customList != nil {
84+
return customList
85+
}
86+
return a.getCustomMapRef(shapeName)
87+
}
88+
89+
// getCustomListRef finds a ShapeRef for a supplied custom list field
90+
func (a *SDKAPI) getCustomListRef(memberShapeName string) *awssdkmodel.ShapeRef {
91+
for _, shape := range a.CustomShapes {
92+
if shape.MemberShapeName != nil && *shape.MemberShapeName == memberShapeName {
93+
return shape.ShapeRef
94+
}
95+
}
96+
return nil
97+
}
98+
99+
// getCustomMapRef finds a ShapeRef for a supplied custom map field
100+
func (a *SDKAPI) getCustomMapRef(valueShapeName string) *awssdkmodel.ShapeRef {
101+
for _, shape := range a.CustomShapes {
102+
if shape.ValueShapeName != nil && *shape.ValueShapeName == valueShapeName {
103+
return shape.ShapeRef
104+
}
105+
}
106+
return nil
107+
}
108+
78109
// GetInputShapeRef finds a ShapeRef for a supplied member path (dot-notation)
79110
// for given API operation
80111
func (a *SDKAPI) GetInputShapeRef(
@@ -267,3 +298,32 @@ func getMemberByPath(
267298
}
268299
return nil, false
269300
}
301+
302+
// CustomShape represents a shape created by the generator that does not exist
303+
// in the standard AWS SDK models.
304+
type CustomShape struct {
305+
Shape *awssdkmodel.Shape
306+
ShapeRef *awssdkmodel.ShapeRef
307+
MemberShapeName *string
308+
ValueShapeName *string
309+
}
310+
311+
// NewCustomListShape creates a custom shape object for a new list.
312+
func NewCustomListShape(shape *awssdkmodel.Shape, ref *awssdkmodel.ShapeRef, memberShapeName string) *CustomShape {
313+
return &CustomShape{
314+
Shape: shape,
315+
ShapeRef: ref,
316+
MemberShapeName: &memberShapeName,
317+
ValueShapeName: nil,
318+
}
319+
}
320+
321+
// NewCustomMapShape creates a custom shape object for a new map.
322+
func NewCustomMapShape(shape *awssdkmodel.Shape, ref *awssdkmodel.ShapeRef, valueShapeName string) *CustomShape {
323+
return &CustomShape{
324+
Shape: shape,
325+
ShapeRef: ref,
326+
MemberShapeName: nil,
327+
ValueShapeName: &valueShapeName,
328+
}
329+
}

pkg/model/types_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ package model_test
33
import (
44
"testing"
55

6-
"github.com/stretchr/testify/assert"
7-
86
"github.com/aws-controllers-k8s/code-generator/pkg/model"
7+
"github.com/stretchr/testify/assert"
98
)
109

1110
func TestReplacePkgName(t *testing.T) {

0 commit comments

Comments
 (0)