Skip to content

Commit 02795c2

Browse files
authored
support custom fields of list/scalar/map types (#268)
Adds functionality for the generator.yaml file to contain a `FieldConfig.Type` attribute that is the Go type override string for a field. If the Go type of the field cannot be inferred via the Create Input/Output shape *or* via the `FieldConfig.From` (SourceFieldConfig) value, we can look at this new `FieldConfig.Type` value and construct a `aws-sdk-go/private/model/api:ShapeRef` object manually. Thus, with this patch, we can do something like this (example from the iam-controller's generator.yaml file, which is what I tested this patch on): ```yaml resources: Role: fields: Policies: type: "[]*string" ``` Signed-off-by: Jay Pipes <[email protected]>
1 parent ff8a108 commit 02795c2

File tree

5 files changed

+151
-1
lines changed

5 files changed

+151
-1
lines changed

Diff for: pkg/generate/config/field.go

+18-1
Original file line numberDiff line numberDiff line change
@@ -352,5 +352,22 @@ type FieldConfig struct {
352352
LateInitialize *LateInitializeConfig `json:"late_initialize,omitempty"`
353353
// References instructs the code generator how to refer this field from
354354
// other custom resource
355-
References *ReferencesConfig `json:"references,omitempty"`
355+
References *ReferencesConfig `json:"references,omitempty"`
356+
// Type *overrides* the inferred Go type of the field. This is required for
357+
// custom fields that are not inferred either as a Create Input/Output
358+
// shape or via the SourceFieldConfig attribute.
359+
//
360+
// As an example, assume you have a Role resource where you want to add a
361+
// custom spec field called Policies that is a slice of string pointers.
362+
// The generator.yaml file might look like this:
363+
//
364+
// resources:
365+
// Role:
366+
// fields:
367+
// Policies:
368+
// type: []*string
369+
//
370+
// TODO(jaypipes,crtbry): Figure out if we can roll the CustomShape stuff
371+
// into this type override...
372+
Type *string `json:"type,omitempty"`
356373
}

Diff for: pkg/model/field_test.go

+38
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,41 @@ func TestMemberFields_Containers_MapOfStruct(t *testing.T) {
104104
assert.Equal(valueField.ShapeRef.Shape.Type, "string")
105105
assert.Equal(valueField.Path, "InputArtifacts.Value")
106106
}
107+
108+
func TestCustomFieldType(t *testing.T) {
109+
assert := assert.New(t)
110+
require := require.New(t)
111+
112+
g := testutil.NewModelForService(t, "iam")
113+
114+
crds, err := g.GetCRDs()
115+
require.Nil(err)
116+
117+
crd := getCRDByName("Role", crds)
118+
require.NotNil(crd)
119+
120+
// The Role resource has a custom field called Policies that is of type
121+
// []*string. This field is custom because it is not inferred via either
122+
// the Create Input/Output shape or the SourceFieldConfig attribute in the
123+
// generator.yaml file but rather via a `type` attribute of the
124+
// FieldConfig, which overrides the Go type of the custom field.
125+
policiesField := crd.Fields["Policies"]
126+
require.NotNil(policiesField)
127+
128+
assert.Equal("[]*string", policiesField.GoType)
129+
require.NotNil(policiesField.ShapeRef)
130+
131+
// A map and a scalar custom field are also added in the testdata
132+
// generator.yaml file.
133+
logConfigField := crd.Fields["LoggingConfig"]
134+
require.NotNil(logConfigField)
135+
136+
assert.Equal("map[string]*bool", logConfigField.GoType)
137+
require.NotNil(logConfigField.ShapeRef)
138+
139+
myIntField := crd.Fields["MyCustomInteger"]
140+
require.NotNil(myIntField)
141+
142+
assert.Equal("*int64", myIntField.GoType)
143+
require.NotNil(myIntField.ShapeRef)
144+
}

Diff for: pkg/model/model.go

+7
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,13 @@ func (m *Model) GetCRDs() ([]*CRD, error) {
183183
)
184184
panic(msg)
185185
}
186+
} else if fieldConfig.Type != nil {
187+
// We have a custom field that has a type override and has not
188+
// been inferred via the normal Create Input shape or via the
189+
// SourceFieldConfig. Manually construct the field and its
190+
// shape reference here.
191+
typeOverride := *fieldConfig.Type
192+
memberShapeRef = m.SDKAPI.GetShapeRefFromType(typeOverride)
186193
} else {
187194
// Spec field is not well defined
188195
continue

Diff for: pkg/model/sdk_api.go

+80
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package model
1515

1616
import (
17+
"fmt"
1718
"strings"
1819

1920
awssdkmodel "github.com/aws/aws-sdk-go/private/model/api"
@@ -30,6 +31,19 @@ const (
3031
ConflictingNameSuffix = "_SDK"
3132
)
3233

34+
var (
35+
// GoTypeToSDKShapeType is a map of Go types to aws-sdk-go
36+
// private/model/api.Shape types
37+
GoTypeToSDKShapeType = map[string]string{
38+
"int": "integer",
39+
"int64": "integer",
40+
"float64": "float",
41+
"string": "string",
42+
"bool": "boolean",
43+
"time.Time": "timestamp",
44+
}
45+
)
46+
3347
// SDKAPI contains an API model for a single AWS service API
3448
type SDKAPI struct {
3549
API *awssdkmodel.API
@@ -76,6 +90,72 @@ func (a *SDKAPI) GetOperationMap(cfg *ackgenconfig.Config) *OperationMap {
7690
return &opMap
7791
}
7892

93+
// GetShapeRefFromType returns a ShapeRef given a string representing the Go
94+
// type. If no shape can be determined, returns nil.
95+
func (a *SDKAPI) GetShapeRefFromType(
96+
typeOverride string,
97+
) *awssdkmodel.ShapeRef {
98+
elemType := typeOverride
99+
isSlice := strings.HasPrefix(typeOverride, "[]")
100+
// TODO(jaypipes): Only handling maps with string keys at the moment...
101+
isMap := strings.HasPrefix(typeOverride, "map[string]")
102+
if isMap {
103+
elemType = typeOverride[11:len(typeOverride)]
104+
}
105+
if isSlice {
106+
elemType = typeOverride[2:len(typeOverride)]
107+
}
108+
isPtrElem := strings.HasPrefix(elemType, "*")
109+
if isPtrElem {
110+
elemType = elemType[1:len(elemType)]
111+
}
112+
// first check to see if the element type is a scalar and if it is, just
113+
// create a ShapeRef to represent the type.
114+
switch elemType {
115+
case "string", "bool", "int", "int64", "float64", "time.Time":
116+
sdkType, found := GoTypeToSDKShapeType[elemType]
117+
if !found {
118+
msg := fmt.Sprintf("GetShapeRefFromType: unsupported element type %s", elemType)
119+
panic(msg)
120+
}
121+
if isSlice {
122+
return &awssdkmodel.ShapeRef{
123+
Shape: &awssdkmodel.Shape{
124+
Type: "list",
125+
MemberRef: awssdkmodel.ShapeRef{
126+
Shape: &awssdkmodel.Shape{
127+
Type: sdkType,
128+
},
129+
},
130+
},
131+
}
132+
} else if isMap {
133+
return &awssdkmodel.ShapeRef{
134+
Shape: &awssdkmodel.Shape{
135+
Type: "map",
136+
KeyRef: awssdkmodel.ShapeRef{
137+
Shape: &awssdkmodel.Shape{
138+
Type: sdkType,
139+
},
140+
},
141+
ValueRef: awssdkmodel.ShapeRef{
142+
Shape: &awssdkmodel.Shape{
143+
Type: sdkType,
144+
},
145+
},
146+
},
147+
}
148+
} else {
149+
return &awssdkmodel.ShapeRef{
150+
Shape: &awssdkmodel.Shape{
151+
Type: sdkType,
152+
},
153+
}
154+
}
155+
}
156+
return nil
157+
}
158+
79159
// GetCustomShapeRef finds a ShapeRef for a custom shape using either its member
80160
// or its value shape name.
81161
func (a *SDKAPI) GetCustomShapeRef(shapeName string) *awssdkmodel.ShapeRef {

Diff for: pkg/testdata/models/apis/iam/0000-00-00/generator.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,11 @@ resources:
3535
set:
3636
# The input and output shapes are different...
3737
- from: PermissionsBoundary.PermissionsBoundaryArn
38+
# Test the custom field creation inference for simple scalar, list or map
39+
# fields
40+
Policies:
41+
type: "[]*string"
42+
LoggingConfig:
43+
type: "map[string]*bool"
44+
MyCustomInteger:
45+
type: "*int64"

0 commit comments

Comments
 (0)