Skip to content

Commit 6b6f1c2

Browse files
committed
replace nested field SecretKeyReference GoType
In this patch, we post-process the `pkg/model.TypeDef` objects that are constructed by the code generator for nested fields of type "structure". This post-processing is used to replace the GoType of an Attr representing a Secret field in a nested parent field. For example, the AmazonMQ `Broker` resource has a `Spec.Users` top-level field. This top-level field is of type `[]*User`. The `User` Go struct type is constructed from a `pkg/model.TypeDef` object that describes the struct's attributes. One of those struct attributes is called `Password`, and we want to replace the Go type for this `Attr` from `string` to `*ackv1alpha1.SecretKeyReference`. In order to do this Go type replacement, we need to search through the TypeDef objects, looking for the parent field TypeDef, then look through that TypeDef's Attr collection for the Attr with the same name as our Field that has a FieldConfig.IsSecret setting of `true`. Issue aws-controllers-k8s/community#743
1 parent 58c5ed9 commit 6b6f1c2

File tree

5 files changed

+204
-5
lines changed

5 files changed

+204
-5
lines changed

pkg/generate/generator.go

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,12 +399,129 @@ func (g *Generator) GetTypeDefs() ([]*ackmodel.TypeDef, map[string]string, error
399399
sort.Slice(tdefs, func(i, j int) bool {
400400
return tdefs[i].Names.Camel < tdefs[j].Names.Camel
401401
})
402+
g.processNestedFieldTypeDefs(tdefs)
402403
g.typeDefs = tdefs
403404
g.typeImports = timports
404405
g.typeRenames = trenames
405406
return tdefs, timports, nil
406407
}
407408

409+
// processNestedFieldTypeDefs updates the supplied TypeDef structs' if a nested
410+
// field has been configured with a type overriding FieldConfig -- such as
411+
// FieldConfig.IsSecret.
412+
func (g *Generator) processNestedFieldTypeDefs(
413+
tdefs []*ackmodel.TypeDef,
414+
) {
415+
crds, _ := g.GetCRDs()
416+
for _, crd := range crds {
417+
for fieldPath, field := range crd.Fields {
418+
if !strings.Contains(fieldPath, ".") {
419+
// top-level fields have already had their structure
420+
// transformed during the CRD.AddSpecField and
421+
// CRD.AddStatusField methods. All we need to do here is look
422+
// at nested fields, which are identifiable as fields with
423+
// field paths contains a dot (".")
424+
continue
425+
}
426+
if field.FieldConfig == nil {
427+
// Likewise, we don't need to transform any TypeDef if the
428+
// nested field doesn't have a FieldConfig instructing us to
429+
// treat this field differently.
430+
continue
431+
}
432+
if field.FieldConfig.IsSecret {
433+
// Find the TypeDef that was created for the *containing*
434+
// secret field struct. For example, assume the nested field
435+
// path `Users..Password`, we'd want to find the TypeDef that
436+
// was created for the `Users` field's element type (which is a
437+
// struct)
438+
replaceSecretAttrGoType(crd, field, tdefs)
439+
}
440+
}
441+
}
442+
}
443+
444+
// replaceSecretAttrGoType replaces a nested field ackmodel.Attr's GoType with
445+
// `*ackv1alpha1.SecretKeyReference`.
446+
func replaceSecretAttrGoType(
447+
crd *ackmodel.CRD,
448+
field *ackmodel.Field,
449+
tdefs []*ackmodel.TypeDef,
450+
) {
451+
fieldPath := field.Path
452+
parentFieldPath := ackmodel.ParentFieldPath(field.Path)
453+
parentField, ok := crd.Fields[parentFieldPath]
454+
if !ok {
455+
msg := fmt.Sprintf(
456+
"Cannot find parent field at parent path %s for %s",
457+
parentFieldPath,
458+
fieldPath,
459+
)
460+
panic(msg)
461+
}
462+
if parentField.ShapeRef == nil {
463+
msg := fmt.Sprintf(
464+
"parent field at parent path %s has a nil ShapeRef!",
465+
parentFieldPath,
466+
)
467+
panic(msg)
468+
}
469+
parentFieldShape := parentField.ShapeRef.Shape
470+
parentFieldShapeName := parentField.ShapeRef.ShapeName
471+
parentFieldShapeType := parentFieldShape.Type
472+
// For list and map types, we need to grab the element/value
473+
// type, since that's the type def we need to modify.
474+
if parentFieldShapeType == "list" {
475+
if parentFieldShape.MemberRef.Shape.Type != "structure" {
476+
msg := fmt.Sprintf(
477+
"parent field at parent path %s is a list type with a non-structure element member shape %s!",
478+
parentFieldPath,
479+
parentFieldShape.MemberRef.Shape.Type,
480+
)
481+
panic(msg)
482+
}
483+
parentFieldShapeName = parentField.ShapeRef.Shape.MemberRef.ShapeName
484+
} else if parentFieldShapeType == "map" {
485+
if parentFieldShape.ValueRef.Shape.Type != "structure" {
486+
msg := fmt.Sprintf(
487+
"parent field at parent path %s is a map type with a non-structure value member shape %s!",
488+
parentFieldPath,
489+
parentFieldShape.ValueRef.Shape.Type,
490+
)
491+
panic(msg)
492+
}
493+
parentFieldShapeName = parentField.ShapeRef.Shape.ValueRef.ShapeName
494+
}
495+
var parentTypeDef *ackmodel.TypeDef
496+
for _, tdef := range tdefs {
497+
if tdef.Names.Original == parentFieldShapeName {
498+
parentTypeDef = tdef
499+
}
500+
}
501+
if parentTypeDef == nil {
502+
msg := fmt.Sprintf(
503+
"unable to find associated TypeDef for parent field "+
504+
"at parent path %s!",
505+
parentFieldPath,
506+
)
507+
panic(msg)
508+
}
509+
// Now we modify the parent type def's Attr that corresponds to
510+
// the secret field...
511+
attr, found := parentTypeDef.Attrs[field.Names.Camel]
512+
if !found {
513+
msg := fmt.Sprintf(
514+
"unable to find attr %s in parent TypeDef %s "+
515+
"at parent path %s!",
516+
field.Names.Camel,
517+
parentTypeDef.Names.Original,
518+
parentFieldPath,
519+
)
520+
panic(msg)
521+
}
522+
attr.GoType = "*ackv1alpha1.SecretKeyReference"
523+
}
524+
408525
// processNestedFields is responsible for walking all of the CRDs' Spec and
409526
// Status fields' Shape objects and adding `pkg/model.Field` objects for all
410527
// nested fields along with that `Field`'s `Config` object that allows us to
@@ -447,7 +564,6 @@ func (g *Generator) processNestedField(
447564
g.processNestedMapField(crd, field.Path+"..", field)
448565
}
449566
}
450-
// TODO(jaypipes): Handle Attribute-based fields...
451567
}
452568

