rubber 2.3.1 → 2.4.0
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 +4 -4
- data/CHANGELOG +15 -0
- data/lib/rubber/cloud.rb +1 -1
- data/lib/rubber/cloud/aws.rb +376 -10
- data/lib/rubber/cloud/base.rb +152 -0
- data/lib/rubber/cloud/digital_ocean.rb +96 -0
- data/lib/rubber/cloud/fog.rb +8 -200
- data/lib/rubber/cloud/generic.rb +68 -0
- data/lib/rubber/instance.rb +6 -1
- data/lib/rubber/recipes/rubber/deploy.rb +16 -14
- data/lib/rubber/recipes/rubber/instances.rb +21 -14
- data/lib/rubber/recipes/rubber/security_groups.rb +4 -187
- data/lib/rubber/recipes/rubber/setup.rb +25 -1
- data/lib/rubber/recipes/rubber/utils.rb +7 -4
- data/lib/rubber/util.rb +4 -0
- data/lib/rubber/version.rb +1 -1
- data/templates/base/config/deploy.rb +11 -5
- data/templates/base/config/rubber/rubber.yml +15 -2
- data/templates/postgresql/config/rubber/role/postgresql/pg_hba.conf +4 -2
- data/test/cloud/aws_test.rb +1 -1
- data/test/cloud/digital_ocean_test.rb +70 -0
- data/test/cloud/fog_test.rb +4 -2
- data/test/fixtures/basic/test.pem.pub +1 -0
- data/test/util_test.rb +8 -0
- metadata +7 -3
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'rubber/cloud/fog'
|
2
|
+
|
3
|
+
module Rubber
|
4
|
+
module Cloud
|
5
|
+
|
6
|
+
class DigitalOcean < Fog
|
7
|
+
|
8
|
+
def initialize(env, capistrano)
|
9
|
+
compute_credentials = {
|
10
|
+
:provider => 'DigitalOcean',
|
11
|
+
:digitalocean_api_key => env.api_key,
|
12
|
+
:digitalocean_client_id => env.client_key
|
13
|
+
}
|
14
|
+
|
15
|
+
if env.cloud_providers && env.cloud_providers.aws
|
16
|
+
storage_credentials = {
|
17
|
+
:provider => 'AWS',
|
18
|
+
:aws_access_key_id => env.cloud_providers.aws.access_key,
|
19
|
+
:aws_secret_access_key => env.cloud_providers.aws.secret_access_key
|
20
|
+
}
|
21
|
+
|
22
|
+
env['storage_credentials'] = storage_credentials
|
23
|
+
end
|
24
|
+
|
25
|
+
env['compute_credentials'] = compute_credentials
|
26
|
+
super(env, capistrano)
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_instance(instance_alias, image_name, image_type, security_groups, availability_zone, region)
|
30
|
+
do_region = compute_provider.regions.find { |r| r.name == region }
|
31
|
+
if do_region.nil?
|
32
|
+
raise "Invalid region for DigitalOcean: #{region}"
|
33
|
+
end
|
34
|
+
|
35
|
+
image = compute_provider.images.find { |i| i.name == image_name }
|
36
|
+
if image.nil?
|
37
|
+
raise "Invalid image name for DigitalOcean: #{image_name}"
|
38
|
+
end
|
39
|
+
|
40
|
+
flavor = compute_provider.flavors.find { |f| f.name == image_type }
|
41
|
+
if flavor.nil?
|
42
|
+
raise "Invalid image type for DigitalOcean: #{image_type}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Check if the SSH key has been added to DigitalOcean yet.
|
46
|
+
# TODO (nirvdrum 03/23/13): DigitalOcean has an API for getting a single SSH key, but it hasn't been added to fog yet. We should add it.
|
47
|
+
ssh_key = compute_provider.list_ssh_keys.body['ssh_keys'].find { |key| key['name'] == env.key_name }
|
48
|
+
if ssh_key.nil?
|
49
|
+
if env.key_file
|
50
|
+
ssh_key = compute_provider.create_ssh_key(env.key_name, File.read("#{env.key_file}.pub"))
|
51
|
+
else
|
52
|
+
raise 'Missing key_file for DigitalOcean'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
response = compute_provider.servers.create(:name => "#{Rubber.env}-#{instance_alias}",
|
57
|
+
:image_id => image.id,
|
58
|
+
:flavor_id => flavor.id,
|
59
|
+
:region_id => do_region.id,
|
60
|
+
:ssh_key_ids => [ssh_key['id']])
|
61
|
+
|
62
|
+
response.id
|
63
|
+
end
|
64
|
+
|
65
|
+
def describe_instances(instance_id=nil)
|
66
|
+
instances = []
|
67
|
+
opts = {}
|
68
|
+
|
69
|
+
if instance_id
|
70
|
+
response = [compute_provider.servers.get(instance_id)]
|
71
|
+
else
|
72
|
+
response = compute_provider.servers.all(opts)
|
73
|
+
end
|
74
|
+
|
75
|
+
response.each do |item|
|
76
|
+
instance = {}
|
77
|
+
instance[:id] = item.id
|
78
|
+
instance[:state] = item.state
|
79
|
+
instance[:type] = item.flavor_id
|
80
|
+
instance[:external_ip] = item.ip_address
|
81
|
+
instance[:internal_ip] = item.ip_address
|
82
|
+
instance[:region_id] = item.region_id
|
83
|
+
instance[:provider] = 'digital_ocean'
|
84
|
+
instance[:platform] = 'linux'
|
85
|
+
instances << instance
|
86
|
+
end
|
87
|
+
|
88
|
+
return instances
|
89
|
+
end
|
90
|
+
|
91
|
+
def active_state
|
92
|
+
'active'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/rubber/cloud/fog.rb
CHANGED
@@ -10,9 +10,12 @@ module Rubber
|
|
10
10
|
|
11
11
|
def initialize(env, capistrano)
|
12
12
|
super(env, capistrano)
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
|
14
|
+
compute_credentials = Rubber::Util.symbolize_keys(env.compute_credentials)
|
15
|
+
storage_credentials = Rubber::Util.symbolize_keys(env.storage_credentials) if env.storage_credentials
|
16
|
+
|
17
|
+
@compute_provider = ::Fog::Compute.new(compute_credentials)
|
18
|
+
@storage_provider = storage_credentials ? ::Fog::Storage.new(storage_credentials) : nil
|
16
19
|
end
|
17
20
|
|
18
21
|
def storage(bucket)
|
@@ -23,71 +26,16 @@ module Rubber
|
|
23
26
|
raise NotImplementedError, "No table store available for generic fog adapter"
|
24
27
|
end
|
25
28
|
|
26
|
-
def create_instance(ami, ami_type, security_groups, availability_zone)
|
29
|
+
def create_instance(instance_alias, ami, ami_type, security_groups, availability_zone, region)
|
27
30
|
response = @compute_provider.servers.create(:image_id => ami,
|
28
31
|
:flavor_id => ami_type,
|
29
32
|
:groups => security_groups,
|
30
33
|
:availability_zone => availability_zone,
|
31
34
|
:key_name => env.key_name)
|
32
|
-
instance_id = response.id
|
33
|
-
return instance_id
|
34
|
-
end
|
35
|
-
|
36
|
-
def create_spot_instance_request(spot_price, ami, ami_type, security_groups, availability_zone)
|
37
|
-
response = @compute_provider.spot_requests.create(:price => spot_price,
|
38
|
-
:image_id => ami,
|
39
|
-
:flavor_id => ami_type,
|
40
|
-
:groups => security_groups,
|
41
|
-
:availability_zone => availability_zone,
|
42
|
-
:key_name => env.key_name)
|
43
|
-
request_id = response.id
|
44
|
-
return request_id
|
45
|
-
end
|
46
|
-
|
47
|
-
def describe_instances(instance_id=nil)
|
48
|
-
instances = []
|
49
|
-
opts = {}
|
50
|
-
opts["instance-id"] = instance_id if instance_id
|
51
|
-
|
52
|
-
response = @compute_provider.servers.all(opts)
|
53
|
-
response.each do |item|
|
54
|
-
instance = {}
|
55
|
-
instance[:id] = item.id
|
56
|
-
instance[:type] = item.flavor_id
|
57
|
-
instance[:external_host] = item.dns_name
|
58
|
-
instance[:external_ip] = item.public_ip_address
|
59
|
-
instance[:internal_host] = item.private_dns_name
|
60
|
-
instance[:internal_ip] = item.private_ip_address
|
61
|
-
instance[:state] = item.state
|
62
|
-
instance[:zone] = item.availability_zone
|
63
|
-
instance[:platform] = item.platform || 'linux'
|
64
|
-
instance[:root_device_type] = item.root_device_type
|
65
|
-
instances << instance
|
66
|
-
end
|
67
|
-
|
68
|
-
return instances
|
69
|
-
end
|
70
35
|
|
71
|
-
|
72
|
-
requests = []
|
73
|
-
opts = {}
|
74
|
-
opts["spot-instance-request-id"] = request_id if request_id
|
75
|
-
response = @compute_provider.spot_requests.all(opts)
|
76
|
-
response.each do |item|
|
77
|
-
request = {}
|
78
|
-
request[:id] = item.id
|
79
|
-
request[:spot_price] = item.price
|
80
|
-
request[:state] = item.state
|
81
|
-
request[:created_at] = item.created_at
|
82
|
-
request[:type] = item.flavor_id
|
83
|
-
request[:image_id] = item.image_id
|
84
|
-
request[:instance_id] = item.instance_id
|
85
|
-
requests << request
|
86
|
-
end
|
87
|
-
return requests
|
36
|
+
response.id
|
88
37
|
end
|
89
38
|
|
90
|
-
|
91
39
|
def destroy_instance(instance_id)
|
92
40
|
response = @compute_provider.servers.get(instance_id).destroy()
|
93
41
|
end
|
@@ -109,96 +57,6 @@ module Rubber
|
|
109
57
|
response = @compute_provider.servers.get(instance_id).start()
|
110
58
|
end
|
111
59
|
|
112
|
-
def describe_availability_zones
|
113
|
-
zones = []
|
114
|
-
response = @compute_provider.describe_availability_zones()
|
115
|
-
items = response.body["availabilityZoneInfo"]
|
116
|
-
items.each do |item|
|
117
|
-
zone = {}
|
118
|
-
zone[:name] = item["zoneName"]
|
119
|
-
zone[:state] =item["zoneState"]
|
120
|
-
zones << zone
|
121
|
-
end
|
122
|
-
return zones
|
123
|
-
end
|
124
|
-
|
125
|
-
def create_security_group(group_name, group_description)
|
126
|
-
@compute_provider.security_groups.create(:name => group_name, :description => group_description)
|
127
|
-
end
|
128
|
-
|
129
|
-
def describe_security_groups(group_name=nil)
|
130
|
-
groups = []
|
131
|
-
|
132
|
-
opts = {}
|
133
|
-
opts["group-name"] = group_name if group_name
|
134
|
-
response = @compute_provider.security_groups.all(opts)
|
135
|
-
|
136
|
-
response.each do |item|
|
137
|
-
group = {}
|
138
|
-
group[:name] = item.name
|
139
|
-
group[:description] = item.description
|
140
|
-
|
141
|
-
item.ip_permissions.each do |ip_item|
|
142
|
-
group[:permissions] ||= []
|
143
|
-
rule = {}
|
144
|
-
|
145
|
-
rule[:protocol] = ip_item["ipProtocol"]
|
146
|
-
rule[:from_port] = ip_item["fromPort"]
|
147
|
-
rule[:to_port] = ip_item["toPort"]
|
148
|
-
|
149
|
-
ip_item["groups"].each do |rule_group|
|
150
|
-
rule[:source_groups] ||= []
|
151
|
-
source_group = {}
|
152
|
-
source_group[:account] = rule_group["userId"]
|
153
|
-
source_group[:name] = rule_group["groupName"]
|
154
|
-
rule[:source_groups] << source_group
|
155
|
-
end if ip_item["groups"]
|
156
|
-
|
157
|
-
ip_item["ipRanges"].each do |ip_range|
|
158
|
-
rule[:source_ips] ||= []
|
159
|
-
rule[:source_ips] << ip_range["cidrIp"]
|
160
|
-
end if ip_item["ipRanges"]
|
161
|
-
|
162
|
-
group[:permissions] << rule
|
163
|
-
end
|
164
|
-
|
165
|
-
groups << group
|
166
|
-
|
167
|
-
end
|
168
|
-
|
169
|
-
return groups
|
170
|
-
end
|
171
|
-
|
172
|
-
def add_security_group_rule(group_name, protocol, from_port, to_port, source)
|
173
|
-
group = @compute_provider.security_groups.get(group_name)
|
174
|
-
opts = {:ip_protocol => protocol || 'tcp'}
|
175
|
-
|
176
|
-
if source.instance_of? Hash
|
177
|
-
opts[:group] = {source[:account] => source[:name]}
|
178
|
-
else
|
179
|
-
opts[:cidr_ip] = source
|
180
|
-
end
|
181
|
-
|
182
|
-
group.authorize_port_range(from_port.to_i..to_port.to_i, opts)
|
183
|
-
end
|
184
|
-
|
185
|
-
def remove_security_group_rule(group_name, protocol, from_port, to_port, source)
|
186
|
-
group = @compute_provider.security_groups.get(group_name)
|
187
|
-
opts = {:ip_protocol => protocol || 'tcp'}
|
188
|
-
|
189
|
-
if source.instance_of? Hash
|
190
|
-
opts[:group] = {source[:account] => source[:name]}
|
191
|
-
else
|
192
|
-
opts[:cidr_ip] = source
|
193
|
-
end
|
194
|
-
|
195
|
-
group.revoke_port_range(from_port.to_i..to_port.to_i, opts)
|
196
|
-
end
|
197
|
-
|
198
|
-
def destroy_security_group(group_name)
|
199
|
-
@compute_provider.security_groups.get(group_name).destroy
|
200
|
-
end
|
201
|
-
|
202
60
|
def create_static_ip
|
203
61
|
address = @compute_provider.addresses.create()
|
204
62
|
return address.public_ip
|
@@ -236,45 +94,6 @@ module Rubber
|
|
236
94
|
return address.destroy
|
237
95
|
end
|
238
96
|
|
239
|
-
def create_volume(size, zone)
|
240
|
-
volume = @compute_provider.volumes.create(:size => size.to_s, :availability_zone => zone)
|
241
|
-
return volume.id
|
242
|
-
end
|
243
|
-
|
244
|
-
def attach_volume(volume_id, instance_id, device)
|
245
|
-
volume = @compute_provider.volumes.get(volume_id)
|
246
|
-
server = @compute_provider.servers.get(instance_id)
|
247
|
-
volume.device = device
|
248
|
-
volume.server = server
|
249
|
-
end
|
250
|
-
|
251
|
-
def detach_volume(volume_id, force=true)
|
252
|
-
volume = @compute_provider.volumes.get(volume_id)
|
253
|
-
force ? volume.force_detach : (volume.server = nil)
|
254
|
-
end
|
255
|
-
|
256
|
-
def describe_volumes(volume_id=nil)
|
257
|
-
volumes = []
|
258
|
-
opts = {}
|
259
|
-
opts[:'volume-id'] = volume_id if volume_id
|
260
|
-
response = @compute_provider.volumes.all(opts)
|
261
|
-
response.each do |item|
|
262
|
-
volume = {}
|
263
|
-
volume[:id] = item.id
|
264
|
-
volume[:status] = item.state
|
265
|
-
if item.server_id
|
266
|
-
volume[:attachment_instance_id] = item.server_id
|
267
|
-
volume[:attachment_status] = item.attached_at ? "attached" : "waiting"
|
268
|
-
end
|
269
|
-
volumes << volume
|
270
|
-
end
|
271
|
-
return volumes
|
272
|
-
end
|
273
|
-
|
274
|
-
def destroy_volume(volume_id)
|
275
|
-
@compute_provider.volumes.get(volume_id).destroy
|
276
|
-
end
|
277
|
-
|
278
97
|
def create_image(image_name)
|
279
98
|
raise NotImplementedError, "create_image not implemented in generic fog adapter"
|
280
99
|
end
|
@@ -301,17 +120,6 @@ module Rubber
|
|
301
120
|
def describe_load_balancers(name=nil)
|
302
121
|
raise NotImplementedError, "describe_load_balancers not implemented in generic fog adapter"
|
303
122
|
end
|
304
|
-
|
305
|
-
# resource_id is any Amazon resource ID (e.g., instance ID or volume ID)
|
306
|
-
# tags is a hash of tag_name => tag_value pairs
|
307
|
-
def create_tags(resource_id, tags)
|
308
|
-
# Tags need to be created individually in fog
|
309
|
-
tags.each do |k, v|
|
310
|
-
@compute_provider.tags.create(:resource_id => resource_id,
|
311
|
-
:key => k.to_s, :value => v.to_s)
|
312
|
-
end
|
313
|
-
end
|
314
|
-
|
315
123
|
end
|
316
124
|
|
317
125
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'fog'
|
2
|
+
|
3
|
+
module Rubber
|
4
|
+
module Cloud
|
5
|
+
class Generic < Base
|
6
|
+
MUTEX = Mutex.new
|
7
|
+
|
8
|
+
attr_reader :storage_provider
|
9
|
+
|
10
|
+
def initialize(env, capistrano)
|
11
|
+
# TODO (nirvdrum 05/23/13): This is here until the storage provider stuff is cleaned up. That's why this class inherits from Base rather than Fog.
|
12
|
+
if env.cloud_providers && env.cloud_providers.aws
|
13
|
+
storage_credentials = {
|
14
|
+
:provider => 'AWS',
|
15
|
+
:aws_access_key_id => env.cloud_providers.aws.access_key,
|
16
|
+
:aws_secret_access_key => env.cloud_providers.aws.secret_access_key
|
17
|
+
}
|
18
|
+
|
19
|
+
env['storage_credentials'] = storage_credentials
|
20
|
+
@storage_provider = ::Fog::Storage.new(Rubber::Util.symbolize_keys(env.storage_credentials))
|
21
|
+
end
|
22
|
+
|
23
|
+
super(env, capistrano)
|
24
|
+
end
|
25
|
+
|
26
|
+
def active_state
|
27
|
+
'active'
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_instance(instance_alias, image_name, image_type, security_groups, availability_zone, region)
|
31
|
+
instance = {}
|
32
|
+
instance[:id] = instance_alias
|
33
|
+
instance[:state] = active_state
|
34
|
+
instance[:external_ip] = capistrano.rubber.get_env('EXTERNAL_IP', "External IP address for host '#{instance_alias}'", true)
|
35
|
+
instance[:internal_ip] = capistrano.rubber.get_env('INTERNAL_IP', "Internal IP address for host '#{instance_alias}'", true, instance[:external_ip])
|
36
|
+
instance[:provider] = 'generic'
|
37
|
+
instance[:platform] = 'linux'
|
38
|
+
|
39
|
+
Generic.add_instance(instance)
|
40
|
+
|
41
|
+
instance_alias
|
42
|
+
end
|
43
|
+
|
44
|
+
def describe_instances(instance_id=nil)
|
45
|
+
# Since there's no API to query for instance details, the best we can do is use what we have in memory from
|
46
|
+
# the :create_instance operation or ask the user for the details again.
|
47
|
+
unless Generic.instances
|
48
|
+
create_instance(instance_id, nil, nil, nil, nil)
|
49
|
+
end
|
50
|
+
|
51
|
+
Generic.instances
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.add_instance(instance)
|
55
|
+
MUTEX.synchronize do
|
56
|
+
@instances ||= []
|
57
|
+
@instances << instance
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.instances
|
62
|
+
MUTEX.synchronize do
|
63
|
+
@instances
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/rubber/instance.rb
CHANGED
@@ -201,7 +201,7 @@ module Rubber
|
|
201
201
|
attr_accessor :internal_host, :internal_ip
|
202
202
|
attr_accessor :static_ip, :volumes, :partitions, :root_device_type
|
203
203
|
attr_accessor :spot_instance_request_id
|
204
|
-
attr_accessor :platform
|
204
|
+
attr_accessor :provider, :platform
|
205
205
|
|
206
206
|
def initialize(name, domain, roles, instance_id, image_type, image_id, security_group_list=[])
|
207
207
|
@name = name
|
@@ -246,6 +246,11 @@ module Rubber
|
|
246
246
|
roles.collect {|r| r.name}
|
247
247
|
end
|
248
248
|
|
249
|
+
def provider
|
250
|
+
# Deal with old instance configurations that don't have a provider value persisted.
|
251
|
+
@provider || 'aws'
|
252
|
+
end
|
253
|
+
|
249
254
|
def platform
|
250
255
|
# Deal with old instance configurations that don't have a platform value persisted.
|
251
256
|
@platform || 'linux'
|
@@ -24,28 +24,30 @@ namespace :rubber do
|
|
24
24
|
task :post_stop do
|
25
25
|
end
|
26
26
|
|
27
|
-
|
28
|
-
# until it is 'committed' by the symlink task (and doing so causes it to run
|
29
|
-
# for bootstrap_db which should only config the db config file). However,
|
30
|
-
# deploy:migrations doesn't call update, so we need an additional trigger for
|
31
|
-
# it
|
32
|
-
after "deploy:update", "rubber:config"
|
27
|
+
after "deploy:update_code", "rubber:config"
|
33
28
|
after "deploy:rollback_code", "rubber:config"
|
34
|
-
before "deploy:migrate", "rubber:config"
|
35
29
|
|
36
30
|
desc <<-DESC
|
37
31
|
Configures the deployed rails application by running the rubber configuration process
|
38
32
|
DESC
|
39
33
|
task :config do
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
34
|
+
# Don't want to do rubber:config during bootstrap_db where it's triggered by
|
35
|
+
# deploy:update_code, because the user could be requiring the rails env inside
|
36
|
+
# some of their config templates (which fails because rails can't connect to
|
37
|
+
# the db)
|
38
|
+
if fetch(:rubber_updating_code_for_bootstrap_db, false)
|
39
|
+
logger.info "Updating code for bootstrap, skipping rubber:config"
|
40
|
+
else
|
41
|
+
opts = {}
|
42
|
+
opts[:no_post] = true if ENV['NO_POST']
|
43
|
+
opts[:force] = true if ENV['FORCE']
|
44
|
+
opts[:file] = ENV['FILE'] if ENV['FILE']
|
44
45
|
|
45
|
-
|
46
|
-
|
46
|
+
# when running deploy:migrations, we need to run config against release_path
|
47
|
+
opts[:deploy_path] = current_release if fetch(:migrate_target, :current).to_sym == :latest
|
47
48
|
|
48
|
-
|
49
|
+
run_config(opts)
|
50
|
+
end
|
49
51
|
end
|
50
52
|
|
51
53
|
# because we start server as appserver user, but migrate as root, server needs to be able to write logs, etc.
|