Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 17 additions & 18 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
module github.com/aws-controllers-k8s/code-generator

go 1.22.0

toolchain go1.22.4
go 1.24.0

require (
github.com/aws-controllers-k8s/pkg v0.0.15
Expand All @@ -21,7 +19,7 @@ require (
github.com/samber/lo v1.37.0
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
golang.org/x/mod v0.17.0
golang.org/x/mod v0.24.0
k8s.io/apimachinery v0.31.0
sigs.k8s.io/controller-runtime v0.19.0
)
Expand All @@ -48,17 +46,17 @@ require (
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
Expand All @@ -74,6 +72,7 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kro-run/kro v0.2.2
github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
Expand All @@ -92,27 +91,27 @@ require (
github.com/xanzy/ssh-agent v0.3.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.36.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.org/x/tools v0.24.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.31.0 // indirect
k8s.io/apiextensions-apiserver v0.31.0 // indirect
k8s.io/apiextensions-apiserver v0.31.0
k8s.io/client-go v0.31.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/kube-openapi v0.0.0-20240816214639-573285566f34 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
Expand Down
78 changes: 36 additions & 42 deletions go.sum

Large diffs are not rendered by default.

15 changes: 6 additions & 9 deletions pkg/api/load.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


package api

import (
Expand Down Expand Up @@ -91,7 +89,6 @@ func loadAPI(modelPath, baseImport string, opts ...func(*API)) (*API, error) {
// fmt.Println("\n\n\n\n",*a.Operations["CreateFileSystem"])
// fmt.Println("\n",*a.Shapes["FileSystemDescription"])


if err = a.Setup(); err != nil {
return nil, err
}
Expand Down Expand Up @@ -127,11 +124,11 @@ func attachModelFiles(modelPath string, modelFiles ...modelLoader) error {
// pattern passed in. Returns the path of the model file to be loaded. Includes
// all versions of a service model.
//
// e.g:
// models/apis/*/*/api-2.json
// e.g:
// models/apis/*/*/api-2.json
//
// Or with specific model file:
// models/apis/service/version/api-2.json
// Or with specific model file:
// models/apis/service/version/api-2.json
func ExpandModelGlobPath(globs ...string) ([]string, error) {
modelPaths := []string{}

Expand All @@ -154,7 +151,7 @@ func ExpandModelGlobPath(globs ...string) ([]string, error) {
// Uses the third from last path element to determine unique service. Only one
// service version will be included.
//
// models/apis/service/version/api-2.json
// models/apis/service/version/api-2.json
func TrimModelServiceVersions(modelPaths []string) (include, exclude []string) {
sort.Strings(modelPaths)

Expand Down Expand Up @@ -229,7 +226,7 @@ func (a *API) Setup() error {

a.fixStutterNames()
if err := a.validateShapeNames(); err != nil {
log.Fatalf(err.Error())
log.Fatalf("%v", err.Error())
}
a.renameExportable()
a.applyShapeNameAliases()
Expand Down
16 changes: 16 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ type Config struct {
// documentdb.
// This will also change the helm chart and image names.
ControllerName string `json:"controller_name,omitempty"`
// CustomShapes defines custom structure types that can be referenced in resource fields.
// The outer map is keyed by shape name (e.g., "ReplicaStatus"), and the inner map
// is keyed by field name with string values representing field types.
//
// Example:
// custom_shapes:
// ReplicaStatus:
// Status: string
// Region: string
// Description: string
// These shapes can be referenced in a resource's fields section using:
// custom_field:
// list_of: ShapeName # For arrays of the shape
// map_of: ShapeName # For maps with the shape as values
//
CustomShapes map[string]map[string]interface{} `json:"custom_shapes,omitempty"`
}

// SDKNames contains information on the SDK Client package. More precisely
Expand Down
5 changes: 2 additions & 3 deletions pkg/model/crd.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,7 @@ func (r *CRD) GetPrimaryKeyField() (*Field, error) {
var found bool
primaryField, found = r.Fields[fPath]
if !found {
return nil, fmt.Errorf("could not find field with path " + fPath +
" for primary key " + fieldName)
return nil, fmt.Errorf("could not find field with path %s for primary key %s", fPath, fieldName)
}
}
return primaryField, nil
Expand Down Expand Up @@ -835,7 +834,7 @@ func (r *CRD) ReferencedServiceNames() (serviceNames []string) {
}
}

for serviceName, _ := range serviceNamesMap {
for serviceName := range serviceNamesMap {
serviceNames = append(serviceNames, serviceName)
}
sort.Strings(serviceNames)
Expand Down
40 changes: 40 additions & 0 deletions pkg/model/model_dynamodb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,43 @@ func TestDynamoDB_Table(t *testing.T) {
}
assert.Equal(expSpecFieldCamel, attrCamelNames(specFields))
}

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

g := testutil.NewModelForServiceWithOptions(t, "dynamodb", &testutil.TestingModelOptions{
GeneratorConfigFile: "generator-with-custom-shapes.yaml",
})

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

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

// Verify the ReplicaStates field exists
assert.Contains(crd.StatusFields, "ReplicaStates")
replicasDescField := crd.StatusFields["ReplicaStates"]
require.NotNil(replicasDescField)

replicasStateShape := replicasDescField.ShapeRef.Shape.MemberRef.Shape
require.NotNil(replicasStateShape)

// Verify all the expected fields exist in the RepicasState shape
expectedFields := []string{
"RegionName",
"RegionStatus",
"RegionStatusDescription",
"RegionStatusPercentProgress",
"RegionInaccessibleDateTime",
}

for _, fieldName := range expectedFields {
assert.Contains(replicasStateShape.MemberRefs, fieldName, "RepicasState shape is missing field: "+fieldName)
field := replicasStateShape.MemberRefs[fieldName]
assert.Equal("string", field.Shape.Type, "Field "+fieldName+" should be of type string")
}

assert.Equal(len(expectedFields), len(replicasStateShape.MemberRefs), "ReplicasState shape has unexpected number of fields")
}
2 changes: 1 addition & 1 deletion pkg/model/multiversion/delta.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ func ComputeFieldDeltas(
if !ok2 {
// if a field was renamed and we can't find it in dstNames, something
// very wrong happened during CRD loading.
return nil, fmt.Errorf("cannot find renamed field %s " + newName)
return nil, fmt.Errorf("cannot find renamed field %s in %s", newName, srcName)
}

// mark field as visited, both old and new names.
Expand Down
6 changes: 3 additions & 3 deletions pkg/model/sdk_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,14 @@ func (a *SDKAPI) GetShapeRefFromType(
// TODO(jaypipes): Only handling maps with string keys at the moment...
isMap := strings.HasPrefix(typeOverride, "map[string]")
if isMap {
elemType = typeOverride[11:len(typeOverride)]
elemType = typeOverride[11:]
}
if isSlice {
elemType = typeOverride[2:len(typeOverride)]
elemType = typeOverride[2:]
}
isPtrElem := strings.HasPrefix(elemType, "*")
if isPtrElem {
elemType = elemType[1:len(elemType)]
elemType = elemType[1:]
}
// first check to see if the element type is a scalar and if it is, just
// create a ShapeRef to represent the type.
Expand Down
88 changes: 86 additions & 2 deletions pkg/sdk/custom_shapes.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import (
"errors"
"fmt"

awssdkmodel "github.com/aws-controllers-k8s/code-generator/pkg/api"
simpleschema "github.com/kro-run/kro/pkg/simpleschema"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"

awssdkmodel "github.com/aws-controllers-k8s/code-generator/pkg/api"
ackmodel "github.com/aws-controllers-k8s/code-generator/pkg/model"
)

Expand All @@ -43,6 +45,10 @@ type customShapeInjector struct {
func (h *Helper) InjectCustomShapes(sdkapi *ackmodel.SDKAPI) error {
injector := customShapeInjector{sdkapi}

if err := injector.injectSimpleSchemaShapes(h.cfg.CustomShapes); err != nil {
return err
}

for _, memberShape := range h.cfg.GetCustomMapFieldMembers() {
customShape, err := injector.newMap(memberShape)
if err != nil {
Expand All @@ -52,7 +58,6 @@ func (h *Helper) InjectCustomShapes(sdkapi *ackmodel.SDKAPI) error {
sdkapi.API.Shapes[customShape.Shape.ShapeName] = customShape.Shape
sdkapi.CustomShapes = append(sdkapi.CustomShapes, customShape)
}

for _, memberShape := range h.cfg.GetCustomListFieldMembers() {
customShape, err := injector.newList(memberShape)
if err != nil {
Expand All @@ -62,10 +67,89 @@ func (h *Helper) InjectCustomShapes(sdkapi *ackmodel.SDKAPI) error {
sdkapi.API.Shapes[customShape.Shape.ShapeName] = customShape.Shape
sdkapi.CustomShapes = append(sdkapi.CustomShapes, customShape)
}
return nil
}

// injectSimpleSchemaShapes processes custom shapes from the top-level custom_shapes
// section and injects them into the SDK API model.
// Only string types are supported - all other types will cause a panic.
func (i *customShapeInjector) injectSimpleSchemaShapes(customShapes map[string]map[string]interface{}) error {
if len(customShapes) == 0 {
return nil
}

apiShapeNames := i.sdkAPI.API.ShapeNames()
for shapeName, fieldsMap := range customShapes {
// check for duplicates
for _, as := range apiShapeNames {
if as == shapeName {
return fmt.Errorf("CustomType name %s already exists in the API", shapeName)
}
}
openAPISchema, err := simpleschema.ToOpenAPISpec(fieldsMap)
if err != nil {
return err
}

// Create and register the base structure shape
shape, shapeRef := i.newStructureShape(shapeName, openAPISchema)
i.sdkAPI.API.Shapes[shape.ShapeName] = shape
i.sdkAPI.CustomShapes = append(i.sdkAPI.CustomShapes, &ackmodel.CustomShape{
Shape: shape,
ShapeRef: shapeRef,
MemberShapeName: nil,
ValueShapeName: nil,
})
}

return nil
}

// newStructureShape creates a base shape with its member fields
func (i *customShapeInjector) newStructureShape(
shapeName string,
openAPISchema *apiextv1.JSONSchemaProps,
) (*awssdkmodel.Shape, *awssdkmodel.ShapeRef) {
shape := &awssdkmodel.Shape{
API: i.sdkAPI.API,
ShapeName: shapeName,
Type: "structure",
Documentation: "// Custom ACK type for " + shapeName,
MemberRefs: make(map[string]*awssdkmodel.ShapeRef),
}

properties := openAPISchema.Properties
for fieldName, propObj := range properties {
propType := propObj.Type
if propType != "string" {
panic(fmt.Sprintf("Field %s in shape %s has non-string type '%s'",
fieldName, shapeName, propType))
}
addStringFieldToShape(i.sdkAPI, shape, fieldName, shapeName)
}

shapeRef := i.createShapeRefForMember(shape)
return shape, shapeRef
}

// addStringFieldToShape adds a string field to the parent shape
func addStringFieldToShape(
sdkapi *ackmodel.SDKAPI,
parentShape *awssdkmodel.Shape,
fieldName string,
shapeName string,
) {
injector := customShapeInjector{sdkapi}
fieldShape := &awssdkmodel.Shape{
API: sdkapi.API,
ShapeName: fieldName,
Type: "string",
}

sdkapi.API.Shapes[fieldShape.ShapeName] = fieldShape
parentShape.MemberRefs[fieldName] = injector.createShapeRefForMember(fieldShape)
}

// createShapeRefForMember creates a minimal ShapeRef type to encapsulate a
// shape.
func (i *customShapeInjector) createShapeRefForMember(shape *awssdkmodel.Shape) *awssdkmodel.ShapeRef {
Expand Down
6 changes: 4 additions & 2 deletions pkg/sdk/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ func (h *Helper) API(serviceModelName string) (*model.SDKAPI, error) {
_ = api.ServicePackageDoc()
sdkapi := model.NewSDKAPI(api, h.APIGroupSuffix)

h.InjectCustomShapes(sdkapi)

err := h.InjectCustomShapes(sdkapi)
if err != nil {
return nil, err
}
return sdkapi, nil
}
return nil, ErrServiceNotFound
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
custom_shapes:
RepicasState:
RegionName: string
RegionStatus: string
RegionStatusDescription: string
RegionStatusPercentProgress: string
RegionInaccessibleDateTime: string

resources:
Table:
fields:
ReplicaStates:
custom_field:
list_of: RepicasState
is_read_only: true