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.
@@ -263,16 +263,17 @@ namespace :rubber do
263
263
  role_names = instance_roles.collect{|x| x.name}
264
264
  env = rubber_cfg.environment.bind(role_names, instance_alias)
265
265
 
266
- # We need to use security_groups during create, so create them up front
267
266
  mutex.synchronize do
268
- setup_security_groups(instance_alias, role_names)
267
+ cloud.before_create_instance(instance_alias, role_names)
269
268
  end
269
+
270
270
  security_groups = get_assigned_security_groups(instance_alias, role_names)
271
271
 
272
272
  cloud_env = env.cloud_providers[env.cloud_provider]
273
273
  ami = cloud_env.image_id
274
274
  ami_type = cloud_env.image_type
275
- availability_zone = env.availability_zone
275
+ availability_zone = cloud_env.availability_zone
276
+ region = cloud_env.region
276
277
 
277
278
  create_spot_instance ||= cloud_env.spot_instance
278
279
 
@@ -306,8 +307,8 @@ namespace :rubber do
306
307
  end
307
308
 
308
309
  if !create_spot_instance || (create_spot_instance && max_wait_time < 0)
309
- logger.info "Creating instance #{ami}/#{ami_type}/#{security_groups.join(',') rescue 'Default'}/#{availability_zone || 'Default'}"
310
- instance_id = cloud.create_instance(ami, ami_type, security_groups, availability_zone)
310
+ logger.info "Creating instance #{ami}/#{ami_type}/#{security_groups.join(',') rescue 'Default'}/#{availability_zone || region || 'Default'}"
311
+ instance_id = cloud.create_instance(instance_alias, ami, ami_type, security_groups, availability_zone, region)
311
312
  end
312
313
 
313
314
  logger.info "Instance #{instance_alias} created: #{instance_id}"
@@ -317,10 +318,8 @@ namespace :rubber do
317
318
  rubber_instances.add(instance_item)
318
319
  rubber_instances.save()
319
320
 
320
- # Sometimes tag creation will fail, indicating that the instance doesn't exist yet even though it does. It seems to
321
- # be a propagation delay on Amazon's end, so the best we can do is wait and try again.
322
- Rubber::Util.retry_on_failure(Exception, :retry_sleep => 0.5, :retry_count => 100) do
323
- Rubber::Tag::update_instance_tags(instance_alias)
321
+ mutex.synchronize do
322
+ cloud.after_create_instance(instance_item)
324
323
  end
325
324
  end
326
325
 
@@ -349,9 +348,13 @@ namespace :rubber do
349
348
 
350
349
  env = rubber_cfg.environment.bind(instance_item.role_names, instance_alias)
351
350
 
352
- instance = cloud.describe_instances(instance_item.instance_id).first rescue {}
351
+ instance = cloud.describe_instances(instance_item.instance_id).first
352
+
353
+ mutex.synchronize do
354
+ cloud.before_refresh_instance(instance_item)
355
+ end
353
356
 
354
- if instance[:state] == "running"
357
+ if instance[:state] == cloud.active_state
355
358
  print "\n"
356
359
  logger.info "Instance running, fetching hostname/ip data"
357
360
  instance_item.external_host = instance[:external_host]
@@ -359,6 +362,7 @@ namespace :rubber do
359
362
  instance_item.internal_host = instance[:internal_host]
360
363
  instance_item.internal_ip = instance[:internal_ip]
361
364
  instance_item.zone = instance[:zone]
365
+ instance_item.provider = instance[:provider]
362
366
  instance_item.platform = instance[:platform]
363
367
  instance_item.root_device_type = instance[:root_device_type]
364
368
  rubber_instances.save()
@@ -367,6 +371,8 @@ namespace :rubber do
367
371
  # weird cap/netssh bug, sometimes just hangs forever on initial connect, so force a timeout
368
372
  begin
369
373
  Timeout::timeout(30) do
374
+ puts 'Trying to enable root login'
375
+
370
376
  # turn back on root ssh access if we are using root as the capistrano user for connecting
371
377
  enable_root_ssh(instance_item.external_ip, fetch(:initial_ssh_user, 'ubuntu')) if user == 'root'
372
378
  # force a connection so if above isn't enabled we still timeout if initial connection hangs
@@ -380,6 +386,10 @@ namespace :rubber do
380
386
  end
381
387
  end
382
388
 
389
+ mutex.synchronize do
390
+ cloud.after_refresh_instance(instance_item)
391
+ end
392
+
383
393
  return true
384
394
  end
385
395
  return false
