Skip to content

Commit

Permalink
Merge pull request #125 from ericzbeard/module-ref
Browse files Browse the repository at this point in the history
Module refs
  • Loading branch information
ericzbeard authored Apr 21, 2023
2 parents 54c0bb0 + de078ba commit 059e4b3
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 19 deletions.
54 changes: 40 additions & 14 deletions cft/pkg/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,34 +99,39 @@ func renamePropRefs(
// C: !Ref D

if prop.Kind == yaml.ScalarNode {
config.Debugf("Scalar %v %v", propName, node.ToJson(prop))
if propName == "Ref" {

// Find the module parameter that matches the !Ref
_, param := s11n.GetMapValue(moduleParams, prop.Value)
if param != nil {
config.Debugf("Found param for %v", prop.Value)
// We need to get the parameter value from the parent template.
// Module params are set by the parent template resource properties.

// Look for this property name in the parent template
_, parentVal := s11n.GetMapValue(templateProps, prop.Value)
if parentVal == nil {
return fmt.Errorf("did not find %v in parent template Resource Properties", prop.Value)
}

config.Debugf("parentVal: %v", node.ToJson(parentVal))

// We can't just set prop.Value, since we would end up with Prop: !Ref Value instead of just Prop: Value
// Get the property's parent and set the entire map value for the property
config.Debugf("Calling GetParent for %v\nroot:\n%v", node.ToJson(prop), node.ToJson(ext))

// Get the map parent within the extension node we created
refMap := node.GetParent(prop, ext, nil)
if refMap.Value == nil {
return fmt.Errorf("could not find parent for %v", prop)
}
config.Debugf("refMap.Value: %v", node.ToJson(refMap.Value))
propParentPair := node.GetParent(refMap.Value, ext, nil)

config.Debugf("propParentPair.Value: %v", node.ToJson(propParentPair.Value))
newValue := &yaml.Node{Kind: yaml.ScalarNode, Value: parentVal.Value}
config.Debugf("About to set %v to newValue: %v", prop.Value, node.ToJson(newValue))
// TODO - Bug - look up the map value to replace, not [0]

// Create a new node to replace what's defined in the module
newValue := node.Clone(parentVal)

config.Debugf("Setting %v to:\n%v", parentName, node.ToJson(newValue))

node.SetMapValue(propParentPair.Value, parentName, newValue)
config.Debugf("propParentPair.Value after: %v", node.ToJson(propParentPair.Value))
} else {
config.Debugf("Did not find param for %v", prop.Value)
// Look for a resource in the module
Expand Down Expand Up @@ -202,18 +207,13 @@ func processModule(module *yaml.Node,
typeNode *yaml.Node, parent node.NodePair) (bool, error) {

// The parent arg is the map in the template resource's Content[1] that contains Type, Properties, etc
// p, _ := json.MarshalIndent(parent, "", " ")
// config.Debugf("parent: %v", string(p))

// config.Debugf("module: %v", node.ToJson(module))

if parent.Key == nil {
return false, errors.New("expected parent.Key to not be nil. The !Rain::Module directive should come after Type: ")
}

// Get the logical id of the resource we are transforming
logicalId := parent.Key.Value
//config.Debugf("logicalId: %v", logicalId)

// Make a new node that will hold our additions to the original template
outputNode.Content = make([]*yaml.Node, 0)
Expand Down Expand Up @@ -345,6 +345,32 @@ func processModule(module *yaml.Node,
if resource.Kind == yaml.MappingNode {
name := moduleResources.Content[i-1].Value
if name != "ModuleExtension" {

// Resolve Conditions. Rain handles this differently, since a rain
// module cannot have a Condition section. This value must be a module parameter
// name, and the value must be set in the parent template as the name of
// a Condition that is defined in the parent.
_, condition := s11n.GetMapValue(resource, "Condition")
if condition != nil {
conditionErr := errors.New("a Condition in a rain module must be the name of a Parameter that is set the name of a Condition in the parent template")
// The value must be present in the module's parameters
if condition.Kind != yaml.ScalarNode {
return false, conditionErr
}
_, param := s11n.GetMapValue(moduleParams, condition.Value)
if param == nil {
return false, conditionErr
}
_, conditionVal := s11n.GetMapValue(templateProps, condition.Value)
if conditionVal == nil {
return false, conditionErr
}
if conditionVal.Kind != yaml.ScalarNode {
return false, conditionErr
}
condition.Value = conditionVal.Value
}

// This is an additional resource to be added
nameNode := node.Clone(moduleResources.Content[i-1])
nameNode.Value = rename(logicalId, nameNode.Value)
Expand Down
2 changes: 2 additions & 0 deletions docs/README.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ cfn-lint, Guard and more:

* **Predict deployment failures** (EXPERIMENTAL): `rain forecast` analyzes a template and the target deployment account to predict things that might go wrong when you attempt to create, update, or delete a stack. This command speeds up development by giving you advanced notice for issues like missing permissions, resources that already exist, and a variety of other common resource-specific deployment blockers.

* **Modules** (EXPERIMENTAL): `rain pkg` supports client-side module development with the `!Rain::Module` directive. Rain modules are partial templates that are inserted into the parent template, with some extra functionality added to enable extending existing resource types.

_Note that in order to use experimental commands, you have to add `--experimental` or `-x` as an argument._

## Getting started
Expand Down
6 changes: 5 additions & 1 deletion test/modules/bucket.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ Description: |
Parameters:
LogBucketName:
Type: String
Description: The name of the log bucket
RetentionPolicy:
Type: String
ConditionName:
Type: String

Resources:
ModuleExtension:
Expand Down Expand Up @@ -82,5 +83,8 @@ Resources:
Type: AWS::S3::Bucket
DependsOn: ModuleExtension

ConditionalResource:
Condition: ConditionName
Type: AWS::S3::Bucket


21 changes: 19 additions & 2 deletions test/modules/expect.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
Parameters:
BucketRetentionPolicy:
Type: String
AllowedValues:
- Delete
- Retain

Conditions:
ConditionA:
!Equals:
- true
- true

Resources:
SecondResourceInOriginal:
Type: AWS::SQS::Queue

ModuleExample:
DeletionPolicy: Retain
DeletionPolicy: !Ref BucketRetentionPolicy
UpdateReplacePolicy: Delete
Type: AWS::S3::Bucket
DependsOn:
Expand Down Expand Up @@ -36,7 +49,7 @@ Resources:

ModuleExampleLogBucket:
DeletionPolicy: Delete
UpdateReplacePolicy: Retain
UpdateReplacePolicy: !Ref BucketRetentionPolicy
Type: AWS::S3::Bucket
DependsOn:
- ModuleExampleAdditionalResource1
Expand Down Expand Up @@ -74,6 +87,10 @@ Resources:
DependsOn:
- ModuleExample

ModuleExampleConditionalResource:
Type: AWS::S3::Bucket
Condition: ConditionA

WithoutExtensionBucketA:
Type: AWS::S3::Bucket
Properties:
Expand Down
6 changes: 5 additions & 1 deletion test/templates/ext-ref-module.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
Parameters:
ThePolicy:
Type: String

Resources:

ModuleExample:
Type: !Rain::Module "file://../../modules/ext-ref.yaml"
Properties:
BucketName: ezbeard-cep-test-module-bucket
RetentionPolicy: Retain
RetentionPolicy: !Ref ThePolicy


18 changes: 17 additions & 1 deletion test/templates/module.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
Parameters:

BucketRetentionPolicy:
Type: String
AllowedValues:
- Delete
- Retain

Conditions:

ConditionA:
!Equals:
- true
- true

Resources:

ModuleExample:
Expand All @@ -8,7 +23,8 @@ Resources:
Properties:
LogBucketName: ezbeard-cep-test-module-log-bucket
BucketName: ezbeard-cep-test-module-bucket
RetentionPolicy: Retain
RetentionPolicy: !Ref BucketRetentionPolicy
ConditionName: ConditionA
Tags:
- Key: test-tag
Value: test-value2
Expand Down

0 comments on commit 059e4b3

Please sign in to comment.