Skip to content

Commit

Permalink
support custom fields of list/scalar/map types (#268)
Browse files Browse the repository at this point in the history
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 <jaypipes@gmail.com>
  • Loading branch information
jaypipes authored Jan 18, 2022
1 parent ff8a108 commit 02795c2
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 1 deletion.
19 changes: 18 additions & 1 deletion pkg/generate/config/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,5 +352,22 @@ type FieldConfig struct {
LateInitialize *LateInitializeConfig `json:"late_initialize,omitempty"`
// References instructs the code generator how to refer this field from
// other custom resource
References *ReferencesConfig `json:"references,omitempty"`
References *ReferencesConfig `json:"references,omitempty"`
// Type *overrides* the inferred Go type of the field. This is required for
// custom fields that are not inferred either as a Create Input/Output
// shape or via the SourceFieldConfig attribute.
//
// As an example, assume you have a Role resource where you want to add a
// custom spec field called Policies that is a slice of string pointers.
// The generator.yaml file might look like this:
//
// resources:
// Role:
// fields:
// Policies:
// type: []*string
//
// TODO(jaypipes,crtbry): Figure out if we can roll the CustomShape stuff
// into this type override...
Type *string `json:"type,omitempty"`
}
38 changes: 38 additions & 0 deletions pkg/model/field_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,41 @@ func TestMemberFields_Containers_MapOfStruct(t *testing.T) {
assert.Equal(valueField.ShapeRef.Shape.Type, "string")
assert.Equal(valueField.Path, "InputArtifacts.Value")
}

func TestCustomFieldType(t *testing.T) {
assert := assert.New(t)
require := require.New(t)

g := testutil.NewModelForService(t, "iam")

crds, err := g.GetCRDs()
require.Nil(err)

crd := getCRDByName("Role", crds)
require.NotNil(crd)

// The Role resource has a custom field called Policies that is of type
// []*string. This field is custom because it is not inferred via either
// the Create Input/Output shape or the SourceFieldConfig attribute in the
// generator.yaml file but rather via a `type` attribute of the
// FieldConfig, which overrides the Go type of the custom field.
policiesField := crd.Fields["Policies"]
require.NotNil(policiesField)

assert.Equal("[]*string", policiesField.GoType)
require.NotNil(policiesField.ShapeRef)

// A map and a scalar custom field are also added in the testdata
// generator.yaml file.
logConfigField := crd.Fields["LoggingConfig"]
require.NotNil(logConfigField)

assert.Equal("map[string]*bool", logConfigField.GoType)
require.NotNil(logConfigField.ShapeRef)

myIntField := crd.Fields["MyCustomInteger"]
require.NotNil(myIntField)

assert.Equal("*int64", myIntField.GoType)
require.NotNil(myIntField.ShapeRef)
}
7 changes: 7 additions & 0 deletions pkg/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,13 @@ func (m *Model) GetCRDs() ([]*CRD, error) {
)
panic(msg)
}
} else if fieldConfig.Type != nil {
// We have a custom field that has a type override and has not
// been inferred via the normal Create Input shape or via the
// SourceFieldConfig. Manually construct the field and its
// shape reference here.
typeOverride := *fieldConfig.Type
memberShapeRef = m.SDKAPI.GetShapeRefFromType(typeOverride)
} else {
// Spec field is not well defined
continue
Expand Down
80 changes: 80 additions & 0 deletions pkg/model/sdk_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package model

import (
"fmt"
"strings"

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

var (
// GoTypeToSDKShapeType is a map of Go types to aws-sdk-go
// private/model/api.Shape types
GoTypeToSDKShapeType = map[string]string{
"int": "integer",
"int64": "integer",
"float64": "float",
"string": "string",
"bool": "boolean",
"time.Time": "timestamp",
}
)

// SDKAPI contains an API model for a single AWS service API
type SDKAPI struct {
API *awssdkmodel.API
Expand Down Expand Up @@ -76,6 +90,72 @@ func (a *SDKAPI) GetOperationMap(cfg *ackgenconfig.Config) *OperationMap {
return &opMap
}

// GetShapeRefFromType returns a ShapeRef given a string representing the Go
// type. If no shape can be determined, returns nil.
func (a *SDKAPI) GetShapeRefFromType(
typeOverride string,
) *awssdkmodel.ShapeRef {
elemType := typeOverride
isSlice := strings.HasPrefix(typeOverride, "[]")
// TODO(jaypipes): Only handling maps with string keys at the moment...
isMap := strings.HasPrefix(typeOverride, "map[string]")
if isMap {
elemType = typeOverride[11:len(typeOverride)]
}
if isSlice {
elemType = typeOverride[2:len(typeOverride)]
}
isPtrElem := strings.HasPrefix(elemType, "*")
if isPtrElem {
elemType = elemType[1:len(elemType)]
}
// first check to see if the element type is a scalar and if it is, just
// create a ShapeRef to represent the type.
switch elemType {
case "string", "bool", "int", "int64", "float64", "time.Time":
sdkType, found := GoTypeToSDKShapeType[elemType]
if !found {
msg := fmt.Sprintf("GetShapeRefFromType: unsupported element type %s", elemType)
panic(msg)
}
if isSlice {
return &awssdkmodel.ShapeRef{
Shape: &awssdkmodel.Shape{
Type: "list",
MemberRef: awssdkmodel.ShapeRef{
Shape: &awssdkmodel.Shape{
Type: sdkType,
},
},
},
}
} else if isMap {
return &awssdkmodel.ShapeRef{
Shape: &awssdkmodel.Shape{
Type: "map",
KeyRef: awssdkmodel.ShapeRef{
Shape: &awssdkmodel.Shape{
Type: sdkType,
},
},
ValueRef: awssdkmodel.ShapeRef{
Shape: &awssdkmodel.Shape{
Type: sdkType,
},
},
},
}
} else {
return &awssdkmodel.ShapeRef{
Shape: &awssdkmodel.Shape{
Type: sdkType,
},
}
}
}
return nil
}

// GetCustomShapeRef finds a ShapeRef for a custom shape using either its member
// or its value shape name.
func (a *SDKAPI) GetCustomShapeRef(shapeName string) *awssdkmodel.ShapeRef {
Expand Down
8 changes: 8 additions & 0 deletions pkg/testdata/models/apis/iam/0000-00-00/generator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,11 @@ resources:
set:
# The input and output shapes are different...
- from: PermissionsBoundary.PermissionsBoundaryArn
# Test the custom field creation inference for simple scalar, list or map
# fields
Policies:
type: "[]*string"
LoggingConfig:
type: "map[string]*bool"
MyCustomInteger:
type: "*int64"

0 comments on commit 02795c2

Please sign in to comment.