applb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,386 @@
1
+ require 'pp'
2
+ require 'applb/client_wrapper'
3
+ require 'applb/converter'
4
+ require 'applb/dsl'
5
+ require 'applb/dsl/load_balancer'
6
+ require 'applb/error'
7
+ require 'applb/filterable'
8
+ require 'applb/utils'
9
+
10
+ module Applb
11
+ class Client
12
+ include Filterable
13
+
14
+ MAGIC_COMMENT = <<-EOS
15
+ # -*- mode: ruby -*-
16
+ # vi: set ft=ruby :
17
+ EOS
18
+
19
+ def initialize(filepath, options = {})
20
+ @filepath = filepath
21
+ @options = options
22
+ end
23
+
24
+ def apply
25
+ dsl = load_file(@filepath)
26
+
27
+ dsl_ec2s = dsl.ec2s
28
+ aws_ec2s = client.load_balancers.group_by(&:vpc_id)
29
+
30
+ dsl.ec2s.each do |vpc_id, dsl_ec2|
31
+ aws_ec2 = aws_ec2s[vpc_id] || []
32
+
33
+ traverse_ec2(vpc_id, dsl_ec2, aws_ec2)
34
+ end
35
+ end
36
+
37
+ def export
38
+ result = {}
39
+
40
+ lbs = client.load_balancers
41
+ tags_by_arn = describe_tags(lbs)
42
+
43
+ lbs.each do |lb|
44
+ attributes = client.load_balancer_attributes(load_balancer_arn: lb.load_balancer_arn)
45
+ target_groups = describe_target_groups(lb)
46
+ listeners = describe_listeners(lb)
47
+ rules_by_listener_arn = listeners.each_with_object({}) do |listener, rules_by_listener_arn|
48
+ rules_by_listener_arn[listener.listener_arn] = describe_rules(listener)
49
+ end
50
+ (result[lb.vpc_id] ||= {})[lb.load_balancer_name] = export_lb(
51
+ lb,
52
+ attributes,
53
+ target_groups,
54
+ listeners,
55
+ rules_by_listener_arn,
56
+ )
57
+ end
58
+
59
+ path = Pathname.new(@filepath)
60
+ base_dir = path.parent
61
+ if @options[:split_more]
62
+ result.each do |vpc_id, lbs_by_name|
63
+ lbs_by_name.each do |name, lbs|
64
+ Converter.new({vpc_id => {name => lbs}}, tags_by_arn).convert do |vpc_id, dsl|
65
+ alb_base_dir = base_dir.join("#{vpc_id}")
66
+ FileUtils.mkdir_p(alb_base_dir)
67
+ alb_file = alb_base_dir.join("#{name}.alb")
68
+ Applb.logger.info("export #{alb_file}")
69
+ open(alb_file, 'wb') do |f|
70
+ f.puts MAGIC_COMMENT
71
+ f.puts dsl
72
+ end
73
+ end
74
+ end
75
+ end
76
+ elsif @options[:split]
77
+ Converter.new(result, tags_by_arn).convert do |vpc_id, dsl|
78
+ FileUtils.mkdir_p(base_dir)
79
+ alb_file = base_dir.join("#{vpc_id}.alb")
80
+ Applb.logger.info("export #{alb_file}")
81
+ open(alb_file, 'wb') do |f|
82
+ f.puts MAGIC_COMMENT
83
+ f.puts dsl
84
+ end
85
+ end
86
+ else
87
+ dsls = []
88
+ Converter.new(result, tags_by_arn).convert do |vpc_id, dsl|
89
+ dsls << dsl
90
+ end
91
+
92
+ FileUtils.mkdir_p(base_dir)
93
+ Applb.logger.info("export #{path}")
94
+ open(path, 'wb') do |f|
95
+ f.puts MAGIC_COMMENT
96
+ f.puts dsls.join("\n")
97
+ end
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def load_file(file)
104
+ open(file) do |f|
105
+ DSL.define(f.read, file, @options).result
106
+ end
107
+ end
108
+
109
+ def traverse_ec2(vpc_id, dsl_ec2, aws_ec2)
110
+ dsl_lb_by_name = dsl_ec2.load_balancers.group_by(&:name).each_with_object({}) do |(k, v), h|
111
+ h[k] = v.first if target?(k)
112
+ end
113
+ aws_lb_by_name = aws_ec2.group_by(&:load_balancer_name).each_with_object({}) do |(k, v), h|
114
+ h[k] = v.first if target?(k)
115
+ end
116
+
117
+ # create
118
+ dsl_lb_by_name.reject { |n| aws_lb_by_name[n] }.each do |name, dsl_lb|
119
+ aws_lb_by_name[name] = dsl_lb.create
120
+ end
121
+
122
+ # modify
123
+ dsl_lb_by_name.each do |name, dsl_lb|
124
+ next unless aws_lb = aws_lb_by_name.delete(name)
125
+
126
+ dsl_lb.aws(aws_lb)
127
+ traverse_lb(dsl_lb, aws_lb)
128
+ end
129
+
130
+ # delete
131
+ aws_lb_by_name.each do |name, aws_lb|
132
+ Applb.logger.info "Delete ELB v2 #{name}"
133
+ aws_tgs = client.describe_target_groups(
134
+ load_balancer_arn: aws_lb.load_balancer_arn,
135
+ ).target_groups
136
+ unless @options[:dry_run]
137
+ client.delete_load_balancer(aws_lb.load_balancer_arn)
138
+ # wait until load_balancer is deleted
139
+ sleep 3
140
+ end
141
+ aws_tgs.each do |tg|
142
+ Applb.logger.info "Delete target_group associated #{tg.target_group_name}"
143
+ next if @options[:dry_run]
144
+ client.delete_target_group(
145
+ target_group_arn: tg.target_group_arn,
146
+ )
147
+ end
148
+ end
149
+ end
150
+
151
+ def traverse_lb(dsl_lb, aws_lb)
152
+ dsl_lb.modify_subnets
153
+ dsl_lb.modify_security_groups
154
+ dsl_lb.modify_ip_address_type
155
+ dsl_lb.modify_load_balancer_attributes
156
+
157
+ traverse_target_groups(dsl_lb, aws_lb)
158
+ traverse_listeners(dsl_lb, aws_lb)
159
+ end
160
+
161
+ def traverse_target_groups(dsl_lb, aws_lb)
162
+ aws_tg_by_name = @client.target_groups(load_balancer_arn: aws_lb.load_balancer_arn).group_by(&:target_group_name).each_with_object({}) do |(k, v), h|
163
+ h[k] = v.first
164
+ end
165
+ dsl_tg_by_name = dsl_lb.target_groups.group_by(&:name).each_with_object({}) do |(k, v), h|
166
+ h[k] = v.first
167
+ end
168
+
169
+ # create
170
+ dsl_tg_by_name.reject { |n, _| aws_tg_by_name[n] }.each do |name, dsl_tg|
171
+ aws_tg_by_name[name] = dsl_tg.create
172
+ end
173
+
174
+ # modify
175
+ dsl_tg_by_name.each do |name, dsl_tg|
176
+ aws_tg = aws_tg_by_name.delete(name)
177
+ next unless aws_tg
178
+
179
+ dsl_tg.aws(aws_tg).modify
180
+ end
181
+
182
+ aws_tg_by_name.each do |name, aws_tg|
183
+ Applb.logger.info("Delete target group #{name}")
184
+ next if @options[:dry_run]
185
+ # client.modify_listener({}) TODO remove from listener first
186
+ client.delete_target_group(target_group_arn: aws_tg.target_group_arn)
187
+ end
188
+ end
189
+
190
+ def traverse_listeners(dsl_lb, aws_lb)
191
+ aws_listener_by_port = @client.listeners(load_balancer_arn: aws_lb.load_balancer_arn).group_by(&:port).each_with_object({}) do |(k, v), h|
192
+ h[k] = v.first
193
+ end
194
+
195
+ aws_target_group_by_name = @client.target_groups.group_by(&:target_group_name).each_with_object({}) do |(k, v), h|
196
+ h[k] = v.first
197
+ end
198
+ aws_target_group_by_arn = aws_target_group_by_name.each_with_object({}) do |(k, v), h|
199
+ h[v.target_group_arn] = v
200
+ end
201
+
202
+ dsl_listener_by_port = dsl_lb.listeners.group_by(&:port).each_with_object({}) do |(k, v), h|
203
+ dsl_listener = v.first
204
+ dsl_listener.load_balancer_arn = aws_lb.load_balancer_arn
205
+ h[k] = dsl_listener
206
+ end
207
+
208
+ # create
209
+ dsl_listener_by_port.reject { |port, _| aws_listener_by_port[port] }.each do |port, dsl_listener|
210
+ # resolve target_group_arn by target_group_name
211
+ target_group_name = dsl_listener.default_actions.first[:target_group_name]
212
+ if target_group_name
213
+ dsl_listener.default_actions.first[:target_group_arn] = aws_target_group_by_name[target_group_name].target_group_arn
214
+ end
215
+ aws_listener_by_port[port] = dsl_listener.create
216
+ end
217
+
218
+ # modify
219
+ dsl_listener_by_port.each do |port, dsl_listener|
220
+ aws_listener = aws_listener_by_port.delete(port)
221
+ next unless aws_listener
222
+
223
+ target_group_name = dsl_listener.default_actions.first[:target_group_name]
224
+ if target_group_name
225
+ dsl_listener.default_actions.first[:target_group_arn] = aws_target_group_by_name[target_group_name].target_group_arn
226
+ end
227
+ dsl_listener.aws(aws_listener).modify
228
+ traverse_rule(dsl_listener, aws_listener, aws_target_group_by_name)
229
+ end
230
+
231
+ # delete
232
+ aws_listener_by_port.each do |port, aws_listener|
233
+ aws_actions = client.describe_rules(listener_arn: aws_listener.listener_arn).rules.map(&:actions).flatten
234
+ Applb.logger.info("#{aws_lb.load_balancer_name} Delete listener for port #{port}")
235
+ unless @options[:dry_run]
236
+ client.delete_listener(listener_arn: aws_listener.listener_arn)
237
+ end
238
+ (aws_actions + aws_listener.default_actions).each do |action|
239
+ aws_tg = aws_target_group_by_arn.delete(action.target_group_arn)
240
+ next unless aws_tg
241
+ Applb.logger.info("#{aws_lb.load_balancer_name} Delete target_group associated #{aws_tg.target_group_name}")
242
+ next if @options[:dry_run]
243
+ client.delete_target_group(target_group_arn: action.target_group_arn)
244
+ end
245
+ end
246
+ end
247
+
248
+ class TargetGroupResolveError < Error
249
+ end
250
+
251
+ def traverse_rule(dsl_listener, aws_listener, aws_target_group_by_name)
252
+ dummy_idx = 0
253
+ dsl_rule_by_arn = (dsl_listener.rules || []).each_with_object({}) do |dsl_rule, h|
254
+ # give dummy arn for grouping
255
+ arn = dsl_rule.rule_arn ? dsl_rule.rule_arn : "dummy-#{(dummy_idx += 1)}"
256
+ # set listener_arn here
257
+ dsl_rule.listener_arn = aws_listener.listener_arn
258
+ h[arn] = dsl_rule
259
+ end
260
+ aws_rules = client.describe_rules(listener_arn: aws_listener.listener_arn).rules
261
+ aws_rule_by_arn = aws_rules.reject(&:is_default).group_by(&:rule_arn).each_with_object({}) do |(k, v), h|
262
+ h[k] = v.first
263
+ end
264
+
265
+ # create
266
+ dsl_rule_by_arn.reject { |arn, _| aws_rule_by_arn[arn] }.each do |arn, dsl_rule|
267
+ # resolve target_group_arn by target_group_name
268
+ target_group_name = dsl_rule.actions.first[:target_group_name]
269
+ if target_group_name
270
+ Applb.logger.debug("Resolve target_group_arn by target_group_name. -> #{target_group_name}")
271
+ aws_tg = aws_target_group_by_name[target_group_name]
272
+ unless aws_tg
273
+ Applb.logger.error("AWS target groups by name:\n#{aws_target_group_by_name.pretty_inspect}")
274
+ raise TargetGroupResolveError.new("target_group_name #{target_group_name}")
275
+ end
276
+ dsl_rule.actions.first[:target_group_arn] = aws_tg.target_group_arn
277
+ end
278
+
279
+ rule = dsl_rule.create
280
+ next if @options[:dry_run]
281
+ dsl_rule_by_arn[rule.rule_arn] = dsl_rule_by_arn.delete(arn)
282
+ end
283
+
284
+ # modify
285
+ dsl_rule_by_arn.each do |arn, dsl_rule|
286
+ aws_rule = aws_rule_by_arn.delete(arn)
287
+ next unless aws_rule
288
+
289
+ # resolve target_group_arn by target_group_name
290
+ target_group_name = dsl_rule.actions.first[:target_group_name]
291
+ if target_group_name
292
+ Applb.logger.debug("Resolve target_group_arn by target_group_name. -> #{target_group_name}")
293
+ aws_tg = aws_target_group_by_name[target_group_name]
294
+ unless aws_tg
295
+ Applb.logger.error("AWS target groups by name:\n#{aws_target_group_by_name.pretty_inspect}")
296
+ raise TargetGroupResolveError.new("target_group_name #{target_group_name}")
297
+ end
298
+ dsl_rule.actions.first[:target_group_arn] = aws_tg.target_group_arn
299
+ end
300
+
301
+ dsl_rule.aws(aws_rule).modify
302
+ end
303
+
304
+ # delete
305
+ aws_rule_by_arn.values.each do |aws_rule|
306
+ Applb.logger.info("Delete rule #{aws_rule.conditions.first[:values].first}")
307
+ next if @options[:dry_run]
308
+ Applb.logger.debug("deleting rule_arn #{aws_rule.rule_arn}")
309
+ client.delete_rule(rule_arn: aws_rule.rule_arn)
310
+ end
311
+ end
312
+
313
+ # @param [Aws::ElasticLodaBalancingV2::Types::LoadBalancer] lb
314
+ def describe_tags(lbs)
315
+ result = {}
316
+ arns = lbs.map(&:load_balancer_arn)
317
+ unless arns.empty?
318
+ resp = client.describe_tags(resource_arns: lbs.map(&:load_balancer_arn))
319
+ resp.tag_descriptions.each do |tag_desc|
320
+ result[tag_desc.resource_arn] = Hash[tag_desc.tags.map { |tag| [tag.key, tag.value] }]
321
+ end
322
+ end
323
+ result
324
+ end
325
+
326
+ def describe_target_groups(lb)
327
+ client.target_groups(load_balancer_arn: lb.load_balancer_arn)
328
+ end
329
+
330
+ def describe_listeners(lb)
331
+ client.listeners(load_balancer_arn: lb.load_balancer_arn)
332
+ end
333
+
334
+ def describe_rules(listener)
335
+ client.rules(listener_arn: listener.listener_arn)
336
+ end
337
+
338
+ # @param [Aws::ElasticLodaBalancingV2::Types::LoadBalancer] lb
339
+ # @param [Array<Types::LoadBalancerAttribute>] attrs
340
+ # @param [Array<Types::TargetGroup>] target_groups
341
+ # @param [Array<Types::Listener>] listeners
342
+ # @param [Hash] rules_by_listener_arn
343
+ def export_lb(lb, attrs, target_groups, listeners, rules_by_listener_arn)
344
+ {
345
+ availability_zones: lb.availability_zones.map { |az| Hash[az.each_pair.to_a] },
346
+ canonical_hosted_zone_id: lb.canonical_hosted_zone_id,
347
+ created_time: lb.created_time,
348
+ dns_name: lb.dns_name,
349
+ ip_address_type: lb.ip_address_type,
350
+ load_balancer_arn: lb.load_balancer_arn,
351
+ load_balancer_name: lb.load_balancer_name,
352
+ scheme: lb.scheme,
353
+ security_groups: lb.security_groups,
354
+ state: lb.state,
355
+ type: lb.type,
356
+ vpc_id: lb.vpc_id,
357
+ attributes: export_attributes(attrs),
358
+ target_groups: target_groups,
359
+ listeners: listeners,
360
+ rules_by_listener_arn: rules_by_listener_arn,
361
+ }
362
+ end
363
+
364
+ # @param [Array<Types::LoadBalancerAttribute>] attrs
365
+ def export_attributes(attrs)
366
+ result = {}
367
+ attrs.each do |attr|
368
+ case attr.key
369
+ when 'access_logs.s3.enabled' then
370
+ (result['access_logs'] ||= {s3: {}})[:s3][:enabled] = attr.value
371
+ when 'access_logs.s3.prefix' then
372
+ result['access_logs'] ||= {s3: {}}[:s3][:prefix] = attr.value
373
+ when 'access_logs.s3.bucket' then
374
+ result['access_logs'] ||= {s3: {}}[:s3][:bucket] = attr.value
375
+ else
376
+ result[attr.key] = attr.value
377
+ end
378
+ end
379
+ result
380
+ end
381
+
382
+ def client
383
+ @client ||= ClientWrapper.new(@options)
384
+ end
385
+ end
386
+ end
@@ -0,0 +1,85 @@
1
+ require 'forwardable'
2
+ require 'aws-sdk'
3
+
4
+ module Applb
5
+ class ClientWrapper
6
+ extend Forwardable
7
+ def_delegators :@client, *%i/
8
+ describe_load_balancer_attributes describe_tags set_subnets set_security_groups
9
+ set_ip_address_type modify_load_balancer_attributes create_target_group
10
+ modify_target_group delete_target_group create_listener modify_listener
11
+ delete_listener describe_rules delete_rule create_load_balancer
12
+ create_rule modify_rule set_rule_priorities describe_target_groups/
13
+
14
+ def initialize(options)
15
+ @includes = options[:includes] || []
16
+ @excludes = options[:excludes] || []
17
+ @client = Aws::ElasticLoadBalancingV2::Client.new
18
+ end
19
+
20
+ def load_balancers
21
+ results = []
22
+ next_marker = nil
23
+ begin
24
+ resp = @client.describe_load_balancers(marker: next_marker)
25
+ resp.load_balancers.each do |lb|
26
+ results << lb if target?(lb)
27
+ end
28
+ next_marker = resp.next_marker
29
+ end while next_marker
30
+ results
31
+ end
32
+
33
+ def delete_load_balancer(arn)
34
+ @client.delete_load_balancer(load_balancer_arn: arn)
35
+ end
36
+
37
+ def target_groups(*argv)
38
+ results = []
39
+ next_marker = nil
40
+ begin
41
+ resp = @client.describe_target_groups(*argv)
42
+ results.push(*resp.target_groups)
43
+ end while next_marker
44
+ results
45
+ end
46
+
47
+ def listeners(*argv)
48
+ results = []
49
+ next_marker = nil
50
+ begin
51
+ resp = @client.describe_listeners(*argv)
52
+ results.push(*resp.listeners)
53
+ end while next_marker
54
+ results
55
+ end
56
+
57
+ def rules(*argv)
58
+ results = []
59
+ next_marker = nil
60
+ begin
61
+ resp = @client.describe_rules(*argv)
62
+ results.push(*resp.rules)
63
+ end while next_marker
64
+ results
65
+ end
66
+
67
+ def load_balancer_attributes(*argv)
68
+ resp = @client.describe_load_balancer_attributes(*argv)
69
+ resp.attributes
70
+ end
71
+
72
+ private
73
+
74
+ def target?(lb)
75
+ name = lb.load_balancer_name
76
+ unless @includes.empty?
77
+ return @includes.include?(name)
78
+ end
79
+ unless @excludes.empty?
80
+ return !@excludes.any? { |regex| name =~ regex }
81
+ end
82
+ true
83
+ end
84
+ end
85
+ end