Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix] Add Change and Update policies to the Unmarshal method #288

Merged
merged 6 commits into from
Jul 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions cloudformation/autoscaling/aws-autoscaling-autoscalinggroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ func (r *AutoScalingGroup) UnmarshalJSON(b []byte) error {
DeletionPolicy string
UpdateReplacePolicy string
Condition string
UpdatePolicy *policies.UpdatePolicy
CreationPolicy *policies.CreationPolicy
}{}

dec := json.NewDecoder(bytes.NewReader(b))
Expand Down Expand Up @@ -222,5 +224,13 @@ func (r *AutoScalingGroup) UnmarshalJSON(b []byte) error {
if res.Condition != "" {
r.AWSCloudFormationCondition = res.Condition
}
if res.UpdatePolicy != nil {
r.AWSCloudFormationUpdatePolicy = res.UpdatePolicy
}

if res.CreationPolicy != nil {
r.AWSCloudFormationCreationPolicy = res.CreationPolicy
}

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ func (r *WaitCondition) UnmarshalJSON(b []byte) error {
DeletionPolicy string
UpdateReplacePolicy string
Condition string

CreationPolicy *policies.CreationPolicy
}{}

dec := json.NewDecoder(bytes.NewReader(b))
Expand Down Expand Up @@ -119,5 +121,9 @@ func (r *WaitCondition) UnmarshalJSON(b []byte) error {
if res.Condition != "" {
r.AWSCloudFormationCondition = res.Condition
}
if res.CreationPolicy != nil {
r.AWSCloudFormationCreationPolicy = res.CreationPolicy
}

return nil
}
6 changes: 6 additions & 0 deletions cloudformation/ec2/aws-ec2-instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ func (r *Instance) UnmarshalJSON(b []byte) error {
DeletionPolicy string
UpdateReplacePolicy string
Condition string

CreationPolicy *policies.CreationPolicy
}{}

dec := json.NewDecoder(bytes.NewReader(b))
Expand Down Expand Up @@ -290,5 +292,9 @@ func (r *Instance) UnmarshalJSON(b []byte) error {
if res.Condition != "" {
r.AWSCloudFormationCondition = res.Condition
}
if res.CreationPolicy != nil {
r.AWSCloudFormationCreationPolicy = res.CreationPolicy
}

return nil
}
63 changes: 44 additions & 19 deletions cloudformation/intrinsics.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,29 @@ func strWrap(fn func(interface{}) string) intrinsics.IntrinsicHandler {
func strSplit2Wrap(fn func(string, string) string) intrinsics.IntrinsicHandler {
delim := "."
return func(name string, input interface{}, template interface{}) interface{} {
if str, ok := input.(string); ok {
arr := strings.SplitN(str, delim, 2)
switch v := input.(type) {
case string:
arr := strings.SplitN(v, delim, 2)
if len(arr) != 2 {
return nil
}
return fn(arr[0], arr[1])
case []interface{}:
if len(v) != 2 {
return nil
}

str1, ok := v[0].(string)
if !ok {
return nil
}

str2, ok := v[1].(string)
if !ok {
return nil
}

return fn(str1, str2)
}
return nil
}
Expand Down Expand Up @@ -105,93 +122,93 @@ var EncoderIntrinsics = map[string]intrinsics.IntrinsicHandler{

// Ref creates a CloudFormation Reference to another resource in the template
func Ref(logicalName interface{}) string {
return encode(fmt.Sprintf(`{ "Ref": "%v" }`, logicalName))
return encode(fmt.Sprintf(`{ "Ref": %q }`, logicalName))
}

// ImportValue returns the value of an output exported by another stack. You typically use this function to create cross-stack references. In the following example template snippets, Stack A exports VPC security group values and Stack B imports them.
func ImportValue(name interface{}) string {
return encode(fmt.Sprintf(`{ "Fn::ImportValue": "%v" }`, name))
return encode(fmt.Sprintf(`{ "Fn::ImportValue": %q }`, name))
}

// Base64 returns the Base64 representation of the input string. This function is typically used to pass encoded data to Amazon EC2 instances by way of the UserData property
func Base64(input interface{}) string {
return encode(fmt.Sprintf(`{ "Fn::Base64": "%v" }`, input))
return encode(fmt.Sprintf(`{ "Fn::Base64": %q }`, input))
}

// GetAZs returns an array that lists Availability Zones for a specified region. Because customers have access to different Availability Zones, the intrinsic function Fn::GetAZs enables template authors to write templates that adapt to the calling user's access. That way you don't have to hard-code a full list of Availability Zones for a specified region.
func GetAZs(region interface{}) string {
return encode(fmt.Sprintf(`{ "Fn::GetAZs": "%v" }`, region))
return encode(fmt.Sprintf(`{ "Fn::GetAZs": %q }`, region))
}

// 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" : "%v" }`, value))
return encode(fmt.Sprintf(`{ "Fn::Sub" : %q }`, value))
}

// (str, str) -> str

// GetAtt returns the value of an attribute from a resource in the template.
func GetAtt(logicalName string, attribute string) string {
return encode(fmt.Sprintf(`{ "Fn::GetAtt" : [ "%v", "%v" ] }`, logicalName, attribute))
return encode(fmt.Sprintf(`{ "Fn::GetAtt" : [ %q, %q ] }`, logicalName, attribute))
}

// 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" : [ "%v", "%v" ] }`, delimiter, source))
return encode(fmt.Sprintf(`{ "Fn::Split" : [ %q, %q ] }`, delimiter, source))
}

// Equals compares if two values are equal. Returns true if the two values are equal or false if they aren't.
func Equals(value1, value2 interface{}) string {
return encode(fmt.Sprintf(`{ "Fn::Equals" : [ "%v", "%v" ] }`, value1, value2))
return encode(fmt.Sprintf(`{ "Fn::Equals" : [ %q, %q ] }`, value1, value2))
}

// (str, str, str) -> str

// CIDR returns an array of CIDR address blocks. The number of CIDR blocks returned is dependent on the count parameter.
func CIDR(ipBlock, count, cidrBits interface{}) string {
return encode(fmt.Sprintf(`{ "Fn::Cidr" : [ "%v", "%v", "%v" ] }`, ipBlock, count, cidrBits))
return encode(fmt.Sprintf(`{ "Fn::Cidr" : [ %q, %q, %q ] }`, ipBlock, count, cidrBits))
}

// FindInMap returns the value corresponding to keys in a two-level map that is declared in the Mappings section.
func FindInMap(mapName, topLevelKey, secondLevelKey interface{}) string {
return encode(fmt.Sprintf(`{ "Fn::FindInMap" : [ "%v", "%v", "%v" ] }`, mapName, topLevelKey, secondLevelKey))
return encode(fmt.Sprintf(`{ "Fn::FindInMap" : [ %q, %q, %q ] }`, mapName, topLevelKey, secondLevelKey))
}

// If returns one value if the specified condition evaluates to true and another value if the specified condition evaluates to false. Currently, AWS CloudFormation supports the Fn::If intrinsic function in the metadata attribute, update policy attribute, and property values in the Resources section and Outputs sections of a template. You can use the AWS::NoValue pseudo parameter as a return value to remove the corresponding property.
func If(value, ifEqual, ifNotEqual interface{}) string {
return encode(fmt.Sprintf(`{ "Fn::If" : [ "%v", "%v", "%v" ] }`, value, ifEqual, ifNotEqual))
return encode(fmt.Sprintf(`{ "Fn::If" : [ %q, %q, %q ] }`, value, ifEqual, ifNotEqual))
}

// (str, []str) -> str

// Join appends a set of values into a single value, separated by the specified delimiter. If a delimiter is the empty string, the set of values are concatenated with no delimiter.
func Join(delimiter interface{}, values []string) string {
return encode(fmt.Sprintf(`{ "Fn::Join": [ "%v", [ "%v" ] ] }`, delimiter, strings.Trim(strings.Join(values, `", "`), `, "`)))
return encode(fmt.Sprintf(`{ "Fn::Join": [ %q, [ %v ] ] }`, delimiter, printList(values)))
}

// 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": [ "%v", "%v" ] }`, index, list[0]))
return encode(fmt.Sprintf(`{ "Fn::Select": [ %q, %q ] }`, index, list[0]))
}
return encode(fmt.Sprintf(`{ "Fn::Select": [ "%v", [ "%v" ] ] }`, index, strings.Trim(strings.Join(list, `", "`), `, "`)))
return encode(fmt.Sprintf(`{ "Fn::Select": [ %q, [ %v ] ] }`, index, printList(list)))
}

// ([]str) -> str

// And returns true if all the specified conditions evaluate to true, or returns false if any one of the conditions evaluates to false. Fn::And acts as an AND operator. The minimum number of conditions that you can include is 2, and the maximum is 10.
func And(conditions []string) string {
return encode(fmt.Sprintf(`{ "Fn::And": [ "%v" ] }`, strings.Trim(strings.Join(conditions, `", "`), `, "`)))
return encode(fmt.Sprintf(`{ "Fn::And": [ %v ] }`, printList(conditions)))
}

// Not returns true for a condition that evaluates to false or returns false for a condition that evaluates to true. Fn::Not acts as a NOT operator.
func Not(conditions []string) string {
return encode(fmt.Sprintf(`{ "Fn::Not": [ "%v" ] }`, strings.Trim(strings.Join(conditions, `", "`), `, "`)))
return encode(fmt.Sprintf(`{ "Fn::Not": [ %v ] }`, printList(conditions)))
}

// Or returns true if any one of the specified conditions evaluate to true, or returns false if all of the conditions evaluates to false. Fn::Or acts as an OR operator. The minimum number of conditions that you can include is 2, and the maximum is 10.
func Or(conditions []string) string {
return encode(fmt.Sprintf(`{ "Fn::Or": [ "%v" ] }`, strings.Trim(strings.Join(conditions, `", "`), `, "`)))
return encode(fmt.Sprintf(`{ "Fn::Or": [ %v ] }`, printList(conditions)))
}

// encode takes a string representation of an intrinsic function, and base64 encodes it.
Expand All @@ -200,6 +217,14 @@ func encode(value string) string {
return base64.StdEncoding.EncodeToString([]byte(value))
}

func printList(values []string) string {
escaped := make([]string, len(values))
for i := range values {
escaped[i] = fmt.Sprintf("%q", values[i])
}
return strings.Join(escaped, `,`)
}

func interfaceAtostrA(values []interface{}) []string {
converted := make([]string, len(values))
for i := range values {
Expand Down
7 changes: 7 additions & 0 deletions cloudformation/intrinsics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ var _ = Describe("Goformation", func() {
Input: `Description: !Or [a, b, c]`,
Expected: `{"Description":{"Fn::Or":["a","b","c"]}}`,
},
{
Name: "JSON escaped",
Input: `Description: !Sub |
"quote"
newline`,
Expected: `{"Description":{"Fn::Sub":"\"quote\"\nnewline"}}`,
},
}

for _, test := range tests {
Expand Down
5 changes: 5 additions & 0 deletions cloudformation/lambda/aws-lambda-alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func (r *Alias) UnmarshalJSON(b []byte) error {
DeletionPolicy string
UpdateReplacePolicy string
Condition string
UpdatePolicy *policies.UpdatePolicy
}{}

dec := json.NewDecoder(bytes.NewReader(b))
Expand Down Expand Up @@ -132,5 +133,9 @@ func (r *Alias) UnmarshalJSON(b []byte) error {
if res.Condition != "" {
r.AWSCloudFormationCondition = res.Condition
}
if res.UpdatePolicy != nil {
r.AWSCloudFormationUpdatePolicy = res.UpdatePolicy
}

return nil
}
10 changes: 9 additions & 1 deletion generate/templates/resource.template
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ func (r {{.StructName}}) MarshalJSON() ([]byte, error) {
})
}



// UnmarshalJSON is a custom JSON unmarshalling hook that strips the outer
// AWS CloudFormation resource object, and just keeps the 'Properties' field.
func (r *{{.StructName}}) UnmarshalJSON(b []byte) error {
Expand All @@ -95,6 +97,8 @@ func (r *{{.StructName}}) UnmarshalJSON(b []byte) error {
DeletionPolicy string
UpdateReplacePolicy string
Condition string
{{if .HasUpdatePolicy}}UpdatePolicy *policies.UpdatePolicy {{end}}
{{if .HasCreationPolicy}}CreationPolicy *policies.CreationPolicy{{end}}
}{}

dec := json.NewDecoder(bytes.NewReader(b))
Expand Down Expand Up @@ -123,7 +127,11 @@ func (r *{{.StructName}}) UnmarshalJSON(b []byte) error {
}
if res.Condition != "" {
r.AWSCloudFormationCondition = res.Condition
}
} {{if .HasUpdatePolicy }}
if res.UpdatePolicy != nil { r.AWSCloudFormationUpdatePolicy = res.UpdatePolicy }
{{end}} {{if .HasCreationPolicy}}
if res.CreationPolicy != nil { r.AWSCloudFormationCreationPolicy = res.CreationPolicy }
{{end}}
return nil
}

Expand Down