Repository: aws-cloudformation/rain (go)

Issue:
The format command does not support yaml anchors when converting to json
YAML anchors are supported by Go, but when trying to convert to JSON with `rain fmt -j`, we get an error:

`yaml: unknown anchor 'fooval' referenced`

```yaml
Resources:
  MyLambda:
    Type: AWS::Lambda::Function
    Properties:
      Code: &code
        ZipFile: "print(\"Hello World!\")\n"
      FunctionName: my-lambda
      Handler: index.lambda_handler
      Role: !ImportValue 'MyLambdaRoleArn'
      Runtime: python3.8
      Tags:
        - Key: foo
          Value: &fooval myval
        - Key: bar
          Value: *fooval
    Metadata:
      Comment: *code
 ```


Pull request:
Fixed template packaging to resolve anchors
Fixes #257 

Instead of using just the `fmt` command, first `pkg` and then pipe into `fmt`.

Going to leave this a draft for now. All tests are passing but there are some templates with anchors that will cause `fmt` to fail, which means you would have to package first. That may be unexpected for some users, although the anchors would fail when you send them to CloudFormation, so maybe it doesn't matter. The issue is due to default sorting, which puts the anchors out of order and the go yaml parser can't resolve them. Maybe that's ok? If you want to format a template with anchors you can add `--unsorted` and it will work.

@hariprakash-j, @khmoryz, @null93, @iainelder 
@j5nb4l 



Patch:
diff --git a/cft/pkg/pkg.go b/cft/pkg/pkg.go
index f6a5896b..8fc36ddd 100644
--- a/cft/pkg/pkg.go
+++ b/cft/pkg/pkg.go
@@ -70,12 +70,31 @@ func Template(t cft.Template, rootDir string) (cft.Template, error) {
 	// config.Debugf("Original template: %v", string(j))
 
 	changed, err := transform(templateNode, rootDir, t, nil)
+	if err != nil {
+		return t, err
+	}
 
 	// j, _ = json.MarshalIndent(templateNode, "", "  ")
 	// config.Debugf("Transformed template: %v", string(j))
 
 	if changed {
 		t, err = parse.Node(templateNode)
+		if err != nil {
+			return t, err
+		}
+	}
+
+	// Encode and Decode to resolve anchors
+	var decoded interface{}
+	err = templateNode.Decode(&decoded)
+	if err != nil {
+		return t, err
+	}
+	config.Debugf("decoded: %v", decoded)
+
+	err = templateNode.Encode(&decoded)
+	if err != nil {
+		return t, err
 	}
 
 	return t, err
diff --git a/cft/pkg/tmpl/anchors-expect.yaml b/cft/pkg/tmpl/anchors-expect.yaml
new file mode 100644
index 00000000..9d28aaa4
--- /dev/null
+++ b/cft/pkg/tmpl/anchors-expect.yaml
@@ -0,0 +1,20 @@
+Resources:
+  MyLambda:
+    Type: AWS::Lambda::Function
+    Metadata:
+      Comment:
+        ZipFile: |
+          print("Hello World!")
+    Properties:
+      Code:
+        ZipFile: |
+          print("Hello World!")
+      FunctionName: my-lambda
+      Handler: index.lambda_handler
+      Role: !ImportValue MyLambdaRoleArn
+      Runtime: python3.8
+      Tags:
+        - Key: foo
+          Value: myval
+        - Key: bar
+          Value: myval
diff --git a/cft/pkg/tmpl/anchors.yaml b/cft/pkg/tmpl/anchors.yaml
new file mode 100644
index 00000000..bf579bad
--- /dev/null
+++ b/cft/pkg/tmpl/anchors.yaml
@@ -0,0 +1,18 @@
+Resources:
+  MyLambda:
+    Type: AWS::Lambda::Function
+    Properties:
+      Code: &code
+        ZipFile: "print(\"Hello World!\")\n"
+      FunctionName: my-lambda
+      Handler: index.lambda_handler
+      Role: !ImportValue 'MyLambdaRoleArn'
+      Runtime: python3.8
+      Tags:
+        - Key: foo
+          Value: &fooval myval
+        - Key: bar
+          Value: *fooval
+    Metadata:
+      Comment: *code
+


Test patch:
diff --git a/cft/pkg/anchor_test.go b/cft/pkg/anchor_test.go
new file mode 100644
index 00000000..bb6926d8
--- /dev/null
+++ b/cft/pkg/anchor_test.go
@@ -0,0 +1,33 @@
+package pkg
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestAnchors(t *testing.T) {
+	source := "./tmpl/anchors.yaml"
+	template, err := File(source)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected, err := File("./tmpl/anchors-expect.yaml")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// This actually succeeds, because the diff package uses YAML decoding
+	// to make the comparison, and the yaml package understands anchors
+	//d := diff.New(template, expected)
+	//if d.Mode() != "=" {
+	//	t.Errorf("template does not match expected: %v", d.Format(true))
+	//}
+
+	// This correctly shows differences in the templates
+	if d := cmp.Diff(expected, template); d != "" {
+		t.Error(d)
+	}
+
+}