rubber 2.0.5 → 2.0.6

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.
Files changed (46) hide show
  1. data/.travis.yml +2 -2
  2. data/CHANGELOG +50 -0
  3. data/lib/rubber/commands/cron.rb +9 -11
  4. data/lib/rubber/commands/util.rb +1 -0
  5. data/lib/rubber/dns.rb +29 -4
  6. data/lib/rubber/dns/aws.rb +181 -0
  7. data/lib/rubber/dns/nettica.rb +74 -36
  8. data/lib/rubber/dns/zerigo.rb +110 -4
  9. data/lib/rubber/instance.rb +1 -1
  10. data/lib/rubber/recipes/rubber/instances.rb +13 -5
  11. data/lib/rubber/recipes/rubber/security_groups.rb +1 -1
  12. data/lib/rubber/recipes/rubber/setup.rb +80 -64
  13. data/lib/rubber/util.rb +7 -0
  14. data/lib/rubber/version.rb +1 -1
  15. data/templates/base/config/rubber/deploy-setup.rb +12 -0
  16. data/templates/base/config/rubber/rubber-dns.yml +17 -21
  17. data/templates/base/config/rubber/rubber.yml +2 -2
  18. data/templates/collectd/config/rubber/role/passenger/passenger-status-sudoers.conf +1 -1
  19. data/templates/elasticsearch/config/rubber/rubber-elasticsearch.yml +1 -1
  20. data/templates/graphite/config/rubber/role/graphite_web/dashboard.html +5 -3
  21. data/templates/jenkins/config/environments/jenkins.rb +1 -1
  22. data/templates/jenkins/config/rubber/rubber-jenkins.yml +1 -1
  23. data/templates/passenger/config/rubber/deploy-passenger.rb +0 -8
  24. data/templates/postgresql/config/rubber/rubber-postgresql.yml +2 -2
  25. data/templates/resque/config/initializers/resque.rb +2 -2
  26. data/templates/resque/config/resque.yml +4 -0
  27. data/templates/resque/config/rubber/common/resque.yml +1 -1
  28. data/templates/resque_scheduler/config/rubber/common/resque_schedule.yml +9 -0
  29. data/templates/resque_scheduler/config/rubber/deploy-resque_scheduler.rb +38 -0
  30. data/templates/resque_scheduler/config/rubber/role/resque_scheduler/resque-scheduler-upstart.conf +20 -0
  31. data/templates/resque_scheduler/config/rubber/rubber-resque_scheduler.yml +7 -0
  32. data/templates/resque_scheduler/lib/tasks/resque-scheduler.rake +28 -0
  33. data/templates/resque_scheduler/templates.rb +1 -0
  34. data/templates/resque_scheduler/templates.yml +3 -0
  35. data/templates/torquebox/config/rubber/rubber-torquebox.yml +1 -1
  36. data/templates/zookeeper/config/rubber/role/zookeeper/myid.conf +5 -4
  37. data/templates/zookeeper/config/rubber/role/zookeeper/zoo.cfg +6 -1
  38. data/test/command_test.rb +0 -55
  39. data/test/commands/cron_test.rb +83 -0
  40. data/test/dns/aws_test.rb +192 -0
  41. data/test/dns/zerigo_test.rb +180 -0
  42. data/test/instance_test.rb +17 -2
  43. data/test/test_helper.rb +37 -2
  44. metadata +20 -8
  45. data/lib/rubber/dns/fog.rb +0 -219
  46. data/test/dns/fog_test.rb +0 -169
@@ -1,16 +1,122 @@
1
1
  require 'rubygems'
2
2
  require 'fog'
3
- require 'rubber/dns/fog'
4
3
 
5
4
  module Rubber
6
5
  module Dns
7
6
 
8
- class Zerigo < Fog
7
+ class Zerigo < Base
9
8
 
9
+ attr_accessor :client
10
+
10
11
  def initialize(env)
11
- super(env.merge({"credentials" => { "provider" => 'zerigo', "zerigo_email" => env.email, "zerigo_token" => env.token }}))
12
+ super(env)
13
+ creds = { :provider => 'zerigo', :zerigo_email => env.email, :zerigo_token => env.token }
14
+ @client = ::Fog::DNS.new(creds)
12
15
  end
