Skip to content

Commit 92ea924

Browse files
authored
Merge pull request #662 from ericzbeard/awscli-modules
This PR makes significant changes to the functionality for modules, making Rain compatible with a draft PR that is in progress for the AWS CLI `cloudformation package` command.
2 parents c55e262 + 5212678 commit 92ea924

File tree

592 files changed

+23314
-6763
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

592 files changed

+23314
-6763
lines changed

README.md

Lines changed: 291 additions & 148 deletions
Large diffs are not rendered by default.

cft/cft.go

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,21 @@ type PackageAlias struct {
3030

3131
// Template represents a CloudFormation template. The Template type
3232
// is minimal for now but will likely grow new features as needed by rain.
33+
//
34+
// Node is the only member that is guaranteed to exist after
35+
// parsing a template.
3336
type Template struct {
34-
Node *yaml.Node
35-
36-
Constants map[string]*yaml.Node
37-
Packages map[string]*PackageAlias
37+
FileName string
38+
Name string
39+
Node *yaml.Node
40+
Constants map[string]*yaml.Node
41+
Packages map[string]*PackageAlias
42+
ModuleMapNames map[string][]string
43+
ModuleMaps map[string]*ModuleConfig
44+
ModuleOutputs map[string]*yaml.Node
45+
ModuleResolved []*yaml.Node
3846
}
3947

40-
// TODO - We really need a convenient Template data structure
41-
// that lets us easily access elements.
42-
// t.Resources["MyResource"].Properties["MyProp"]
43-
//
44-
// Add a Model attribute to the struct and an Init function to populate it.
45-
// t.Model.Resources
46-
4748
// Map returns the template as a map[string]interface{}
4849
func (t Template) Map() map[string]interface{} {
4950
var out map[string]interface{}
@@ -57,7 +58,7 @@ func (t Template) Map() map[string]interface{} {
5758
}
5859

5960
// AppendStateMap appends a "State" section to the template
60-
func AppendStateMap(state Template) *yaml.Node {
61+
func AppendStateMap(state *Template) *yaml.Node {
6162
state.Node.Content[0].Content = append(state.Node.Content[0].Content,
6263
&yaml.Node{Kind: yaml.ScalarNode, Value: "State"})
6364
stateMap := &yaml.Node{Kind: yaml.MappingNode, Content: make([]*yaml.Node, 0)}
@@ -81,6 +82,18 @@ const (
8182
Outputs Section = "Outputs"
8283
State Section = "State"
8384
Rain Section = "Rain"
85+
Modules Section = "Modules"
86+
Packages Section = "Packages"
87+
Constants Section = "Constants"
88+
)
89+
90+
type Intrinsic string
91+
92+
const (
93+
Sub Intrinsic = "Fn::Sub"
94+
GetAtt Intrinsic = "Fn::GetAtt"
95+
Ref Intrinsic = "Ref"
96+
If Intrinsic = "Fn::If"
8497
)
8598

8699
// GetResource returns the yaml node for a resource by logical id
@@ -148,9 +161,19 @@ func (t Template) GetSection(section Section) (*yaml.Node, error) {
148161
return s, nil
149162
}
150163

164+
// HasSection returns true if the template has the section
165+
func (t Template) HasSection(section Section) bool {
166+
if t.Node == nil {
167+
return false
168+
}
169+
m := t.Node.Content[0]
170+
_, s, _ := s11n.GetMapValue(m, string(section))
171+
return s != nil
172+
}
173+
151174
// RemoveSection removes a section node from the template
152175
func (t Template) RemoveSection(section Section) error {
153-
return node.RemoveFromMap(t.Node.Content[0], string(Rain))
176+
return node.RemoveFromMap(t.Node.Content[0], string(section))
154177
}
155178

156179
// GetTypes returns all unique type names for resources in the template
@@ -224,3 +247,38 @@ func (t Template) RemoveEmptySections() {
224247
node.RemoveFromMap(m, name)
225248
}
226249
}
250+
251+
// AddMappedModule adds a reference to a module that was mapped to a CSV of keys,
252+
// which duplicates the module in the template. We store a reference here so
253+
// that we can resolve references like Content[0].Arn, which points to the first
254+
// mapped instance of a Module called Content, with an Output called Arn.
255+
func (t *Template) AddMappedModule(copiedConfig *ModuleConfig) {
256+
if t.ModuleMaps == nil {
257+
t.ModuleMaps = make(map[string]*ModuleConfig)
258+
}
259+
t.ModuleMaps[copiedConfig.Name] = copiedConfig
260+
if t.ModuleMapNames == nil {
261+
t.ModuleMapNames = make(map[string][]string)
262+
}
263+
originalName := copiedConfig.OriginalName
264+
var mappedModules []string
265+
var ok bool
266+
if mappedModules, ok = t.ModuleMapNames[originalName]; !ok {
267+
mappedModules = make([]string, 0)
268+
}
269+
if !slices.Contains(mappedModules, copiedConfig.Name) {
270+
mappedModules = append(mappedModules, copiedConfig.Name)
271+
}
272+
t.ModuleMapNames[originalName] = mappedModules
273+
}
274+
275+
func (t *Template) AddResolvedModuleNode(n *yaml.Node) {
276+
if t.ModuleResolved == nil {
277+
t.ModuleResolved = make([]*yaml.Node, 0)
278+
}
279+
t.ModuleResolved = append(t.ModuleResolved, n)
280+
}
281+
282+
func (t *Template) ModuleAlreadyResolved(n *yaml.Node) bool {
283+
return slices.Contains(t.ModuleResolved, n)
284+
}

cft/diff/compare.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
)
99

1010
// New returns a Diff that represents the difference between two templates
11-
func New(a, b cft.Template) Diff {
11+
func New(a, b *cft.Template) Diff {
1212
return CompareMaps(a.Map(), b.Map())
1313
}
1414

cft/format/format.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func CheckMultilineBegin(s string) bool {
3030
}
3131

3232
// String returns a string representation of the supplied cft.Template
33-
func String(t cft.Template, opt Options) string {
33+
func String(t *cft.Template, opt Options) string {
3434
node := t.Node
3535

3636
buf := strings.Builder{}
@@ -122,6 +122,6 @@ func String(t cft.Template, opt Options) string {
122122
}
123123

124124
// CftToYaml converts a template to a YAML string
125-
func CftToYaml(t cft.Template) string {
125+
func CftToYaml(t *cft.Template) string {
126126
return String(t, Options{JSON: false, Unsorted: false})
127127
}

cft/format/pkl.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ func addSection(section cft.Section, n *yaml.Node, sb *strings.Builder, basic bo
457457

458458
// CftToPkl serializes the template as pkl.
459459
// It assumes that the user is importing the cloudformation package
460-
func CftToPkl(t cft.Template, basic bool, pklPackageAlias string) (string, error) {
460+
func CftToPkl(t *cft.Template, basic bool, pklPackageAlias string) (string, error) {
461461
if t.Node == nil || len(t.Node.Content) != 1 {
462462
return "", errors.New("expected t.Node.Content[0]")
463463
}

cft/format/uncdk.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"gopkg.in/yaml.v3"
1313
)
1414

15-
func UnCDK(t cft.Template) error {
15+
func UnCDK(t *cft.Template) error {
1616

1717
// Remove these nodes:
1818
//
@@ -153,7 +153,7 @@ func joinSeqToString(seq *yaml.Node) string {
153153
return retval
154154
}
155155

156-
func joinToSub(t cft.Template) {
156+
func joinToSub(t *cft.Template) {
157157
vf := func(n *visitor.Visitor) {
158158
yamlNode := n.GetYamlNode()
159159
if yamlNode.Kind == yaml.MappingNode {
@@ -173,7 +173,7 @@ func joinToSub(t cft.Template) {
173173

174174
}
175175

176-
func replaceNames(t cft.Template, oldName, newName string) {
176+
func replaceNames(t *cft.Template, oldName, newName string) {
177177
vf := func(n *visitor.Visitor) {
178178
yamlNode := n.GetYamlNode()
179179
if yamlNode.Kind == yaml.ScalarNode {
@@ -187,7 +187,7 @@ func replaceNames(t cft.Template, oldName, newName string) {
187187
}
188188

189189
// getCommonTemplatePrefix attempts to find a common string that begins all resource names.
190-
func getCommonResourcePrefix(t cft.Template) string {
190+
func getCommonResourcePrefix(t *cft.Template) string {
191191
resources, err := t.GetSection(cft.Resources)
192192
if err != nil {
193193
return ""

cft/graph/graph.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func Empty() Graph {
4343
// New returns a Graph representing the connections
4444
// between elements in the provided template.
4545
// The type of each item in the graph is Node
46-
func New(t cft.Template) Graph {
46+
func New(t *cft.Template) Graph {
4747
// Map out parameter and resource names so we know which is which
4848
entities := make(map[string]string)
4949
for typeName, entity := range t.Map() {

cft/graph/graph_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Outputs:
3232
Value: !GetAtt Bucket.Arn
3333
`
3434

35-
var template cft.Template
35+
var template *cft.Template
3636

3737
var g graph.Graph
3838

cft/module_config.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package cft
2+
3+
import (
4+
"errors"
5+
6+
"github.com/aws-cloudformation/rain/internal/node"
7+
"github.com/aws-cloudformation/rain/internal/s11n"
8+
"gopkg.in/yaml.v3"
9+
)
10+
11+
// ModuleConfig is the configuration of the module in the parent template.
12+
type ModuleConfig struct {
13+
14+
// Name is the name of the module, which is used as a logical id prefix
15+
Name string
16+
17+
// Source is the URI for the module, a local file or remote URL
18+
Source string
19+
20+
// PropertiesNode is the yaml node for the Properties
21+
PropertiesNode *yaml.Node
22+
23+
// OverridesNode is the yaml node for overrides
24+
OverridesNode *yaml.Node
25+
26+
// Node is the yaml node for the Module config mapping node
27+
Node *yaml.Node
28+
29+
// Map is the node for mapping (duplicating) the module based on a CSV
30+
Map *yaml.Node
31+
32+
// OriginalName is the Name of the module before it got Mapped (duplicated)
33+
OriginalName string
34+
35+
// If this module was duplicated because of the Map attribute, store the index
36+
MapIndex int
37+
38+
// If this module was duplicated because of the Map attribute, store the key
39+
MapKey string
40+
41+
// IsMapCopy will be true if this instance was a duplicate of a Mapped module
42+
IsMapCopy bool
43+
44+
// The root directory of the template that configures this module
45+
ParentRootDir string
46+
}
47+
48+
func (c *ModuleConfig) Properties() map[string]any {
49+
return node.DecodeMap(c.PropertiesNode)
50+
}
51+
52+
func (c *ModuleConfig) Overrides() map[string]any {
53+
return node.DecodeMap(c.OverridesNode)
54+
}
55+
56+
// ResourceOverridesNode returns the Overrides node for the given resource if it exists
57+
func (c *ModuleConfig) ResourceOverridesNode(name string) *yaml.Node {
58+
if c.OverridesNode == nil {
59+
return nil
60+
}
61+
_, n, _ := s11n.GetMapValue(c.OverridesNode, name)
62+
return n
63+
}
64+
65+
const (
66+
Source string = "Source"
67+
Properties string = "Properties"
68+
Overrides string = "Overrides"
69+
Map string = "Map"
70+
)
71+
72+
// parseModuleConfig parses a single module configuration
73+
// from the Modules section in the template
74+
func (t *Template) ParseModuleConfig(name string, n *yaml.Node) (*ModuleConfig, error) {
75+
if n.Kind != yaml.MappingNode {
76+
return nil, errors.New("not a mapping node")
77+
}
78+
m := &ModuleConfig{}
79+
m.Name = name
80+
m.Node = n
81+
82+
content := n.Content
83+
for i := 0; i < len(content); i += 2 {
84+
attr := content[i].Value
85+
val := content[i+1]
86+
switch attr {
87+
case Source:
88+
m.Source = val.Value
89+
case Properties:
90+
m.PropertiesNode = val
91+
case Overrides:
92+
m.OverridesNode = val
93+
case Map:
94+
m.Map = val
95+
}
96+
}
97+
98+
//err := t.ValidateModuleConfig(m)
99+
//if err != nil {
100+
// return nil, err
101+
//}
102+
103+
return m, nil
104+
}
105+
106+
// ValidateModuleConfig makes sure the configuration does not
107+
// break any rules, such as not having a Property with the
108+
// same name as a Parameter.
109+
//func (t *Template) ValidateModuleConfig(moduleConfig *ModuleConfig) error {
110+
// props := moduleConfig.Properties()
111+
// for key := range props {
112+
// _, err := t.GetParameter(key)
113+
// if err == nil {
114+
// return fmt.Errorf("module %s in %s has Property %s with the same name as a template Parameter",
115+
// moduleConfig.Name, moduleConfig.ParentRootDir, key)
116+
// }
117+
// }
118+
// return nil
119+
//}

0 commit comments

Comments
 (0)