Skip to content

Commit 4857c76

Browse files
Extend ack-generate to generate slice with elements of type K8s Secret (#87)
Issue: aws-controllers-k8s/community#828 Description of changes: With this code change, when a field of type slice is configured as secret type inside Generator config, then the generated CRD yaml allows specifying multiple k8s secret values for that field in input yaml and generated sdk code supplies these values to the resource API input field as slice type. Added unit tests to test the scenario. Details on resultant generated code: Taking ElastiCache User API has `passwords` filed as example: ElastiCache User API has `passwords` filed of type `[]*string` in the API input. [Reference API docs](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/elasticache/create-user.html) When this field is configured as secret type inside Generator config: ``` resources: User: fields: Passwords: is_secret: true ``` and service controller code is generated with changes from this PR then, the generated CRD yaml for password field is: ``` passwords: description: Passwords used for this user. You can create up to two passwords for each user. items: description: SecretKeyReference combines a k8s corev1.SecretReference with a specific key within the referred-to Secret properties: key: description: Key is the key within the secret type: string name: description: Name is unique within a namespace to reference a secret resource. type: string namespace: description: Namespace defines the space within which the secret name must be unique. type: string required: - key type: object type: array ``` Observe that the password field is `array` type with K8s Secreret as element type and User resource sdk.go file contain following code for password field while setting the inputs: ``` if r.ko.Spec.Passwords != nil { f3 := []*string{} for _, f3iter := range r.ko.Spec.Passwords { var f3elem string if f3iter != nil { tmpSecret, err := rm.rr.SecretValueFromReference(ctx, f3iter) if err != nil { return nil, err } if tmpSecret != "" { f3elem = tmpSecret } } f3 = append(f3, &f3elem) } res.SetPasswords(f3) } ``` Observe that it supplies the secrets values as slice type to the input API. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. Testing: 1. ```make test``` passed for `code-generator` which contained already existing tests for secret fields code generation scenarios for MQ, ElastiCache ReplicationGroup. 2. ```make test``` passed for ElastiCache ACK controller generated code. 3. Generated ElastiCache controller code did not have any unrelated/unexpected changes.
1 parent 8988516 commit 4857c76

File tree

6 files changed

+154
-64
lines changed

6 files changed

+154
-64
lines changed

pkg/generate/code/set_sdk.go

Lines changed: 89 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -236,17 +236,6 @@ func SetSDK(
236236
sourceAdaptedVarName += "." + f.Names.Camel
237237
sourceFieldPath := f.Names.Camel
238238

239-
if r.IsSecretField(memberName) {
240-
out += setSDKForSecret(
241-
cfg, r,
242-
memberName,
243-
targetVarName,
244-
sourceAdaptedVarName,
245-
indentLevel,
246-
)
247-
continue
248-
}
249-
250239
memberShapeRef, _ := inputShape.MemberRefs[memberName]
251240
memberShape := memberShapeRef.Shape
252241

@@ -339,16 +328,26 @@ func SetSDK(
339328
)
340329
}
341330
default:
342-
out += setSDKForScalar(
343-
cfg, r,
344-
memberName,
345-
targetVarName,
346-
inputShape.Type,
347-
sourceFieldPath,
348-
sourceAdaptedVarName,
349-
memberShapeRef,
350-
indentLevel+1,
351-
)
331+
if r.IsSecretField(memberName) {
332+
out += setSDKForSecret(
333+
cfg, r,
334+
memberName,
335+
targetVarName,
336+
sourceAdaptedVarName,
337+
indentLevel,
338+
)
339+
} else {
340+
out += setSDKForScalar(
341+
cfg, r,
342+
memberName,
343+
targetVarName,
344+
inputShape.Type,
345+
sourceFieldPath,
346+
sourceAdaptedVarName,
347+
memberShapeRef,
348+
indentLevel+1,
349+
)
350+
}
352351
}
353352
out += fmt.Sprintf(
354353
"%s}\n", indent,
@@ -770,6 +769,25 @@ func setSDKForContainer(
770769
indentLevel,
771770
)
772771
default:
772+
if r.IsSecretField(sourceFieldPath) {
773+
indent := strings.Repeat("\t", indentLevel)
774+
// if ko.Spec.MasterUserPassword != nil {
775+
out := fmt.Sprintf(
776+
"%sif %s != nil {\n",
777+
indent, sourceVarName,
778+
)
779+
out += setSDKForSecret(
780+
cfg, r,
781+
"",
782+
targetVarName,
783+
sourceVarName,
784+
indentLevel,
785+
)
786+
// }
787+
out += fmt.Sprintf("%s}\n", indent)
788+
return out
789+
}
790+
773791
return setSDKForScalar(
774792
cfg, r,
775793
targetFieldName,
@@ -789,15 +807,27 @@ func setSDKForContainer(
789807
//
790808
// The Go code output from this function looks like this:
791809
//
792-
// if ko.Spec.MasterUserPassword != nil {
793810
// tmpSecret, err := rm.rr.SecretValueFromReference(ctx, ko.Spec.MasterUserPassword)
794811
// if err != nil {
795812
// return nil, err
796813
// }
797814
// if tmpSecret != "" {
798815
// res.SetMasterUserPassword(tmpSecret)
799816
// }
800-
// }
817+
//
818+
// or:
819+
//
820+
// tmpSecret, err := rm.rr.SecretValueFromReference(ctx, f3iter)
821+
// if err != nil {
822+
// return nil, err
823+
// }
824+
// if tmpSecret != "" {
825+
// f3elem = tmpSecret
826+
// }
827+
//
828+
// The second case is used when the SecretKeyReference field
829+
// is a slice of `[]*string` in the original AWS API Input shape.
830+
801831
func setSDKForSecret(
802832
cfg *ackgenconfig.Config,
803833
r *model.CRD,
@@ -809,15 +839,11 @@ func setSDKForSecret(
809839
sourceVarName string,
810840
indentLevel int,
811841
) string {
842+
812843
out := ""
813844
indent := strings.Repeat("\t", indentLevel)
814845
secVar := "tmpSecret"
815846

816-
// if ko.Spec.MasterUserPassword != nil {
817-
out += fmt.Sprintf(
818-
"%sif %s != nil {\n",
819-
indent, sourceVarName,
820-
)
821847
// tmpSecret, err := rm.rr.SecretValueFromReference(ctx, ko.Spec.MasterUserPassword)
822848
out += fmt.Sprintf(
823849
"%s\t%s, err := rm.rr.SecretValueFromReference(ctx, %s)\n",
@@ -833,13 +859,18 @@ func setSDKForSecret(
833859
// res.SetMasterUserPassword(tmpSecret)
834860
// }
835861
out += fmt.Sprintf("%s\tif tmpSecret != \"\" {\n", indent)
836-
out += fmt.Sprintf(
837-
"%s\t\t%s.Set%s(%s)\n",
838-
indent, targetVarName, targetFieldName, secVar,
839-
)
862+
if targetFieldName == "" {
863+
out += fmt.Sprintf(
864+
"%s\t\t%s = %s\n",
865+
indent, targetVarName, secVar,
866+
)
867+
} else {
868+
out += fmt.Sprintf(
869+
"%s\t\t%s.Set%s(%s)\n",
870+
indent, targetVarName, targetFieldName, secVar,
871+
)
872+
}
840873
out += fmt.Sprintf("%s\t}\n", indent)
841-
// }
842-
out += fmt.Sprintf("%s}\n", indent)
843874
return out
844875
}
845876

@@ -871,16 +902,7 @@ func setSDKForStruct(
871902
cleanMemberName := cleanMemberNames.Camel
872903
sourceAdaptedVarName := sourceVarName + "." + cleanMemberName
873904
memberFieldPath := sourceFieldPath + "." + cleanMemberName
874-
if r.IsSecretField(memberFieldPath) {
875-
out += setSDKForSecret(
876-
cfg, r,
877-
memberName,
878-
targetVarName,
879-
sourceAdaptedVarName,
880-
indentLevel,
881-
)
882-
continue
883-
}
905+
884906
out += fmt.Sprintf(
885907
"%sif %s != nil {\n", indent, sourceAdaptedVarName,
886908
)
@@ -918,16 +940,26 @@ func setSDKForStruct(
918940
)
919941
}
920942
default:
921-
out += setSDKForScalar(
922-
cfg, r,
923-
memberName,
924-
targetVarName,
925-
targetShape.Type,
926-
memberFieldPath,
927-
sourceAdaptedVarName,
928-
memberShapeRef,
929-
indentLevel+1,
930-
)
943+
if r.IsSecretField(memberFieldPath) {
944+
out += setSDKForSecret(
945+
cfg, r,
946+
memberName,
947+
targetVarName,
948+
sourceAdaptedVarName,
949+
indentLevel,
950+
)
951+
} else {
952+
out += setSDKForScalar(
953+
cfg, r,
954+
memberName,
955+
targetVarName,
956+
targetShape.Type,
957+
memberFieldPath,
958+
sourceAdaptedVarName,
959+
memberShapeRef,
960+
indentLevel+1,
961+
)
962+
}
931963
}
932964
out += fmt.Sprintf(
933965
"%s}\n", indent,
@@ -974,14 +1006,16 @@ func setSDKForSlice(
9741006
//
9751007
// f0elem.SetMyField(*f0iter)
9761008
containerFieldName := ""
1009+
sourceAttributePath := sourceFieldPath
9771010
if targetShape.MemberRef.Shape.Type == "structure" {
9781011
containerFieldName = targetFieldName
1012+
sourceAttributePath = sourceFieldPath+"."
9791013
}
9801014
out += setSDKForContainer(
9811015
cfg, r,
9821016
containerFieldName,
9831017
elemVarName,
984-
sourceFieldPath+".",
1018+
sourceAttributePath,
9851019
iterVarName,
9861020
&targetShape.MemberRef,
9871021
indentLevel+1,

pkg/generate/code/set_sdk_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,49 @@ func TestSetSDK_Elasticache_ReplicationGroup_Update_Override_Values(t *testing.T
11091109
)
11101110
}
11111111

1112+
func TestSetSDK_Elasticache_User_Create_Override_Values(t *testing.T) {
1113+
assert := assert.New(t)
1114+
require := require.New(t)
1115+
1116+
g := testutil.NewGeneratorForService(t, "elasticache")
1117+
1118+
crd := testutil.GetCRDByName(t, g, "User")
1119+
require.NotNil(crd)
1120+
1121+
expected := `
1122+
if r.ko.Spec.AccessString != nil {
1123+
res.SetAccessString(*r.ko.Spec.AccessString)
1124+
}
1125+
if r.ko.Spec.NoPasswordRequired != nil {
1126+
res.SetNoPasswordRequired(*r.ko.Spec.NoPasswordRequired)
1127+
}
1128+
if r.ko.Spec.Passwords != nil {
1129+
f3 := []*string{}
1130+
for _, f3iter := range r.ko.Spec.Passwords {
1131+
var f3elem string
1132+
if f3iter != nil {
1133+
tmpSecret, err := rm.rr.SecretValueFromReference(ctx, f3iter)
1134+
if err != nil {
1135+
return nil, err
1136+
}
1137+
if tmpSecret != "" {
1138+
f3elem = tmpSecret
1139+
}
1140+
}
1141+
f3 = append(f3, &f3elem)
1142+
}
1143+
res.SetPasswords(f3)
1144+
}
1145+
if r.ko.Spec.UserID != nil {
1146+
res.SetUserId(*r.ko.Spec.UserID)
1147+
}
1148+
`
1149+
assert.Equal(
1150+
expected,
1151+
code.SetSDK(crd.Config(), crd, model.OpTypeUpdate, "r.ko", "res", 1),
1152+
)
1153+
}
1154+
11121155
func TestSetSDK_RDS_DBInstance_Create(t *testing.T) {
11131156
assert := assert.New(t)
11141157
require := require.New(t)

pkg/generate/elasticache_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,13 @@ func TestElasticache_ValidateAuthTokenIsSecret(t *testing.T) {
274274

275275
assert := assert.New(t)
276276
assert.Equal("*ackv1alpha1.SecretKeyReference", crd.SpecFields["AuthToken"].GoType)
277-
assert.Equal("ackv1alpha1.SecretKeyReference", crd.SpecFields["AuthToken"].GoTypeElem)
277+
assert.Equal("SecretKeyReference", crd.SpecFields["AuthToken"].GoTypeElem)
278278
assert.Equal("*ackv1alpha1.SecretKeyReference", crd.SpecFields["AuthToken"].GoTypeWithPkgName)
279+
280+
crd = getCRDByName("User", crds)
281+
require.NotNil(crd)
282+
283+
assert.Equal("[]*ackv1alpha1.SecretKeyReference", crd.SpecFields["Passwords"].GoType)
284+
assert.Equal("SecretKeyReference", crd.SpecFields["Passwords"].GoTypeElem)
285+
assert.Equal("[]*ackv1alpha1.SecretKeyReference", crd.SpecFields["Passwords"].GoTypeWithPkgName)
279286
}

pkg/generate/testdata/models/apis/elasticache/0000-00-00/generator.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ resources:
1818
from:
1919
operation: DescribeEvents
2020
path: Events
21+
User:
22+
fields:
23+
Passwords:
24+
is_secret: true
2125
ReplicationGroup:
2226
update_conditions_custom_method_name: CustomUpdateConditions
2327
exceptions:
@@ -126,7 +130,6 @@ ignore:
126130
- GlobalReplicationGroup
127131
- CacheCluster
128132
- CacheSecurityGroup
129-
- User
130133
- UserGroup
131134
field_paths:
132135
- DescribeSnapshotsInput.CacheClusterId

pkg/model/field.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,8 @@ func NewField(
9595
shape = shapeRef.Shape
9696
}
9797

98-
if cfg != nil && cfg.IsSecret {
99-
gt = "*ackv1alpha1.SecretKeyReference"
100-
gte = "ackv1alpha1.SecretKeyReference"
101-
gtwp = "*ackv1alpha1.SecretKeyReference"
102-
} else if shape != nil {
103-
gte, gt, gtwp = cleanGoType(crd.sdkAPI, crd.cfg, shape)
98+
if shape != nil {
99+
gte, gt, gtwp = cleanGoType(crd.sdkAPI, crd.cfg, shape, cfg)
104100
} else {
105101
gte = "string"
106102
gt = "*string"

pkg/model/types.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func cleanGoType(
2929
api *SDKAPI,
3030
cfg *ackgenconfig.Config,
3131
shape *awssdkmodel.Shape,
32+
fieldCfg *ackgenconfig.FieldConfig,
3233
) (string, string, string) {
3334
// There are shapes that are called things like DBProxyStatus that are
3435
// fields in a DBProxy CRD... we need to ensure the type names don't
@@ -48,20 +49,26 @@ func cleanGoType(
4849
} else if shape.Type == "list" {
4950
// If it's a list type, where the element is a structure, we need to
5051
// set the GoType to the cleaned-up Camel-cased name
51-
mgte, mgt, _ := cleanGoType(api, cfg, shape.MemberRef.Shape)
52+
mgte, mgt, mgtwp := cleanGoType(api, cfg, shape.MemberRef.Shape, fieldCfg)
5253
cleanNames := names.New(mgte)
5354
gte = cleanNames.Camel
5455
if api.HasConflictingTypeName(mgte, cfg) {
5556
gte += "_SDK"
5657
}
5758

5859
gt = "[]" + mgt
60+
gtwp = "[]" + mgtwp
5961
} else if shape.Type == "timestamp" {
6062
// time.Time needs to be converted to apimachinery/metav1.Time
6163
// otherwise there is no DeepCopy support
6264
gtwp = "*metav1.Time"
6365
gte = "metav1.Time"
6466
gt = "*metav1.Time"
67+
} else if fieldCfg != nil && fieldCfg.IsSecret {
68+
gt = "*ackv1alpha1.SecretKeyReference"
69+
gte = "SecretKeyReference"
70+
gtwp = "*ackv1alpha1.SecretKeyReference"
71+
return gte, gt, gtwp
6572
}
6673

6774
// Replace the type part of the full type-with-package-name with the

0 commit comments

Comments
 (0)