diff --git a/pkg/generate/ack/apis.go b/pkg/generate/ack/apis.go index f1bca726..765a3efc 100644 --- a/pkg/generate/ack/apis.go +++ b/pkg/generate/ack/apis.go @@ -52,7 +52,7 @@ func APIs( if err != nil { return nil, err } - typeDefs, typeImports, err := g.GetTypeDefs() + typeDefs, err := g.GetTypeDefs() if err != nil { return nil, err } @@ -73,7 +73,6 @@ func APIs( metaVars, enumDefs, typeDefs, - typeImports, } for _, path := range apisTemplatePaths { outPath := strings.TrimSuffix(filepath.Base(path), ".tpl") @@ -101,7 +100,6 @@ type templateAPIVars struct { templateset.MetaVars EnumDefs []*ackmodel.EnumDef TypeDefs []*ackmodel.TypeDef - Imports map[string]string } // templateCRDVars contains template variables for the template that outputs Go diff --git a/pkg/generate/apigwv2_test.go b/pkg/generate/apigwv2_test.go index d55d89ba..dfcfedad 100644 --- a/pkg/generate/apigwv2_test.go +++ b/pkg/generate/apigwv2_test.go @@ -28,21 +28,12 @@ func TestAPIGatewayV2_GetTypeDefs(t *testing.T) { g := testutil.NewGeneratorForService(t, "apigatewayv2") - tdefs, timports, err := g.GetTypeDefs() - require.Nil(err) - - // APIGatewayV2 shapes have time.Time ("timestamp") types and so - // GetTypeDefs() should return the special-cased with apimachinery/metav1 - // import, aliased as "metav1" - expImports := map[string]string{"k8s.io/apimachinery/pkg/apis/meta/v1": "metav1"} - assert.Equal(expImports, timports) - // There is an "Api" Shape that is a struct that is an element of the // GetApis Operation. Its name conflicts with the CRD called API and thus // we need to check that its cleaned name is set to API_SDK (the _SDK // suffix is appended to the type name to avoid the conflict with // CRD-specific structs. - tdef := getTypeDefByName("Api", tdefs) + tdef := testutil.GetTypeDefByName(t, g, "Api") require.NotNil(tdef) assert.Equal("API_SDK", tdef.Names.Camel) diff --git a/pkg/generate/code/set_sdk.go b/pkg/generate/code/set_sdk.go index 7a2c764f..ff838153 100644 --- a/pkg/generate/code/set_sdk.go +++ b/pkg/generate/code/set_sdk.go @@ -165,7 +165,6 @@ func SetSDK( } opConfig, override := cfg.OverrideValues(op.Name) - fieldConfigs := cfg.ResourceFields(r.Names.Original) for memberIndex, memberName := range inputShape.MemberNames() { if r.UnpacksAttributesMap() && memberName == "Attributes" { continue @@ -190,22 +189,6 @@ func SetSDK( } } - fc, ok := fieldConfigs[memberName] - if ok && fc.IsSecret { - out += fmt.Sprintf("%sif %s.Spec.%s != nil {\n", indent, sourceVarName, memberName) - out += fmt.Sprintf("%s%stmpSecret, err := rm.rr.SecretValueFromReference(ctx, %s.Spec.%s)\n", indent, - indent, sourceVarName, memberName) - out += fmt.Sprintf("%s%sif err != nil {\n", indent, indent) - out += fmt.Sprintf("%s%s%sreturn nil, err\n", indent, indent, indent) - out += fmt.Sprintf("%s%s}\n", indent, indent) - out += fmt.Sprintf("%s%sif tmpSecret != \"\" {\n", indent, indent) - out += fmt.Sprintf("%s%s%s%s.Set%s(%s)\n", indent, indent, indent, - targetVarName, memberName, "tmpSecret") - out += fmt.Sprintf("%s%s}\n", indent, indent) - out += fmt.Sprintf("%s}\n", indent) - continue - } - if r.IsPrimaryARNField(memberName) { // if ko.Status.ACKResourceMetadata != nil && ko.Status.ACKResourceMetadata.ARN != nil { // res.SetTopicArn(string(*ko.Status.ACKResourceMetadata.ARN)) @@ -243,7 +226,7 @@ func SetSDK( if found { sourceAdaptedVarName += cfg.PrefixConfig.SpecField } else { - f, found = r.StatusFields[memberName] + f, found = r.StatusFields[renamedName] if !found { // TODO(jaypipes): check generator config for exceptions? continue @@ -251,6 +234,18 @@ func SetSDK( sourceAdaptedVarName += cfg.PrefixConfig.StatusField } sourceAdaptedVarName += "." + f.Names.Camel + sourceFieldPath := f.Names.Camel + + if r.IsSecretField(memberName) { + out += setSDKForSecret( + cfg, r, + memberName, + targetVarName, + sourceAdaptedVarName, + indentLevel, + ) + continue + } memberShapeRef, _ := inputShape.MemberRefs[memberName] memberShape := memberShapeRef.Shape @@ -327,6 +322,7 @@ func SetSDK( cfg, r, memberName, memberVarName, + sourceFieldPath, sourceAdaptedVarName, memberShapeRef, indentLevel+1, @@ -336,6 +332,7 @@ func SetSDK( memberName, targetVarName, inputShape.Type, + sourceFieldPath, memberVarName, memberShapeRef, indentLevel+1, @@ -347,6 +344,7 @@ func SetSDK( memberName, targetVarName, inputShape.Type, + sourceFieldPath, sourceAdaptedVarName, memberShapeRef, indentLevel+1, @@ -514,6 +512,7 @@ func SetSDKGetAttributes( memberName, targetVarName, inputShape.Type, + cleanMemberName, sourceVarPath, field.ShapeRef, indentLevel+1, @@ -708,6 +707,7 @@ func SetSDKSetAttributes( memberName, targetVarName, inputShape.Type, + cleanMemberName, sourceVarPath, field.ShapeRef, indentLevel+1, @@ -730,6 +730,8 @@ func setSDKForContainer( targetFieldName string, // The variable name that we want to set a value to targetVarName string, + // The path to the field that we access our source value from + sourceFieldPath string, // The struct or struct field that we access our source value from sourceVarName string, // ShapeRef of the target struct field @@ -743,6 +745,7 @@ func setSDKForContainer( targetFieldName, targetVarName, targetShapeRef, + sourceFieldPath, sourceVarName, indentLevel, ) @@ -752,6 +755,7 @@ func setSDKForContainer( targetFieldName, targetVarName, targetShapeRef, + sourceFieldPath, sourceVarName, indentLevel, ) @@ -761,6 +765,7 @@ func setSDKForContainer( targetFieldName, targetVarName, targetShapeRef, + sourceFieldPath, sourceVarName, indentLevel, ) @@ -770,6 +775,7 @@ func setSDKForContainer( targetFieldName, targetVarName, targetShapeRef.Shape.Type, + sourceFieldPath, sourceVarName, targetShapeRef, indentLevel, @@ -777,6 +783,66 @@ func setSDKForContainer( } } +// setSDKForSecret returns a string of Go code that sets a target variable to +// the value of a Secret when the type of the source variable is a +// SecretKeyReference. +// +// The Go code output from this function looks like this: +// +// if ko.Spec.MasterUserPassword != nil { +// tmpSecret, err := rm.rr.SecretValueFromReference(ctx, ko.Spec.MasterUserPassword) +// if err != nil { +// return nil, err +// } +// if tmpSecret != "" { +// res.SetMasterUserPassword(tmpSecret) +// } +// } +func setSDKForSecret( + cfg *ackgenconfig.Config, + r *model.CRD, + // The name of the SDK Shape field we're setting + targetFieldName string, + // The variable name that we want to set a value on + targetVarName string, + // The CR field that we access our source value from + sourceVarName string, + indentLevel int, +) string { + out := "" + indent := strings.Repeat("\t", indentLevel) + secVar := "tmpSecret" + + // if ko.Spec.MasterUserPassword != nil { + out += fmt.Sprintf( + "%sif %s != nil {\n", + indent, sourceVarName, + ) + // tmpSecret, err := rm.rr.SecretValueFromReference(ctx, ko.Spec.MasterUserPassword) + out += fmt.Sprintf( + "%s\t%s, err := rm.rr.SecretValueFromReference(ctx, %s)\n", + indent, secVar, sourceVarName, + ) + // if err != nil { + // return nil, err + // } + out += fmt.Sprintf("%s\tif err != nil {\n", indent) + out += fmt.Sprintf("%s\t\treturn nil, err\n", indent) + out += fmt.Sprintf("%s\t}\n", indent) + // if tmpSecret != "" { + // res.SetMasterUserPassword(tmpSecret) + // } + out += fmt.Sprintf("%s\tif tmpSecret != \"\" {\n", indent) + out += fmt.Sprintf( + "%s\t\t%s.Set%s(%s)\n", + indent, targetVarName, targetFieldName, secVar, + ) + out += fmt.Sprintf("%s\t}\n", indent) + // } + out += fmt.Sprintf("%s}\n", indent) + return out +} + // setSDKForStruct returns a string of Go code that sets a target variable // value to a source variable when the type of the source variable is a struct. func setSDKForStruct( @@ -788,6 +854,8 @@ func setSDKForStruct( targetVarName string, // Shape Ref of the target struct field targetShapeRef *awssdkmodel.ShapeRef, + // The path to the field that we access our source value from + sourceFieldPath string, // The struct or struct field that we access our source value from sourceVarName string, indentLevel int, @@ -801,14 +869,28 @@ func setSDKForStruct( memberShape := memberShapeRef.Shape cleanMemberNames := names.New(memberName) cleanMemberName := cleanMemberNames.Camel - memberVarName := fmt.Sprintf("%sf%d", targetVarName, memberIndex) sourceAdaptedVarName := sourceVarName + "." + cleanMemberName + memberFieldPath := sourceFieldPath + "." + cleanMemberName + if r.IsSecretField(memberFieldPath) { + out += setSDKForSecret( + cfg, r, + memberName, + targetVarName, + sourceAdaptedVarName, + indentLevel, + ) + continue + } out += fmt.Sprintf( "%sif %s != nil {\n", indent, sourceAdaptedVarName, ) switch memberShape.Type { case "list", "structure", "map": { + memberVarName := fmt.Sprintf( + "%sf%d", + targetVarName, memberIndex, + ) out += varEmptyConstructorSDKType( cfg, r, memberVarName, @@ -819,6 +901,7 @@ func setSDKForStruct( cfg, r, memberName, memberVarName, + memberFieldPath, sourceAdaptedVarName, memberShapeRef, indentLevel+1, @@ -828,6 +911,7 @@ func setSDKForStruct( memberName, targetVarName, targetShape.Type, + memberFieldPath, memberVarName, memberShapeRef, indentLevel+1, @@ -839,6 +923,7 @@ func setSDKForStruct( memberName, targetVarName, targetShape.Type, + memberFieldPath, sourceAdaptedVarName, memberShapeRef, indentLevel+1, @@ -862,6 +947,8 @@ func setSDKForSlice( targetVarName string, // Shape Ref of the target struct field targetShapeRef *awssdkmodel.ShapeRef, + // The path to the field that we access our source value from + sourceFieldPath string, // The struct or struct field that we access our source value from sourceVarName string, indentLevel int, @@ -894,6 +981,7 @@ func setSDKForSlice( cfg, r, containerFieldName, elemVarName, + sourceFieldPath+".", iterVarName, &targetShape.MemberRef, indentLevel+1, @@ -922,6 +1010,8 @@ func setSDKForMap( targetVarName string, // Shape Ref of the target struct field targetShapeRef *awssdkmodel.ShapeRef, + // The path to the field that we access our source value from + sourceFieldPath string, // The struct or struct field that we access our source value from sourceVarName string, indentLevel int, @@ -955,6 +1045,7 @@ func setSDKForMap( cfg, r, containerFieldName, valVarName, + sourceFieldPath+".", valIterVarName, &targetShape.ValueRef, indentLevel+1, @@ -1063,6 +1154,8 @@ func setSDKForScalar( targetVarName string, // The type of shape of the target variable targetVarType string, + // The path to the field that we access our source value from + sourceFieldPath string, // The struct or struct field that we access our source value from sourceVarName string, shapeRef *awssdkmodel.ShapeRef, diff --git a/pkg/generate/code/set_sdk_test.go b/pkg/generate/code/set_sdk_test.go index 6dc3985d..f280980c 100644 --- a/pkg/generate/code/set_sdk_test.go +++ b/pkg/generate/code/set_sdk_test.go @@ -1405,3 +1405,195 @@ func TestSetSDK_SQS_Queue_GetAttributes(t *testing.T) { code.SetSDKGetAttributes(crd.Config(), crd, "r.ko", "res", 1), ) } + +func TestSetSDK_MQ_Broker_Create(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewGeneratorForService(t, "mq") + + crd := testutil.GetCRDByName(t, g, "Broker") + require.NotNil(crd) + + expected := ` + if r.ko.Spec.AuthenticationStrategy != nil { + res.SetAuthenticationStrategy(*r.ko.Spec.AuthenticationStrategy) + } + if r.ko.Spec.AutoMinorVersionUpgrade != nil { + res.SetAutoMinorVersionUpgrade(*r.ko.Spec.AutoMinorVersionUpgrade) + } + if r.ko.Spec.BrokerName != nil { + res.SetBrokerName(*r.ko.Spec.BrokerName) + } + if r.ko.Spec.Configuration != nil { + f3 := &svcsdk.ConfigurationId{} + if r.ko.Spec.Configuration.ID != nil { + f3.SetId(*r.ko.Spec.Configuration.ID) + } + if r.ko.Spec.Configuration.Revision != nil { + f3.SetRevision(*r.ko.Spec.Configuration.Revision) + } + res.SetConfiguration(f3) + } + if r.ko.Spec.CreatorRequestID != nil { + res.SetCreatorRequestId(*r.ko.Spec.CreatorRequestID) + } + if r.ko.Spec.DeploymentMode != nil { + res.SetDeploymentMode(*r.ko.Spec.DeploymentMode) + } + if r.ko.Spec.EncryptionOptions != nil { + f6 := &svcsdk.EncryptionOptions{} + if r.ko.Spec.EncryptionOptions.KMSKeyID != nil { + f6.SetKmsKeyId(*r.ko.Spec.EncryptionOptions.KMSKeyID) + } + if r.ko.Spec.EncryptionOptions.UseAWSOwnedKey != nil { + f6.SetUseAwsOwnedKey(*r.ko.Spec.EncryptionOptions.UseAWSOwnedKey) + } + res.SetEncryptionOptions(f6) + } + if r.ko.Spec.EngineType != nil { + res.SetEngineType(*r.ko.Spec.EngineType) + } + if r.ko.Spec.EngineVersion != nil { + res.SetEngineVersion(*r.ko.Spec.EngineVersion) + } + if r.ko.Spec.HostInstanceType != nil { + res.SetHostInstanceType(*r.ko.Spec.HostInstanceType) + } + if r.ko.Spec.LdapServerMetadata != nil { + f10 := &svcsdk.LdapServerMetadataInput{} + if r.ko.Spec.LdapServerMetadata.Hosts != nil { + f10f0 := []*string{} + for _, f10f0iter := range r.ko.Spec.LdapServerMetadata.Hosts { + var f10f0elem string + f10f0elem = *f10f0iter + f10f0 = append(f10f0, &f10f0elem) + } + f10.SetHosts(f10f0) + } + if r.ko.Spec.LdapServerMetadata.RoleBase != nil { + f10.SetRoleBase(*r.ko.Spec.LdapServerMetadata.RoleBase) + } + if r.ko.Spec.LdapServerMetadata.RoleName != nil { + f10.SetRoleName(*r.ko.Spec.LdapServerMetadata.RoleName) + } + if r.ko.Spec.LdapServerMetadata.RoleSearchMatching != nil { + f10.SetRoleSearchMatching(*r.ko.Spec.LdapServerMetadata.RoleSearchMatching) + } + if r.ko.Spec.LdapServerMetadata.RoleSearchSubtree != nil { + f10.SetRoleSearchSubtree(*r.ko.Spec.LdapServerMetadata.RoleSearchSubtree) + } + if r.ko.Spec.LdapServerMetadata.ServiceAccountPassword != nil { + f10.SetServiceAccountPassword(*r.ko.Spec.LdapServerMetadata.ServiceAccountPassword) + } + if r.ko.Spec.LdapServerMetadata.ServiceAccountUsername != nil { + f10.SetServiceAccountUsername(*r.ko.Spec.LdapServerMetadata.ServiceAccountUsername) + } + if r.ko.Spec.LdapServerMetadata.UserBase != nil { + f10.SetUserBase(*r.ko.Spec.LdapServerMetadata.UserBase) + } + if r.ko.Spec.LdapServerMetadata.UserRoleName != nil { + f10.SetUserRoleName(*r.ko.Spec.LdapServerMetadata.UserRoleName) + } + if r.ko.Spec.LdapServerMetadata.UserSearchMatching != nil { + f10.SetUserSearchMatching(*r.ko.Spec.LdapServerMetadata.UserSearchMatching) + } + if r.ko.Spec.LdapServerMetadata.UserSearchSubtree != nil { + f10.SetUserSearchSubtree(*r.ko.Spec.LdapServerMetadata.UserSearchSubtree) + } + res.SetLdapServerMetadata(f10) + } + if r.ko.Spec.Logs != nil { + f11 := &svcsdk.Logs{} + if r.ko.Spec.Logs.Audit != nil { + f11.SetAudit(*r.ko.Spec.Logs.Audit) + } + if r.ko.Spec.Logs.General != nil { + f11.SetGeneral(*r.ko.Spec.Logs.General) + } + res.SetLogs(f11) + } + if r.ko.Spec.MaintenanceWindowStartTime != nil { + f12 := &svcsdk.WeeklyStartTime{} + if r.ko.Spec.MaintenanceWindowStartTime.DayOfWeek != nil { + f12.SetDayOfWeek(*r.ko.Spec.MaintenanceWindowStartTime.DayOfWeek) + } + if r.ko.Spec.MaintenanceWindowStartTime.TimeOfDay != nil { + f12.SetTimeOfDay(*r.ko.Spec.MaintenanceWindowStartTime.TimeOfDay) + } + if r.ko.Spec.MaintenanceWindowStartTime.TimeZone != nil { + f12.SetTimeZone(*r.ko.Spec.MaintenanceWindowStartTime.TimeZone) + } + res.SetMaintenanceWindowStartTime(f12) + } + if r.ko.Spec.PubliclyAccessible != nil { + res.SetPubliclyAccessible(*r.ko.Spec.PubliclyAccessible) + } + if r.ko.Spec.SecurityGroups != nil { + f14 := []*string{} + for _, f14iter := range r.ko.Spec.SecurityGroups { + var f14elem string + f14elem = *f14iter + f14 = append(f14, &f14elem) + } + res.SetSecurityGroups(f14) + } + if r.ko.Spec.StorageType != nil { + res.SetStorageType(*r.ko.Spec.StorageType) + } + if r.ko.Spec.SubnetIDs != nil { + f16 := []*string{} + for _, f16iter := range r.ko.Spec.SubnetIDs { + var f16elem string + f16elem = *f16iter + f16 = append(f16, &f16elem) + } + res.SetSubnetIds(f16) + } + if r.ko.Spec.Tags != nil { + f17 := map[string]*string{} + for f17key, f17valiter := range r.ko.Spec.Tags { + var f17val string + f17val = *f17valiter + f17[f17key] = &f17val + } + res.SetTags(f17) + } + if r.ko.Spec.Users != nil { + f18 := []*svcsdk.User{} + for _, f18iter := range r.ko.Spec.Users { + f18elem := &svcsdk.User{} + if f18iter.ConsoleAccess != nil { + f18elem.SetConsoleAccess(*f18iter.ConsoleAccess) + } + if f18iter.Groups != nil { + f18elemf1 := []*string{} + for _, f18elemf1iter := range f18iter.Groups { + var f18elemf1elem string + f18elemf1elem = *f18elemf1iter + f18elemf1 = append(f18elemf1, &f18elemf1elem) + } + f18elem.SetGroups(f18elemf1) + } + if f18iter.Password != nil { + tmpSecret, err := rm.rr.SecretValueFromReference(ctx, f18iter.Password) + if err != nil { + return nil, err + } + if tmpSecret != "" { + f18elem.SetPassword(tmpSecret) + } + } + if f18iter.Username != nil { + f18elem.SetUsername(*f18iter.Username) + } + f18 = append(f18, f18elem) + } + res.SetUsers(f18) + } +` + assert.Equal( + expected, + code.SetSDK(crd.Config(), crd, model.OpTypeCreate, "r.ko", "res", 1), + ) +} diff --git a/pkg/generate/crossplane/crossplane.go b/pkg/generate/crossplane/crossplane.go index fe71666f..352eb414 100644 --- a/pkg/generate/crossplane/crossplane.go +++ b/pkg/generate/crossplane/crossplane.go @@ -32,10 +32,10 @@ var ( "crossplane/apis/groupversion_info.go.tpl", "crossplane/apis/types.go.tpl", } - crdTemplatePath = "crossplane/apis/crd.go.tpl" - controllerTmplPath = "crossplane/pkg/controller.go.tpl" + crdTemplatePath = "crossplane/apis/crd.go.tpl" + controllerTmplPath = "crossplane/pkg/controller.go.tpl" conversionsTmplPath = "crossplane/pkg/conversions.go.tpl" - includePaths = []string{ + includePaths = []string{ "crossplane/boilerplate.go.tpl", "crossplane/apis/enum_def.go.tpl", "crossplane/apis/type_def.go.tpl", @@ -100,7 +100,6 @@ type templateAPIVars struct { templateset.MetaVars EnumDefs []*ackmodel.EnumDef TypeDefs []*ackmodel.TypeDef - Imports map[string]string } // templateCRDVars contains template variables for the template that outputs Go @@ -120,7 +119,7 @@ func Crossplane( if err != nil { return nil, err } - typeDefs, typeImports, err := g.GetTypeDefs() + typeDefs, err := g.GetTypeDefs() if err != nil { return nil, err } @@ -143,7 +142,6 @@ func Crossplane( metaVars, enumDefs, typeDefs, - typeImports, } for _, path := range apisGenericTemplatesPaths { outPath := filepath.Join( diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index 28f9dfc8..24e574b6 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -273,17 +273,13 @@ func (g *Generator) IsShapeUsedInCRDs(shapeName string) bool { return false } -// GetTypeDefs returns a slice of `ackmodel.TypeDef` pointers and a map of -// package import information -func (g *Generator) GetTypeDefs() ([]*ackmodel.TypeDef, map[string]string, error) { +// GetTypeDefs returns a slice of `ackmodel.TypeDef` pointers +func (g *Generator) GetTypeDefs() ([]*ackmodel.TypeDef, error) { if g.typeDefs != nil { - return g.typeDefs, g.typeImports, nil + return g.typeDefs, nil } tdefs := []*ackmodel.TypeDef{} - // Map, keyed by package import path, with the values being an alias to use - // for the package - timports := map[string]string{} // Map, keyed by original Shape GoTypeElem(), with the values being a // renamed type name (due to conflicting names) trenames := map[string]string{} @@ -315,38 +311,6 @@ func (g *Generator) GetTypeDefs() ([]*ackmodel.TypeDef, map[string]string, error if !g.IsShapeUsedInCRDs(memberShape.ShapeName) { continue } - goPkgType := memberRef.Shape.GoTypeWithPkgNameElem() - if strings.Contains(goPkgType, ".") { - if strings.HasPrefix(goPkgType, "[]") { - // For slice types, we just want the element type... - goPkgType = strings.TrimLeft(goPkgType, "[]") - } - if strings.HasPrefix(goPkgType, "map[") { - // Assuming the map keys are always of type string. - goPkgType = strings.TrimLeft(goPkgType, "map[string]") - } - if strings.HasPrefix(goPkgType, "*") { - // For slice and map types, the element type might be a - // pointer to a struct... - goPkgType = goPkgType[1:] - } - pkg := strings.Split(goPkgType, ".")[0] - if pkg != g.SDKAPI.API.PackageName() { - // time.Time needs to be converted to apimachinery/metav1.Time otherwise there is no DeepCopy support - if pkg == "time" { - timports["k8s.io/apimachinery/pkg/apis/meta/v1"] = "metav1" - } else if pkg == "aws" { - // The "aws.JSONValue" type needs to be handled - // specially. - timports["github.com/aws/aws-sdk-go/aws"] = "" - } else { - // Shape.GoPTypeWithPkgNameElem() always returns the type - // as a full package dot-notation name. We only want to add - // imports for "normal" packages - timports[pkg] = "" - } - } - } // There are shapes that are called things like DBProxyStatus that are // fields in a DBProxy CRD... we need to ensure the type names don't // conflict. Also, the name of the Go type in the generated code is @@ -399,10 +363,126 @@ func (g *Generator) GetTypeDefs() ([]*ackmodel.TypeDef, map[string]string, error sort.Slice(tdefs, func(i, j int) bool { return tdefs[i].Names.Camel < tdefs[j].Names.Camel }) + g.processNestedFieldTypeDefs(tdefs) g.typeDefs = tdefs - g.typeImports = timports g.typeRenames = trenames - return tdefs, timports, nil + return tdefs, nil +} + +// processNestedFieldTypeDefs updates the supplied TypeDef structs' if a nested +// field has been configured with a type overriding FieldConfig -- such as +// FieldConfig.IsSecret. +func (g *Generator) processNestedFieldTypeDefs( + tdefs []*ackmodel.TypeDef, +) { + crds, _ := g.GetCRDs() + for _, crd := range crds { + for fieldPath, field := range crd.Fields { + if !strings.Contains(fieldPath, ".") { + // top-level fields have already had their structure + // transformed during the CRD.AddSpecField and + // CRD.AddStatusField methods. All we need to do here is look + // at nested fields, which are identifiable as fields with + // field paths contains a dot (".") + continue + } + if field.FieldConfig == nil { + // Likewise, we don't need to transform any TypeDef if the + // nested field doesn't have a FieldConfig instructing us to + // treat this field differently. + continue + } + if field.FieldConfig.IsSecret { + // Find the TypeDef that was created for the *containing* + // secret field struct. For example, assume the nested field + // path `Users..Password`, we'd want to find the TypeDef that + // was created for the `Users` field's element type (which is a + // struct) + replaceSecretAttrGoType(crd, field, tdefs) + } + } + } +} + +// replaceSecretAttrGoType replaces a nested field ackmodel.Attr's GoType with +// `*ackv1alpha1.SecretKeyReference`. +func replaceSecretAttrGoType( + crd *ackmodel.CRD, + field *ackmodel.Field, + tdefs []*ackmodel.TypeDef, +) { + fieldPath := field.Path + parentFieldPath := ackmodel.ParentFieldPath(field.Path) + parentField, ok := crd.Fields[parentFieldPath] + if !ok { + msg := fmt.Sprintf( + "Cannot find parent field at parent path %s for %s", + parentFieldPath, + fieldPath, + ) + panic(msg) + } + if parentField.ShapeRef == nil { + msg := fmt.Sprintf( + "parent field at parent path %s has a nil ShapeRef!", + parentFieldPath, + ) + panic(msg) + } + parentFieldShape := parentField.ShapeRef.Shape + parentFieldShapeName := parentField.ShapeRef.ShapeName + parentFieldShapeType := parentFieldShape.Type + // For list and map types, we need to grab the element/value + // type, since that's the type def we need to modify. + if parentFieldShapeType == "list" { + if parentFieldShape.MemberRef.Shape.Type != "structure" { + msg := fmt.Sprintf( + "parent field at parent path %s is a list type with a non-structure element member shape %s!", + parentFieldPath, + parentFieldShape.MemberRef.Shape.Type, + ) + panic(msg) + } + parentFieldShapeName = parentField.ShapeRef.Shape.MemberRef.ShapeName + } else if parentFieldShapeType == "map" { + if parentFieldShape.ValueRef.Shape.Type != "structure" { + msg := fmt.Sprintf( + "parent field at parent path %s is a map type with a non-structure value member shape %s!", + parentFieldPath, + parentFieldShape.ValueRef.Shape.Type, + ) + panic(msg) + } + parentFieldShapeName = parentField.ShapeRef.Shape.ValueRef.ShapeName + } + var parentTypeDef *ackmodel.TypeDef + for _, tdef := range tdefs { + if tdef.Names.Original == parentFieldShapeName { + parentTypeDef = tdef + } + } + if parentTypeDef == nil { + msg := fmt.Sprintf( + "unable to find associated TypeDef for parent field "+ + "at parent path %s!", + parentFieldPath, + ) + panic(msg) + } + // Now we modify the parent type def's Attr that corresponds to + // the secret field... + attr, found := parentTypeDef.Attrs[field.Names.Camel] + if !found { + msg := fmt.Sprintf( + "unable to find attr %s in parent TypeDef %s "+ + "at parent path %s!", + field.Names.Camel, + parentTypeDef.Names.Original, + parentFieldPath, + ) + panic(msg) + } + attr.GoType = "*ackv1alpha1.SecretKeyReference" } // processNestedFields is responsible for walking all of the CRDs' Spec and @@ -447,7 +527,6 @@ func (g *Generator) processNestedField( g.processNestedMapField(crd, field.Path+"..", field) } } - // TODO(jaypipes): Handle Attribute-based fields... } // processNestedStructField recurses through the members of a nested field that diff --git a/pkg/generate/mq_test.go b/pkg/generate/mq_test.go index 89f527a4..38e9a280 100644 --- a/pkg/generate/mq_test.go +++ b/pkg/generate/mq_test.go @@ -28,10 +28,7 @@ func TestMQ_Broker(t *testing.T) { g := testutil.NewGeneratorForService(t, "mq") - crds, err := g.GetCRDs() - require.Nil(err) - - crd := getCRDByName("Broker", crds) + crd := testutil.GetCRDByName(t, g, "Broker") require.NotNil(crd) // We want to verify that the `Password` field of the `Spec.Users` field @@ -43,4 +40,14 @@ func TestMQ_Broker(t *testing.T) { require.True(found) require.NotNil(passField.FieldConfig) assert.True(passField.FieldConfig.IsSecret) + + // We now verify that the User TypeDef that is inferred by the code + // generator has had its Go type changed from `string` to + // `*ackv1alpha1.SecretKeyReference` as part of the nested fields + // post-processing of type defs in the GetTypeDefs() method. + tdef := testutil.GetTypeDefByName(t, g, "User") + require.NotNil(tdef) + passAttr, found := tdef.Attrs["Password"] + require.True(found) + assert.Equal("*ackv1alpha1.SecretKeyReference", passAttr.GoType) } diff --git a/pkg/model/crd.go b/pkg/model/crd.go index b2f30704..df4b9c95 100644 --- a/pkg/model/crd.go +++ b/pkg/model/crd.go @@ -292,6 +292,17 @@ func (r *CRD) IsPrimaryARNField(fieldName string) bool { strings.EqualFold(fieldName, r.Names.Original+"arn") } +// IsSecretField returns true if the supplied field *path* refers to a Field +// that is a SecretKeyReference +func (r *CRD) IsSecretField(path string) bool { + fConfigs := r.cfg.ResourceFields(r.Names.Original) + fConfig, found := fConfigs[path] + if found { + return fConfig.IsSecret + } + return false +} + // SetOutputCustomMethodName returns custom set output operation as *string for // given operation on custom resource, if specified in generator config func (r *CRD) SetOutputCustomMethodName( diff --git a/pkg/model/field.go b/pkg/model/field.go index b7798715..d1117d17 100644 --- a/pkg/model/field.go +++ b/pkg/model/field.go @@ -14,6 +14,8 @@ package model import ( + "strings" + ackgenconfig "github.com/aws-controllers-k8s/code-generator/pkg/generate/config" "github.com/aws-controllers-k8s/code-generator/pkg/names" "github.com/aws-controllers-k8s/code-generator/pkg/util" @@ -61,6 +63,24 @@ func (f *Field) IsRequired() bool { return util.InStrings(f.Names.ModelOriginal, f.CRD.Ops.Create.InputRef.Shape.Required) } +// ParentFieldPath takes a field path and returns the field path of the +// containing "parent" field. For example, if the field path +// `Users..Credentials.Login` is passed in, this function returns +// `Users..Credentials`. If `Users..Password` is supplied, this function +// returns `Users`, etc. +func ParentFieldPath(path string) string { + parts := strings.Split(path, ".") + // Pop the last element of the supplied field path + parts = parts[0 : len(parts)-1] + // If the parent field's type is a list or map, there will be two dots ".." + // in the supplied field path. We don't want the returned field path to end + // in a dot, since that would be invalid, so we trim it off here + if parts[len(parts)-1] == "" { + parts = parts[0 : len(parts)-1] + } + return strings.Join(parts, ".") +} + // NewField returns a pointer to a new Field object func NewField( crd *CRD, diff --git a/pkg/model/field_test.go b/pkg/model/field_test.go new file mode 100644 index 00000000..92dc32be --- /dev/null +++ b/pkg/model/field_test.go @@ -0,0 +1,37 @@ +package model_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/aws-controllers-k8s/code-generator/pkg/model" +) + +func TestParentFieldPath(t *testing.T) { + assert := assert.New(t) + testCases := []struct { + subject string + want string + }{ + { + "Repository.Name", + "Repository", + }, + { + "Users..Password", + "Users", + }, + { + "User.Credentials..Password", + "User.Credentials", + }, + } + + for _, tc := range testCases { + result := model.ParentFieldPath( + tc.subject, + ) + assert.Equal(tc.want, result) + } +} diff --git a/pkg/testutil/get.go b/pkg/testutil/get.go index 92a7898d..49aa96d5 100644 --- a/pkg/testutil/get.go +++ b/pkg/testutil/get.go @@ -40,3 +40,22 @@ func GetCRDByName( } return nil } + +// GetTypeDefByName returns a TypeDef model with the supplied name +func GetTypeDefByName( + t *testing.T, + g *generate.Generator, + name string, +) *model.TypeDef { + require := require.New(t) + + tdefs, err := g.GetTypeDefs() + require.Nil(err) + + for _, tdef := range tdefs { + if tdef.Names.Original == name { + return tdef + } + } + return nil +} diff --git a/templates/apis/types.go.tpl b/templates/apis/types.go.tpl index 13c4e776..cceaa4cd 100644 --- a/templates/apis/types.go.tpl +++ b/templates/apis/types.go.tpl @@ -1,15 +1,19 @@ {{- template "boilerplate" }} package {{ .APIVersion }} -{{- if .Imports }} + import ( -{{- end -}} -{{- range $packagePath, $alias := .Imports }} - {{ if $alias -}}{{ $alias }} {{ end -}}"{{ $packagePath }}" -{{ end -}} -{{- if .Imports }} + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + "github.com/aws/aws-sdk-go/aws" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Hack to avoid import errors during build... +var ( + _ = &metav1.Time{} + _ = &aws.JSONValue{} + _ = ackv1alpha1.AWSAccountID("") ) -{{- end -}} {{- range $typeDef := .TypeDefs }} {{ template "type_def" $typeDef }} diff --git a/templates/crossplane/apis/types.go.tpl b/templates/crossplane/apis/types.go.tpl index 9ff0010d..1d9d1933 100644 --- a/templates/crossplane/apis/types.go.tpl +++ b/templates/crossplane/apis/types.go.tpl @@ -3,15 +3,19 @@ // Code generated by ack-generate. DO NOT EDIT. package {{ .APIVersion }} -{{- if .Imports }} + import ( -{{- end -}} -{{- range $packagePath, $alias := .Imports }} - {{ if $alias -}}{{ $alias }} {{ end -}}"{{ $packagePath }}" -{{ end -}} -{{- if .Imports }} + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + "github.com/aws/aws-sdk-go/aws" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Hack to avoid import errors during build... +var ( + _ = &metav1.Time{} + _ = &aws.JSONValue{} + _ = ackv1alpha1.AWSAccountID("") ) -{{- end -}} {{- range $typeDef := .TypeDefs }} {{ template "type_def" $typeDef }}