453569
// processNestedStructField recurses through the members of a nested field that

pkg/generate/mq_test.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@ func TestMQ_Broker(t *testing.T) {
2828

2929
g := testutil.NewGeneratorForService(t, "mq")
3030

31-
crds, err := g.GetCRDs()
32-
require.Nil(err)
33-
34-
crd := getCRDByName("Broker", crds)
31+
crd := testutil.GetCRDByName(t, g, "Broker")
3532
require.NotNil(crd)
3633

3734
// We want to verify that the `Password` field of the `Spec.Users` field
@@ -43,4 +40,14 @@ func TestMQ_Broker(t *testing.T) {
4340
require.True(found)
4441
require.NotNil(passField.FieldConfig)
4542
assert.True(passField.FieldConfig.IsSecret)
43+
44+
// We now verify that the User TypeDef that is inferred by the code
45+
// generator has had its Go type changed from `string` to
46+
// `*ackv1alpha1.SecretKeyReference` as part of the nested fields
47+
// post-processing of type defs in the GetTypeDefs() method.
48+
tdef := testutil.GetTypeDefByName(t, g, "User")
49+
require.NotNil(tdef)
50+
passAttr, found := tdef.Attrs["Password"]
51+
require.True(found)
52+
assert.Equal("*ackv1alpha1.SecretKeyReference", passAttr.GoType)
4653
}

pkg/model/field.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
package model
1515

1616
import (
17+
"strings"
18+
1719
ackgenconfig "github.com/aws-controllers-k8s/code-generator/pkg/generate/config"
1820
"github.com/aws-controllers-k8s/code-generator/pkg/names"
1921
"github.com/aws-controllers-k8s/code-generator/pkg/util"
@@ -61,6 +63,24 @@ func (f *Field) IsRequired() bool {
6163
return util.InStrings(f.Names.ModelOriginal, f.CRD.Ops.Create.InputRef.Shape.Required)
6264
}
6365

66+
// ParentFieldPath takes a field path and returns the field path of the
67+
// containing "parent" field. For example, if the field path
68+
// `Users..Credentials.Login` is passed in, this function returns
69+
// `Users..Credentials`. If `Users..Password` is supplied, this function
70+
// returns `Users`, etc.
71+
func ParentFieldPath(path string) string {
72+
parts := strings.Split(path, ".")
73+
// Pop the last element of the supplied field path
74+
parts = parts[0 : len(parts)-1]
75+
// If the parent field's type is a list or map, there will be two dots ".."
76+
// in the supplied field path. We don't want the returned field path to end
77+
// in a dot, since that would be invalid, so we trim it off here
78+
if parts[len(parts)-1] == "" {
79+
parts = parts[0 : len(parts)-1]
80+
}
81+
return strings.Join(parts, ".")
82+
}
83+
6484
// NewField returns a pointer to a new Field object
6585
func NewField(
6686
crd *CRD,

pkg/model/field_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package model_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
"github.com/aws-controllers-k8s/code-generator/pkg/model"
9+
)
10+
11+
func TestParentFieldPath(t *testing.T) {
12+
assert := assert.New(t)
13+
testCases := []struct {
14+
subject string
15+
want string
16+
}{
17+
{
18+
"Repository.Name",
19+
"Repository",
20+
},
21+
{
22+
"Users..Password",
23+
"Users",
24+
},
25+
{
26+
"User.Credentials..Password",
27+
"User.Credentials",
28+
},
29+
}
30+
31+
for _, tc := range testCases {
32+
result := model.ParentFieldPath(
33+
tc.subject,
34+
)
35+
assert.Equal(tc.want, result)
36+
}
37+
}

pkg/testutil/get.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,22 @@ func GetCRDByName(
4040
}
4141
return nil
4242
}
43+
44+
// GetTypeDefByName returns a TypeDef model with the supplied name
45+
func GetTypeDefByName(
46+
t *testing.T,
47+
g *generate.Generator,
48+
name string,
49+
) *model.TypeDef {
50+
require := require.New(t)
51+
52+
tdefs, _, err := g.GetTypeDefs()
53+
require.Nil(err)
54+
55+
for _, tdef := range tdefs {
56+
if tdef.Names.Original == name {
57+
return tdef
58+
}
59+
}
60+
return nil
61+
}

0 commit comments

Comments
 (0)