@@ -387,9 +397,6 @@ namespace :rubber do
387
397
 
388
398
  def post_refresh
389
399
  env = rubber_cfg.environment.bind(nil, nil)
390
-
391
- # update the remote name/environment tags
392
- update_tags
393
400
 
394
401
  # setup amazon elastic ips if configured to do so
395
402
  setup_static_ips
@@ -6,7 +6,9 @@ namespace :rubber do
6
6
  Likewise, rules within a group will get created, and those not will be removed
7
7
  DESC
8
8
  required_task :setup_security_groups do
9
- setup_security_groups()
9
+ servers = find_servers_for_task(current_task)
10
+
11
+ cloud.setup_security_groups(servers.collect(&:host))
10
12
  end
11
13
 
12
14
  desc <<-DESC
@@ -37,192 +39,7 @@ namespace :rubber do
37
39
  security_groups += roles
38
40
  end
39
41
  security_groups = security_groups.uniq.compact.reject {|x| x.empty? }
40
- security_groups = security_groups.collect {|x| isolate_group_name(x) }
42
+ security_groups = security_groups.collect {|x| cloud.isolate_group_name(x) }
41
43
  return security_groups
42
44
  end
43
-
44
- def setup_security_groups(host=nil, roles=[])
45
- env = rubber_cfg.environment.bind(roles, host)
46
- security_group_defns = Hash[env.security_groups.to_a]
47
- if env.auto_security_groups
48
- sghosts = (rubber_instances.collect{|ic| ic.name } + [host]).uniq.compact
49
- sgroles = (rubber_instances.all_roles + roles).uniq.compact
50
- security_group_defns = inject_auto_security_groups(security_group_defns, sghosts, sgroles)
51
- sync_security_groups(security_group_defns)
52
- else
53
- sync_security_groups(security_group_defns)
54
- end
55
- end
56
-
57
- def inject_auto_security_groups(groups, hosts, roles)
58
- hosts.each do |name|
59
- group_name = name
60
- groups[group_name] ||= {'description' => "Rubber automatic security group for host: #{name}", 'rules' => []}
61
- end
62
- roles.each do |name|
63
- group_name = name
64
- groups[group_name] ||= {'description' => "Rubber automatic security group for role: #{name}", 'rules' => []}
65
- end
66
- return groups
67
- end
68
-
69
- def isolate_prefix
70
- return "#{rubber_env.app_name}_#{Rubber.env}_"
71
- end
72
-
73
- def isolate_group_name(group_name)
74
- if rubber_env.isolate_security_groups
75
- group_name =~ /^#{isolate_prefix}/ ? group_name : "#{isolate_prefix}#{group_name}"
76
- else
77
- group_name
78
- end
79
- end
80
-
81
- def isolate_groups(groups)
82
- renamed = {}
83
- groups.each do |name, group|
84
- new_name = isolate_group_name(name)
85
- new_group = Marshal.load(Marshal.dump(group))
86
- new_group['rules'].each do |rule|
87
- old_ref_name = rule['source_group_name']
88
- if old_ref_name
89
- # don't mangle names if the user specifies this is an external group they are giving access to.
90
- # remove the external_group key to allow this to match with groups retrieved from cloud
91
- is_external = rule.delete('external_group')
92
- if ! is_external && old_ref_name !~ /^#{isolate_prefix}/
93
- rule['source_group_name'] = isolate_group_name(old_ref_name)
94
- end
95
- end
96
- end
97
- renamed[new_name] = new_group
98
- end
99
- return renamed
100
- end
101
-
102
- def sync_security_groups(groups)
103
- return unless groups
104
-
105
- groups = Rubber::Util::stringify(groups)
106
- groups = isolate_groups(groups)
107
- group_keys = groups.keys.clone()
108
-
109
- # For each group that does already exist in cloud
110
- cloud_groups = cloud.describe_security_groups()
111
- cloud_groups.each do |cloud_group|
112
- group_name = cloud_group[:name]
113
-
114
- # skip those groups that don't belong to this project/env
115
- next if rubber_env.isolate_security_groups && group_name !~ /^#{isolate_prefix}/
116
-
117
- if group_keys.delete(group_name)
118
- # sync rules
119
- logger.debug "Security Group already in cloud, syncing rules: #{group_name}"
120
- group = groups[group_name]
121
-
122
- # convert the special case default rule into what it actually looks like when
123
- # we query ec2 so that we can match things up when syncing
124
- rules = group['rules'].clone
125
- group['rules'].each do |rule|
126
- if [2, 3].include?(rule.size) && rule['source_group_name'] && rule['source_group_account']
127
- rules << rule.merge({'protocol' => 'tcp', 'from_port' => '1', 'to_port' => '65535' })
128
- rules << rule.merge({'protocol' => 'udp', 'from_port' => '1', 'to_port' => '65535' })
129
- rules << rule.merge({'protocol' => 'icmp', 'from_port' => '-1', 'to_port' => '-1' })
130
- rules.delete(rule)
131
- end
132
- end
133
-
134
- rule_maps = []
135
-
136
- # first collect the rule maps from the request (group/user pairs are duplicated for tcp/udp/icmp,
137
- # so we need to do this up frnot and remove duplicates before checking against the local rubber rules)
138
- cloud_group[:permissions].each do |rule|
139
- source_groups = rule.delete(:source_groups)
140
- if source_groups
141
- source_groups.each do |source_group|
142
- rule_map = rule.clone
143
- rule_map.delete(:source_ips)
144
- rule_map[:source_group_name] = source_group[:name]
145
- rule_map[:source_group_account] = source_group[:account]
146
- rule_map = Rubber::Util::stringify(rule_map)
147
- rule_maps << rule_map unless rule_maps.include?(rule_map)
148
- end
149
- else
150
- rule_map = Rubber::Util::stringify(rule)
151
- rule_maps << rule_map unless rule_maps.include?(rule_map)
152
- end
153
- end if cloud_group[:permissions]
154
- # For each rule, if it exists, do nothing, otherwise remove it as its no longer defined locally
155
- rule_maps.each do |rule_map|
156
- if rules.delete(rule_map)
157
- # rules match, don't need to do anything
158
- # logger.debug "Rule in sync: #{rule_map.inspect}"
159
- else
160
- # rules don't match, remove them from cloud and re-add below
161
- answer = nil
162
- msg = "Rule '#{rule_map.inspect}' exists in cloud, but not locally"
163
- if rubber_env.prompt_for_security_group_sync
164
- answer = Capistrano::CLI.ui.ask("#{msg}, remove from cloud? [y/N]: ")
165
- else
166
- logger.info(msg)
167
- end
168
-
169
- if answer =~ /^y/
170
- rule_map = Rubber::Util::symbolize_keys(rule_map)
171
- if rule_map[:source_group_name]
172
- cloud.remove_security_group_rule(group_name, rule_map[:protocol], rule_map[:from_port], rule_map[:to_port], {:name => rule_map[:source_group_name], :account => rule_map[:source_group_account]})
173
- else
174
- rule_map[:source_ips].each do |source_ip|
175
- cloud.remove_security_group_rule(group_name, rule_map[:protocol], rule_map[:from_port], rule_map[:to_port], source_ip)
176
- end if rule_map[:source_ips]
177
- end
178
- end
179
- end
180
- end
181
-
182
- rules.each do |rule_map|
183
- # create non-existing rules
184
- logger.debug "Missing rule, creating: #{rule_map.inspect}"
185
- rule_map = Rubber::Util::symbolize_keys(rule_map)
186
- if rule_map[:source_group_name]
187
- cloud.add_security_group_rule(group_name, rule_map[:protocol], rule_map[:from_port], rule_map[:to_port], {:name => rule_map[:source_group_name], :account => rule_map[:source_group_account]})
188
- else
189
- rule_map[:source_ips].each do |source_ip|
190
- cloud.add_security_group_rule(group_name, rule_map[:protocol], rule_map[:from_port], rule_map[:to_port], source_ip)
191
- end if rule_map[:source_ips]
192
- end
193
- end
194
- else
195
- # delete group
196
- answer = nil
197
- msg = "Security group '#{group_name}' exists in cloud but not locally"
198
- if rubber_env.prompt_for_security_group_sync
199
- answer = Capistrano::CLI.ui.ask("#{msg}, remove from cloud? [y/N]: ")
200
- else
201
- logger.debug(msg)
202
- end
203
- cloud.destroy_security_group(group_name) if answer =~ /^y/
204
- end
205
- end
206
-
207
- # For each group that didnt already exist in cloud
208
- group_keys.each do |group_name|
209
- group = groups[group_name]
210
- logger.debug "Creating new security group: #{group_name}"
211
- # create each group
212
- cloud.create_security_group(group_name, group['description'])
213
- # create rules for group
214
- group['rules'].each do |rule_map|
215
- logger.debug "Creating new rule: #{rule_map.inspect}"
216
- rule_map = Rubber::Util::symbolize_keys(rule_map)
217
- if rule_map[:source_group_name]
218
- cloud.add_security_group_rule(group_name, rule_map[:protocol], rule_map[:from_port], rule_map[:to_port], {:name => rule_map[:source_group_name], :account => rule_map[:source_group_account]})
219
- else
220
- rule_map[:source_ips].each do |source_ip|
221
- cloud.add_security_group_rule(group_name, rule_map[:protocol], rule_map[:from_port], rule_map[:to_port], source_ip)
222
- end if rule_map[:source_ips]
223
- end
224
- end
225
- end
226
- end
227
-
228
45
  end
