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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e911951935b531613df5334a7fe6257cc06e3ec8
4
- data.tar.gz: 40ce45a938a1152c61a6831abc8617f9b0e3081a
3
+ metadata.gz: c854bfc3f7bc00704f86c14a3d2fc742da028083
4
+ data.tar.gz: 1c1f01cb79ef33a2b822ce28b2a51a52fea8667b
5
5
  SHA512:
6
- metadata.gz: 5f852171e3d662cc0e8c986911a618fa0ecfba3b4701fee9ac74586860f8cfeb0401af54003ea9b5347f33c450bc982047f7a77a16e392baf50cd22c80f2aa6d
7
- data.tar.gz: 58e75d34a406723ecc471b2c7d66b7de1d336d8d0a92a7bf63dc26cabec2ba56d72df1c1192c05d7a38dae9563b144d9101833658455ece87fe1a75b21eadadb
6
+ metadata.gz: be4caf16f2fe3a6f84bb94669aea7170fe1c9962751e22a0386ee7873db989f1f8be6c9d6dadf5a111ce57f1093f70ee13f532f9c9437c0317dae6c2f3420cb3
7
+ data.tar.gz: 78036ff04e15108b194214087c77210da73cb1b5f9ea33b5c7354ded08cd3f98711cf55d5ae2ce666cdeff82c0fafd6be1d6d2eefac34b947a531c72f21e53fb
@@ -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 "\tcreate\t\t create or update a stack"
27
+ opts.separator "\tupdate\t\t create or update a stack"
28
28
  opts.separator "\tdelete\t\t delete a stack"
29
- opts.separator "\tshow\t\t pretty print template after preprocessing"
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 a template to yaml"
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 /preprocess|show/
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 args.json
77
- puts '// autostacker24 CloudFormation JSON template'
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 JSON.parse(template).to_yaml.sub('---', '# autostacker24 CloudFormation YAML template')
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
- preprocess_string(json)
45
- else
46
- json
47
- end
48
- end
49
-
50
- def self.preprocess_user_data(s)
51
- {'Fn::Base64' => preprocess_string(s)}
52
- end
53
-
54
- def self.preprocess_string(s)
55
- m = /^@file:\/\/(.*)$/.match(s)
56
- s = File.read(m[1]) if m
57
- parts = tokenize(s).map do |token|
58
- case token
59
- when '@@' then '@'
60
- when '@[' then '['
61
- when /\A@/ then parse_ref(token)
62
- else token
63
- end
64
- end
65
-
66
- # merge neighboured strings
67
- parts = parts.reduce([])do |m, p|
68
- if m.last.is_a?(String) && p.is_a?(String)
69
- m[-1] += p
70
- else
71
- m << p
72
- end
73
- m
74
- end
75
-
76
- if parts.length == 1
77
- parts.first
78
- else # we need a join construct
79
- {'Fn::Join' => ['', parts]}
80
- end
81
- end
82
-
83
- def self.parse_ref(token)
84
- m = /\A@([^\[]*)(\[([^,]*)(\s*,\s*(.*))?\])?$/.match(token)
85
- m1 = m[1]
86
- m2 = m[3]
87
- m3 = m[5]
88
- if m2
89
- args = if m3
90
- [m1, m2, m3]
91
- else
92
- [m1 + 'Map', '@' + m1, m2]
93
- end
94
- {'Fn::FindInMap' => [args[0], preprocess_string(args[1]), preprocess_string(args[2])]}
95
- else
96
- {'Ref' => m1}
97
- end
98
- end
99
-
100
- def self.tokenize(s)
101
- # for recursive bracket matching see
102
- # http://stackoverflow.com/questions/19486686/recursive-nested-matching-pairs-of-curly-braces-in-ruby-regex
103
- # but for we limit ourself to one level to make things less complex
104
- pattern = /@@|@\[|@(\w+(::\w+)?)(\[[^\]]*\])?/
105
- tokens = []
106
- loop do
107
- m = pattern.match(s)
108
- if m
109
- tokens << m.pre_match unless m.pre_match.empty?
110
- tokens << m.to_s
111
- s = m.post_match
112
- else
113
- tokens << s unless s.empty? && !tokens.empty?
114
- break
115
- end
116
- end
117
- tokens
118
- end
119
-
120
- end
121
-
122
- end
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: 1.0.71
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-03-29 00:00:00.000000000 Z
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: n/a
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