From 05db69af136f3964a6dcc218a9cb19a6bd209dda Mon Sep 17 00:00:00 2001 From: Jarred Ward Date: Fri, 10 Mar 2023 14:49:21 -0800 Subject: [PATCH] feat(intrinsics): support for nested intrinsics Newly supported examples: ```golang SelectPtr(0, GetAZs("")) Select(2, Split("/", GetAtt("Bucket", "WebsiteURL"))) JoinPtr("", []string{ Sub("arn:aws:nnnnn:${AWS::Region}:${AWS::AccountId}:identity/"), If( "SomeAccount", canonicalDomainName, cloudformation.ImportValue(someOtherDomain), ), }) ``` --- cloudformation/intrinsics.go | 24 +++++++++++++++--------- cloudformation/intrinsics_test.go | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/cloudformation/intrinsics.go b/cloudformation/intrinsics.go index 552268f8f4..b6045a0df5 100644 --- a/cloudformation/intrinsics.go +++ b/cloudformation/intrinsics.go @@ -112,7 +112,7 @@ var EncoderIntrinsics = map[string]intrinsics.IntrinsicHandler{ "Fn::GetAZs": strWrap(GetAZs), "Fn::ImportValue": strWrap(ImportValue), "Fn::Join": str2Wrap(Join), - "Fn::Select": str2AWrap(Select), + "Fn::Select": str2Wrap(Select), "Fn::Split": str2Wrap(Split), "Fn::Sub": strWrap(Sub), "Ref": strWrap(Ref), @@ -159,7 +159,7 @@ func GetAZsPtr(region interface{}) *string { // Sub substitutes variables in an input string with values that you specify. In your templates, you can use this function to construct commands or outputs that include values that aren't available until you create or update a stack. func Sub(value interface{}) string { - return encode(fmt.Sprintf(`{ "Fn::Sub" : %q }`, value)) + return encode(fmt.Sprintf(`{ "Fn::Sub" : %s }`, toJSON(value))) } func SubPtr(value interface{}) *string { @@ -193,7 +193,7 @@ func GetAttPtr(logicalName string, attribute string) *string { // Split splits a string into a list of string values so that you can select an element from the resulting string list, use the Fn::Split intrinsic function. Specify the location of splits with a delimiter, such as , (a comma). After you split a string, use the Fn::Select function to pick a specific element. func Split(delimiter, source interface{}) string { - return encode(fmt.Sprintf(`{ "Fn::Split" : [ %q, %q ] }`, delimiter, source)) + return encode(fmt.Sprintf(`{ "Fn::Split" : [ %q, %s ] }`, delimiter, toJSON(source))) } func SplitPtr(delimiter, source interface{}) *string { @@ -287,14 +287,11 @@ func JoinPtr(delimiter interface{}, value interface{}) *string { } // Select returns a single object from a list of objects by index. -func Select(index interface{}, list []string) string { - if len(list) == 1 { - return encode(fmt.Sprintf(`{ "Fn::Select": [ %q, %q ] }`, index, list[0])) - } - return encode(fmt.Sprintf(`{ "Fn::Select": [ %q, [ %v ] ] }`, index, printList(list))) +func Select(index , list interface{}) string { + return encode(fmt.Sprintf(`{ "Fn::Select": [ %s, %s ] }`, toJSON(index), toJSON(list))) } -func SelectPtr(index interface{}, list []string) *string { +func SelectPtr(index, list interface{}) *string { return String(Select(index, list)) } @@ -368,3 +365,12 @@ func isBase64(s string) bool { _, err := base64.StdEncoding.DecodeString(s) return err == nil } + +func toJSON(value interface{}) string { + j, err := json.Marshal(value) + if err != nil { + fmt.Printf("Unsupported type for intrinsic JSON: %v\n", err) + return fmt.Sprintf("%q", value) + } + return string(j) +} diff --git a/cloudformation/intrinsics_test.go b/cloudformation/intrinsics_test.go index fcdf8475cb..319d17fb6a 100644 --- a/cloudformation/intrinsics_test.go +++ b/cloudformation/intrinsics_test.go @@ -79,11 +79,33 @@ var _ = Describe("Goformation", func() { Input: `Description: !Join [a, [b, c]]`, Expected: `{"Description":{"Fn::Join":["a",["b","c"]]}}`, }, + { + Name: "Join Nested", + Input: `Description: + Fn::Join: + - '' + - - !Sub '${AWS::Region}' + - Fn::If: + - 'A' + - 'A1' + - !ImportValue 'B'`, + Expected: `{"Description":{"Fn::Join":["",[{"Fn::Sub":"${AWS::Region}"},{"Fn::If":["A","A1",{"Fn::ImportValue":"B"}]}]]}}`, + }, { Name: "Select", Input: `Description: !Select [a, [b, c]]`, Expected: `{"Description":{"Fn::Select":["a",["b","c"]]}}`, }, + { + Name: "Select Nested", + Input: `Description: + Fn::Select: + - 2 + - Fn::Split: + - '/' + - !GetAtt: 'site.url'`, + Expected: `{"Description":{"Fn::Select":[2,{"Fn::Split":["/","site.url"]}]}}`, + }, { Name: "And", Input: `Description: !And [a, b, c]`,