autostacker24 1.0.71 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|