Skip to content

Commit f65a1ed

Browse files
authored
Support 'list' type in output_wrapper_field_path (#337)
Issue #, if available: aws-controllers-k8s/community#1285 Description of changes: * adds support for list types to `output_wrapper_field_path` * cleans up logic in `setResourceReadMany` * updates EC2 unit tests in model and code packages By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent de8d784 commit f65a1ed

File tree

5 files changed

+1409
-58
lines changed

5 files changed

+1409
-58
lines changed

pkg/generate/code/set_resource.go

Lines changed: 174 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,7 @@ func SetResource(
9797
case model.OpTypeGet:
9898
op = r.Ops.ReadOne
9999
case model.OpTypeList:
100-
return setResourceReadMany(
101-
cfg, r,
102-
r.Ops.ReadMany, sourceVarName, targetVarName, indentLevel,
103-
)
100+
op = r.Ops.ReadMany
104101
case model.OpTypeUpdate:
105102
op = r.Ops.Update
106103
case model.OpTypeDelete:
@@ -116,9 +113,28 @@ func SetResource(
116113
return ""
117114
}
118115

119-
// Use the wrapper field path if it's given in the ack-generate config file.
116+
// If the output shape has a list containing the resource,
117+
// then call setResourceReadMany to generate for-range loops.
118+
// Output shape will be a list for ReadMany operations or if
119+
// designated via output wrapper config.
120120
wrapperFieldPath := r.GetOutputWrapperFieldPath(op)
121-
if wrapperFieldPath != nil {
121+
if op == r.Ops.ReadMany {
122+
return setResourceReadMany(
123+
cfg, r,
124+
op, sourceVarName, targetVarName, indentLevel,
125+
)
126+
} else if wrapperFieldPath != nil {
127+
// fieldpath api requires fully-qualified path
128+
qwfp := fieldpath.FromString(op.OutputRef.ShapeName + "." + *wrapperFieldPath)
129+
for _, sref := range qwfp.IterShapeRefs(&op.OutputRef) {
130+
// if there's at least 1 list to unpack, call setResourceReadMany
131+
if sref.Shape.Type == "list" {
132+
return setResourceReadMany(
133+
cfg, r,
134+
op, sourceVarName, targetVarName, indentLevel,
135+
)
136+
}
137+
}
122138
sourceVarName += "." + *wrapperFieldPath
123139
} else {
124140
// If the wrapper field path is not specified in the config file and if
@@ -133,6 +149,7 @@ func SetResource(
133149
}
134150
}
135151
}
152+
136153
out := "\n"
137154
indent := strings.Repeat("\t", indentLevel)
138155

@@ -440,14 +457,30 @@ func setResourceReadMany(
440457
var sourceElemShape *awssdkmodel.Shape
441458

442459
// Find the element in the output shape that contains the list of
443-
// resources. This heuristic is simplistic (just look for the field with a
444-
// list type) but seems to be followed consistently by the aws-sdk-go for
445-
// List operations.
446-
for memberName, memberShapeRef := range outputShape.MemberRefs {
447-
if memberShapeRef.Shape.Type == "list" {
448-
listShapeName = memberName
449-
sourceElemShape = memberShapeRef.Shape.MemberRef.Shape
450-
break
460+
// resources:
461+
// Check if there's a wrapperFieldPath, which will
462+
// point directly to the shape.
463+
wrapperFieldPath := r.GetOutputWrapperFieldPath(op)
464+
if wrapperFieldPath != nil {
465+
// fieldpath API needs fully qualified name
466+
wfp := fieldpath.FromString(outputShape.ShapeName + "." + *wrapperFieldPath)
467+
wfpShapeRef := wfp.ShapeRef(&op.OutputRef)
468+
if wfpShapeRef != nil {
469+
listShapeName = wfpShapeRef.ShapeName
470+
sourceElemShape = wfpShapeRef.Shape.MemberRef.Shape
471+
}
472+
}
473+
474+
// If listShape can't be found using wrapperFieldPath,
475+
// then fall back to looking for the first field with a list type;
476+
// this heuristic seems to work for most list operations in aws-sdk-go.
477+
if listShapeName == "" {
478+
for memberName, memberShapeRef := range outputShape.MemberRefs {
479+
if memberShapeRef.Shape.Type == "list" {
480+
listShapeName = memberName
481+
sourceElemShape = memberShapeRef.Shape.MemberRef.Shape
482+
break
483+
}
451484
}
452485
}
453486

@@ -472,47 +505,53 @@ func setResourceReadMany(
472505

473506
// found := false
474507
out += fmt.Sprintf("%sfound := false\n", indent)
508+
elemVarName := "elem"
509+
pathToShape := listShapeName
510+
if wrapperFieldPath != nil {
511+
pathToShape = *wrapperFieldPath
512+
}
513+
475514
// for _, elem := range resp.CacheClusters {
476-
out += fmt.Sprintf(
477-
"%sfor _, elem := range %s.%s {\n",
478-
indent, sourceVarName, listShapeName,
479-
)
515+
opening, closing, flIndentLvl := generateForRangeLoops(&op.OutputRef, pathToShape, sourceVarName, elemVarName, indentLevel)
516+
innerForIndent := strings.Repeat("\t", flIndentLvl)
517+
out += opening
518+
480519
for memberIndex, memberName := range sourceElemShape.MemberNames() {
481520
sourceMemberShapeRef := sourceElemShape.MemberRefs[memberName]
482521
sourceMemberShape := sourceMemberShapeRef.Shape
483-
sourceAdaptedVarName := "elem." + memberName
522+
sourceAdaptedVarName := elemVarName + "." + memberName
484523
if r.IsPrimaryARNField(memberName) {
485524
out += fmt.Sprintf(
486-
"%s\tif %s != nil {\n", indent, sourceAdaptedVarName,
525+
"%sif %s != nil {\n", innerForIndent, sourceAdaptedVarName,
487526
)
488527
// if ko.Status.ACKResourceMetadata == nil {
489528
// ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{}
490529
// }
491530
out += fmt.Sprintf(
492-
"%s\t\tif %s.Status.ACKResourceMetadata == nil {\n",
493-
indent, targetVarName,
531+
"%s\tif %s.Status.ACKResourceMetadata == nil {\n",
532+
innerForIndent, targetVarName,
494533
)
495534
out += fmt.Sprintf(
496-
"%s\t\t\t%s.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{}\n",
497-
indent, targetVarName,
535+
"%s\t\t%s.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{}\n",
536+
innerForIndent, targetVarName,
498537
)
499538
out += fmt.Sprintf(
500-
"\t\t%s}\n", indent,
539+
"\t%s}\n", innerForIndent,
501540
)
502541
// tmpARN := ackv1alpha1.AWSResourceName(*elemARN)
503542
// ko.Status.ACKResourceMetadata.ARN = &tmpARN
504543
out += fmt.Sprintf(
505-
"%s\t\ttmpARN := ackv1alpha1.AWSResourceName(*%s)\n",
506-
indent,
544+
"%s\ttmpARN := ackv1alpha1.AWSResourceName(*%s)\n",
545+
innerForIndent,
507546
sourceAdaptedVarName,
508547
)
509548
out += fmt.Sprintf(
510-
"%s\t\t%s.Status.ACKResourceMetadata.ARN = &tmpARN\n",
511-
indent,
549+
"%s\t%s.Status.ACKResourceMetadata.ARN = &tmpARN\n",
550+
innerForIndent,
512551
targetVarName,
513552
)
514553
out += fmt.Sprintf(
515-
"\t%s}\n", indent,
554+
"%s}\n", innerForIndent,
516555
)
517556
continue
518557
}
@@ -550,7 +589,7 @@ func setResourceReadMany(
550589

551590
targetMemberShapeRef = f.ShapeRef
552591
out += fmt.Sprintf(
553-
"%s\tif %s != nil {\n", indent, sourceAdaptedVarName,
592+
"%sif %s != nil {\n", innerForIndent, sourceAdaptedVarName,
554593
)
555594

556595
//ex: r.ko.Spec.CacheClusterID
@@ -565,7 +604,7 @@ func setResourceReadMany(
565604
cfg, r,
566605
memberVarName,
567606
targetMemberShapeRef.Shape,
568-
indentLevel+2,
607+
flIndentLvl+1,
569608
)
570609
out += setResourceForContainer(
571610
cfg, r,
@@ -577,13 +616,13 @@ func setResourceReadMany(
577616
sourceMemberShapeRef,
578617
f.Names.Camel,
579618
model.OpTypeList,
580-
indentLevel+2,
619+
flIndentLvl+1,
581620
)
582621
out += setResourceForScalar(
583622
qualifiedTargetVar,
584623
memberVarName,
585624
sourceMemberShapeRef,
586-
indentLevel+2,
625+
flIndentLvl+1,
587626
)
588627
}
589628
default:
@@ -594,45 +633,45 @@ func setResourceReadMany(
594633
// }
595634
if util.InStrings(fieldName, matchFieldNames) {
596635
out += fmt.Sprintf(
597-
"%s\t\tif %s.%s != nil {\n",
598-
indent,
636+
"%s\tif %s.%s != nil {\n",
637+
innerForIndent,
599638
targetAdaptedVarName,
600639
f.Names.Camel,
601640
)
602641
out += fmt.Sprintf(
603-
"%s\t\t\tif *%s != *%s.%s {\n",
604-
indent,
642+
"%s\t\tif *%s != *%s.%s {\n",
643+
innerForIndent,
605644
sourceAdaptedVarName,
606645
targetAdaptedVarName,
607646
f.Names.Camel,
608647
)
609648
out += fmt.Sprintf(
610-
"%s\t\t\t\tcontinue\n", indent,
649+
"%s\t\t\tcontinue\n", innerForIndent,
611650
)
612651
out += fmt.Sprintf(
613-
"%s\t\t\t}\n", indent,
652+
"%s\t\t}\n", innerForIndent,
614653
)
615654
out += fmt.Sprintf(
616-
"%s\t\t}\n", indent,
655+
"%s\t}\n", innerForIndent,
617656
)
618657
}
619658
// r.ko.Spec.CacheClusterID = elem.CacheClusterId
620659
out += setResourceForScalar(
621660
qualifiedTargetVar,
622661
sourceAdaptedVarName,
623662
sourceMemberShapeRef,
624-
indentLevel+2,
663+
flIndentLvl+1,
625664
)
626665
}
627666
out += fmt.Sprintf(
628-
"%s%s} else {\n", indent, indent,
667+
"%s} else {\n", innerForIndent,
629668
)
630669
out += fmt.Sprintf(
631-
"%s%s%s%s.%s = nil\n", indent, indent, indent,
670+
"%s\t%s.%s = nil\n", innerForIndent,
632671
targetAdaptedVarName, f.Names.Camel,
633672
)
634673
out += fmt.Sprintf(
635-
"%s%s}\n", indent, indent,
674+
"%s}\n", innerForIndent,
636675
)
637676
}
638677
// When we don't have custom matching/filtering logic for the list
@@ -642,12 +681,12 @@ func setResourceReadMany(
642681
// match. Thus, we will break here only when getting a record where
643682
// all match fields have matched.
644683
out += fmt.Sprintf(
645-
"%s\tfound = true\n", indent,
684+
"%sfound = true\n", innerForIndent,
646685
)
647-
out += fmt.Sprintf(
648-
"%s\tbreak\n", indent,
649-
)
650-
out += fmt.Sprintf("%s}\n", indent)
686+
687+
// End of for-range loops
688+
out += closing
689+
651690
// if !found {
652691
// return nil, ackerr.NotFound
653692
// }
@@ -1566,3 +1605,88 @@ func setResourceForScalar(
15661605
out += fmt.Sprintf("%s%s = %s\n", indent, targetVar, setTo)
15671606
return out
15681607
}
1608+
1609+
// generateForRangeLoops returns strings of Go code and an int
1610+
// representing indentLevel of the inner-most for loop + 1.
1611+
// This function unpacks a collection from a shapeRef
1612+
// using path and builds the opening and closing
1613+
// pieces of a for-range loop written in Go in order
1614+
// to access the element contained within the collection.
1615+
// The name of this element value is designated with outputVarName:
1616+
// ex: 'for _, <outputVarName> := ...'
1617+
// Limitations: path supports lists and structs only and
1618+
// the 'break' is coupled with for-range loop to only take
1619+
// first element of a list.
1620+
//
1621+
// Sample Input:
1622+
// - shapeRef: DescribeInstancesOutputShape
1623+
// - path: Reservations.Instances
1624+
// - sourceVarName: resp
1625+
// - outputVarName: elem
1626+
// - indentLevel: 1
1627+
//
1628+
// Sample Output (omit formatting for readability):
1629+
// - opening: "for _, iter0 := range resp.Reservations {
1630+
// for _, elem := range iter0.Instances {"
1631+
// - closing: "break } break }"
1632+
// - updatedIndentLevel: 3
1633+
func generateForRangeLoops(
1634+
// shapeRef of the shape containing element
1635+
shapeRef *awssdkmodel.ShapeRef,
1636+
// path is the path to the element relative to shapeRef
1637+
path string,
1638+
// sourceVarName is the name of struct or field used to access source value
1639+
sourceVarName string,
1640+
// outputVarName is the desired name of the element, once unwrapped
1641+
outputVarName string,
1642+
indentLevel int,
1643+
) (string, string, int) {
1644+
opening, closing := "", ""
1645+
updatedIndentLevel := indentLevel
1646+
1647+
fp := fieldpath.FromString(path)
1648+
unwrapCount := 0
1649+
iterVarName := fmt.Sprintf("iter%d", unwrapCount)
1650+
collectionVarName := sourceVarName
1651+
unpackShape := shapeRef.Shape
1652+
1653+
for fp.Size() > 0 {
1654+
pathPart := fp.PopFront()
1655+
partShapeRef, _ := unpackShape.MemberRefs[pathPart]
1656+
unpackShape = partShapeRef.Shape
1657+
indent := strings.Repeat("\t", updatedIndentLevel)
1658+
iterVarName = fmt.Sprintf("iter%d", unwrapCount)
1659+
collectionVarName += "." + pathPart
1660+
1661+
// Using the fieldpath as a guide, unwrap the shapeRef
1662+
// to generate for-range loops. If pathPart points
1663+
// to a struct member, then simply append struct name
1664+
// to collectionVarName and move on to unwrap the next pathPart/shape.
1665+
// If pathPart points to a list member, then generate for-range loop
1666+
// code and update collectionVarName, unpackShape, and updatedIndentLevel
1667+
// for processing the next loop, if applicable.
1668+
if partShapeRef.Shape.Type == "list" {
1669+
// ex: for _, iter0 := range resp.Reservations {
1670+
opening += fmt.Sprintf("%sfor _, %s := range %s {\n", indent, iterVarName, collectionVarName)
1671+
// ex:
1672+
// break
1673+
// }
1674+
closeLoop := fmt.Sprintf("%s\tbreak\n%s}\n", indent, indent)
1675+
if closing != "" {
1676+
// nested loops need to output inner most closing braces first
1677+
closeLoop += closing
1678+
closing = closeLoop
1679+
} else {
1680+
closing += closeLoop
1681+
}
1682+
// reference iterVarName in subsequent for-loop, if any
1683+
collectionVarName = iterVarName
1684+
unpackShape = partShapeRef.Shape.MemberRef.Shape
1685+
updatedIndentLevel += 1
1686+
}
1687+
unwrapCount += 1
1688+
}
1689+
// replace inner-most range loop value's name with desired outputVarName
1690+
opening = strings.Replace(opening, iterVarName, outputVarName, 1)
1691+
return opening, closing, updatedIndentLevel
1692+
}

0 commit comments

Comments
 (0)