rubber 2.0.5 → 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
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