kelbim 0.0.1

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.
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