applb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ require 'erb'
2
+
3
+ module Applb
4
+ class Converter
5
+ def initialize(lbs_by_vpc_id, tags_by_arn)
6
+ @lbs_by_vpc_id = lbs_by_vpc_id
7
+ @tags_by_arn = tags_by_arn
8
+ end
9
+
10
+ def convert
11
+ @lbs_by_vpc_id.each do |vpc_id, lbs_by_name|
12
+ yield vpc_id, output_alb(vpc_id, @tags_by_arn, lbs_by_name)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def output_alb(vpc_id, tags_by_arn, lbs_by_name)
19
+ ERB.new(File.read('lib/applb/output_alb.erb'), nil, '-').result(binding)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,56 @@
1
+ require 'hashie'
2
+ require 'applb/template_helper'
3
+ require 'applb/dsl/ec2'
4
+
5
+ module Applb
6
+ class DSL
7
+ include Applb::TemplateHelper
8
+
9
+ class << self
10
+ def define(source, filepath, options)
11
+ self.new(filepath, options) do
12
+ eval(source, binding, filepath)
13
+ end
14
+ end
15
+ end
16
+
17
+ attr_reader :result
18
+
19
+ def initialize(filepath, options,&block)
20
+ @filepath = filepath
21
+ @result = OpenStruct.new(ec2s: {})
22
+
23
+ @context = Hashie::Mash.new(
24
+ filepath: filepath,
25
+ templates: {},
26
+ options: options,
27
+ )
28
+
29
+ instance_eval(&block)
30
+ end
31
+
32
+ def require(file)
33
+ albfile = (file =~ %r|\A/|) ? file : File.expand_path(File.join(File.dirname(@path), file))
34
+
35
+ if File.exist?(albfile)
36
+ instance_eval(File.read(albfile), albfile)
37
+ elsif File.exist?("#{albfile}.rb")
38
+ instance_eval(File.read("#{albfile}.rb"), "#{albfile}.rb")
39
+ else
40
+ Kernel.require(file)
41
+ end
42
+ end
43
+
44
+ def template(name, &block)
45
+ @context.templates[name.to_s] = block
46
+ end
47
+
48
+ def ec2(vpc_id, &block)
49
+ if ec2_result = @result.ec2s[vpc_id]
50
+ @result.ec2s[vpc_id] = EC2.new(@context, vpc_id, ec2_result.load_balancers, &block).result
51
+ else
52
+ @result.ec2s[vpc_id] = EC2.new(@context, vpc_id, [], &block).result
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,49 @@
1
+ module Applb
2
+ class DSL
3
+ class EC2
4
+ class LoadBalancer
5
+ class Attributes
6
+ include Applb::DSL::Checker
7
+ include Applb::TemplateHelper
8
+
9
+ attr_reader :result
10
+
11
+ def initialize(context, lb_name, &block)
12
+ @context = context.dup
13
+ @lb_name = lb_name
14
+
15
+ @result = {
16
+ 'access_logs.s3.enabled' => false,
17
+ 'access_logs.s3.bucket' => '',
18
+ 'access_logs.s3.prefix' => '',
19
+ 'idle_timeout.timeout_seconds' => 60,
20
+ 'deletion_protection.enabled' => false,
21
+ }
22
+
23
+ instance_eval(&block)
24
+ end
25
+
26
+ def result
27
+ @result.map { |k, v| {key: k, value: v} }
28
+ end
29
+
30
+ private
31
+
32
+ def access_logs(args)
33
+ @result['access_logs.s3.enabled'] = args[:s3_enabled] if args[:s3_enabled]
34
+ @result['access_logs.s3.bucket'] = args[:s3_bucket] if args[:s3_bucket]
35
+ @result['access_logs.s3.prefix'] = args[:s3_prefix] if args[:s3_prefix]
36
+ end
37
+
38
+ def idle_timeout(timeout_seconds:)
39
+ @result['idle_timeout.timeout_seconds'] = timeout_seconds if timeout_seconds
40
+ end
41
+
42
+ def deletion_protection(enabled:)
43
+ @result['deletion_protection.enabled'] = enabled if enabled
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,27 @@
1
+ require 'applb/error'
2
+
3
+ module Applb
4
+ class DSL
5
+ module Checker
6
+ private
7
+
8
+ class ValidationError < Error
9
+ end
10
+
11
+ def required(name, value)
12
+ if value
13
+ case value
14
+ when String
15
+ invalid = value.strip.empty?
16
+ when Array, Hash
17
+ invalid = value.empty?
18
+ end
19
+ else
20
+ invalid = true
21
+ end
22
+
23
+ raise ValidationError.new("`#{name}' is required") if invalid
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ require 'ostruct'
2
+ require 'applb/dsl/load_balancer'
3
+
4
+ module Applb
5
+ class DSL
6
+ class EC2
7
+ include Applb::TemplateHelper
8
+
9
+ attr_reader :result
10
+
11
+ def initialize(context, vpc_id, lbs, &block)
12
+ @context = context.merge(vpc_id: vpc_id)
13
+
14
+ @result = OpenStruct.new({
15
+ vpc_id: vpc_id,
16
+ load_balancers: lbs,
17
+ })
18
+
19
+ @names = lbs.map(&:name)
20
+ instance_eval(&block)
21
+ end
22
+
23
+ private
24
+
25
+ def elb_v2(name, &block)
26
+ if @names.include?(name)
27
+ raise "#{@result.vpc_id}: #{name} is already defined"
28
+ end
29
+
30
+ @result.load_balancers << LoadBalancer.new(@context, name, @result.vpc_id, &block).result
31
+ @names << name
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,140 @@
1
+ require 'applb/dsl/rules'
2
+ require 'applb/utils'
3
+ require 'applb/client_wrapper'
4
+
5
+ module Applb
6
+ class DSL
7
+ class EC2
8
+ class LoadBalancer
9
+ class Listeners
10
+ class Listener
11
+ include Checker
12
+ include Applb::TemplateHelper
13
+
14
+ class Result
15
+ ATTRIBUTES = %i/certificates ssl_policy port protocol default_actions rules load_balancer_arn/
16
+ attr_accessor *ATTRIBUTES
17
+
18
+ def initialize(context, lb_name)
19
+ @context = context
20
+ @options = context.options
21
+ @lb_name = lb_name
22
+ end
23
+
24
+ def to_h
25
+ Hash[ATTRIBUTES.sort.map { |name| [name, public_send(name)] }]
26
+ end
27
+
28
+ def aws(aws_listener)
29
+ @aws_listener = aws_listener
30
+ self
31
+ end
32
+
33
+ def create
34
+ Applb.logger.info("#{@lb_name} Create listener for port #{port}")
35
+ return if @options[:dry_run]
36
+ client.create_listener(create_option).listeners.first
37
+ end
38
+
39
+ def modify
40
+ dsl_hash = to_diff_h
41
+ aws_hash = to_diff_h_aws
42
+ return if dsl_hash == aws_hash
43
+
44
+ Applb.logger.info("#{@lb_name} Modify listener for port #{port}")
45
+ Applb.logger.info("<diff>\n#{Applb::Utils.diff(aws_hash, dsl_hash, color: @options[:color])}")
46
+ return if @options[:dry_run]
47
+
48
+ client.modify_listener(modify_option).listeners.first
49
+ end
50
+
51
+ private
52
+
53
+ def to_diff_h
54
+ options = Applb::Utils.normalize_hash(to_h)
55
+ target_group_name = options[:default_actions].first.delete(:target_group_name)
56
+ if options[:ssl_policy] && options[:ssl_policy].empty?
57
+ options.delete(:certificates)
58
+ options.delete(:ssl_policy)
59
+ end
60
+ options.reject! { |k, v| %i/listener_arn load_balancer_arn rules/.include?(k) }
61
+ end
62
+
63
+ def to_diff_h_aws
64
+ options = Applb::Utils.normalize_hash(@aws_listener.to_h)
65
+ if options[:ssl_policy] && options[:ssl_policy].empty?
66
+ options.delete(:certificates)
67
+ options.delete(:ssl_policy)
68
+ end
69
+ options.reject! { |k, v| %i/listener_arn load_balancer_arn rules/.include?(k) }
70
+ end
71
+
72
+ def create_option
73
+ options = to_h.reject { |k, _| %i/policy_name rules/.include?(k) }
74
+ options[:default_actions].first.delete(:target_group_name)
75
+ options
76
+ end
77
+
78
+ def modify_option
79
+ to_diff_h.merge(listener_arn: @aws_listener.listener_arn)
80
+ end
81
+
82
+ def client
83
+ @client ||= Applb::ClientWrapper.new(@options)
84
+ end
85
+ end
86
+
87
+ attr_reader :result
88
+
89
+ def initialize(context, lb_name, &block)
90
+ @context = context.dup
91
+ @lb_name = lb_name
92
+
93
+ @result = Result.new(@context, @lb_name)
94
+
95
+ instance_eval(&block)
96
+ end
97
+
98
+ private
99
+
100
+ def certificates(certificate_arn:)
101
+ @result.certificates ||= []
102
+ @result.certificates << {certificate_arn: certificate_arn}
103
+ end
104
+
105
+ def ssl_policy(ssl_policy)
106
+ @result.ssl_policy = ssl_policy if ssl_policy
107
+ end
108
+
109
+ def port(port)
110
+ @result.port = port
111
+ end
112
+
113
+ def protocol(protocol)
114
+ @result.protocol = protocol
115
+ end
116
+
117
+ def default_actions(target_group_name: nil, target_group_arn: nil, type:)
118
+ unless target_group_name || target_group_arn
119
+ raise "target_group_name or target_group_arn is required"
120
+ end
121
+ @result.default_actions ||= []
122
+ @result.default_actions << {
123
+ target_group_arn: target_group_arn,
124
+ target_group_name: target_group_name,
125
+ type: type,
126
+ }
127
+ end
128
+
129
+ def rules(&block)
130
+ rules = Rules.new(@context, self, &block).result
131
+ unless rules.empty?
132
+ @result.rules = rules
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,29 @@
1
+ require 'applb/dsl/listener'
2
+
3
+ module Applb
4
+ class DSL
5
+ class EC2
6
+ class LoadBalancer
7
+ class Listeners
8
+ include Applb::TemplateHelper
9
+
10
+ attr_reader :result
11
+
12
+ def initialize(context, lb_name, &block)
13
+ @context = context.dup
14
+ @lb_name = lb_name
15
+ @result = []
16
+
17
+ instance_eval(&block)
18
+ end
19
+
20
+ private
21
+
22
+ def listener(&block)
23
+ @result << Listener.new(@context, @lb_name, &block).result
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,209 @@
1
+ require 'applb/dsl/checker'
2
+ require 'applb/dsl/attributes'
3
+ require 'applb/dsl/listeners'
4
+ require 'applb/dsl/target_groups'
5
+
6
+ module Applb
7
+ class DSL
8
+ class EC2
9
+ class LoadBalancer
10
+ include Applb::DSL::Checker
11
+ include Applb::TemplateHelper
12
+
13
+ class Result
14
+ ATTRIBUTES = %i/name instances scheme subnets security_groups tags ip_address_type attributes target_groups listeners load_balancer_arn/
15
+ attr_accessor *ATTRIBUTES
16
+
17
+ def initialize(context)
18
+ @context = context
19
+ @options = context.options
20
+ end
21
+
22
+ def to_h
23
+ Hash[ATTRIBUTES.sort.map { |name| [name, public_send(name)] }]
24
+ end
25
+
26
+ CREATE_KEYS = %i/name subnets security_groups scheme tags ip_address_type/
27
+ def create_option
28
+ to_h.select { |k, _| CREATE_KEYS.include?(k) }
29
+ end
30
+
31
+ def create
32
+ Applb.logger.info "Create ELB v2 #{name}"
33
+ return if @options[:dry_run]
34
+
35
+ client.create_load_balancer(create_option).load_balancers.first
36
+ end
37
+
38
+ def aws(aws_lb)
39
+ @aws_lb = aws_lb
40
+ self
41
+ end
42
+
43
+ def subnets_updated?
44
+ subnets.sort != @aws_lb.availability_zones.map(&:subnet_id).sort
45
+ end
46
+
47
+ def security_groups_updated?
48
+ security_groups.sort != @aws_lb.security_groups.sort
49
+ end
50
+
51
+ def ip_address_type_updated?
52
+ ip_address_type != @aws_lb.ip_address_type
53
+ end
54
+
55
+ def modify_subnets
56
+ return unless subnets_updated?
57
+
58
+ Applb.logger.info("Modify #{name} subnets")
59
+ diff = Applb::Utils.diff(
60
+ @aws_lb.availability_zones.map(&:subnet_id).sort,
61
+ subnets.sort,
62
+ color: @options[:color],
63
+ )
64
+ Applb.logger.info("<diff>\n#{diff}")
65
+ return if @options[:dry_run]
66
+
67
+ client.set_subnets(
68
+ load_balancer_arn: @aws_lb.load_balancer_arn,
69
+ subnets: subnets,
70
+ ).availability_zones
71
+ end
72
+
73
+ def modify_security_groups
74
+ return unless security_groups_updated?
75
+
76
+ Applb.logger.info "Modify #{name} security_groups"
77
+ diff = Applb::Utils.diff(
78
+ @aws_lb.security_groups.sort,
79
+ security_groups.sort,
80
+ color: @options[:color],
81
+ )
82
+ Applb.logger.info("<diff>\n#{diff}")
83
+ return if @options[:dry_run]
84
+
85
+ client.set_security_groups(
86
+ load_balancer_arn: @aws_lb.load_balancer_arn,
87
+ security_groups: security_groups,
88
+ ).security_group_ids
89
+ end
90
+
91
+ def modify_ip_address_type
92
+ return unless ip_address_type_updated?
93
+
94
+ Applb.logger.info "Modify #{name} ip_address_type"
95
+ diff = Applb::Utils.diff(
96
+ @aws_lb.ip_address_type,
97
+ ip_address_type,
98
+ color: @options[:color],
99
+ )
100
+ Applb.logger.info("<diff>\n#{diff}")
101
+
102
+ return if @options[:dry_run]
103
+ client.set_ip_address_type(
104
+ load_balancer_arn: @aws_lb.load_balancer_arn,
105
+ ip_address_type: ip_address_type,
106
+ ).ip_address_type
107
+ end
108
+
109
+ def modify_load_balancer_attributes
110
+ attrs = attributes.map do |attr|
111
+ {key: attr[:key], value: attr[:value].to_s}
112
+ end
113
+ log_enabled = attrs.find { |attr| attr[:key] == 'access_logs.s3.enabled' }[:value]
114
+ if log_enabled.to_s == 'false'
115
+ attrs.reject! do |attr|
116
+ %w/access_logs.s3.bucket access_logs.s3.prefix/.include?(attr[:key])
117
+ end
118
+ end
119
+ dsl_hash = attrs.map { |a| a.to_h }.sort { |a, b| a[:key] <=> b[:key] }
120
+
121
+ aws_attributes = client.describe_load_balancer_attributes(
122
+ load_balancer_arn: @aws_lb.load_balancer_arn,
123
+ ).attributes
124
+ aws_hash = aws_attributes.map { |a| a.to_h }.sort { |a, b| a[:key] <=> b[:key] }
125
+ aws_log_enabled = aws_attributes.find { |attr| attr[:key] == 'access_logs.s3.enabled' }[:value]
126
+ if aws_log_enabled == 'false'
127
+ aws_hash.reject! do |attr|
128
+ %w/access_logs.s3.bucket access_logs.s3.prefix/.include?(attr[:key])
129
+ end
130
+ end
131
+
132
+ return if dsl_hash == aws_hash
133
+
134
+ Applb.logger.info "Modify #{name} load_balancer_attributes"
135
+ Applb.logger.info("<diff>\n#{Applb::Utils.diff(aws_hash, dsl_hash, color: @options[:color])}")
136
+ return if @options[:dry_run]
137
+
138
+ client.modify_load_balancer_attributes(
139
+ load_balancer_arn: @aws_lb.load_balancer_arn,
140
+ attributes: attrs,
141
+ ).attributes
142
+ end
143
+
144
+ private
145
+
146
+ def client
147
+ @client ||= Applb::ClientWrapper.new(@options)
148
+ end
149
+ end
150
+
151
+ def initialize(context, name, vpc_id, &block)
152
+ @name = name
153
+ @vpc_id = vpc_id
154
+ @context = context.merge(name: name)
155
+
156
+ @result = Result.new(@context)
157
+ @result.name = name
158
+ @result.attributes = Attributes.new(@context, @name) {}.result
159
+ @result.instances = []
160
+ @result.target_groups = []
161
+ @result.listeners = []
162
+
163
+ instance_eval(&block)
164
+ end
165
+
166
+ def result
167
+ required(:subnets, @result.subnets)
168
+ required(:security_groups, @result.security_groups)
169
+
170
+ @result
171
+ end
172
+
173
+ private
174
+
175
+ def subnets(*subnets)
176
+ @result.subnets = subnets
177
+ end
178
+
179
+ def security_groups(*security_groups)
180
+ @result.security_groups = security_groups
181
+ end
182
+
183
+ def scheme(scheme)
184
+ @result.scheme = scheme
185
+ end
186
+
187
+ def tags(tags)
188
+ @result.tags = tags.map { |k, v| { key: k, value: v } }
189
+ end
190
+
191
+ def ip_address_type(ip_address_type)
192
+ @result.ip_address_type = ip_address_type
193
+ end
194
+
195
+ def attributes(&block)
196
+ @result.attributes = Attributes.new(@context, @name, &block).result
197
+ end
198
+
199
+ def target_groups(&block)
200
+ @result.target_groups = TargetGroups.new(@context, @name, &block).result
201
+ end
202
+
203
+ def listeners(&block)
204
+ @result.listeners = Listeners.new(@context, @name, &block).result
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end