Skip to content

Commit

Permalink
feat: switch go-yaml implementation to most recent version (awslabs#535)
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenfonseca committed Jan 13, 2023
1 parent 5741ed8 commit 0ca6ce2
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 50 deletions.
12 changes: 6 additions & 6 deletions cloudformation/policies_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cloudformation_test

import (
"github.com/sanathkr/yaml"
"gopkg.in/yaml.v3"

"github.com/awslabs/goformation/v7/cloudformation"
"github.com/awslabs/goformation/v7/cloudformation/autoscaling"
Expand Down Expand Up @@ -47,9 +47,9 @@ var _ = Describe("Goformation", func() {
},
Expected: map[string]interface{}{
"AutoScalingRollingUpdate": map[string]interface{}{
"MaxBatchSize": float64(10),
"MinInstancesInService": float64(11),
"MinSuccessfulInstancesPercent": float64(12),
"MaxBatchSize": 10,
"MinInstancesInService": 11,
"MinSuccessfulInstancesPercent": 12,
"PauseTime": "test-pause-time",
"SuspendProcesses": []interface{}{"test-suspend1", "test-suspend2"},
"WaitOnResourceSignals": true,
Expand Down Expand Up @@ -146,10 +146,10 @@ var _ = Describe("Goformation", func() {
},
Expected: map[string]interface{}{
"AutoScalingCreationPolicy": map[string]interface{}{
"MinSuccessfulInstancesPercent": float64(10),
"MinSuccessfulInstancesPercent": 10,
},
"ResourceSignal": map[string]interface{}{
"Count": float64(11),
"Count": 11,
"Timeout": "test-timeout",
},
},
Expand Down
16 changes: 13 additions & 3 deletions cloudformation/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"strings"

"github.com/awslabs/goformation/v7/intrinsics"
"github.com/sanathkr/yaml"
"gopkg.in/yaml.v3"
)

// Template represents an AWS CloudFormation template
Expand Down Expand Up @@ -231,12 +231,22 @@ func (t *Template) JSON() ([]byte, error) {

// YAML converts an AWS CloudFormation template object to YAML
func (t *Template) YAML() ([]byte, error) {

j, err := t.JSON()
if err != nil {
return nil, err
}

return yaml.JSONToYAML(j)
var jsonObj interface{}
// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
// Go JSON library doesn't try to pick the right number type (int, float,
// etc.) when unmarshalling to interface{}, it just picks float64
// universally. go-yaml does go through the effort of picking the right
// number type, so we can preserve number type throughout this process.
err = yaml.Unmarshal(j, &jsonObj)
if err != nil {
return nil, err
}

// Marshal this object into YAML
return yaml.Marshal(jsonObj)
}
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ module github.com/awslabs/goformation/v7
require (
github.com/onsi/ginkgo/v2 v2.3.1
github.com/onsi/gomega v1.22.1
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b
github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522
github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand All @@ -16,7 +15,6 @@ require (
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

go 1.17
26 changes: 13 additions & 13 deletions goformation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"encoding/json"
"fmt"

"github.com/sanathkr/yaml"
"gopkg.in/yaml.v3"

"github.com/awslabs/goformation/v7"
"github.com/awslabs/goformation/v7/cloudformation"
Expand Down Expand Up @@ -98,19 +98,19 @@ func Example_to_yaml() {
}

// Output:
// AWSTemplateFormatVersion: 2010-09-09
// AWSTemplateFormatVersion: "2010-09-09"
// Resources:
// MyTopic:
// Properties:
// TopicName: my-topic-1575143970
// Type: AWS::SNS::Topic
// MyTopicSubscription:
// Properties:
// Endpoint: some.email@example.com
// Protocol: email
// TopicArn:
// Ref: MyTopic
// Type: AWS::SNS::Subscription
// MyTopic:
// Properties:
// TopicName: my-topic-1575143970
// Type: AWS::SNS::Topic
// MyTopicSubscription:
// Properties:
// Endpoint: some.email@example.com
// Protocol: email
// TopicArn:
// Ref: MyTopic
// Type: AWS::SNS::Subscription

}

Expand Down
14 changes: 11 additions & 3 deletions intrinsics/intrinsics.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"encoding/json"
"fmt"

yamlwrapper "github.com/sanathkr/yaml"
"gopkg.in/yaml.v3"
)

// IntrinsicHandler is a function that applies an intrinsic function and returns
Expand Down Expand Up @@ -61,13 +61,21 @@ func ProcessYAML(input []byte, options *ProcessorOptions) ([]byte, error) {
// Convert short form intrinsic functions (e.g. !Sub) to long form
registerTagMarshallers()

data, err := yamlwrapper.YAMLToJSON(input)
// Convert YAML to an object
var yamlObj interface{}
err := yaml.Unmarshal(input, &customTagProcessor{
target: &yamlObj,
})
if err != nil {
return nil, fmt.Errorf("invalid YAML template: %s", err)
}

return ProcessJSON(data, options)
data, err := json.Marshal(yamlObj)
if err != nil {
return nil, err
}

return ProcessJSON(data, options)
}

// ProcessJSON recursively searches through a byte array of JSON data for all
Expand Down
96 changes: 75 additions & 21 deletions intrinsics/tags.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package intrinsics

import (
"reflect"

yaml "github.com/sanathkr/go-yaml"
yaml "gopkg.in/yaml.v3"
"strings"
)

var allTags = []string{
Expand All @@ -12,36 +11,91 @@ var allTags = []string{
"Equals", "Cidr", "And", "If", "Not", "Or",
}

type tagUnmarshalerType struct {
}
var tagResolvers = make(map[string]func(*yaml.Node) (*yaml.Node, error))

func (t *tagUnmarshalerType) UnmarshalYAMLTag(tag string, fieldValue reflect.Value) reflect.Value {
type fragment struct {
content *yaml.Node
}

prefix := "Fn::"
if tag == "Ref" || tag == "Condition" {
prefix = ""
}
func (f *fragment) UnmarshalYAML(value *yaml.Node) error {
var err error
f.content, err = resolveTags(value)
return err
}

tag = prefix + tag
type customTagProcessor struct {
target interface{}
}

output := reflect.ValueOf(make(map[interface{}]interface{}))
key := reflect.ValueOf(tag)
func (i *customTagProcessor) UnmarshalYAML(value *yaml.Node) error {
resolved, err := resolveTags(value)
if err != nil {
return err
}

output.SetMapIndex(key, fieldValue)
return resolved.Decode(i.target)
}

return output
func addTagResolver(tag string, resolver func(*yaml.Node) (*yaml.Node, error)) {
tagResolvers[tag] = resolver
}

var tagUnmarshaller = &tagUnmarshalerType{}
func resolveTags(node *yaml.Node) (*yaml.Node, error) {
for tag, fn := range tagResolvers {
if node.Tag == tag {
return fn(node)
}
}

func registerTagMarshallers() {
for _, tag := range allTags {
yaml.RegisterTagUnmarshaler("!"+tag, tagUnmarshaller)
if node.Kind == yaml.SequenceNode || node.Kind == yaml.MappingNode {
var err error
for i := range node.Content {
node.Content[i], err = resolveTags(node.Content[i])
if err != nil {
return nil, err
}
}
}

return node, nil
}

func unregisterTagMarshallers() {
func registerTagMarshallers() {
for _, tag := range allTags {
yaml.UnRegisterTagUnmarshaler("!" + tag)
addTagResolver("!"+tag, func(tag string) func(node *yaml.Node) (*yaml.Node, error) {
return func(node *yaml.Node) (*yaml.Node, error) {
prefix := "Fn::"
if tag == "Ref" || tag == "Condition" || strings.HasPrefix(tag, prefix) {
prefix = ""
}

tag = prefix + tag

output := &yaml.Node{
Kind: yaml.MappingNode,
Tag: "!!map",
Value: "",
Content: []*yaml.Node{
{Kind: yaml.ScalarNode, Tag: "!!str", Value: tag},
},
}

if len(node.Content) == 0 {
output.Content = append(output.Content, &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: node.Value,
})
} else {
output.Content = append(output.Content, &yaml.Node{
Kind: yaml.SequenceNode,
Tag: "!!seq",
Content: node.Content,
})
}

return output, nil
}
}(tag))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ Resources:
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaFunction.Arn}/invocations
responses: {}
swagger: '2.0'



Expand Down

0 comments on commit 0ca6ce2

Please sign in to comment.