Skip to content

Commit 6c651fd

Browse files
authored
Merge pull request #626 from ericzbeard/uncdk
Added uncdk to fmt command
2 parents d455e2e + 79a3208 commit 6c651fd

File tree

8 files changed

+779
-11
lines changed

8 files changed

+779
-11
lines changed

cft/cft.go

+23-2
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ func (t Template) GetParameter(name string) (*yaml.Node, error) {
9797
func (t Template) GetNode(section Section, name string) (*yaml.Node, error) {
9898
_, resMap, _ := s11n.GetMapValue(t.Node.Content[0], string(section))
9999
if resMap == nil {
100-
config.Debugf("GetNode t.Node: %s", node.ToSJson(t.Node))
101100
return nil, fmt.Errorf("unable to locate the %s node", section)
102101
}
103102
// TODO: Some Sections are not Maps
@@ -144,7 +143,6 @@ func (t Template) GetSection(section Section) (*yaml.Node, error) {
144143
m := t.Node.Content[0]
145144
_, s, _ := s11n.GetMapValue(m, string(section))
146145
if s == nil {
147-
config.Debugf("GetSection t.Node: %s", node.ToSJson(t.Node))
148146
return nil, fmt.Errorf("unable to locate the %s node", section)
149147
}
150148
return s, nil
@@ -203,3 +201,26 @@ func (t Template) GetResourcesOfType(typeName string) []*Resource {
203201
}
204202
return retval
205203
}
204+
205+
// RemoveEmptySections removes sections from the template that have no content
206+
func (t Template) RemoveEmptySections() {
207+
if t.Node == nil {
208+
config.Debugf("t.Node is nil")
209+
return
210+
}
211+
m := t.Node.Content[0]
212+
sectionsToRemove := make([]string, 0)
213+
for i := 0; i < len(m.Content); i++ {
214+
if i%2 != 0 {
215+
continue
216+
}
217+
name := m.Content[i].Value
218+
node := m.Content[i+1]
219+
if len(node.Content) == 0 {
220+
sectionsToRemove = append(sectionsToRemove, name)
221+
}
222+
}
223+
for _, name := range sectionsToRemove {
224+
node.RemoveFromMap(m, name)
225+
}
226+
}

cft/format/json.go

-9
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ package format
33
import (
44
"bytes"
55
"encoding/json"
6-
"fmt"
76
"strings"
87

98
"github.com/aws-cloudformation/rain/cft/parse"
10-
"github.com/aws-cloudformation/rain/internal/config"
119
"gopkg.in/yaml.v3"
1210
)
1311

@@ -34,8 +32,6 @@ func handleScalar(node *yaml.Node) interface{} {
3432
panic(err)
3533
}
3634

37-
config.Debugf("intermediate: %v", string(intermediate))
38-
3935
var out interface{}
4036
err = yaml.Unmarshal(intermediate, &out)
4137
if err != nil {
@@ -72,8 +68,6 @@ func Jsonise(node *yaml.Node) interface{} {
7268

7369
func convertToJSON(in string) string {
7470

75-
config.Debugf("convertToJson: %v", in)
76-
7771
var d yaml.Node
7872

7973
err := yaml.Unmarshal([]byte(in), &d)
@@ -104,14 +98,11 @@ func PrettyPrint(i interface{}) string {
10498

10599
// ToJson overrides the default behavior of json.Marshal to leave < > alone
106100
func ToJson(i interface{}, indent string) ([]byte, error) {
107-
si := fmt.Sprintf("%#v", i)
108-
config.Debugf("ToJson(%s)", si)
109101
buf := &bytes.Buffer{}
110102
enc := json.NewEncoder(buf)
111103
enc.SetEscapeHTML(false)
112104
enc.SetIndent("", indent)
113105
err := enc.Encode(i)
114106
retval := bytes.TrimRight(buf.Bytes(), "\n")
115-
config.Debugf("ToJson retval: %s", retval)
116107
return retval, err
117108
}

cft/format/uncdk.go

+294
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
package format
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"unicode"
7+
8+
"github.com/aws-cloudformation/rain/cft"
9+
"github.com/aws-cloudformation/rain/cft/visitor"
10+
"github.com/aws-cloudformation/rain/internal/node"
11+
"github.com/aws-cloudformation/rain/internal/s11n"
12+
"gopkg.in/yaml.v3"
13+
)
14+
15+
func UnCDK(t cft.Template) error {
16+
17+
// Remove these nodes:
18+
//
19+
// Resources:
20+
// CDKMetadata:
21+
// {*}:
22+
// Metadata:
23+
// aws:cdk:path:
24+
// aws:asset:path:
25+
// aws:asset:property:
26+
// Conditions:
27+
// CDKMetadataAvailable:
28+
// Parameters:
29+
// BootstrapVersion:
30+
// Rules:
31+
// CheckBootstrapVersion:
32+
33+
removals := make(map[string][]string)
34+
removals[string(cft.Resources)] = []string{"CDKMetadata"}
35+
removals[string(cft.Conditions)] = []string{"CDKMetadataAvailable"}
36+
removals[string(cft.Parameters)] = []string{"BootstrapVersion"}
37+
removals[string(cft.Rules)] = []string{"CheckBootstrapVersion"}
38+
39+
for k, v := range removals {
40+
section, err := t.GetSection(cft.Section(k))
41+
if err != nil {
42+
continue // Section not found
43+
}
44+
for _, name := range v {
45+
n := s11n.GetMap(section, name)
46+
if n != nil {
47+
node.RemoveFromMap(section, name)
48+
}
49+
}
50+
}
51+
52+
// Iterate through all the resources to remove cdk metadata,
53+
// And fix the logical ids so they are easier to read
54+
55+
resources, err := t.GetSection(cft.Resources)
56+
if err != nil {
57+
return err
58+
}
59+
60+
commonPrefix := getCommonResourcePrefix(t)
61+
62+
// Store the resource logical id node each time we see a repeated name
63+
// Start without a number, for example "Bucket"
64+
// If we see another one, fix the first one to be "Bucket0"
65+
allNames := make(map[string][]*yaml.Node)
66+
67+
for i := 0; i < len(resources.Content); i += 1 {
68+
if i%2 != 0 {
69+
continue
70+
}
71+
logicalId := resources.Content[i].Value
72+
resource := resources.Content[i+1]
73+
74+
// Simplify the logical id
75+
_, typ, _ := s11n.GetMapValue(resource, "Type")
76+
if typ == nil {
77+
return fmt.Errorf("expected %s to have Type", logicalId)
78+
}
79+
oldName := resources.Content[i].Value
80+
newName := createNewName(typ.Value, logicalId, commonPrefix)
81+
if nameNodes, ok := allNames[newName]; ok {
82+
// We've seen this one before
83+
nameNodes = append(nameNodes, resources.Content[i])
84+
allNames[newName] = nameNodes
85+
for nodeIdx, node := range nameNodes {
86+
sequential := fmt.Sprintf("%s%d", newName, nodeIdx)
87+
priorValue := node.Value
88+
node.Value = sequential
89+
replaceNames(t, priorValue, sequential)
90+
}
91+
} else {
92+
// We haven't seen this name yet
93+
resources.Content[i].Value = newName
94+
allNames[newName] = make([]*yaml.Node, 0)
95+
allNames[newName] = append(allNames[newName], resources.Content[i])
96+
replaceNames(t, oldName, newName)
97+
}
98+
99+
// Remove the cdk path and asset metadata
100+
_, metadata, _ := s11n.GetMapValue(resource, string(cft.Metadata))
101+
if metadata != nil {
102+
stringsToRemove := []string{
103+
"aws:cdk:path",
104+
"aws:asset:path",
105+
"aws:asset:property",
106+
"aws:asset:is-bundled",
107+
"cfn_nag",
108+
}
109+
for _, s := range stringsToRemove {
110+
node.RemoveFromMap(metadata, s)
111+
}
112+
// If the resource Metadata node is empty, remove it
113+
if len(metadata.Content) == 0 {
114+
node.RemoveFromMap(resource, string(cft.Metadata))
115+
}
116+
}
117+
}
118+
119+
// Remove any empty sections
120+
t.RemoveEmptySections()
121+
122+
// Replace Joins with Subs to make them easier to read
123+
joinToSub(t)
124+
125+
return nil // TODO
126+
127+
}
128+
129+
func joinSeqToString(seq *yaml.Node) string {
130+
if len(seq.Content) != 2 {
131+
return "Invalid Join"
132+
}
133+
j := seq.Content[0].Value
134+
tokens := seq.Content[1]
135+
retval := ""
136+
for i, token := range tokens.Content {
137+
if i != 0 {
138+
retval += j
139+
}
140+
if token.Kind == yaml.ScalarNode {
141+
retval += token.Value
142+
}
143+
if token.Kind == yaml.MappingNode {
144+
if token.Content[0].Value == "Ref" {
145+
retval += "${" + token.Content[1].Value + "}"
146+
}
147+
if token.Content[0].Value == "Fn::GetAtt" {
148+
retval += "${" + token.Content[1].Content[0].Value
149+
retval += "." + token.Content[1].Content[1].Value + "}"
150+
}
151+
}
152+
}
153+
return retval
154+
}
155+
156+
func joinToSub(t cft.Template) {
157+
vf := func(n *visitor.Visitor) {
158+
yamlNode := n.GetYamlNode()
159+
if yamlNode.Kind == yaml.MappingNode {
160+
if len(yamlNode.Content) == 2 && yamlNode.Content[0].Value == "Fn::Join" {
161+
seq := yamlNode.Content[1]
162+
if seq.Kind == yaml.SequenceNode {
163+
yamlNode.Content[0].Value = "Fn::Sub"
164+
yamlNode.Content[1].Value = joinSeqToString(seq)
165+
yamlNode.Content[1].Kind = yaml.ScalarNode
166+
yamlNode.Content[1].Content = make([]*yaml.Node, 0)
167+
}
168+
}
169+
}
170+
}
171+
visitor := visitor.NewVisitor(t.Node)
172+
visitor.Visit(vf)
173+
174+
}
175+
176+
func replaceNames(t cft.Template, oldName, newName string) {
177+
vf := func(n *visitor.Visitor) {
178+
yamlNode := n.GetYamlNode()
179+
if yamlNode.Kind == yaml.ScalarNode {
180+
if yamlNode.Value == oldName {
181+
yamlNode.Value = newName
182+
}
183+
}
184+
}
185+
visitor := visitor.NewVisitor(t.Node)
186+
visitor.Visit(vf)
187+
}
188+
189+
// getCommonTemplatePrefix attempts to find a common string that begins all resource names.
190+
func getCommonResourcePrefix(t cft.Template) string {
191+
resources, err := t.GetSection(cft.Resources)
192+
if err != nil {
193+
return ""
194+
}
195+
logicalIds := make([]string, 0)
196+
for i := 0; i < len(resources.Content); i += 2 {
197+
logicalId := resources.Content[i].Value
198+
logicalIds = append(logicalIds, logicalId)
199+
}
200+
return getCommonPrefix(logicalIds)
201+
}
202+
203+
// getCommonPrefix attempts to find a common string that begins all strings in the slice.
204+
func getCommonPrefix(logicalIds []string) string {
205+
if len(logicalIds) < 2 {
206+
return ""
207+
}
208+
retval := ""
209+
prefixes := make([]string, 0)
210+
for j := 1; j < len(logicalIds); j++ {
211+
prefix := ""
212+
for i, c := range logicalIds[0] {
213+
second := []rune(logicalIds[j])
214+
if len(second) > i && second[i] == c {
215+
prefix += string(c)
216+
} else {
217+
prefixes = append(prefixes, prefix)
218+
if retval == "" {
219+
retval = prefix
220+
}
221+
for _, p := range prefixes {
222+
// Pick the shortest prefix
223+
if len(p) < len(retval) && retval != "" {
224+
retval = p
225+
}
226+
}
227+
break
228+
}
229+
}
230+
}
231+
232+
common := true
233+
for _, id := range logicalIds {
234+
if !strings.HasPrefix(id, retval) {
235+
common = false
236+
break
237+
}
238+
}
239+
if common {
240+
return retval
241+
}
242+
return ""
243+
}
244+
245+
// stripSuffix attempts to remove the random 8 characters at the end of ids
246+
func stripSuffix(s string) string {
247+
248+
if len(s) <= 8 {
249+
return s
250+
}
251+
252+
// Too simple. For imported constructs, you can end up with several
253+
// Strip off the random 8 digit string at the end
254+
//return newName[:len(newName)-8]
255+
256+
suffixLen := 0
257+
258+
for i := len(s) - 1; i >= 0; i-- {
259+
isUpper := unicode.IsUpper(rune(s[i])) && unicode.IsLetter(rune(s[i]))
260+
isDigit := unicode.IsDigit(rune(s[i]))
261+
if isUpper || isDigit {
262+
suffixLen += 1
263+
} else {
264+
break
265+
}
266+
}
267+
268+
if suffixLen == len(s) {
269+
return s
270+
}
271+
272+
// Round to the nearest 8 in case a name ended with a capital letter or number
273+
suffixLen = suffixLen - (suffixLen % 8)
274+
275+
return s[:len(s)-suffixLen]
276+
}
277+
278+
// createNewName converts the cdk generated name into something that is easier to read.
279+
func createNewName(typeName string, logicalId string, commonPrefix string) string {
280+
newName := ""
281+
if commonPrefix != "" {
282+
newName = strings.Replace(logicalId, commonPrefix, "", -1)
283+
return stripSuffix(newName)
284+
}
285+
tokens := strings.Split(typeName, "::")
286+
if len(tokens) == 3 {
287+
newName = tokens[2]
288+
} else if len(tokens) == 2 && tokens[0] == "Custom" {
289+
newName = strings.Replace(tokens[1], "-", "", -1)
290+
} else {
291+
newName = strings.Replace(typeName, "::", "", -1)
292+
}
293+
return newName
294+
}

0 commit comments

Comments
 (0)