@@ -9,6 +9,7 @@ namespace :rubber do
9
9
  link_bash
10
10
  set_timezone
11
11
  enable_multiverse
12
+ install_core_packages
12
13
  upgrade_packages
13
14
  install_packages
14
15
  setup_volumes
@@ -19,9 +20,16 @@ namespace :rubber do
19
20
 
20
21
  # Sets up instance to allow root access (e.g. recent canonical AMIs)
21
22
  def enable_root_ssh(ip, initial_ssh_user)
23
+ # Capistrano uses the :password variable for sudo commands. Since this setting is generally used for the deploy user,
24
+ # but we need it this one time for the initial SSH user, we need to swap out and restore the password.
25
+ #
26
+ # We special-case the 'ubuntu' user since Amazon doesn't since the Canonical AMIs on EC2 don't set the password for
27
+ # this account, making any password prompt potentially confusing.
28
+ orig_password = fetch(:password)
29
+ set(:password, initial_ssh_user == 'ubuntu' ? nil : Capistrano::CLI.password_prompt("Password for #{initial_ssh_user} @ #{ip}: "))
22
30
 
23
31
  task :_allow_root_ssh, :hosts => "#{initial_ssh_user}@#{ip}" do
24
- rsudo "cp /home/#{initial_ssh_user}/.ssh/authorized_keys /root/.ssh/"
32
+ rsudo "mkdir -p /root/.ssh && cp /home/#{initial_ssh_user}/.ssh/authorized_keys /root/.ssh/"
25
33
  end
