@@ -751,6 +751,27 @@ func identifierNameOrIDGuardConstructor(
751
751
return out
752
752
}
753
753
754
+ // identifierNameOrIDGuardConstructor returns Go code representing a nil-guard
755
+ // and returns a `MissingNameIdentifier` error:
756
+ //
757
+ // if fields[${requiredField}] == "" {
758
+ // return ackerrors.MissingNameIdentifier
759
+ // }
760
+ func requiredFieldGuardContructor (
761
+ // String representing the name of the identifier that should have the
762
+ // `NameOrID` pointer defined
763
+ sourceVarName string ,
764
+ // Number of levels of indentation to use
765
+ indentLevel int ,
766
+ ) string {
767
+ indent := strings .Repeat ("\t " , indentLevel )
768
+ out := fmt .Sprintf ("%stmp, ok := %s[\" arn\" ]\n " , indent , sourceVarName )
769
+ out += fmt .Sprintf ("%sif !ok {\n " , indent )
770
+ out += fmt .Sprintf ("%s\t return ackerrors.MissingNameIdentifier\n " , indent )
771
+ out += fmt .Sprintf ("%s}\n " , indent )
772
+ return out
773
+ }
774
+
754
775
// SetResourceGetAttributes returns the Go code that sets the Status fields
755
776
// from the Output shape returned from a resource's GetAttributes operation.
756
777
//
@@ -1101,6 +1122,250 @@ func SetResourceIdentifiers(
1101
1122
return primaryKeyConditionalOut + primaryKeyOut + additionalKeyOut
1102
1123
}
1103
1124
1125
+ // PopulateResourceFromAnnotation returns the Go code that sets an empty CR object with
1126
+ // Spec and Status field values that correspond to the primary identifier (be
1127
+ // that an ARN, ID or Name) and any other "additional keys" required for the AWS
1128
+ // service to uniquely identify the object.
1129
+ //
1130
+ // The method will attempt to look for the field denoted with a value of true
1131
+ // for `is_primary_key`, or will use the ARN if the resource has a value of true
1132
+ // for `is_arn_primary_key`. Otherwise, the method will attempt to use the
1133
+ // `ReadOne` operation, if present, falling back to using `ReadMany`.
1134
+ // If it detects the operation uses an ARN to identify the resource it will read
1135
+ // it from the metadata status field. Otherwise it will use any field with a
1136
+ // name that matches the primary identifier from the operation, pulling from
1137
+ // top-level spec or status fields.
1138
+ //
1139
+ // An example of code with no additional keys:
1140
+ //
1141
+ // ```
1142
+ // tmp, ok := field["brokerID"]
1143
+ // if !ok {
1144
+ // return ackerrors.MissingNameIdentifier
1145
+ // }
1146
+ // r.ko.Status.BrokerID = &tmp
1147
+ //
1148
+ // ```
1149
+ //
1150
+ // An example of code with additional keys:
1151
+ //
1152
+ // ```
1153
+ //
1154
+ // tmp, ok := field["resourceID"]
1155
+ // if !ok {
1156
+ // return ackerrors.MissingNameIdentifier
1157
+ // }
1158
+ //
1159
+ // r.ko.Spec.ResourceID = &tmp
1160
+ //
1161
+ // f0, f0ok := fields["scalableDimension"]
1162
+ //
1163
+ // if f0ok {
1164
+ // r.ko.Spec.ScalableDimension = &f0
1165
+ // }
1166
+ //
1167
+ // f1, f1ok := fields["serviceNamespace"]
1168
+ //
1169
+ // if f1ok {
1170
+ // r.ko.Spec.ServiceNamespace = &f1
1171
+ // }
1172
+ //
1173
+ // ```
1174
+ // An example of code that uses the ARN:
1175
+ //
1176
+ // ```
1177
+ // tmpArn, ok := field["arn"]
1178
+ // if !ok {
1179
+ // return ackerrors.MissingNameIdentifier
1180
+ // }
1181
+ // if r.ko.Status.ACKResourceMetadata == nil {
1182
+ // r.ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{}
1183
+ // }
1184
+ // arn := ackv1alpha1.AWSResourceName(tmp)
1185
+ //
1186
+ // r.ko.Status.ACKResourceMetadata.ARN = &arn
1187
+ //
1188
+ // f0, f0ok := fields["modelPackageName"]
1189
+ //
1190
+ // if f0ok {
1191
+ // r.ko.Spec.ModelPackageName = &f0
1192
+ // }
1193
+ //
1194
+ // ```
1195
+ func PopulateResourceFromAnnotation (
1196
+ cfg * ackgenconfig.Config ,
1197
+ r * model.CRD ,
1198
+ // String representing the name of the variable that we will grab the Input
1199
+ // shape from. This will likely be "fields" since in the templates that
1200
+ // call this method, the "source variable" is the CRD struct which is used
1201
+ // to populate the target variable, which is the struct of unique
1202
+ // identifiers
1203
+ sourceVarName string ,
1204
+ // String representing the name of the variable that we will be **setting**
1205
+ // with values we get from the Output shape. This will likely be
1206
+ // "r.ko" since that is the name of the "target variable" that the
1207
+ // templates that call this method use for the Input shape.
1208
+ targetVarName string ,
1209
+ // Number of levels of indentation to use
1210
+ indentLevel int ,
1211
+ ) string {
1212
+ op := r .Ops .ReadOne
1213
+ if op == nil {
1214
+ switch {
1215
+ case r .Ops .GetAttributes != nil :
1216
+ // If single lookups can only be done with GetAttributes
1217
+ op = r .Ops .GetAttributes
1218
+ case r .Ops .ReadMany != nil :
1219
+ // If single lookups can only be done using ReadMany
1220
+ op = r .Ops .ReadMany
1221
+ default :
1222
+ return ""
1223
+ }
1224
+ }
1225
+ inputShape := op .InputRef .Shape
1226
+ if inputShape == nil {
1227
+ return ""
1228
+ }
1229
+
1230
+ primaryKeyOut := ""
1231
+ additionalKeyOut := "\n "
1232
+
1233
+ indent := strings .Repeat ("\t " , indentLevel )
1234
+ arnOut := "\n "
1235
+ out := "\n "
1236
+ // Check if the CRD defines the primary keys
1237
+ primaryKeyConditionalOut := "\n "
1238
+ primaryKeyConditionalOut += requiredFieldGuardContructor (sourceVarName , indentLevel )
1239
+ arnOut += ackResourceMetadataGuardConstructor (fmt .Sprintf ("%s.Status" , targetVarName ), indentLevel )
1240
+ arnOut += fmt .Sprintf (
1241
+ "%sarn := ackv1alpha1.AWSResourceName(tmp)\n " ,
1242
+ indent ,
1243
+ )
1244
+ arnOut += fmt .Sprintf (
1245
+ "%s%s.Status.ACKResourceMetadata.ARN = &arn" ,
1246
+ indent , targetVarName ,
1247
+ )
1248
+ if r .IsARNPrimaryKey () {
1249
+ return primaryKeyConditionalOut + arnOut
1250
+ }
1251
+ primaryField , err := r .GetPrimaryKeyField ()
1252
+ if err != nil {
1253
+ panic (err )
1254
+ }
1255
+
1256
+ var primaryCRField , primaryShapeField string
1257
+ isPrimarySet := primaryField != nil
1258
+ if isPrimarySet {
1259
+ memberPath , _ := findFieldInCR (cfg , r , primaryField .Names .Original )
1260
+ targetVarPath := fmt .Sprintf ("%s%s" , targetVarName , memberPath )
1261
+ primaryKeyOut += fmt .Sprintf ("%stmp, ok := %s[\" %s\" ]\n " , indent , sourceVarName , primaryField .Names .CamelLower )
1262
+ primaryKeyOut += fmt .Sprintf ("%sif !ok {\n " , indent )
1263
+ primaryKeyOut += fmt .Sprintf ("%s\t return ackerrors.MissingNameIdentifier\n " , indent )
1264
+ primaryKeyOut += fmt .Sprintf ("%s}\n " , indent )
1265
+ primaryKeyOut += setResourceIdentifierPrimaryIdentifierAnn (cfg , r ,
1266
+ primaryField ,
1267
+ targetVarPath ,
1268
+ sourceVarName ,
1269
+ indentLevel ,
1270
+ )
1271
+ } else {
1272
+ primaryCRField , primaryShapeField = FindPrimaryIdentifierFieldNames (cfg , r , op )
1273
+ if primaryShapeField == PrimaryIdentifierARNOverride {
1274
+ return primaryKeyConditionalOut + arnOut
1275
+ }
1276
+ }
1277
+
1278
+ paginatorFieldLookup := []string {
1279
+ "NextToken" ,
1280
+ "MaxResults" ,
1281
+ }
1282
+
1283
+
1284
+ for memberIndex , memberName := range inputShape .MemberNames () {
1285
+ if util .InStrings (memberName , paginatorFieldLookup ) {
1286
+ continue
1287
+ }
1288
+
1289
+ inputShapeRef := inputShape .MemberRefs [memberName ]
1290
+ inputMemberShape := inputShapeRef .Shape
1291
+
1292
+ // Only strings and list of strings are currently accepted as valid
1293
+ // inputs for additional key fields
1294
+ if inputMemberShape .Type != "string" &&
1295
+ (inputMemberShape .Type != "list" ||
1296
+ inputMemberShape .MemberRef .Shape .Type != "string" ) {
1297
+ continue
1298
+ }
1299
+
1300
+ if r .IsSecretField (memberName ) {
1301
+ // Secrets cannot be used as fields in identifiers
1302
+ continue
1303
+ }
1304
+
1305
+ if r .IsPrimaryARNField (memberName ) {
1306
+ continue
1307
+ }
1308
+
1309
+ // Handles field renames, if applicable
1310
+ fieldName := cfg .GetResourceFieldName (
1311
+ r .Names .Original ,
1312
+ op .ExportedName ,
1313
+ memberName ,
1314
+ )
1315
+
1316
+ // Check to see if we've already set the field as the primary identifier
1317
+ if isPrimarySet && fieldName == primaryField .Names .Camel {
1318
+ continue
1319
+ }
1320
+
1321
+ isPrimaryIdentifier := fieldName == primaryShapeField
1322
+
1323
+ searchField := ""
1324
+ if isPrimaryIdentifier {
1325
+ searchField = primaryCRField
1326
+ } else {
1327
+ searchField = fieldName
1328
+ }
1329
+
1330
+ memberPath , targetField := findFieldInCR (cfg , r , searchField )
1331
+ if targetField == nil || (isPrimarySet && targetField == primaryField ) {
1332
+ continue
1333
+ }
1334
+
1335
+ switch targetField .ShapeRef .Shape .Type {
1336
+ case "list" , "structure" , "map" :
1337
+ panic ("primary identifier '" + targetField .Path + "' must be a scalar type since NameOrID is a string" )
1338
+ default :
1339
+ break
1340
+ }
1341
+
1342
+ targetVarPath := fmt .Sprintf ("%s%s" , targetVarName , memberPath )
1343
+ if isPrimaryIdentifier {
1344
+ targetVarPath := fmt .Sprintf ("%s%s" , targetVarName , memberPath )
1345
+ primaryKeyOut += fmt .Sprintf ("%stmp, ok := %s[\" %s\" ]\n " , indent , sourceVarName , targetField .Names .CamelLower )
1346
+ primaryKeyOut += fmt .Sprintf ("%sif !ok {\n " , indent )
1347
+ primaryKeyOut += fmt .Sprintf ("%s\t return ackerrors.MissingNameIdentifier\n " , indent )
1348
+ primaryKeyOut += fmt .Sprintf ("%s}\n " , indent )
1349
+ primaryKeyOut += setResourceIdentifierPrimaryIdentifierAnn (cfg , r ,
1350
+ targetField ,
1351
+ targetVarPath ,
1352
+ sourceVarName ,
1353
+ indentLevel )
1354
+ } else {
1355
+ additionalKeyOut += setResourceIdentifierAdditionalKeyAnn (
1356
+ cfg , r ,
1357
+ memberIndex ,
1358
+ targetField ,
1359
+ targetVarPath ,
1360
+ sourceVarName ,
1361
+ names .New (fieldName ).CamelLower ,
1362
+ indentLevel )
1363
+ }
1364
+ }
1365
+
1366
+ return out + primaryKeyOut + additionalKeyOut
1367
+ }
1368
+
1104
1369
// findFieldInCR will search for a given field, by its name, in a CR and returns
1105
1370
// the member path and Field type if one is found.
1106
1371
func findFieldInCR (
@@ -1152,6 +1417,34 @@ func setResourceIdentifierPrimaryIdentifier(
1152
1417
)
1153
1418
}
1154
1419
1420
+ // AnotherOne returns a string of Go code that sets
1421
+ // the primary identifier Spec or Status field on a given resource to the value
1422
+ // in the identifier `NameOrID` field:
1423
+ //
1424
+ // r.ko.Status.BrokerID = &identifier.NameOrID
1425
+ func setResourceIdentifierPrimaryIdentifierAnn (
1426
+ cfg * ackgenconfig.Config ,
1427
+ r * model.CRD ,
1428
+ // The field that will be set on the target variable
1429
+ targetField * model.Field ,
1430
+ // The variable name that we want to set a value to
1431
+ targetVarName string ,
1432
+ // The struct or struct field that we access our source value from
1433
+ sourceVarName string ,
1434
+ // Number of levels of indentation to use
1435
+ indentLevel int ,
1436
+ ) string {
1437
+ adaptedMemberPath := fmt .Sprintf ("&tmp" )
1438
+ qualifiedTargetVar := fmt .Sprintf ("%s.%s" , targetVarName , targetField .Path )
1439
+
1440
+ return setResourceForScalar (
1441
+ qualifiedTargetVar ,
1442
+ adaptedMemberPath ,
1443
+ targetField .ShapeRef ,
1444
+ indentLevel ,
1445
+ )
1446
+ }
1447
+
1155
1448
// setResourceIdentifierAdditionalKey returns a string of Go code that sets a
1156
1449
// Spec or Status field on a given resource to the value in the identifier's
1157
1450
// `AdditionalKeys` mapping:
@@ -1199,6 +1492,44 @@ func setResourceIdentifierAdditionalKey(
1199
1492
return additionalKeyOut
1200
1493
}
1201
1494
1495
+ func setResourceIdentifierAdditionalKeyAnn (
1496
+ cfg * ackgenconfig.Config ,
1497
+ r * model.CRD ,
1498
+ fieldIndex int ,
1499
+ // The field that will be set on the target variable
1500
+ targetField * model.Field ,
1501
+ // The variable name that we want to set a value to
1502
+ targetVarName string ,
1503
+ // The struct or struct field that we access our source value from
1504
+ sourceVarName string ,
1505
+ // The key in the `AdditionalKeys` map storing the source variable
1506
+ sourceVarKey string ,
1507
+ // Number of levels of indentation to use
1508
+ indentLevel int ,
1509
+ ) string {
1510
+ indent := strings .Repeat ("\t " , indentLevel )
1511
+
1512
+ additionalKeyOut := ""
1513
+
1514
+ fieldIndexName := fmt .Sprintf ("f%d" , fieldIndex )
1515
+ sourceAdaptedVarName := fmt .Sprintf ("%s[\" %s\" ]" , sourceVarName , sourceVarKey )
1516
+
1517
+ // TODO(RedbackThomson): If the identifiers don't exist, we should be
1518
+ // throwing an error accessible to the user
1519
+ additionalKeyOut += fmt .Sprintf ("%s%s, %sok := %s\n " , indent , fieldIndexName , fieldIndexName , sourceAdaptedVarName )
1520
+ additionalKeyOut += fmt .Sprintf ("%sif %sok {\n " , indent , fieldIndexName )
1521
+ qualifiedTargetVar := fmt .Sprintf ("%s.%s" , targetVarName , targetField .Path )
1522
+ additionalKeyOut += setResourceForScalar (
1523
+ qualifiedTargetVar ,
1524
+ fmt .Sprintf ("&%s" , fieldIndexName ),
1525
+ targetField .ShapeRef ,
1526
+ indentLevel + 1 ,
1527
+ )
1528
+ additionalKeyOut += fmt .Sprintf ("%s}\n " , indent )
1529
+
1530
+ return additionalKeyOut
1531
+ }
1532
+
1202
1533
// setResourceForContainer returns a string of Go code that sets the value of a
1203
1534
// target variable to that of a source variable. When the source variable type
1204
1535
// is a map, struct or slice type, then this function is called recursively on
0 commit comments