demeter-cli 0.0.4

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.
@@ -0,0 +1,371 @@
1
+ require 'digest/md5'
2
+ require 'hashdiff'
3
+
4
+ module Demeter
5
+ module Aws
6
+ class SecurityGroup
7
+ def initialize(ec2)
8
+ @ec2 = ec2
9
+ @_sg = {}
10
+ @_local_sg = {}
11
+ end
12
+
13
+ def group_id
14
+ Demeter::vars["security_group.#{project_key}.id"]
15
+ end
16
+
17
+ def load_aws(object)
18
+ if object.is_a?(::Aws::EC2::Types::SecurityGroup)
19
+ name_tag = object['tags'].detect{|tag| tag['key'].downcase == 'name'}
20
+ @_sg[:name] = (name_tag ? name_tag['value'] : object.group_name)
21
+ @_sg[:description] = object.description
22
+ @_sg[:vpc_id] = object.vpc_id
23
+ @_sg[:ingress] = []
24
+ @_sg[:egress] = []
25
+
26
+ Demeter::set_var("security_group.#{project_key}.id", object.group_id)
27
+
28
+ # INGRESS
29
+ object.ip_permissions.each do |rule|
30
+ rule.ip_ranges.each do |cidr_block|
31
+ @_sg[:ingress] << {
32
+ protocol: rule.ip_protocol.to_s,
33
+ from_port: rule.from_port.to_i,
34
+ to_port: rule.to_port.to_i,
35
+ cidr_block: cidr_block.cidr_ip
36
+ }
37
+ end
38
+
39
+ rule.user_id_group_pairs.each do |source_security_group|
40
+ @_sg[:ingress] << {
41
+ protocol: rule.ip_protocol.to_s,
42
+ from_port: rule.from_port.to_i,
43
+ to_port: rule.to_port.to_i,
44
+ source_security_group: source_security_group.group_id
45
+ }
46
+ end
47
+ end
48
+
49
+ # EGRESS
50
+ object.ip_permissions_egress.each do |rule|
51
+ rule.ip_ranges.each do |cidr_block|
52
+ @_sg[:egress] << {
53
+ protocol: rule.ip_protocol.to_s,
54
+ from_port: rule.from_port.to_i,
55
+ to_port: rule.to_port.to_i,
56
+ cidr_block: cidr_block.cidr_ip
57
+ }
58
+ end
59
+
60
+ rule.user_id_group_pairs.each do |source_security_group|
61
+ @_sg[:egress] << {
62
+ protocol: rule.ip_protocol.to_s,
63
+ from_port: rule.from_port.to_i,
64
+ to_port: rule.to_port.to_i,
65
+ source_security_group: source_security_group.group_id
66
+ }
67
+ end
68
+ end
69
+ return true
70
+ end
71
+ end
72
+
73
+ def load_local(object)
74
+ if object.is_a?(Hash)
75
+ @_local_sg[:name] = object['name']
76
+ @_local_sg[:description] = 'Managed by Demeter'
77
+ @_local_sg[:ingress] = []
78
+ @_local_sg[:egress] = []
79
+ @_local_sg[:vpc_id] = object['vpc_id']
80
+
81
+ if !Demeter::vars.has_key?("security_group.#{project_key}.id")
82
+ Demeter::set_var("security_group.#{project_key}.id", "security_group.#{project_key}.id")
83
+ end
84
+
85
+ # INGRESS
86
+ if object['ingress']
87
+ object['ingress'].each do |rule|
88
+ if rule.has_key?('cidr_blocks')
89
+ rule['cidr_blocks'].to_a.each do |cidr_block|
90
+ @_local_sg[:ingress] << {
91
+ protocol: rule['protocol'].to_s,
92
+ from_port: rule['from_port'].to_i,
93
+ to_port: rule['to_port'].to_i,
94
+ cidr_block: cidr_block
95
+ }
96
+ end
97
+ end
98
+
99
+ if rule.has_key?('source_security_groups')
100
+ rule['source_security_groups'].to_a.each do |source_security_group|
101
+ @_local_sg[:ingress] << {
102
+ protocol: rule['protocol'].to_s,
103
+ from_port: rule['from_port'].to_i,
104
+ to_port: rule['to_port'].to_i,
105
+ source_security_group: source_security_group
106
+ }
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ # EGRESS
113
+ if object['egress']
114
+ object['egress'].each do |rule|
115
+ if rule.has_key?('cidr_blocks')
116
+ rule['cidr_blocks'].to_a.each do |cidr_block|
117
+ @_local_sg[:egress] << {
118
+ protocol: rule['protocol'].to_s,
119
+ from_port: rule['from_port'].to_i,
120
+ to_port: rule['to_port'].to_i,
121
+ cidr_block: cidr_block
122
+ }
123
+ end
124
+ end
125
+
126
+ if rule.has_key?('source_security_groups')
127
+ rule['source_security_groups'].to_a.each do |source_security_group|
128
+ @_local_sg[:egress] << {
129
+ protocol: rule['protocol'].to_s,
130
+ from_port: rule['from_port'].to_i,
131
+ to_port: rule['to_port'].to_i,
132
+ source_security_group: source_security_group
133
+ }
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ return true
140
+ end
141
+ end
142
+
143
+ def security_group
144
+ if @_sg.empty?
145
+ @_local_sg
146
+ else
147
+ @_sg
148
+ end
149
+ end
150
+
151
+ def hash
152
+ group_name
153
+ end
154
+
155
+ def project_key
156
+ security_group[:name]
157
+ .gsub('::', '_')
158
+ .gsub('/', '_')
159
+ .gsub('-', '_')
160
+ .gsub(' ', '_')
161
+ .downcase
162
+ end
163
+
164
+ def project_name
165
+ security_group[:name].split('::')[1]
166
+ end
167
+
168
+ def group_name
169
+ security_group[:name]
170
+ end
171
+
172
+ def diff
173
+ sg = @_sg.select{ |key, value| true if key == :ingress || key == :egress }
174
+
175
+ # update variables
176
+ local_sg = update_vars(@_local_sg.select{ |key, value| true if key == :ingress || key == :egress })
177
+ # update once again to replace deep variable links
178
+ local_sg = update_vars(local_sg)
179
+
180
+ if sg[:ingress]
181
+ sg[:ingress].sort_by! { |x| x.to_s }
182
+ end
183
+
184
+ if local_sg[:ingress]
185
+ local_sg[:ingress].sort_by! { |x| x.to_s }
186
+ end
187
+
188
+ if sg[:egress]
189
+ sg[:egress].sort_by! { |x| x.to_s }
190
+ end
191
+
192
+ if local_sg[:egress]
193
+ local_sg[:egress].sort_by! { |x| x.to_s }
194
+ end
195
+ diff = HashDiff.diff(sg, local_sg)
196
+ end
197
+
198
+ def update_vars(hash=@_local_sg)
199
+ hash.each do |k, v|
200
+ if v.is_a?(String)
201
+ if /\<\%(.*)\%\>/.match(v)
202
+ var_keys = /\<\%(.*)\%\>/.match(v).captures
203
+ if Demeter::vars.has_key?(var_keys[0].strip)
204
+ hash[k] = Demeter::vars[var_keys[0].strip]
205
+ if hash[k].is_a?(Array)
206
+ extended = []
207
+ hash[k].each do |value|
208
+ h1 = hash.clone
209
+ h1[k] = value
210
+ extended << h1
211
+ end
212
+ hash = extended
213
+ end
214
+ else
215
+ fail "Key #{v} not found!"
216
+ end
217
+ end
218
+ elsif v.is_a?(Hash)
219
+ hash[k] = update_vars v
220
+ elsif v.is_a?(Array)
221
+ tmp = []
222
+ v.flatten.each do |x|
223
+ if x.is_a?(Hash)
224
+ tmp << update_vars(x)
225
+ end
226
+ end
227
+ hash[k] = tmp.flatten
228
+ end
229
+ end
230
+ hash
231
+ end
232
+
233
+ def create
234
+ if @_sg.empty?
235
+ # update variables
236
+ local_sg = update_vars(@_local_sg)
237
+ # update once again to replace deep variable links
238
+ local_sg = update_vars(@_local_sg)
239
+
240
+ resp = @ec2.create_security_group({
241
+ group_name: local_sg[:name],
242
+ description: local_sg[:description], # required
243
+ vpc_id: local_sg[:vpc_id]
244
+ })
245
+
246
+ @ec2.create_tags(:resources => [resp.group_id], :tags => [
247
+ { :key => 'Name', :value => local_sg[:name] }
248
+ ])
249
+
250
+ puts "Created SG: #{local_sg['name']} (#{resp.group_id})"
251
+ true
252
+ end
253
+ end
254
+
255
+ def modify
256
+ the_diff = diff
257
+ pluses = the_diff.select { |s| s[0] == "+" }
258
+ minuses = the_diff.select { |s| s[0] == "-" }
259
+ pluses.each do |plus|
260
+ next if plus[1] == "description"
261
+ values = plus[2]
262
+ if values.has_key?(:cidr_block)
263
+ if plus[1].include?('ingress')
264
+ @ec2.authorize_security_group_ingress({
265
+ group_id: group_id,
266
+ ip_protocol: values[:protocol],
267
+ from_port: values[:from_port],
268
+ to_port: values[:to_port],
269
+ cidr_ip: values[:cidr_block],
270
+ })
271
+ else
272
+ @ec2.authorize_security_group_egress({
273
+ group_id: group_id,
274
+ ip_permissions: [{
275
+ ip_protocol: values[:protocol],
276
+ from_port: values[:from_port],
277
+ to_port: values[:to_port],
278
+ ip_ranges: [
279
+ {
280
+ cidr_ip: values[:cidr_block]
281
+ }
282
+ ]
283
+ }]
284
+ })
285
+ end
286
+ elsif values.has_key?(:source_security_group)
287
+ if plus[1].include?('ingress')
288
+ @ec2.authorize_security_group_ingress({
289
+ group_id: group_id,
290
+ ip_permissions: [{
291
+ ip_protocol: values[:protocol],
292
+ from_port: values[:from_port],
293
+ to_port: values[:to_port],
294
+ user_id_group_pairs: [{
295
+ group_id: values[:source_security_group]
296
+ }]
297
+ }]
298
+ })
299
+ else
300
+ @ec2.authorize_security_group_egress({
301
+ group_id: group_id,
302
+ ip_permissions: [{
303
+ ip_protocol: values[:protocol],
304
+ from_port: values[:from_port],
305
+ to_port: values[:to_port],
306
+ user_id_group_pairs: [{
307
+ group_id: values[:source_security_group]
308
+ }]
309
+ }]
310
+ })
311
+ end
312
+ end
313
+ end
314
+ minuses.each do |minus|
315
+ values = minus[2]
316
+ if values.has_key?(:cidr_block)
317
+ if minus[1].include?('ingress')
318
+ @ec2.revoke_security_group_ingress({
319
+ group_id: group_id,
320
+ ip_protocol: values[:protocol],
321
+ from_port: values[:from_port],
322
+ to_port: values[:to_port],
323
+ cidr_ip: values[:cidr_block],
324
+ })
325
+ else
326
+ @ec2.revoke_security_group_egress({
327
+ group_id: group_id,
328
+ ip_permissions: [{
329
+ ip_protocol: values[:protocol],
330
+ from_port: values[:from_port],
331
+ to_port: values[:to_port],
332
+ ip_ranges: [
333
+ {
334
+ cidr_ip: values[:cidr_block]
335
+ }
336
+ ]
337
+ }]
338
+ })
339
+ end
340
+ elsif values.has_key?(:source_security_group)
341
+ if minus[1].include?('ingress')
342
+ @ec2.revoke_security_group_ingress({
343
+ group_id: group_id,
344
+ ip_permissions: [{
345
+ ip_protocol: values[:protocol],
346
+ from_port: values[:from_port],
347
+ to_port: values[:to_port],
348
+ user_id_group_pairs: [{
349
+ group_id: values[:source_security_group]
350
+ }]
351
+ }]
352
+ })
353
+ else
354
+ @ec2.revoke_security_group_egress({
355
+ group_id: group_id,
356
+ ip_permissions: [{
357
+ ip_protocol: values[:protocol],
358
+ from_port: values[:from_port],
359
+ to_port: values[:to_port],
360
+ user_id_group_pairs: [{
361
+ group_id: values[:source_security_group]
362
+ }]
363
+ }]
364
+ })
365
+ end
366
+ end
367
+ end
368
+ end
369
+ end
370
+ end
371
+ end
@@ -0,0 +1,92 @@
1
+ require 'thor'
2
+
3
+ module Demeter
4
+ class Cli < Thor
5
+ # include Thor::Actions
6
+
7
+ option :debug, desc: 'displays the debug backtrace', type: :boolean, default: false
8
+ def initialize(args = [], local_options = {}, config = {})
9
+ super
10
+ end
11
+
12
+ desc 'version', 'prints Demeter version'
13
+ long_desc <<-EOS
14
+ `demeter version` prints the version of the app.
15
+ EOS
16
+ def version
17
+ puts "v#{Demeter::VERSION}"
18
+ end
19
+
20
+
21
+ desc 'status', 'Show current status of maneged and unmaneged security groups'
22
+ long_desc <<-EOS
23
+ `demeter status` shows current status of managed and unmaneged security groups.
24
+
25
+ $ > demeter plan -e development
26
+ EOS
27
+ option :environment, aliases: '-e', :required => true, desc: 'The environment to plan against'
28
+ def status
29
+ if options[:help]
30
+ invoke :help, ['status']
31
+ else
32
+ require 'demeter/commands/status'
33
+ Demeter::Commands::Status.new(options).start
34
+ end
35
+ end
36
+
37
+
38
+ desc 'plan', 'Generate and show an execution plan'
39
+ long_desc <<-EOS
40
+ `demeter plan` generates and shows the execution plan.
41
+
42
+ $ > demeter plan -e development
43
+ EOS
44
+ option :environment, aliases: '-e', :required => true, desc: 'The environment to plan against'
45
+ def plan
46
+ if options[:help]
47
+ invoke :help, ['plan']
48
+ else
49
+ require 'demeter/commands/plan'
50
+ Demeter::Commands::Plan.new(options).start
51
+ end
52
+ end
53
+
54
+
55
+ desc 'apply', 'Apply an execution plan'
56
+ long_desc <<-EOS
57
+ `demeter apply` applies the execution plan.
58
+
59
+ $ > demeter apply -e development
60
+ EOS
61
+ option :environment, aliases: '-e', :required => true, desc: 'The environment to plan against'
62
+ def apply
63
+ if options[:help]
64
+ invoke :help, ['apply']
65
+ else
66
+ require 'demeter/commands/apply'
67
+ Demeter::Commands::Apply.new(options).start
68
+ end
69
+ end
70
+
71
+
72
+ desc 'generate', 'Generate local config from aws describe call'
73
+ long_desc <<-EOS
74
+ `demeter generate -e development -ids <sg-id> ...`
75
+
76
+ $ > demeter generate -e development -ids sg-000000 sg-111111
77
+
78
+ $ > demeter generate -e development -ids sg-000000
79
+ EOS
80
+ option :environment, aliases: '-e', :required => true, desc: 'The environment to plan against'
81
+ option :ids, aliases: '-ids', type: :array, :required => true, desc: 'List of security group ids (sg-000000)'
82
+ def generate
83
+ if options[:help]
84
+ invoke :help, ['generate']
85
+ else
86
+ require 'demeter/commands/generate'
87
+ Demeter::Commands::Generate.new(options).start
88
+ end
89
+ end
90
+
91
+ end
92
+ end