26
34
 
27
35
  begin
@@ -35,6 +43,9 @@ namespace :rubber do
35
43
  retry
36
44
  end
37
45
  end
46
+
47
+ # Restore the original deploy password.
48
+ set(:password, orig_password)
38
49
  end
39
50
 
40
51
  # Forces a direct connection
@@ -285,10 +296,22 @@ namespace :rubber do
285
296
  Install extra packages and gems.
286
297
  DESC
287
298
  task :install do
299
+ install_core_packages
288
300
  install_packages
289
301
  install_gems
290
302
  end
291
303
 
304
+ desc <<-DESC
305
+ Install core packages that are needed before the general install_packages phase.
306
+ DESC
307
+ task :install_core_packages do
308
+ core_packages = [
309
+ 'python-software-properties', # Needed for add-apt-repository, which we use for adding PPAs.
310
+ 'bc' # Needed for comparing version numbers in bash, which we do for various setup functions.
311
+ ]
312
+ rsudo "export DEBIAN_FRONTEND=noninteractive; apt-get -q -o Dpkg::Options::=--force-confold -y --force-yes install #{core_packages.join(' ')}"
313
+ end
314
+
292
315
  desc <<-DESC
293
316
  Install Ubuntu packages. Set 'packages' in rubber.yml to \
294
317
  be an array of strings.
@@ -463,6 +486,7 @@ namespace :rubber do
463
486
  expanded_pkg_list << pkg_spec
464
487
  end
465
488
  end
489
+ expanded_pkg_list << 'ec2-ami-tools' if rubber_env.cloud_provider == 'aws'
466
490
  expanded_pkg_list.join(' ')
467
491
  end
468
492
 
@@ -154,24 +154,24 @@ namespace :rubber do
154
154
  return local_alias
155
155
  end
156
156
 
157
- def prepare_script(name, contents, stop_on_error_cmd=rubber_env.stop_on_error_cmd)
157
+ def prepare_script(name, contents, stop_on_error_cmd=rubber_env.stop_on_error_cmd, opts = {})
158
158
  script = "/tmp/#{name}"
159
159
  # this lets us abort a script if a command in the middle of it errors out
160
160
  contents = "#{stop_on_error_cmd}\n#{contents}" if stop_on_error_cmd
161
- put(contents, script)
161
+ put(contents, script, opts)
162
162
  return script
163
163
  end
164
164
 
165
165
  def run_script(name, contents, opts = {})