13
-
16
+
17
+ # multiple hosts with same name/type convert to a single rubber-dns.yml opts format
18
+ def hosts_to_opts(hosts)
19
+ opts = {}
20
+
21
+ hosts.each do |host|
22
+ opts[:host] ||= host.name || ''
23
+ opts[:domain] ||= host.zone.domain
24
+ opts[:type] ||= host.type
25
+ opts[:ttl] ||= host.ttl.to_i if host.ttl
26
+
27
+ opts[:data] ||= []
28
+ if host.type =~ /MX/i
29
+ opts[:data] << {:priority => host.priority, :value => host.value}
30
+ else
31
+ opts[:data] << host.value
32
+ end
33
+ end
34
+
35
+ return opts
36
+ end
37
+
38
+ # a single rubber-dns.yml opts format converts to multiple hosts with same name/type
39
+ def opts_to_hosts(opts)
40
+ hosts = []
41
+
42
+ opts[:data].each do |o|
43
+ host = {}
44
+ host[:name] = opts[:host]
45
+ host[:type] = opts[:type]
46
+ host[:ttl] = opts[:ttl] if opts[:ttl]
47
+ if o.kind_of?(Hash) && o[:priority]
48
+ host[:priority] = o[:priority]
49
+ host[:value] = o[:value]
50
+ else
51
+ host[:value] = o
52
+ end
53
+ hosts << host
54
+ end
55
+
56
+ return hosts
57
+ end
58
+
59
+ def find_or_create_zone(domain)
60
+ zone = @client.zones.all.find {|z| z.domain =~ /^#{domain}\.?/}
61
+ if ! zone
62
+ zone = @client.zones.create(:domain => domain)
63
+ end
64
+ return zone
65
+ end
66
+
67
+ def find_hosts(opts = {})
68
+ opts = setup_opts(opts, [:host, :domain])
69
+ result = []
70
+ zone = find_or_create_zone(opts[:domain])
71
+
72
+ # TODO: revert this when zerigo fog gets fixed to allow parameters
73
+ # hosts = fqdn ? (zone.records.all(:name => fqdn) rescue []) : zone.records.all
74
+ hosts = zone.records.all
75
+ hosts = hosts.select {|h| name = h.name || ''; name == opts[:host] } if opts.has_key?(:host) && opts[:host] != '*'
76
+ hosts = hosts.select {|h| h.type == opts[:type] } if opts.has_key?(:type) && opts[:type] != '*'
77
+
78
+ return hosts
79
+ end
80
+
81
+ def find_host_records(opts = {})
82
+ hosts = find_hosts(opts)
83
+ group = {}
84
+ hosts.each do |h|
85
+ key = "#{h.name}.#{h.domain} #{h.type}"
86
+ group[key] ||= []
87
+ group[key] << h
88
+ end
89
+ result = group.values.collect {|h| hosts_to_opts(h).merge(:domain => opts[:domain])}
90
+ return result
91
+ end
92
+
93
+ def create_host_record(opts = {})
94
+ opts = setup_opts(opts, [:host, :data, :domain, :type, :ttl])
95
+ zone = find_or_create_zone(opts[:domain])
96
+ opts_to_hosts(opts).each do |host|
97
+ zone.records.create(host)
98
+ end
99
+ end
100
+
101
+ def destroy_host_record(opts = {})
102
+ opts = setup_opts(opts, [:host, :domain])
103
+
104
+ find_hosts(opts).each do |h|
105
+ h.destroy || raise("Failed to destroy #{h.hostname}")
106
+ end
107
+ end
108
+
109
+ def update_host_record(old_opts={}, new_opts={})
110
+ old_opts = setup_opts(old_opts, [:host, :domain, :type])
111
+ new_opts = setup_opts(new_opts, [:host, :domain, :type, :data])
112
+
113
+ # Tricky to update existing hosts since zerigo needs a separate host
114
+ # entry for multiple records of same type (MX, etc), so take the easy
115
+ # way out and destroy/create instead of update
116
+ destroy_host_record(old_opts)
117
+ create_host_record(new_opts)
118
+ end
119
+
14
120
  end
15
121
 
16
122
  end
@@ -41,7 +41,7 @@ module Rubber
41
41
  bucket = location.split("/")[0]
42
42
  key = location.split("/")[1..-1].join("/")
43
43
  data = Rubber.cloud.storage(bucket).fetch(key)
44
- StringIO.open(data, 'r') {|f| load_from_file(f) }
44
+ StringIO.open(data, 'r') {|f| load_from_file(f) } if data
45
45
  when /table:(.*)/
46
46
  location = $1
47
47
  load_from_table(location)
@@ -68,9 +68,11 @@ namespace :rubber do
68
68
  Reboot the EC2 instance for the give ALIAS
69
69
  DESC
70
70
  required_task :reboot do
71
- instance_alias = get_env('ALIAS', "Instance alias (e.g. web01)", true)
71
+ instance_aliases = get_env('ALIAS', "Instance alias (e.g. web01 or web01~web05,web09)", true)
72
+
73
+ aliases = Rubber::Util::parse_aliases(instance_aliases)
72
74
  ENV.delete('ROLES') # so we don't get an error if people leave ROLES in env from :create CLI
73
- reboot_instance(instance_alias)
75
+ reboot_instances(aliases, ENV['FORCE'] =~ /^(t|y)/)
74
76
  end
75
77
 
76
78
  desc <<-DESC
@@ -443,15 +445,21 @@ namespace :rubber do
443
445
  setup_aliases
444
446
  end
445
447
 
448
+ def reboot_instances(instance_aliases, force=false)
449
+ instance_aliases.each do |instance_alias|
450
+ reboot_instance(instance_alias, force)
451
+ end
452
+ end
453
+
446
454
  # Reboots the given ec2 instance
447
- def reboot_instance(instance_alias)
455
+ def reboot_instance(instance_alias, force=false)
448
456
  instance_item = rubber_instances[instance_alias]
449
457
  fatal "Instance does not exist: #{instance_alias}" if ! instance_item
450
458
 
451
459
  env = rubber_cfg.environment.bind(instance_item.role_names, instance_item.name)
452
460
 
453
- value = Capistrano::CLI.ui.ask("About to REBOOT #{instance_alias} (#{instance_item.instance_id}) in mode #{Rubber.env}. Are you SURE [yes/NO]?: ")
454
- fatal("Exiting", 0) if value != "yes"
461
+ value = Capistrano::CLI.ui.ask("About to REBOOT #{instance_alias} (#{instance_item.instance_id}) in mode #{Rubber.env}. Are you SURE [yes/NO]?: ") unless force
462
+ fatal("Exiting", 0) if value != "yes" && ! force
455
463
 
456
464
  logger.info "Rebooting instance alias=#{instance_alias}, instance_id=#{instance_item.instance_id}"
457
465
 
@@ -43,7 +43,7 @@ namespace :rubber do
43
43
 
44
44
  def setup_security_groups(host=nil, roles=[])
45
45
  env = rubber_cfg.environment.bind(roles, host)
46
- security_group_defns = env.security_groups
46
+ security_group_defns = Hash[env.security_groups.to_a]
47
47
  if env.auto_security_groups
48
48
  sghosts = (rubber_instances.collect{|ic| ic.name } + [host]).uniq.compact
49
49
  sgroles = (rubber_instances.all_roles + roles).uniq.compact
@@ -161,80 +161,96 @@ namespace :rubber do
161
161
  end
162
162
  end
163
163
 
164
+ def record_key(record)
165
+ "#{record[:host]}.#{record[:domain]} #{record[:type]}"
166
+ end
167
+
168
+ def convert_to_new_dns_format(records)
169
+ record = {}
170
+ records.each do |r|
171
+ record[:host] ||= r[:host]
172
+ record[:domain] ||= r[:domain]
173
+ record[:type] ||= r[:type]
174
+ record[:ttl] ||= r[:ttl] if r[:ttl]
175
+ record[:data] ||= []
176
+ record[:data].concat(Array(r[:data]))
177
+ end
178
+ return record
179
+ end
180
+
164
181
  desc <<-DESC
165
182
  Sets up the additional dns records supplied in the dns_records config in rubber.yml
166
183
  DESC
167
184
  required_task :setup_dns_records do
168
185
  records = rubber_env.dns_records
169
186
  if records && rubber_env.dns_provider
170
- provider = Rubber::Dns::get_provider(rubber_env.dns_provider, rubber_env)
171
-
172
- # collect the round robin records (those with the same host/domain/type)
173
- rr_records = []
174
- records.each_with_index do |record, i|
175
- m = records.find_all {|r| record['host'] == r['host'] && record['domain'] == r['domain'] && record['type'] == r['type']}
176
- m = m.sort {|a,b| a.object_id <=> b.object_id}
177
- rr_records << m if m.size > 1 && ! rr_records.include?(m)
178
- end
179
-
180
- # simple records are those that aren't round robin ones
181
- simple_records = records - rr_records.flatten
182
187
 
183
- # for each simple record, create or update as necessary
184
- simple_records.each do |record|
185
- matching = provider.find_host_records(:host => record['host'], :domain =>record['domain'], :type => record['type'])
186
- if matching.size > 1
187
- msg = "Multiple records in dns provider, but not in rubber.yml\n"
188
- msg << "Round robin records need to be in both, or neither.\n"
189
- msg << "Please fix manually:\n"
190
- msg << matching.pretty_inspect
191
- fatal(msg)
192
- end
193
-
194
- record = provider.setup_opts(record)
195
- if matching.size == 1
196
- match = matching.first
197
- if provider.host_records_equal?(record, match)
198
- logger.info "Simple dns record already up to date: #{record[:host]}.#{record[:domain]}:#{record[:type]} => #{record[:data]}"
199
- else
200
- logger.info "Updating simple dns record: #{record[:host]}.#{record[:domain]}:#{record[:type]} => #{record[:data]}"
201
- provider.update_host_record(match, record)
202
- end
203
- else
204
- logger.info "Creating simple dns record: #{record[:host]}.#{record[:domain]}:#{record[:type]} => #{record[:data]}"
205
- provider.create_host_record(record)
206
- end
188
+ provider_name = rubber_env.dns_provider
189
+ provider = Rubber::Dns::get_provider(provider_name, rubber_env)
190
+
191
+ # records in rubber_env.dns_records can either have a value which
192
+ # is an array, or multiple equivalent (same host+type)items with
193
+ # value being a string, so try and normalize them
194
+ rubber_records = {}
195
+ records.each do |record|
196
+ record = Rubber::Util.symbolize_keys(record)
197
+ key = record_key(record)
198
+ rubber_records[key] ||= []
199
+ rubber_records[key] << record
200
+ end
201
+ rubber_records = Hash[rubber_records.collect {|key, records| [key, convert_to_new_dns_format(records)] }]
202
+
203
+ provider_records = {}
204
+ domains = rubber_records.values.collect {|r| r[:domain] }.uniq
205
+ precords = domains.collect {|d| provider.find_host_records(:host => '*', :type => '*', :domain => d) }.flatten
206
+ precords.each do |record|
207
+ key = record_key(record)
208
+ raise "unmerged provider records" if provider_records[key]
209
+ provider_records[key] = record
207
210
  end
208
211
 
209
- # group round robin records
210
- rr_records.each do |rr_group|
211
- host = rr_group.first['host']
212
- domain = rr_group.first['domain']
213
- type = rr_group.first['type']
214
- matching = provider.find_host_records(:host => host, :domain => domain, :type => type)
215
-
216
- # remove from consideration the local records that are the same as remote ones
217
- matching.clone.each do |r|
218
- rr_group.delete_if {|rg| provider.host_records_equal?(r, rg) }
219
- matching.delete_if {|rg| provider.host_records_equal?(r, rg) }
220
- end
221
- if rr_group.size == 0 && matching.size == 0
222
- logger.info "Round robin dns records already up to date: #{host}.#{domain}:#{type}"
223
- end
224
-
225
- # create the local records that don't exist remotely
226
- rr_group.each do |r|
227
- r = provider.setup_opts(r)
228
- logger.info "Creating round robin dns record: #{r[:host]}.#{r[:domain]}:#{r[:type]} => #{r[:data]}"
229
- provider.create_host_record(r)
230
- end
231
-
232
- # remove the remote records that don't exist locally
233
- matching.each do |r|
234
- logger.info "Removing round robin dns record: #{r[:host]}.#{r[:domain]}:#{r[:type]} => #{r[:data]}"
235
- provider.destroy_host_record(r)
212
+ changes = Hash[(rubber_records.to_a - provider_records.to_a) | (provider_records.to_a - rubber_records.to_a)]
213
+
214
+ logger.info "Applying #{changes.size} changes"
215
+ changes.each do |key, record|
216
+ old_record = provider_records[key]
217
+ new_record = rubber_records[key]
218
+ if old_record && new_record
219
+ # already exists in provider, so modify it
220
+ diff = Hash[(old_record.to_a - new_record.to_a) | (new_record.to_a - old_record.to_a)]
221
+ logger.info "Updating dns record: #{old_record.inspect} changes: #{diff.inspect}"
222
+ #provider.update_host_record(old_record, new_record)
223
+ elsif !old_record && new_record
224
+ # doesn't yet exist in provider, so create it
225
+ logger.info "Creating dns record: #{new_record.inspect}"
226
+ #provider.create_host_record(new_record)
227
+ elsif old_record && ! new_record
228
+ # ignore these since it shows all the instances created by rubber
229
+ #
230
+ #logger.info "Provider record doesn't exist locally: #{old_record.inspect}"
231
+ #if ENV['FORCE']
232
+ # destroy_dns_record = ENV['FORCE'] =~ /^(t|y)/
233
+ #else
234
+ # destroy_dns_record = get_env('DESTROY_DNS', "Destroy DNS record in provider [y/N]?", true)
235
+ #end
236
+ #provider.destroy_host_record(old_record) if destroy_dns_record
236
237
  end
237
238
  end
239
+
240
+ end
241
+ end
242
+
243
+ desc <<-DESC
244
+ Exports dns records from your provider into the format readable by rubber in rubber-dns.yml
245
+ DESC
246
+ required_task :export_dns_records do
247
+ if rubber_env.dns_provider
248
+
249
+ provider_name = rubber_env.dns_provider
250
+ provider = Rubber::Dns::get_provider(provider_name, rubber_env)
251
+
252
+ provider_records = provider.find_host_records(:host => '*', :type => '*', :domain => rubber_env.domain)
253
+ puts({'dns_records' => provider_records.collect {|r| Rubber::Util.stringify_keys(r)}}.to_yaml)
238
254
  end
239
255
  end
240
256
 
@@ -371,7 +387,7 @@ namespace :rubber do
371
387
  task :set_timezone do
372
388
  opts = get_host_options('timezone')
373
389
  rsudo "echo $CAPISTRANO:VAR$ > /etc/timezone", opts
374
- rsudo "cp /usr/share/zoneinfo/$CAPISTRANO:VAR$ /etc/localtime", opts
390
+ rsudo "ln -sf /usr/share/zoneinfo/$CAPISTRANO:VAR$ /etc/localtime", opts
375
391
  # restart syslog so that times match timezone
376
392
  sudo_script 'restart_syslog', <<-ENDSCRIPT
377
393
  if [[ -x /etc/init.d/sysklogd ]]; then
data/lib/rubber/util.rb CHANGED
@@ -10,6 +10,13 @@ module Rubber
10
10
  end
11
11
  end
12
12
 
13
+ def stringify_keys(map)
14
+ map.inject({}) do |options, (key, value)|
15
+ options[key.to_s || key] = value
16
+ options
17
+ end
18
+ end
19
+
13
20
  def stringify(val)
14
21
  case val
15
22
  when String
@@ -1,4 +1,4 @@
1
1
  module Rubber
2
- VERSION = "2.0.5"
2
+ VERSION = "2.0.6"
3
3
  end
4
4
 
@@ -103,5 +103,17 @@ namespace :rubber do
103
103
  ENDSCRIPT
104
104
  end
105
105
 
106
+ # Update /etc/sudoers so that SSH-related environment variables so capistrano/rubber tasks can take advantage of ssh-agent forwarding
107
+ before "rubber:bootstrap", "rubber:base:update_sudoers"
108
+ task :update_sudoers do
109
+ rubber.sudo_script "update_sudoers", <<-ENDSCRIPT
110
+ if [[ ! `grep 'SSH_CLIENT SSH_TTY SSH_CONNECTION SSH_AUTH_SOCK' /etc/sudoers` =~ "SSH_CLIENT SSH_TTY SSH_CONNECTION SSH_AUTH_SOCK" ]]; then
111
+ echo '' >> /etc/sudoers
112
+ echo '# whitelist SSH-related environment variables so capistrano tasks can take advantage of ssh-agent forwarding' >> /etc/sudoers
113
+ echo 'Defaults env_keep += "SSH_CLIENT SSH_TTY SSH_CONNECTION SSH_AUTH_SOCK"' >> /etc/sudoers
114
+ fi
115
+ ENDSCRIPT
116
+ end
117
+
106
118
  end
107
119
  end
@@ -5,11 +5,9 @@
5
5
  # This lets rubber update a dynamic dns service with the instance alias and ip
6
6
  #
7
7
  dns_providers:
8
- fog:
9
- credentials:
10
- provider: aws
11
- aws_access_key_id: "#{cloud_providers.aws.access_key}"
12
- aws_secret_access_key: "#{cloud_providers.aws.secret_access_key}"
8
+ aws:
9
+ access_key: "#{cloud_providers.aws.access_key}"
10
+ access_secret: "#{cloud_providers.aws.secret_access_key}"
13
11
  type: A
14
12
  ttl: 300
15
13
  nettica:
@@ -35,18 +33,18 @@ dns_providers:
35
33
  # dns_records:
36
34
  # # simple A record
37
35
  # - host: bar
38
- # data: 1.1.1.1
36
+ # data: [1.1.1.1]
39
37
  #
40
38
  # # more detailed A record
41
39
  # - host: bar
42
40
  # domain: otherdomain.com
43
- # data: 1.1.1.1
41
+ # data: [1.1.1.1]
44
42
  # type: A
45
43
  # ttl: 300
46
44
  #
47
45
  # # tld A record
48
46
  # - host: ''
49
- # data: 1.1.1.1
47
+ # data: [1.1.1.1]
50
48
  # type: A
51
49
  #
52
50
  # # tld ALIAS record -- Route53 extension to point tld at an Elastic Load Balancer group
@@ -54,40 +52,38 @@ dns_providers:
54
52
  # domain: example.com
55
53
  # type: A
56
54
  # data:
57
- # hosted_zone_id: XXYYZZ
58
- # dns_name: my-app-elb-group.us-east-1.elb.amazonaws.com
55
+ # - hosted_zone_id: XXYYZZ
56
+ # dns_name: my-app-elb-group.us-east-1.elb.amazonaws.com
59
57
  #
60
58
  # # simple CNAME record
61
59
  # - host: otherbar
62
60
  # domain: foo.com
63
- # data: bar.foo.com
61
+ # data: [bar.foo.com]
64
62
  # type: CNAME
65
63
  # ttl: 300
66
64
  #
67
- # # 2 of the same A records is a round robin dns
65
+ # # Round robin dns record
68
66
  # - host: rr
69
67
  # domain: foo.com
70
- # data: 1.1.1.1
71
- # type: A
72
- # ttl: 300
73
- # - host: rr
74
- # domain: foo.com
75
- # data: 1.1.1.2
68
+ # data: [1.1.1.1, 1.1.1.2]
76
69
  # type: A
77
70
  # ttl: 300
78
71
  #
79
72
  # # A record, grabbing ip from instance config
80
73
  # - host: baz
81
74
  # domain: foo.com
82
- # data: "#{rubber_instances.for_role('web').first.external_ip}"
75
+ # data: ["#{rubber_instances.for_role('web').first.external_ip}"]
83
76
  # type: A
84
77
  # ttl: 300
85
78
  #
86
79
  # # MX record
87
80
  # - host: ''
88
81
  # domain: foo.com
89
- # data: mail.foo.com
82
+ # data:
83
+ # - priority: 5
84
+ # value: mail1.foo.com
85
+ # - priority: 10
86
+ # value: mail2.foo.com
90
87
  # type: MX
91
88
  # ttl: 300
92
- # priority: 10
93
89