@@ -751,6 +751,30 @@ func identifierNameOrIDGuardConstructor(
751751 return out
752752}
753753
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+
754778// SetResourceGetAttributes returns the Go code that sets the Status fields
755779// from the Output shape returned from a resource's GetAttributes operation.
756780//
@@ -1101,6 +1125,243 @@ func SetResourceIdentifiers(
11011125 return primaryKeyConditionalOut + primaryKeyOut + additionalKeyOut
11021126}
11031127
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+
11041365// findFieldInCR will search for a given field, by its name, in a CR and returns
11051366// the member path and Field type if one is found.
11061367func findFieldInCR (
@@ -1152,6 +1413,34 @@ func setResourceIdentifierPrimaryIdentifier(
11521413 )
11531414}
11541415
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+
11551444// setResourceIdentifierAdditionalKey returns a string of Go code that sets a
11561445// Spec or Status field on a given resource to the value in the identifier's
11571446// `AdditionalKeys` mapping:
@@ -1199,6 +1488,44 @@ func setResourceIdentifierAdditionalKey(
11991488 return additionalKeyOut
12001489}
12011490
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+
12021529// setResourceForContainer returns a string of Go code that sets the value of a
12031530// target variable to that of a source variable. When the source variable type
12041531// is a map, struct or slice type, then this function is called recursively on
0 commit comments