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.
@@ -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
@@ -10,9 +10,12 @@ module Rubber
10
10
 
11
11
  def initialize(env, capistrano)
12
12
  super(env, capistrano)
13
- credentials = Rubber::Util.symbolize_keys(env.credentials)
14
- @compute_provider = ::Fog::Compute.new(credentials)
15
- @storage_provider = ::Fog::Storage.new(credentials)
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
- def describe_spot_instance_requests(request_id=nil)
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
@@ -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
- # Don't want to do rubber:config for update_code as that tree isn't official
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
- opts = {}
41
- opts[:no_post] = true if ENV['NO_POST']
42
- opts[:force] = true if ENV['FORCE']
43
- opts[:file] = ENV['FILE'] if ENV['FILE']
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
- # when running deploy:migrations, we need to run config against release_path
46
- opts[:deploy_path] = current_release if fetch(:migrate_target, :current).to_sym == :latest
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
- run_config(opts)
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.