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 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