@@ -751,6 +751,30 @@ func identifierNameOrIDGuardConstructor(
751
751
return out
752
752
}
753
753
754
+ // requiredFieldGuardContructor returns Go code checking if user provided
755
+ // the required field for a read, or returning an error here
756
+ // and returns a `MissingNameIdentifier` error:
757
+ //
758
+ // if fields[${requiredField}] == "" {
759
+ // return ackerrors.MissingNameIdentifier
760
+ // }
761
+ func requiredFieldGuardContructor (
762
+ // String representing the fields map that contains the required
763
+ // fields for adoption
764
+ sourceVarName string ,
765
+ // String representing the name of the required field
766
+ requiredField string ,
767
+ // Number of levels of indentation to use
768
+ indentLevel int ,
769
+ ) string {
770
+ indent := strings .Repeat ("\t " , indentLevel )
771
+ out := fmt .Sprintf ("%stmp, ok := %s[\" %s\" ]\n " , indent , sourceVarName , requiredField )
772
+ out += fmt .Sprintf ("%sif !ok {\n " , indent )
773
+ out += fmt .Sprintf ("%s\t return ackerrors.MissingNameIdentifier\n " , indent )
774
+ out += fmt .Sprintf ("%s}\n " , indent )
775
+ return out
776
+ }
777
+
754
778
// SetResourceGetAttributes returns the Go code that sets the Status fields
755
779
// from the Output shape returned from a resource's GetAttributes operation.
756
780
//
@@ -1101,6 +1125,243 @@ func SetResourceIdentifiers(
1101
1125
return primaryKeyConditionalOut + primaryKeyOut + additionalKeyOut
1102
1126
}
1103
1127
1128
+ // PopulateResourceFromAnnotation returns the Go code that sets an empty CR object with
1129
+ // Spec and Status field values that correspond to the primary identifier (be
1130
+ // that an ARN, ID or Name) and any other "additional keys" required for the AWS
1131
+ // service to uniquely identify the object.
1132
+ //
1133
+ // The method will attempt to look for the field denoted with a value of true
1134
+ // for `is_primary_key`, or will use the ARN if the resource has a value of true
1135
+ // for `is_arn_primary_key`. Otherwise, the method will attempt to use the
1136
+ // `ReadOne` operation, if present, falling back to using `ReadMany`.
1137
+ // If it detects the operation uses an ARN to identify the resource it will read
1138
+ // it from the metadata status field. Otherwise it will use any field with a
1139
+ // name that matches the primary identifier from the operation, pulling from
1140
+ // top-level spec or status fields.
1141
+ //
1142
+ // An example of code with no additional keys:
1143
+ //
1144
+ // ```
1145
+ // tmp, ok := field["brokerID"]
1146
+ // if !ok {
1147
+ // return ackerrors.MissingNameIdentifier
1148
+ // }
1149
+ // r.ko.Status.BrokerID = &tmp
1150
+ //
1151
+ // ```
1152
+ //
1153
+ // An example of code with additional keys:
1154
+ //
1155
+ // ```
1156
+ //
1157
+ // tmp, ok := field["resourceID"]
1158
+ // if !ok {
1159
+ // return ackerrors.MissingNameIdentifier
1160
+ // }
1161
+ //
1162
+ // r.ko.Spec.ResourceID = &tmp
1163
+ //
1164
+ // f0, f0ok := fields["scalableDimension"]
1165
+ //
1166
+ // if f0ok {
1167
+ // r.ko.Spec.ScalableDimension = &f0
1168
+ // }
1169
+ //
1170
+ // f1, f1ok := fields["serviceNamespace"]
1171
+ //
1172
+ // if f1ok {
1173
+ // r.ko.Spec.ServiceNamespace = &f1
1174
+ // }
1175
+ //
1176
+ // ```
1177
+ // An example of code that uses the ARN:
1178
+ //
1179
+ // ```
1180
+ // tmpArn, ok := field["arn"]
1181
+ // if !ok {
1182
+ // return ackerrors.MissingNameIdentifier
1183
+ // }
1184
+ // if r.ko.Status.ACKResourceMetadata == nil {
1185
+ // r.ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{}
1186
+ // }
1187
+ // arn := ackv1alpha1.AWSResourceName(tmp)
1188
+ //
1189
+ // r.ko.Status.ACKResourceMetadata.ARN = &arn
1190
+ //
1191
+ // f0, f0ok := fields["modelPackageName"]
1192
+ //
1193
+ // if f0ok {
1194
+ // r.ko.Spec.ModelPackageName = &f0
1195
+ // }
1196
+ //
1197
+ // ```
1198
+ func PopulateResourceFromAnnotation (
1199
+ cfg * ackgenconfig.Config ,
1200
+ r * model.CRD ,
1201
+ // String representing the name of the variable that we will grab the Input
1202
+ // shape from. This will likely be "fields" since in the templates that
1203
+ // call this method, the "source variable" is the CRD struct which is used
1204
+ // to populate the target variable, which is the struct of unique
1205
+ // identifiers
1206
+ sourceVarName string ,
1207
+ // String representing the name of the variable that we will be **setting**
1208
+ // with values we get from the Output shape. This will likely be
1209
+ // "r.ko" since that is the name of the "target variable" that the
1210
+ // templates that call this method use for the Input shape.
1211
+ targetVarName string ,
1212
+ // Number of levels of indentation to use
1213
+ indentLevel int ,
1214
+ ) string {
1215
+ op := r .Ops .ReadOne
1216
+ if op == nil {
1217
+ switch {
1218
+ case r .Ops .GetAttributes != nil :
1219
+ // If single lookups can only be done with GetAttributes
1220
+ op = r .Ops .GetAttributes
1221
+ case r .Ops .ReadMany != nil :
1222
+ // If single lookups can only be done using ReadMany
1223
+ op = r .Ops .ReadMany
1224
+ default :
1225
+ return ""
1226
+ }
1227
+ }
1228
+ inputShape := op .InputRef .Shape
1229
+ if inputShape == nil {
1230
+ return ""
1231
+ }
1232
+
1233
+ primaryKeyOut := ""
1234
+ additionalKeyOut := "\n "
1235
+
1236
+ indent := strings .Repeat ("\t " , indentLevel )
1237
+ arnOut := "\n "
1238
+ out := "\n "
1239
+ // Check if the CRD defines the primary keys
1240
+ primaryKeyConditionalOut := "\n "
1241
+ primaryKeyConditionalOut += requiredFieldGuardContructor (sourceVarName , "arn" , indentLevel )
1242
+ arnOut += ackResourceMetadataGuardConstructor (fmt .Sprintf ("%s.Status" , targetVarName ), indentLevel )
1243
+ arnOut += fmt .Sprintf (
1244
+ "%sarn := ackv1alpha1.AWSResourceName(tmp)\n " ,
1245
+ indent ,
1246
+ )
1247
+ arnOut += fmt .Sprintf (
1248
+ "%s%s.Status.ACKResourceMetadata.ARN = &arn\n " ,
1249
+ indent , targetVarName ,
1250
+ )
1251
+ if r .IsARNPrimaryKey () {
1252
+ return primaryKeyConditionalOut + arnOut
1253
+ }
1254
+ primaryField , err := r .GetPrimaryKeyField ()
1255
+ if err != nil {
1256
+ panic (err )
1257
+ }
1258
+
1259
+ var primaryCRField , primaryShapeField string
1260
+ isPrimarySet := primaryField != nil
1261
+ if isPrimarySet {
1262
+ memberPath , _ := findFieldInCR (cfg , r , primaryField .Names .Original )
1263
+ primaryKeyOut += requiredFieldGuardContructor (sourceVarName , primaryField .Names .CamelLower , indentLevel )
1264
+ targetVarPath := fmt .Sprintf ("%s%s" , targetVarName , memberPath )
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
+ primaryKeyOut += requiredFieldGuardContructor (sourceVarName , targetField .Names .CamelLower , indentLevel )
1345
+ primaryKeyOut += setResourceIdentifierPrimaryIdentifierAnn (cfg , r ,
1346
+ targetField ,
1347
+ targetVarPath ,
1348
+ sourceVarName ,
1349
+ indentLevel )
1350
+ } else {
1351
+ additionalKeyOut += setResourceIdentifierAdditionalKeyAnn (
1352
+ cfg , r ,
1353
+ memberIndex ,
1354
+ targetField ,
1355
+ targetVarPath ,
1356
+ sourceVarName ,
1357
+ names .New (fieldName ).CamelLower ,
1358
+ indentLevel )
1359
+ }
1360
+ }
1361
+
1362
+ return out + primaryKeyOut + additionalKeyOut
1363
+ }
1364
+
1104
1365
// findFieldInCR will search for a given field, by its name, in a CR and returns
1105
1366
// the member path and Field type if one is found.
1106
1367
func findFieldInCR (
@@ -1152,6 +1413,34 @@ func setResourceIdentifierPrimaryIdentifier(
1152
1413
)
1153
1414
}
1154
1415
1416
+ // AnotherOne returns a string of Go code that sets
1417
+ // the primary identifier Spec or Status field on a given resource to the value
1418
+ // in the identifier `NameOrID` field:
1419
+ //
1420
+ // r.ko.Status.BrokerID = &identifier.NameOrID
1421
+ func setResourceIdentifierPrimaryIdentifierAnn (
1422
+ cfg * ackgenconfig.Config ,
1423
+ r * model.CRD ,
1424
+ // The field that will be set on the target variable
1425
+ targetField * model.Field ,
1426
+ // The variable name that we want to set a value to
1427
+ targetVarName string ,
1428
+ // The struct or struct field that we access our source value from
1429
+ sourceVarName string ,
1430
+ // Number of levels of indentation to use
1431
+ indentLevel int ,
1432
+ ) string {
1433
+ adaptedMemberPath := fmt .Sprintf ("&tmp" )
1434
+ qualifiedTargetVar := fmt .Sprintf ("%s.%s" , targetVarName , targetField .Path )
1435
+
1436
+ return setResourceForScalar (
1437
+ qualifiedTargetVar ,
1438
+ adaptedMemberPath ,
1439
+ targetField .ShapeRef ,
1440
+ indentLevel ,
1441
+ )
1442
+ }
1443
+
1155
1444
// setResourceIdentifierAdditionalKey returns a string of Go code that sets a
1156
1445
// Spec or Status field on a given resource to the value in the identifier's
1157
1446
// `AdditionalKeys` mapping:
@@ -1199,6 +1488,44 @@ func setResourceIdentifierAdditionalKey(
1199
1488
return additionalKeyOut
1200
1489
}
1201
1490
1491
+ func setResourceIdentifierAdditionalKeyAnn (
1492
+ cfg * ackgenconfig.Config ,
1493
+ r * model.CRD ,
1494
+ fieldIndex int ,
1495
+ // The field that will be set on the target variable
1496
+ targetField * model.Field ,
1497
+ // The variable name that we want to set a value to
1498
+ targetVarName string ,
1499
+ // The struct or struct field that we access our source value from
1500
+ sourceVarName string ,
1501
+ // The key in the `AdditionalKeys` map storing the source variable
1502
+ sourceVarKey string ,
1503
+ // Number of levels of indentation to use
1504
+ indentLevel int ,
1505
+ ) string {
1506
+ indent := strings .Repeat ("\t " , indentLevel )
1507
+
1508
+ additionalKeyOut := ""
1509
+
1510
+ fieldIndexName := fmt .Sprintf ("f%d" , fieldIndex )
1511
+ sourceAdaptedVarName := fmt .Sprintf ("%s[\" %s\" ]" , sourceVarName , sourceVarKey )
1512
+
1513
+ // TODO(RedbackThomson): If the identifiers don't exist, we should be
1514
+ // throwing an error accessible to the user
1515
+ additionalKeyOut += fmt .Sprintf ("%s%s, %sok := %s\n " , indent , fieldIndexName , fieldIndexName , sourceAdaptedVarName )
1516
+ additionalKeyOut += fmt .Sprintf ("%sif %sok {\n " , indent , fieldIndexName )
1517
+ qualifiedTargetVar := fmt .Sprintf ("%s.%s" , targetVarName , targetField .Path )
1518
+ additionalKeyOut += setResourceForScalar (
1519
+ qualifiedTargetVar ,
1520
+ fmt .Sprintf ("&%s" , fieldIndexName ),
1521
+ targetField .ShapeRef ,
1522
+ indentLevel + 1 ,
1523
+ )
1524
+ additionalKeyOut += fmt .Sprintf ("%s}\n " , indent )
1525
+
1526
+ return additionalKeyOut
1527
+ }
1528
+
1202
1529
// setResourceForContainer returns a string of Go code that sets the value of a
1203
1530
// target variable to that of a source variable. When the source variable type
1204
1531
// is a map, struct or slice type, then this function is called recursively on
0 commit comments