166
166
  args = opts.delete(:script_args)
167
- script = prepare_script(name, contents)
167
+ script = prepare_script(name, contents, rubber_env.stop_on_error_cmd, opts)
168
168
  run "bash #{script} #{args}", opts
169
169
  end
170
170
 
171
171
  def sudo_script(name, contents, opts = {})
172
172
  user = opts.delete(:as)
173
173
  args = opts.delete(:script_args)
174
- script = prepare_script(name, contents)
174
+ script = prepare_script(name, contents, rubber_env.stop_on_error_cmd, opts)
175
175
 
176
176
  sudo_args = user ? "-H -u #{user}" : ""
177
177
  run "#{sudo} #{sudo_args} bash -l #{script} #{args}", opts
@@ -228,7 +228,10 @@ namespace :rubber do
228
228
  def update_code_for_bootstrap
229
229
  unless (fetch(:rubber_code_was_updated, false))
230
230
  deploy.setup
231
+ logger.info "updating code for bootstrap"
232
+ set :rubber_updating_code_for_bootstrap_db, true
231
233
  deploy.update_code
234
+ set :rubber_updating_code_for_bootstrap_db, false
232
235
  end
233
236
  end
234
237
 
@@ -115,6 +115,10 @@ module Rubber
115
115
  end
116
116
  end
117
117
 
118
+ def camelcase(str)
119
+ str.split('_').map{ |part| part.capitalize }.join
120
+ end
121
+
118
122
  extend self
119
123
  end
120
124
  end
@@ -1,3 +1,3 @@
1
1
  module Rubber
2
- VERSION = "2.3.1"
2
+ VERSION = "2.4.0"
3
3
  end
@@ -90,11 +90,17 @@ task :cleanup, :except => { :no_release => true } do
90
90
  end
91
91
 
92
92
  if Rubber::Util.has_asset_pipeline?
93
- # load asset pipeline tasks, and reorder them to run after
94
- # rubber:config so that database.yml/etc has been generated
93
+ # load asset pipeline task, disable precompile from being triggered
94
+ # by deploy:update_code during bootstrap_db, and reorder to run after
95
+ # rubber:config has generated database.yml/etc.
95
96
  load 'deploy/assets'
96
97
  callbacks[:after].delete_if {|c| c.source == "deploy:assets:precompile"}
97
- callbacks[:before].delete_if {|c| c.source == "deploy:assets:symlink"}
98
- before "deploy:assets:precompile", "deploy:assets:symlink"
99
- after "rubber:config", "deploy:assets:precompile"
98
+ task :_skip_assets_precompile_if_bootstrapping_db do
99
+ if fetch(:rubber_updating_code_for_bootstrap_db, false)
100
+ logger.info "Skipping assets precompilation"
101
+ else
102
+ deploy.assets.precompile
103
+ end
104
+ end
105
+ after 'deploy:update_code', '_skip_assets_precompile_if_bootstrapping_db'
100
106
  end
@@ -98,7 +98,20 @@ cloud_providers:
98
98
  # If a spot instance request can't be fulfilled in 3 minutes, fallback to on-demand instance creation. If not set,
99
99
  # the default is infinite.
100
100
  # spot_instance_request_timeout: 180
101
-
101
+
102
+ digital_ocean:
103
+ # REQUIRED: The Digital Ocean region that you want to use.
104
+ #
105
+ # Options include
106
+ # New York 1
107
+ # Amsterdam 1
108
+ #
109
+ region: New York 1
110
+
111
+ # REQUIRED: The image name and type for creating instances.
112
+ image_id: Ubuntu 12.04 x64 Server
113
+ image_type: 512MB
114
+
102
115
  # Use an alternate cloud provider supported by fog. This doesn't fully work
103
116
  # yet due to differences in providers within fog, but gives you a starting
104
117
  # point for contributing a new provider to rubber. See rubber/lib/rubber/cloud(.rb)
@@ -221,7 +234,7 @@ prompt_for_security_group_sync: true
221
234
  # OPTIONAL: The packages to install on all instances
222
235
  # You can install a specific version of a package by using a sub-array of pkg, version
223
236
  # For example, packages: [[rake, 0.7.1], irb]
224
- packages: [postfix, build-essential, git-core, ec2-ami-tools, libxslt-dev, ntp]
237
+ packages: [postfix, build-essential, git-core, libxslt-dev, ntp]
225
238
 
226
239
  # OPTIONAL: gem sources to setup for rubygems
227
240
  # gemsources: ["https://rubygems.org"]