autostacker24 1.0.71 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/autostacker24 +9 -9
- data/lib/autostacker24/template_preprocessor.rb +193 -122
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c854bfc3f7bc00704f86c14a3d2fc742da028083
|
4
|
+
data.tar.gz: 1c1f01cb79ef33a2b822ce28b2a51a52fea8667b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be4caf16f2fe3a6f84bb94669aea7170fe1c9962751e22a0386ee7873db989f1f8be6c9d6dadf5a111ce57f1093f70ee13f532f9c9437c0317dae6c2f3420cb3
|
7
|
+
data.tar.gz: 78036ff04e15108b194214087c77210da73cb1b5f9ea33b5c7354ded08cd3f98711cf55d5ae2ce666cdeff82c0fafd6be1d6d2eefac34b947a531c72f21e53fb
|
data/bin/autostacker24
CHANGED
@@ -24,22 +24,22 @@ parser = OptionParser.new do |opts|
|
|
24
24
|
opts.banner = 'Usage: autostacker24 command [options]'
|
25
25
|
opts.separator ''
|
26
26
|
opts.separator 'Commands:'
|
27
|
-
opts.separator "\
|
27
|
+
opts.separator "\tupdate\t\t create or update a stack"
|
28
28
|
opts.separator "\tdelete\t\t delete a stack"
|
29
|
-
opts.separator "\
|
29
|
+
opts.separator "\tprocess\t\t pretty print processed template"
|
30
30
|
opts.separator "\tvalidate\t validate the template"
|
31
|
-
opts.separator "\tconvert\t\t convert
|
31
|
+
opts.separator "\tconvert\t\t convert template to and from YAML"
|
32
32
|
opts.separator ''
|
33
33
|
opts.separator 'Options:'
|
34
34
|
opts.on('--template TEMPLATE', 'Path to template') {|v| args.template = v}
|
35
35
|
opts.on('--stack STACK', 'Name of stack') {|v| args.stack = v}
|
36
36
|
opts.on('--parent PARENT', 'Name of parent stack') {|v| args.parent = v}
|
37
|
-
opts.on('--to-json', 'Convert yaml template to json') {|_| args.json = true}
|
38
37
|
opts.on('--region REGION', 'AWS region') {|v| args.region = v}
|
39
38
|
opts.on('--profile PROFILE', 'AWS profile (use aws configure)') {|v| args.profile = v}
|
40
39
|
opts.on('--param KEY=VALUE', 'Stack Parameter') {|v| args.params.store(*v.split('=')) }
|
41
40
|
opts.on('--help', 'Show this help') {|_| puts opts; exit!;}
|
42
41
|
opts.on('--version', 'Show version') {|_| puts `gem list autostacker24`; exit!;}
|
42
|
+
opts.separator ''
|
43
43
|
end
|
44
44
|
USAGE = parser.to_s
|
45
45
|
|
@@ -67,17 +67,17 @@ case args.command
|
|
67
67
|
when /delete/
|
68
68
|
check_stack(args)
|
69
69
|
Stacker.delete_stack(args.stack)
|
70
|
-
when /
|
70
|
+
when /process|show/
|
71
71
|
check_template(args)
|
72
72
|
puts JSON.pretty_generate(JSON.parse(Stacker.template_body(args.template)))
|
73
73
|
when /convert/
|
74
74
|
check_template(args)
|
75
75
|
template = File.read(args.template)
|
76
|
-
if
|
77
|
-
puts '
|
78
|
-
puts JSON.pretty_generate(YAML.load(template))
|
76
|
+
if template =~ /\A(\s*\{)|(\s*\/{2})/
|
77
|
+
puts JSON.parse(template).to_yaml.sub('---', '# AutoStacker24 CloudFormation YAML Template')
|
79
78
|
else
|
80
|
-
puts
|
79
|
+
puts '// AutoStacker24 CloudFormation JSON Template '
|
80
|
+
puts JSON.pretty_generate(YAML.load(template))
|
81
81
|
end
|
82
82
|
else
|
83
83
|
error("unknown command '#{args.command}'")
|
@@ -1,122 +1,193 @@
|
|
1
|
-
|
2
|
-
require 'json'
|
3
|
-
require 'set'
|
4
|
-
require 'yaml'
|
5
|
-
|
6
|
-
module AutoStacker24
|
7
|
-
|
8
|
-
module Preprocessor
|
9
|
-
|
10
|
-
def self.preprocess(template)
|
11
|
-
if template =~ /\A\s*\{/
|
12
|
-
template
|
13
|
-
elsif template =~ /\A\s*\/{2}/
|
14
|
-
preprocess_json(parse_json(template)).to_json
|
15
|
-
else
|
16
|
-
preprocess_json(parse_yaml(template)).to_json
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.parse_json(template)
|
21
|
-
JSON(template)
|
22
|
-
rescue JSON::ParserError => e
|
23
|
-
require 'json/pure' # pure ruby parser has better error diagnostics
|
24
|
-
JSON(template)
|
25
|
-
raise e
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.parse_yaml(template)
|
29
|
-
YAML.load(template)
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.preprocess_json(json)
|
33
|
-
if json.is_a?(Hash)
|
34
|
-
json.inject({}) do |m, (k, v)|
|
35
|
-
if k == 'UserData' && v.is_a?(String)
|
36
|
-
m.merge(k => preprocess_user_data(v))
|
37
|
-
else
|
38
|
-
m.merge(k => preprocess_json(v))
|
39
|
-
end
|
40
|
-
end
|
41
|
-
elsif json.is_a?(Array)
|
42
|
-
json.map{|v| preprocess_json(v)}
|
43
|
-
elsif json.is_a?(String)
|
44
|
-
|
45
|
-
else
|
46
|
-
json
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.preprocess_user_data(s)
|
51
|
-
{'Fn::Base64' =>
|
52
|
-
end
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
{'Fn::Join' => ['', parts]}
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def self.
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
1
|
+
|
2
|
+
require 'json'
|
3
|
+
require 'set'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
module AutoStacker24
|
7
|
+
|
8
|
+
module Preprocessor
|
9
|
+
|
10
|
+
def self.preprocess(template)
|
11
|
+
if template =~ /\A\s*\{/
|
12
|
+
template
|
13
|
+
elsif template =~ /\A\s*\/{2}/
|
14
|
+
preprocess_json(parse_json(template)).to_json
|
15
|
+
else
|
16
|
+
preprocess_json(parse_yaml(template)).to_json
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parse_json(template)
|
21
|
+
JSON(template)
|
22
|
+
rescue JSON::ParserError => e
|
23
|
+
require 'json/pure' # pure ruby parser has better error diagnostics
|
24
|
+
JSON(template)
|
25
|
+
raise e
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.parse_yaml(template)
|
29
|
+
YAML.load(template)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.preprocess_json(json)
|
33
|
+
if json.is_a?(Hash)
|
34
|
+
json.inject({}) do |m, (k, v)|
|
35
|
+
if k == 'UserData' && v.is_a?(String)
|
36
|
+
m.merge(k => preprocess_user_data(v))
|
37
|
+
else
|
38
|
+
m.merge(k => preprocess_json(v))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
elsif json.is_a?(Array)
|
42
|
+
json.map{|v| preprocess_json(v)}
|
43
|
+
elsif json.is_a?(String)
|
44
|
+
interpolate(json)
|
45
|
+
else
|
46
|
+
json
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.preprocess_user_data(s)
|
51
|
+
{'Fn::Base64' => interpolate(s)}
|
52
|
+
end
|
53
|
+
|
54
|
+
# interpolates a string with '@' expressions and returns a string or a hash
|
55
|
+
# it implements the following non context free grammar in pseudo antlr3
|
56
|
+
#
|
57
|
+
# string : RAW? (expr RAW?)*;
|
58
|
+
# expr : '@' '{'? name (attr+ | map)? '}'?;
|
59
|
+
# name : ID ('::' ID)?;
|
60
|
+
# attr : ('.' ID)+;
|
61
|
+
# map : '[' key (',' key)? ']';
|
62
|
+
# key : ID | expr;
|
63
|
+
# ID : [a-zA-Z0-9]+;
|
64
|
+
# RAW : (~['@']* | FILE)=;
|
65
|
+
# FILE : '@file://' [^@\s]+ ('@' | ' ');
|
66
|
+
# FILE_C : '@{file://' [^}] '}';
|
67
|
+
#
|
68
|
+
def self.interpolate(s)
|
69
|
+
parts = []
|
70
|
+
while s.length > 0
|
71
|
+
raw, s = parse_raw(s)
|
72
|
+
parts << raw unless raw.empty?
|
73
|
+
expr, s = parse_expr(s)
|
74
|
+
parts << expr if expr
|
75
|
+
end
|
76
|
+
case parts.length
|
77
|
+
when 0 then ''
|
78
|
+
when 1 then parts[0]
|
79
|
+
else {'Fn::Join' => ['', parts]}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.parse_raw(s)
|
84
|
+
i = -1
|
85
|
+
loop do
|
86
|
+
i = s.index('@', i + 1)
|
87
|
+
return s, '' if i.nil?
|
88
|
+
|
89
|
+
file_match = /\A@file:\/\/([^@\s]+)@?/.match(s[i..-1])
|
90
|
+
file_curly_match = /\A@\{file:\/\/([^\}]+)\}/.match(s[i..-1])
|
91
|
+
if file_match # inline file
|
92
|
+
s = s[0, i] + File.read(file_match[1]) + file_match.post_match
|
93
|
+
i -= 1
|
94
|
+
elsif file_curly_match # inline file with curly braces
|
95
|
+
s = s[0, i] + File.read(file_curly_match[1]) + file_curly_match.post_match
|
96
|
+
i -= 1
|
97
|
+
elsif s[i, 2] =~ /\A@@/ # escape
|
98
|
+
s = s[0, i] + s[i+1..-1]
|
99
|
+
elsif s[i, 2] =~ /\A@(\w|\{)/
|
100
|
+
return s[0, i], s[i..-1] # return raw, '@...'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Parse given string as AutoStacker24 expression, and produce CloudFormation
|
107
|
+
# function from it (Fn::GetAtt, Fn::FindInMap, Ref).
|
108
|
+
#
|
109
|
+
# == Parameters:
|
110
|
+
# s::
|
111
|
+
# the string to parse
|
112
|
+
# embedded::
|
113
|
+
# whether the string is embedded in an AutoStacker24 expression already.
|
114
|
+
# Embedded expressions may not start with '@{', only with '@'.
|
115
|
+
#
|
116
|
+
def self.parse_expr(s, embedded = false)
|
117
|
+
return nil, s if s.length == 0
|
118
|
+
|
119
|
+
at, s = parse(AT, s)
|
120
|
+
raise "expected '@' but got #{s}" unless at
|
121
|
+
curly, s = parse(LEFT_CURLY, s) unless embedded
|
122
|
+
name, s = parse(NAME, s)
|
123
|
+
raise "expected parameter name #{s}" unless name
|
124
|
+
|
125
|
+
if curly
|
126
|
+
# try attribute, then map, then fallback to simple ref.
|
127
|
+
expr, s = parse_attribute(s, name)
|
128
|
+
expr, s = parse_map(s, name) unless expr
|
129
|
+
expr, s = parse_reference(s, name) unless expr
|
130
|
+
|
131
|
+
closing_curly, s = parse(RIGHT_CURLY, s)
|
132
|
+
raise "expected '}' but got #{s}" unless closing_curly
|
133
|
+
else
|
134
|
+
# try attribute, then map, then fallback to simple ref.
|
135
|
+
# these are allowed in both embedded and top-level expressions
|
136
|
+
# starting with @...
|
137
|
+
expr, s = parse_attribute(s, name)
|
138
|
+
expr, s = parse_map(s, name) unless expr
|
139
|
+
expr, s = parse_reference(s, name) unless expr
|
140
|
+
end
|
141
|
+
|
142
|
+
return expr, s
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.parse_reference(s, name)
|
146
|
+
return {'Ref' => name}, s
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.parse_attribute(s, name)
|
150
|
+
attr, s = parse(ATTRIB, s)
|
151
|
+
if attr
|
152
|
+
return {'Fn::GetAtt' => [name, attr[1..-1]]}, s
|
153
|
+
else
|
154
|
+
return nil, s
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.parse_map(s, name)
|
159
|
+
bracket, s = parse(LEFT_BRACKET, s)
|
160
|
+
return nil, s unless bracket
|
161
|
+
top, s = parse(KEY, s)
|
162
|
+
top, s = parse_expr(s, nested=true) unless top
|
163
|
+
comma, s = parse(COMMA, s)
|
164
|
+
second, s = parse(KEY, s) if comma
|
165
|
+
second, s = parse_expr(s, embedded=true) if comma and second.nil?
|
166
|
+
bracket, s = parse(RIGHT_BRACKET, s)
|
167
|
+
raise "Expected closing ']' #{s}" unless bracket
|
168
|
+
map = [top, second]
|
169
|
+
if second # two arguments found
|
170
|
+
return {'Fn::FindInMap' => [name, top, second]}, s
|
171
|
+
else
|
172
|
+
return {'Fn::FindInMap' => [name + 'Map', {'Ref' => name}, top]}, s
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.parse(re, s)
|
177
|
+
m = re.match(s)
|
178
|
+
return m.to_s, m.post_match if m
|
179
|
+
return nil, s
|
180
|
+
end
|
181
|
+
|
182
|
+
# Tokens
|
183
|
+
AT = /\A@/
|
184
|
+
NAME = /\A\w+(::\w+)?/
|
185
|
+
LEFT_BRACKET = /\A\[\s*/
|
186
|
+
RIGHT_BRACKET = /\A\s*\]/
|
187
|
+
LEFT_CURLY = /\A\{/
|
188
|
+
RIGHT_CURLY = /\A\}/
|
189
|
+
COMMA = /\A\s*,\s*/
|
190
|
+
KEY = /\A(\w+)/
|
191
|
+
ATTRIB = /\A(\.\w+)+/
|
192
|
+
end
|
193
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: autostacker24
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Johannes Mueller
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-04-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: aws-sdk-core
|
@@ -95,7 +95,13 @@ dependencies:
|
|
95
95
|
- - "~>"
|
96
96
|
- !ruby/object:Gem::Version
|
97
97
|
version: '3.4'
|
98
|
-
description:
|
98
|
+
description: AutoStacker24 is a small ruby gem for managing AWS CloudFormation stacks.
|
99
|
+
It is a thin wrapper around the AWS Ruby SDK. It lets you write simple and convenient
|
100
|
+
automation scripts, especially if you have lots of parameters or dependencies between
|
101
|
+
stacks. You can use it directly from Ruby code or from the command line. It enhances
|
102
|
+
CloudFormation templates by parameter expansion in strings and it is even possible
|
103
|
+
to write templates in YAML which is much friendlier to humans than JSON. You can
|
104
|
+
use autostacker24 cli to convert existing templates to YAML.
|
99
105
|
email:
|
100
106
|
- jmueller@autoscout24.com
|
101
107
|
- crodemeyer@autoscout24.com
|