demeter-cli 0.0.4

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