kelbim 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7eedb5e0c3b9983991baaaacd8233fe41d9ed137
4
+ data.tar.gz: 73b2b07df5e2219bfaaf81c9d0d39b9b56d363eb
5
+ SHA512:
6
+ metadata.gz: 6aa83ac6a4a98111e0f3816c15d200236256bbe6cff68c24087be96c11b27da1952e1b7f7e9dd66ecc91f2a73b3c4e43430c59ac1b87927cb87bcf5705f59f07
7
+ data.tar.gz: 430af7b5270717adddd3cd7452f15e392cb118c845b5db5930a9097c458740233d1d14583ba88affbd4d655562c1d01977b03d307fb003c3eb559b0e081ae160
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # Kelbim
2
+
3
+ Kelbim is a tool to manage ELB.
4
+
5
+ It defines the state of ELB using DSL, and updates ELB according to DSL.
6
+
7
+ **Attention! This is the alpha version!**
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'kelbim'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install kelbim
22
+
23
+ ## Usage
24
+
25
+ ```sh
26
+ export AWS_ACCESS_KEY_ID='...'
27
+ export AWS_SECRET_ACCESS_KEY='...'
28
+ export AWS_REGION='ap-northeast-1'
29
+ kelbim -e -o ELBfile # export EKB
30
+ vi ELB
31
+ kelbim -a --dry-run
32
+ kelbim -a # apply `ELBfile` to ELB
33
+ ```
34
+
35
+ ## ELBfile example
36
+
37
+ ```ruby
38
+ require 'other/elbfile'
39
+
40
+ # EC2 Classic
41
+ ec2 do
42
+ load_balancer "my-load-balancer" do
43
+ instances(
44
+ "cthulhu",
45
+ "nyar",
46
+ )
47
+
48
+ listeners do
49
+ listener [:http, 80] => [:http, 80]
50
+ end
51
+
52
+ health_check do
53
+ target "HTTP:80/index.html"
54
+ timeout 5
55
+ interval 30
56
+ healthy_threshold 10
57
+ unhealthy_threshold 2
58
+ end
59
+
60
+ availability_zones(
61
+ "ap-northeast-1a",
62
+ "ap-northeast-1b"
63
+ )
64
+ end
65
+ end
66
+
67
+ # EC2 VPC
68
+ ec2 "vpc-XXXXXXXXX" do
69
+ load_balancer "my-load-balancer", :internal => true do
70
+ instances(
71
+ "nyar",
72
+ "yog"
73
+ )
74
+
75
+ listeners do
76
+ listener [:tcp, 80] => [:tcp, 80]
77
+ listener [:https, 443] => [:http, 80] do
78
+ app_cookie_stickiness "CookieName"=>"20"
79
+ ssl_negotiation ["Protocol-TLSv1", "Protocol-SSLv3", "AES256-SHA", ...]
80
+ server_certificate "my-cert"
81
+ end
82
+ end
83
+
84
+ health_check do
85
+ target "TCP:80"
86
+ timeout 5
87
+ interval 30
88
+ healthy_threshold 10
89
+ unhealthy_threshold 2
90
+ end
91
+
92
+ subnets(
93
+ "subnet-XXXXXXXX"
94
+ )
95
+
96
+ security_groups(
97
+ "default"
98
+ )
99
+ end
100
+ end
101
+ ```
102
+
103
+ ## Link
104
+ * [RubyGems.org site](http://rubygems.org/gems/kelbim)
data/bin/kelbim ADDED
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path("#{File.dirname __FILE__}/../lib")
3
+ require 'rubygems'
4
+ require 'kelbim'
5
+ require 'optparse'
6
+
7
+ DEFAULT_FILENAME = 'ELBfile'
8
+
9
+ mode = nil
10
+ file = DEFAULT_FILENAME
11
+ output_file = '-'
12
+ split = false
13
+
14
+ options = {
15
+ :dry_run => false,
16
+ :color => true,
17
+ :debug => false,
18
+ }
19
+
20
+ ARGV.options do |opt|
21
+ begin
22
+ access_key = nil
23
+ secret_key = nil
24
+ region = nil
25
+
26
+ opt.on('-k', '--access-key ACCESS_KEY') {|v| access_key = v }
27
+ opt.on('-s', '--secret-key SECRET_KEY') {|v| secret_key = v }
28
+ opt.on('-r', '--region REGION') {|v| region = v }
29
+ opt.on('-a', '--apply') {|v| mode = :apply }
30
+ opt.on('-f', '--file FILE') {|v| file = v }
31
+ opt.on('', '--dry-run') {|v| options[:dry_run] = true }
32
+ opt.on('-e', '--export') {|v| mode = :export }
33
+ opt.on('-o', '--output FILE') {|v| output_file = v }
34
+ opt.on('', '--split') {|v| split = true }
35
+ opt.on('-t', '--test') {|v| mode = :test }
36
+ opt.on('' , '--no-color') { options[:color] = false }
37
+ opt.on('' , '--debug') { options[:debug] = true }
38
+ opt.parse!
39
+
40
+ if access_key and secret_key
41
+ aws_opts = {
42
+ :access_key_id => access_key,
43
+ :secret_access_key => secret_key,
44
+ }
45
+ aws_opts[:region] = region if region
46
+ AWS.config(aws_opts)
47
+ elsif (access_key and !secret_key) or (!access_key and secret_key) or mode.nil?
48
+ puts opt.help
49
+ exit 1
50
+ end
51
+ rescue => e
52
+ $stderr.puts("[ERROR] #{e.message}")
53
+ exit 1
54
+ end
55
+ end
56
+
57
+ String.colorize = options[:color]
58
+
59
+ if options[:debug]
60
+ AWS.config({
61
+ :http_wire_trace => true,
62
+ :logger => Kelbim::Logger.instance,
63
+ })
64
+ end
65
+
66
+ begin
67
+ logger = Kelbim::Logger.instance
68
+ logger.set_debug(options[:debug])
69
+ client = Kelbim::Client.new(options)
70
+
71
+ case mode
72
+ when :export
73
+ if split
74
+ logger.info('Export ELB')
75
+
76
+ output_file = DEFAULT_FILENAME if output_file == '-'
77
+ requires = []
78
+
79
+ client.export do |exported, converter|
80
+ exported.each do |vpc, elbs|
81
+ elb_file = File.join(File.dirname(output_file), "#{vpc || :classic}.elb")
82
+ requires << elb_file
83
+
84
+ logger.info(" write `#{elb_file}`")
85
+
86
+ open(elb_file, 'wb') do |f|
87
+ f.puts converter.call(vpc => elbs)
88
+ end
89
+ end
90
+ end
91
+
92
+ logger.info(" write `#{output_file}`")
93
+
94
+ open(output_file, 'wb') do |f|
95
+ requires.each do |elb_file|
96
+ f.puts "require '#{File.basename elb_file}'"
97
+ end
98
+ end
99
+ else
100
+ if output_file == '-'
101
+ logger.info('# Export ELB')
102
+ puts client.export
103
+ else
104
+ logger.info("Export ELB to `#{output_file}`")
105
+ open(output_file, 'wb') {|f| f.puts client.export }
106
+ end
107
+ end
108
+ when :apply
109
+ unless File.exist?(file)
110
+ raise "No ELBfile found (looking for: #{file})"
111
+ end
112
+
113
+ msg = "Apply `#{file}` to ELB"
114
+ msg << ' (dry-run)' if options[:dry_run]
115
+ logger.info(msg)
116
+
117
+ updated = client.apply(file)
118
+
119
+ logger.info('No change'.intense_blue) unless updated
120
+ when :test
121
+ unless File.exist?(file)
122
+ raise "No ELBfile found (looking for: #{file})"
123
+ end
124
+
125
+ logger.info("Test `#{file}`")
126
+ # XXX:
127
+ client.test(file)
128
+ else
129
+ raise 'must not happen'
130
+ end
131
+ rescue => e
132
+ if options[:debug]
133
+ raise e
134
+ else
135
+ $stderr.puts("[ERROR] #{e.message}".red)
136
+ exit 1
137
+ end
138
+ end
@@ -0,0 +1,217 @@
1
+ require 'aws-sdk'
2
+ require 'kelbim/dsl'
3
+ require 'kelbim/exporter'
4
+ require 'kelbim/ext/ec2-ext'
5
+ require 'kelbim/ext/elb-load-balancer-ext'
6
+ require 'kelbim/policy-types'
7
+ require 'kelbim/wrapper/elb-wrapper'
8
+ require 'kelbim/logger'
9
+
10
+ module Kelbim
11
+ class Client
12
+ include Logger::ClientHelper
13
+
14
+ def initialize(options = {})
15
+ @options = OpenStruct.new(options)
16
+ @options.elb = AWS::ELB.new
17
+ @options.ec2 = AWS::EC2.new
18
+ @options.iam = AWS::IAM.new
19
+ end
20
+
21
+ def apply(file)
22
+ AWS.memoize { walk(file) }
23
+ end
24
+
25
+ def test(file)
26
+ # XXX:
27
+ end
28
+
29
+ def export
30
+ exported = nil
31
+ instance_names = nil
32
+
33
+ AWS.memoize do
34
+ exported = Exporter.export(@options.elb)
35
+ instance_names = @options.ec2.instance_names
36
+ end
37
+
38
+ if block_given?
39
+ converter = proc do |src|
40
+ DSL.convert(src, instance_names)
41
+ end
42
+
43
+ yield(exported, converter)
44
+ else
45
+ DSL.convert(exported, instance_names)
46
+ end
47
+ end
48
+
49
+ private
50
+ def load_file(file)
51
+ if file.kind_of?(String)
52
+ open(file) do |f|
53
+ DSL.define(f.read, file).result
54
+ end
55
+ elsif file.respond_to?(:read)
56
+ DSL.define(file.read, file.path).result
57
+ else
58
+ raise TypeError, "can't convert #{file} into File"
59
+ end
60
+ end
61
+
62
+ def walk(file)
63
+ dsl = load_file(file)
64
+
65
+ dsl_ec2s = dsl.ec2s
66
+ elb = ELBWrapper.new(@options.elb, @options)
67
+
68
+ aws_ec2s = collect_to_hash(elb.load_balancers, :has_many => true) do |item|
69
+ item.vpc_id
70
+ end
71
+
72
+ dsl_ec2s.each do |vpc, ec2_dsl|
73
+ ec2_aws = aws_ec2s[vpc]
74
+
75
+ if ec2_aws
76
+ walk_ec2(vpc, ec2_dsl, ec2_aws, elb.load_balancers)
77
+ else
78
+ log(:warn, "EC2 `#{vpc || :classic}` is not found", :yellow)
79
+ end
80
+ end
81
+
82
+ elb.updated?
83
+ end
84
+
85
+ def walk_ec2(vpc, ec2_dsl, ec2_aws, collection_api)
86
+ lb_list_dsl = collect_to_hash(ec2_dsl.load_balancers, :name)
87
+ lb_list_aws = collect_to_hash(ec2_aws, :name)
88
+
89
+ lb_list_dsl.each do |key, lb_dsl|
90
+ name = key[0]
91
+ lb_aws = lb_list_aws[key]
92
+
93
+ unless lb_aws
94
+ lb_aws = collection_api.create(lb_dsl, vpc)
95
+ lb_list_aws[key] = lb_aws
96
+ end
97
+ end
98
+
99
+ lb_list_dsl.each do |key, lb_dsl|
100
+ lb_aws = lb_list_aws.delete(key)
101
+ walk_load_balancer(lb_dsl, lb_aws)
102
+ end
103
+
104
+ lb_list_aws.each do |key, lb_aws|
105
+ lb_aws.delete
106
+ end
107
+ end
108
+
109
+ def walk_load_balancer(load_balancer_dsl, load_balancer_aws)
110
+ unless load_balancer_aws.eql?(load_balancer_dsl)
111
+ load_balancer_aws.update(load_balancer_dsl)
112
+ end
113
+
114
+ lstnr_list_dsl = collect_to_hash(load_balancer_dsl.listeners, :port)
115
+ listeners = load_balancer_aws.listeners
116
+ lstnr_list_aws = collect_to_hash(listeners, :port)
117
+
118
+ walk_listeners(lstnr_list_dsl, lstnr_list_aws, listeners)
119
+ end
120
+
121
+ def walk_listeners(listeners_dsl, listeners_aws, collection_api)
122
+ listeners_dsl.each do |key, lstnr_dsl|
123
+ lstnr_aws = listeners_aws[key]
124
+
125
+ unless lstnr_aws
126
+ lstnr_aws = collection_api.create(lstnr_dsl)
127
+ listeners_aws[key] = lstnr_aws
128
+ end
129
+ end
130
+
131
+ listeners_dsl.each do |key, lstnr_dsl|
132
+ lstnr_aws = listeners_aws.delete(key)
133
+ walk_listener(lstnr_dsl, lstnr_aws, collection_api)
134
+ end
135
+
136
+ listeners_aws.each do |key, lstnr_aws|
137
+ lstnr_aws.delete
138
+ end
139
+ end
140
+
141
+ def walk_listener(listener_dsl, listener_aws, collection_api)
142
+ unless listener_aws.eql?(listener_dsl)
143
+ if listener_aws.has_difference_protocol_port?(listener_dsl)
144
+ listener_aws.delete
145
+ listener_aws = collection_api.create(listener_dsl)
146
+ else
147
+ listener_aws.update(listener_dsl)
148
+ end
149
+ end
150
+
151
+ plcy_list_dsl = collect_to_hash(listener_dsl.policies) do |policy_type, name_or_attrs|
152
+ [PolicyTypes.symbol_to_string(policy_type)]
153
+ end
154
+
155
+ policies = listener_aws.policies
156
+ plcy_list_aws = collect_to_hash(policies, :type)
157
+
158
+ walk_policies(listener_aws, plcy_list_dsl, plcy_list_aws, policies)
159
+ end
160
+
161
+ def walk_policies(listener, policies_dsl, policies_aws, collection_api)
162
+ orig_policy_names = policies_aws.map {|k, v| v.name }
163
+ new_policies = []
164
+ old_policies = []
165
+
166
+ policies_dsl.each do |key, plcy_dsl|
167
+ unless policies_aws[key]
168
+ new_policies << collection_api.create(plcy_dsl)
169
+ end
170
+ end
171
+
172
+ policies_dsl.each do |key, plcy_dsl|
173
+ plcy_aws = policies_aws.delete(key)
174
+ next unless plcy_aws
175
+
176
+ if plcy_aws.eql?(plcy_dsl)
177
+ new_policies << plcy_aws
178
+ else
179
+ new_policies << collection_api.create(plcy_dsl)
180
+ old_policies << plcy_aws
181
+ end
182
+ end
183
+
184
+ policies_aws.each do |key, plcy_aws|
185
+ old_policies << plcy_aws
186
+ end
187
+
188
+ if not new_policies.empty? and orig_policy_names.sort != new_policies.map {|i| i.name }.sort
189
+ listener.policies = new_policies
190
+
191
+ unless @options.without_deleting_policy
192
+ old_policies.each do |plcy_aws|
193
+ plcy_aws.delete
194
+ end
195
+ end
196
+ end
197
+ end
198
+
199
+ def collect_to_hash(collection, *key_attrs)
200
+ options = key_attrs.last.kind_of?(Hash) ? key_attrs.pop : {}
201
+ hash = {}
202
+
203
+ collection.each do |item|
204
+ key = block_given? ? yield(item) : key_attrs.map {|k| item.send(k) }
205
+
206
+ if options[:has_many]
207
+ hash[key] ||= []
208
+ hash[key] << item
209
+ else
210
+ hash[key] = item
211
+ end
212
+ end
213
+
214
+ return hash
215
+ end
216
+ end # Client
217
+ end # Kelbim
@@ -0,0 +1,65 @@
1
+ module Kelbim
2
+ class DSL
3
+ module Checker
4
+ private
5
+ def required(name, value)
6
+ invalid = false
7
+
8
+ if value
9
+ case value
10
+ when String
11
+ invalid = value.strip.empty?
12
+ when Array, Hash
13
+ invalid = value.empty?
14
+ end
15
+ else
16
+ invalid = true
17
+ end
18
+
19
+ raise __identify("`#{name}` is required") if invalid
20
+ end
21
+
22
+ def expected_type(value, *types)
23
+ unless types.any? {|t| value.kind_of?(t) }
24
+ raise __identify("Invalid type: #{value}")
25
+ end
26
+ end
27
+
28
+ def expected_length(value, length)
29
+ if value.length != length
30
+ raise __identify("Invalid length: #{value}")
31
+ end
32
+ end
33
+
34
+ def expected_value(value, *list)
35
+ unless list.any? {|i| value == i }
36
+ raise __identify("Invalid value: #{value}")
37
+ end
38
+ end
39
+
40
+ def not_include(value, list)
41
+ if list.include?(value)
42
+ raise __identify("`#{value}` is already included")
43
+ end
44
+ end
45
+
46
+ def call_once(method_name)
47
+ @called ||= []
48
+
49
+ if @called.include?(method_name)
50
+ raise __identify("`#{method_name}` is already defined")
51
+ end
52
+
53
+ @called << method_name
54
+ end
55
+
56
+ def __identify(errmsg)
57
+ if @error_identifier
58
+ errmsg = "#{@error_identifier}: #{errmsg}"
59
+ end
60
+
61
+ return errmsg
62
+ end
63
+ end # Checker
64
+ end # DSL
65
+ end # Kelbim
@@ -0,0 +1,177 @@
1
+ require 'kelbim/policy-types'
2
+
3
+ module Kelbim
4
+ class DSL
5
+ class Converter
6
+ class << self
7
+ def convert(exported, instance_names)
8
+ self.new(exported, instance_names).convert
9
+ end
10
+ end # of class methods
11
+
12
+ def initialize(exported, instance_names)
13
+ @exported = exported
14
+ @instance_names = instance_names
15
+ end
16
+
17
+ def convert
18
+ @exported.each.map {|vpc, load_balancers|
19
+ output_ec2(vpc, load_balancers)
20
+ }.join("\n")
21
+ end
22
+
23
+ private
24
+ def output_ec2(vpc, load_balancers)
25
+ arg_vpc = vpc ? vpc.inspect + ' ' : ''
26
+ load_balancers = load_balancers.map {|name, load_balancer|
27
+ output_load_balancer(vpc, name, load_balancer)
28
+ }.join("\n").strip
29
+
30
+ <<-EOS
31
+ ec2 #{arg_vpc}do
32
+ #{load_balancers}
33
+ end
34
+ EOS
35
+ end
36
+
37
+ def output_load_balancer(vpc, name, load_balancer)
38
+ name = name.inspect
39
+ internal = (load_balancer[:scheme] == 'internal') ? ', :internal => true ' : ' '
40
+ instances = output_instances(load_balancer[:instances], vpc).strip
41
+ listeners = output_listeners(load_balancer[:listeners]).strip
42
+ health_check = output_health_check(load_balancer[:health_check]).strip
43
+ dns_name = load_balancer[:dns_name]
44
+
45
+ out = <<-EOS
46
+ load_balancer #{name}#{internal}do
47
+ #test do
48
+ # #host = #{dns_name.inspect}
49
+ # #expect(Net::HTTP.start(host, 80).get("/")).to be_a(Net::HTTPOK)
50
+ #end
51
+
52
+ #{instances}
53
+
54
+ #{listeners}
55
+
56
+ #{health_check}
57
+ EOS
58
+
59
+ if vpc
60
+ subnets = load_balancer[:subnets]
61
+ subnets = subnets.map {|i| i.inspect }.join(",\n ")
62
+
63
+ out << "\n"
64
+ out.concat(<<-EOS)
65
+ subnets(
66
+ #{subnets}
67
+ )
68
+ EOS
69
+
70
+ security_groups = load_balancer[:security_groups]
71
+ security_groups = security_groups.map {|i| i.inspect }.join(",\n ")
72
+
73
+ out << "\n"
74
+ out.concat(<<-EOS)
75
+ security_groups(
76
+ #{security_groups}
77
+ )
78
+ EOS
79
+ else
80
+ availability_zones = load_balancer[:availability_zones]
81
+ availability_zones = availability_zones.map {|i| i.inspect }.join(",\n ")
82
+
83
+ out << "\n"
84
+ out.concat(<<-EOS)
85
+ availability_zones(
86
+ #{availability_zones}
87
+ )
88
+ EOS
89
+ end
90
+
91
+ out << " end\n"
92
+ return out
93
+ end
94
+
95
+ def output_instances(instances, vpc)
96
+ if instances.empty?
97
+ instances = '# not registered'
98
+ else
99
+ instance_id_names = @instance_names[vpc] || {}
100
+
101
+ instances = instances.map {|instance_id|
102
+ instance_id_names.fetch(instance_id, instance_id).inspect
103
+ }.join(",\n ")
104
+ end
105
+
106
+ <<-EOS
107
+ instances(
108
+ #{instances}
109
+ )
110
+ EOS
111
+ end
112
+
113
+ def output_listeners(listeners)
114
+ items = listeners.map {|listener|
115
+ output_listener(listener).strip
116
+ }.join("\n ")
117
+
118
+ <<-EOS
119
+ listeners do
120
+ #{items}
121
+ end
122
+ EOS
123
+ end
124
+
125
+ def output_listener(listener)
126
+ protocol = listener[:protocol].inspect
127
+ port = listener[:port]
128
+ instance_protocol = listener[:instance_protocol].inspect
129
+ instance_port = listener[:instance_port]
130
+ policies = listener[:policies]
131
+ server_certificate = listener[:server_certificate]
132
+
133
+ out = "listener [#{protocol}, #{port}] => [#{instance_protocol}, #{instance_port}]"
134
+
135
+ if policies.empty? and server_certificate.nil?
136
+ return out
137
+ end
138
+
139
+ out << " do\n"
140
+
141
+ unless policies.empty?
142
+ policies_dsl = policies.map {|policy|
143
+ PolicyTypes.convert_to_dsl(policy)
144
+ }.join("\n ")
145
+
146
+ out << " #{policies_dsl}\n"
147
+ end
148
+
149
+ if server_certificate
150
+ out << " server_certificate #{server_certificate.name.inspect}\n"
151
+ end
152
+
153
+ out << " end"
154
+
155
+ return out
156
+ end
157
+
158
+ def output_health_check(health_check)
159
+ target = health_check[:target].inspect
160
+ timeout = health_check[:timeout]
161
+ interval = health_check[:interval]
162
+ healthy_threshold = health_check[:healthy_threshold]
163
+ unhealthy_threshold = health_check[:unhealthy_threshold]
164
+
165
+ <<-EOS
166
+ health_check do
167
+ target #{target}
168
+ timeout #{timeout}
169
+ interval #{interval}
170
+ healthy_threshold #{healthy_threshold}
171
+ unhealthy_threshold #{unhealthy_threshold}
172
+ end
173
+ EOS
174
+ end
175
+ end # Converter
176
+ end # DSL
177
+ end # Kelbim