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

Add DependOn, DeletionPolicy and others to CustomResource #350

Merged
merged 2 commits into from
Feb 25, 2021
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
130 changes: 73 additions & 57 deletions cloudformation/custom_resource.go
Original file line number Diff line number Diff line change
@@ -1,87 +1,103 @@
package cloudformation

import (
"bytes"
"encoding/json"
"fmt"

"github.com/awslabs/goformation/v4/cloudformation/policies"
)

// See: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html
// CustomResource AWS CloudFormation Resource (AWS::CloudFormation::CustomResource)
// See: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html
type CustomResource struct {
Type string `json:"Type,omitempty"`
Properties map[string]interface{} `json:"Properties,omitempty"`

// _deletionPolicy represents a CloudFormation DeletionPolicy
_deletionPolicy policies.DeletionPolicy
// AWSCloudFormationDeletionPolicy represents a CloudFormation DeletionPolicy
AWSCloudFormationDeletionPolicy policies.DeletionPolicy `json:"-"`

// _dependsOn stores the logical ID of the resources to be created before this resource
_dependsOn []string
// AWSCloudFormationUpdateReplacePolicy represents a CloudFormation UpdateReplacePolicy
AWSCloudFormationUpdateReplacePolicy policies.UpdateReplacePolicy `json:"-"`

// _metadata stores structured data associated with this resource
_metadata map[string]interface{}
// AWSCloudFormationDependsOn stores the logical ID of the resources to be created before this resource
AWSCloudFormationDependsOn []string `json:"-"`

// AWSCloudFormationMetadata stores structured data associated with this resource
AWSCloudFormationMetadata map[string]interface{} `json:"-"`

// AWSCloudFormationCondition stores the logical ID of the condition that must be satisfied for this resource to be created
AWSCloudFormationCondition string `json:"-"`
}

// AWSCloudFormationType returns the AWS CloudFormation resource type
func (r *CustomResource) AWSCloudFormationType() string {
return r.Type
}

// DependsOn returns a slice of logical ID names this resource depends on.
// see: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html
func (r *CustomResource) DependsOn() []string {
return r._dependsOn
// 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 CustomResource) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Type string
Properties interface{}
DependsOn []string `json:"DependsOn,omitempty"`
Metadata map[string]interface{} `json:"Metadata,omitempty"`
DeletionPolicy policies.DeletionPolicy `json:"DeletionPolicy,omitempty"`
UpdateReplacePolicy policies.UpdateReplacePolicy `json:"UpdateReplacePolicy,omitempty"`
Condition string `json:"Condition,omitempty"`
}{
Type: r.AWSCloudFormationType(),
Properties: (map[string]interface{})(r.Properties),
DependsOn: r.AWSCloudFormationDependsOn,
Metadata: r.AWSCloudFormationMetadata,
DeletionPolicy: r.AWSCloudFormationDeletionPolicy,
UpdateReplacePolicy: r.AWSCloudFormationUpdateReplacePolicy,
Condition: r.AWSCloudFormationCondition,
})
}

// SetDependsOn specify that the creation of this resource follows another.
// see: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html
func (r *CustomResource) SetDependsOn(dependencies []string) {
r._dependsOn = dependencies
}
// UnmarshalJSON is a custom JSON unmarshalling hook that strips the outer
// AWS CloudFormation resource object, and just keeps the 'Properties' field.
func (r *CustomResource) UnmarshalJSON(b []byte) error {
res := &struct {
Type string
Properties map[string]interface{}
DependsOn []string
Metadata map[string]interface{}
DeletionPolicy string
UpdateReplacePolicy string
Condition string
}{}

// Metadata returns the metadata associated with this resource.
// see: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-metadata.html
func (r *CustomResource) Metadata() map[string]interface{} {
return r._metadata
}
dec := json.NewDecoder(bytes.NewReader(b))
dec.DisallowUnknownFields() // Force error if unknown field is found

// SetMetadata enables you to associate structured data with this resource.
// see: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-metadata.html
func (r *CustomResource) SetMetadata(metadata map[string]interface{}) {
r._metadata = metadata
}
if err := dec.Decode(&res); err != nil {
fmt.Printf("ERROR: %s\n", err)
return err
}

// DeletionPolicy returns the AWS CloudFormation DeletionPolicy to this resource
// see: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html
func (r *CustomResource) DeletionPolicy() policies.DeletionPolicy {
return r._deletionPolicy
}
r.Type = res.Type

// SetDeletionPolicy applies an AWS CloudFormation DeletionPolicy to this resource
// see: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html
func (r *CustomResource) SetDeletionPolicy(policy policies.DeletionPolicy) {
r._deletionPolicy = policy
}

// GetAllCustomResourceResources retrieves all CustomResource items from an AWS CloudFormation template
func (t *Template) GetAllCustomResources() map[string]*CustomResource {
results := map[string]*CustomResource{}
for name, untyped := range t.Resources {
switch resource := untyped.(type) {
case *CustomResource:
results[name] = resource
}
// If the resource has no Properties set, it could be nil
if res.Properties != nil {
r.Properties = res.Properties
}
return results
}

// GetCustomResourceWithName retrieves all CustomResource items from an AWS CloudFormation template
// whose logical ID matches the provided name. Returns an error if not found.
func (t *Template) GetCustomResourceWithName(name string) (*CustomResource, error) {
if untyped, ok := t.Resources[name]; ok {
switch resource := untyped.(type) {
case *CustomResource:
return resource, nil
}
if res.DependsOn != nil {
r.AWSCloudFormationDependsOn = res.DependsOn
}
if res.Metadata != nil {
r.AWSCloudFormationMetadata = res.Metadata
}
if res.DeletionPolicy != "" {
r.AWSCloudFormationDeletionPolicy = policies.DeletionPolicy(res.DeletionPolicy)
}
if res.UpdateReplacePolicy != "" {
r.AWSCloudFormationUpdateReplacePolicy = policies.UpdateReplacePolicy(res.UpdateReplacePolicy)
}
if res.Condition != "" {
r.AWSCloudFormationCondition = res.Condition
}
return nil, fmt.Errorf("resource %q of type CustomResource not found", name)
return nil
}
7 changes: 0 additions & 7 deletions goformation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,6 @@ var _ = Describe("Goformation", func() {
Expect(template).ShouldNot(BeNil())
})

resources := template.GetAllCustomResources()

It("should have exactly one resource", func() {
Expect(resources).To(HaveLen(1))
Expect(resources).To(HaveKey("MyCustomResource"))
})

Comment on lines -252 to -258
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for removing this test?

Copy link
Contributor Author

@xrn xrn Feb 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 reasons

  1. I removed GetAllCustomResources() function seems that it do not make bigger sense to have it - then implementation of a resource is not necessarily different that others
  2. In the next lines (269-271) we can find

resources, ok := result["Resources"].(map[string]interface{})
Expect(ok).To(BeTrue())
Expect(resources).To(HaveLen(1))
Expect(resources).To(HaveKey("MyCustomResource"))

Which is in my opinion almost the same test but without - template.GetAllCustomResources() - this is why I think we can remove that part of the test

I think the rest of the test is fair enough for CoustomResource check

It("should correctly Marshal the custom resource", func() {
data, err := template.JSON()
Expect(err).To(BeNil())
Expand Down