diff --git a/cloudformation/all.go b/cloudformation/all.go index 68f014acf2..d37f5c480f 100644 --- a/cloudformation/all.go +++ b/cloudformation/all.go @@ -2,6 +2,7 @@ package cloudformation import ( "fmt" + "github.com/awslabs/goformation/v5/cloudformation/accessanalyzer" "github.com/awslabs/goformation/v5/cloudformation/acmpca" "github.com/awslabs/goformation/v5/cloudformation/amazonmq" @@ -28,6 +29,7 @@ import ( "github.com/awslabs/goformation/v5/cloudformation/batch" "github.com/awslabs/goformation/v5/cloudformation/budgets" "github.com/awslabs/goformation/v5/cloudformation/cassandra" + "github.com/awslabs/goformation/v5/cloudformation/cdkmetadata" "github.com/awslabs/goformation/v5/cloudformation/ce" "github.com/awslabs/goformation/v5/cloudformation/certificatemanager" "github.com/awslabs/goformation/v5/cloudformation/chatbot" @@ -83,6 +85,7 @@ import ( "github.com/awslabs/goformation/v5/cloudformation/frauddetector" "github.com/awslabs/goformation/v5/cloudformation/fsx" "github.com/awslabs/goformation/v5/cloudformation/gamelift" + "github.com/awslabs/goformation/v5/cloudformation/global" "github.com/awslabs/goformation/v5/cloudformation/globalaccelerator" "github.com/awslabs/goformation/v5/cloudformation/glue" "github.com/awslabs/goformation/v5/cloudformation/greengrass" @@ -183,13 +186,12 @@ import ( "github.com/awslabs/goformation/v5/cloudformation/wisdom" "github.com/awslabs/goformation/v5/cloudformation/workspaces" "github.com/awslabs/goformation/v5/cloudformation/xray" - - "github.com/awslabs/goformation/v5/cloudformation/global" ) // AllResources fetches an iterable map all CloudFormation and SAM resources func AllResources() map[string]Resource { return map[string]Resource{ + "AWS::CDK::Metadata": &cdkmetadata.CDKMetadata{}, "AWS::ACMPCA::Certificate": &acmpca.Certificate{}, "AWS::ACMPCA::CertificateAuthority": &acmpca.CertificateAuthority{}, "AWS::ACMPCA::CertificateAuthorityActivation": &acmpca.CertificateAuthorityActivation{}, diff --git a/cloudformation/cdkmetadata/aws-cdkmetadata-cdkmetadata.go b/cloudformation/cdkmetadata/aws-cdkmetadata-cdkmetadata.go new file mode 100644 index 0000000000..0b0eb3bb24 --- /dev/null +++ b/cloudformation/cdkmetadata/aws-cdkmetadata-cdkmetadata.go @@ -0,0 +1,59 @@ +package cdkmetadata + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// CDKMetadata AWS CloudFormation Resource (AWS::CDK::Metadata) +type CDKMetadata struct { + Analytics string `json:"Analytics,omitempty"` + + Metadata map[string]interface{} `json:"-"` +} + +// AWSCloudFormationType returns the AWS CloudFormation resource type +func (r *CDKMetadata) AWSCloudFormationType() string { + return "AWS::CDK::Metadata" +} + +// MarshalJSON is a custom JSON marshalling hook that embeds this object into +// an AWS CloudFormation JSON resource's 'Properties' field and adds a 'Type'. +func (r CDKMetadata) MarshalJSON() ([]byte, error) { + type Properties CDKMetadata + return json.Marshal(&struct { + Type string + Properties Properties + Metadata map[string]interface{} `json:"Metadata,omitempty"` + }{ + Type: r.AWSCloudFormationType(), + Properties: (Properties)(r), + Metadata: r.Metadata, + }) +} + +// UnmarshalJSON is a custom JSON unmarshalling hook that strips the outer +// AWS CloudFormation resource object, and just keeps the 'Properties' field. +func (r *CDKMetadata) UnmarshalJSON(b []byte) error { + type Properties CDKMetadata + res := &struct { + Type string + Properties *Properties + Metadata map[string]interface{} + }{} + + dec := json.NewDecoder(bytes.NewReader(b)) + dec.DisallowUnknownFields() // Force error if unknown field is found + + if err := dec.Decode(&res); err != nil { + fmt.Printf("ERROR: %s\n", err) + return err + } + + // If the resource has no Properties set, it could be nil + if res.Properties != nil { + *r = CDKMetadata(*res.Properties) + } + return nil +} diff --git a/cloudformation/template.go b/cloudformation/template.go index 20eeb739aa..8e617de197 100644 --- a/cloudformation/template.go +++ b/cloudformation/template.go @@ -82,6 +82,7 @@ func (resources *Resources) UnmarshalJSON(b []byte) error { func (globals *Globals) UnmarshalJSON(b []byte) error { // Globals + var rawGlobals map[string]*json.RawMessage err := json.Unmarshal(b, &rawGlobals) diff --git a/goformation.go b/goformation.go index bc58c7dbbd..65978a7726 100644 --- a/goformation.go +++ b/goformation.go @@ -43,6 +43,7 @@ func ParseYAML(data []byte) (*cloudformation.Template, error) { // ParseYAMLWithOptions an AWS CloudFormation template (expects a []byte of valid YAML) // Parsing can be tweaked via the specified options. func ParseYAMLWithOptions(data []byte, options *intrinsics.ProcessorOptions) (*cloudformation.Template, error) { + // Process all AWS CloudFormation intrinsic functions (e.g. Fn::Join) intrinsified, err := intrinsics.ProcessYAML(data, options) if err != nil { @@ -73,8 +74,8 @@ func ParseJSONWithOptions(data []byte, options *intrinsics.ProcessorOptions) (*c } func unmarshal(data []byte) (*cloudformation.Template, error) { - template := &cloudformation.Template{} + if err := json.Unmarshal(data, template); err != nil { return nil, err } diff --git a/goformation_test.go b/goformation_test.go index 372355cfe4..846638d022 100644 --- a/goformation_test.go +++ b/goformation_test.go @@ -1267,6 +1267,33 @@ var _ = Describe("Goformation", func() { }) + Context("with a CDKMetada template", func() { + + template, err := goformation.Open("test/yaml/cdkmetadata.yaml") + It("should successfully validate the template", func() { + Expect(err).To(BeNil()) + Expect(template).ShouldNot(BeNil()) + }) + + It("should correctly Marshal the cdkmetadata resource", func() { + data, err := template.JSON() + Expect(err).To(BeNil()) + + var result map[string]interface{} + if err := json.Unmarshal(data, &result); err != nil { + Fail(err.Error()) + } + + resources, ok := result["Resources"].(map[string]interface{}) + Expect(ok).To(BeTrue()) + Expect(resources).To(HaveLen(1)) + Expect(resources).To(HaveKey("CDKMetadata")) + + mcr := resources["CDKMetadata"].(map[string]interface{}) + Expect(mcr["Properties"]).To(HaveKey("Analytics")) + }) + }) + Context("with a template that contains conditional resources", func() { template := &cloudformation.Template{ diff --git a/intrinsics/intrinsics.go b/intrinsics/intrinsics.go index 258c7f7c7f..449937e239 100644 --- a/intrinsics/intrinsics.go +++ b/intrinsics/intrinsics.go @@ -74,7 +74,6 @@ func ProcessYAML(input []byte, options *ProcessorOptions) ([]byte, error) { // AWS CloudFormation intrinsic functions, resolves them, and then returns // the resulting interface{} object. func ProcessJSON(input []byte, options *ProcessorOptions) ([]byte, error) { - // First, unmarshal the JSON to a generic interface{} type var unmarshalled interface{} if err := json.Unmarshal(input, &unmarshalled); err != nil { diff --git a/schema/cloudformation.go b/schema/cloudformation.go index ff96b4cddf..8e7e6b3427 100644 --- a/schema/cloudformation.go +++ b/schema/cloudformation.go @@ -18958,6 +18958,19 @@ var CloudformationSchema = `{ ], "type": "object" }, + "AWS::CDK::Metadata": { + "Type": "AWS::CDK::Metadata", + "Properties": { + "Analytics": { + "type": "string" + } + }, + "Metadata": { + "aws:cdk:path": { + "type": "string" + } + } + }, "AWS::CloudFormation::CustomResource": { "additionalProperties": false, "properties": { diff --git a/schema/cloudformation.schema.json b/schema/cloudformation.schema.json index a7b1b67f96..5a8a925afb 100644 --- a/schema/cloudformation.schema.json +++ b/schema/cloudformation.schema.json @@ -18955,6 +18955,19 @@ ], "type": "object" }, + "AWS::CDK::Metadata": { + "Type": "AWS::CDK::Metadata", + "Properties": { + "Analytics": { + "type": "string" + } + }, + "Metadata": { + "aws:cdk:path": { + "type": "string" + } + } + }, "AWS::CloudFormation::CustomResource": { "additionalProperties": false, "properties": { diff --git a/test/yaml/cdkmetadata.yaml b/test/yaml/cdkmetadata.yaml new file mode 100644 index 0000000000..b9108d120a --- /dev/null +++ b/test/yaml/cdkmetadata.yaml @@ -0,0 +1,9 @@ + +AWSTemplateFormatVersion: 2010-09-09 +Resources: + CDKMetadata: + Type: AWS::CDK::Metadata + Properties: + Analytics: v2:deflate64:foobar + Metadata: + aws:cdk:path: foobar/CDKMetadata/Default \ No newline at end of file