rubber 1.0.2 → 1.1.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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.2
1
+ 1.1.0
@@ -2,6 +2,5 @@
2
2
  roles:
3
3
  apache:
4
4
  packages: [apache2]
5
- roles:
6
5
  web_tools:
7
6
  packages: [libapache2-mod-proxy-html]
@@ -20,26 +20,9 @@ namespace :rubber do
20
20
  if ent_ruby_hosts.size > 0
21
21
 
22
22
  task :_install_enterprise_ruby, :hosts => ent_ruby_hosts do
23
-
24
- # preferences to pick up specific Ruby packages from brightbox
25
- prefs = <<-DATA
26
- Package: *
27
- Pin: release l=brightbox
28
- Pin-Priority: 1001
29
-
30
- Package: ruby1.8-elisp
31
- Pin: release l=Ubuntu
32
- Pin-Priority: 1001
33
- DATA
34
-
35
- prefs.gsub!(/^ */, '') # remove leading whitespace
36
- put(prefs, '/etc/apt/preferences')
37
-
38
- rubber.sudo_script 'install_enterprise_ruby', <<-ENDSCRIPT
39
- wget http://apt.brightbox.net/release.asc -O - | apt-key add -
40
- echo "deb http://apt.brightbox.net/ hardy rubyee" > /etc/apt/sources.list.d/brightbox-rubyee.list
41
- ENDSCRIPT
42
-
23
+ custom_package('http://rubyforge.org/frs/download.php/64479',
24
+ 'ruby-enterprise', '1.8.7-20090928',
25
+ '! `ruby --version 2> /dev/null` =~ "Ruby Enterprise Edition 20090928"')
43
26
  end
44
27
 
45
28
  _install_enterprise_ruby
@@ -0,0 +1,79 @@
1
+ # OPTIONAL: The dns provider to use. Need to exist in dns_providers below
2
+ # dns_provider: nettica
3
+
4
+ # OPTIONAL: The configuration for each dns provider (nettica|zerigo|dyndns)
5
+ # This lets rubber update a dynamic dns service with the instance alias and ip
6
+ #
7
+ dns_providers:
8
+ nettica:
9
+ user: joe
10
+ password: sekret
11
+ type: A
12
+ ttl: 300
13
+ zerigo:
14
+ customer_id: 1234
15
+ email: foo@bar.com
16
+ token: hexxy
17
+ type: A
18
+ ttl: 300
19
+ dyndns:
20
+ user: joe
21
+ password: sekret
22
+ update_url: https://members.dyndns.org/nic/update?hostname=%host%&myip=%ip%'
23
+
24
+ # OPTIONAL: Lets you configure your dns service, for example to add other CNAMES
25
+ # or setup dns round robin, etc. Run "cap rubber:setup_dns_records"
26
+ # to apply them as rubber only sets up instance aliases as part of
27
+ # the standard lifecycle
28
+ #
29
+ # dns_records:
30
+ # # simple A record
31
+ # - host: bar
32
+ # data: 1.1.1.1
33
+ #
34
+ # # more detailed A record
35
+ # - host: bar
36
+ # domain: otherdomain.com
37
+ # data: 1.1.1.1
38
+ # type: A
39
+ # ttl: 300
40
+ #
41
+ # # tld A record
42
+ # - host: ''
43
+ # data: 1.1.1.1
44
+ # type: A
45
+ #
46
+ # # simple CNAME record
47
+ # - host: otherbar
48
+ # domain: foo.com
49
+ # data: bar.foo.com
50
+ # type: CNAME
51
+ # ttl: 300
52
+ #
53
+ # # 2 of the same A records is a round robin dns
54
+ # - host: rr
55
+ # domain: foo.com
56
+ # data: 1.1.1.1
57
+ # type: A
58
+ # ttl: 300
59
+ # - host: rr
60
+ # domain: foo.com
61
+ # data: 1.1.1.2
62
+ # type: A
63
+ # ttl: 300
64
+ #
65
+ # # A record, grabbing ip from instance config
66
+ # - host: baz
67
+ # domain: foo.com
68
+ # data: "#{rubber_instances.for_role('web').first.external_ip}"
69
+ # type: A
70
+ # ttl: 300
71
+ #
72
+ # # MX record
73
+ # - host: ''
74
+ # domain: foo.com
75
+ # data: mail.foo.com
76
+ # type: MX
77
+ # ttl: 300
78
+ # priority: 10
79
+
@@ -32,28 +32,10 @@ timezone: US/Eastern
32
32
  #
33
33
  domain: foo.com
34
34
 
35
- # OPTIONAL: The configuration for each dns provider (nettica|zerigo|dyndns)
36
- # This lets rubber updatea dynamic dns service with the instance alias and ip
37
- #
38
- dns_providers:
39
- nettica:
40
- user: joe
41
- password: sekret
42
- record_type: A
43
- ttl: 300
44
- zerigo:
45
- customer_id: 1234
46
- email: foo@bar.com
47
- token: hexxy
48
- record_type: A
49
- ttl: 300
50
- dyndns:
51
- user: joe
52
- password: sekret
53
- update_url: https://members.dyndns.org/nic/update?hostname=%host%&myip=%ip%'
54
-
55
- # OPTIONAL: The dns provider to use
56
- # dns_provider: nettica
35
+ # OPTIONAL: See rubber-dns.yml for dns configuration
36
+ # This lets rubber update a dynamic dns service with the instance alias
37
+ # and ip when they are created. It also allows setting up arbitrary
38
+ # dns records (CNAME, MX, Round Robin DNS, etc)
57
39
 
58
40
  # OPTIONAL: Additional rubber file to pull config from if it exists. This file will
59
41
  # also be pushed to remote host at RUBBER_ROOT/config/rubber/rubber-secret.yml
@@ -144,12 +126,12 @@ use_enterprise_ruby: false
144
126
  packages: [postfix, build-essential, ruby-full, ruby1.8-dev, rake, irb]
145
127
 
146
128
  # OPTIONAL: gem sources to setup for rubygems
147
- gemsources: ["http://gems.rubyforge.org/", "http://gems.github.com"]
129
+ gemsources: ["http://gemcutter.org", "http://gems.rubyforge.org/", "http://gems.github.com"]
148
130
 
149
131
  # OPTIONAL: The gems to install on all instances
150
132
  # You can install a specific version of a gem by using a sub-array of gem, version
151
133
  # For example, gem: [[rails, 2.2.2], open4, aws-s3]
152
- gems: [wr0ngway-rubber, rails, open4, aws-s3]
134
+ gems: [rubber, rails, open4, aws-s3]
153
135
 
154
136
  # OPTIONAL: A string prepended to shell command strings that cause multi
155
137
  # statement shell commands to fail fast. You may need to comment this out
@@ -8,12 +8,14 @@
8
8
  %>
9
9
 
10
10
  listen passenger_proxy 0.0.0.0:<%= rubber_env.web_port %>
11
+ option forwardfor
11
12
  <% backend_hosts.each do |server| %>
12
13
  server <%= server %> <%= server %>:<%= rubber_env.passenger_listen_port %> maxconn <%= rubber_env.max_app_connections %> check
13
14
  <% end %>
14
15
 
15
16
  listen passenger_proxy 0.0.0.0:<%= rubber_env.web_ssl_port %>
16
17
  mode tcp
18
+ option forwardfor
17
19
  <% backend_hosts.each do |server| %>
18
20
  server <%= server %> <%= server %>:<%= rubber_env.passenger_listen_ssl_port %> maxconn <%= rubber_env.max_app_connections %> check
19
21
  <% end %>
@@ -9,7 +9,7 @@ namespace :rubber do
9
9
 
10
10
  task :custom_install, :roles => :passenger do
11
11
  rubber.sudo_script 'install_passenger', <<-ENDSCRIPT
12
- if [[ -z `ls /usr/lib/ruby/gems/*/gems/passenger-#{rubber_env.passenger_version}/ext/apache2/mod_passenger.so 2> /dev/null` ]]; then
12
+ if [[ -z `ls /usr/local/lib/ruby/gems/*/gems/passenger-#{rubber_env.passenger_version}/ext/apache2/mod_passenger.so 2> /dev/null` ]]; then
13
13
  echo -en "\n\n\n\n" | passenger-install-apache2-module
14
14
  fi
15
15
  ENDSCRIPT
@@ -2,9 +2,9 @@
2
2
  @path = '/etc/apache2/mods-available/passenger.conf'
3
3
  @post = 'cd /etc/apache2/mods-enabled && ln -fs ../mods-available/passenger.conf'
4
4
  %>
5
- LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-<%= rubber_env.passenger_version %>/ext/apache2/mod_passenger.so
6
- PassengerRoot /usr/lib/ruby/gems/1.8/gems/passenger-<%= rubber_env.passenger_version %>
7
- PassengerRuby /usr/bin/ruby
5
+ LoadModule passenger_module /usr/local/lib/ruby/gems/1.8/gems/passenger-<%= rubber_env.passenger_version %>/ext/apache2/mod_passenger.so
6
+ PassengerRoot /usr/local/lib/ruby/gems/1.8/gems/passenger-<%= rubber_env.passenger_version %>
7
+ PassengerRuby /usr/local/bin/ruby
8
8
  PassengerUseGlobalQueue on
9
9
 
10
10
  PassengerMaxPoolSize <%= rubber_env.max_app_connections %>
@@ -255,6 +255,7 @@ module Rubber
255
255
 
256
256
  def destroy_image(image_id)
257
257
  image = describe_images(image_id).first
258
+ raise "Could not find image: #{image_id}, aborting destroy_image"
258
259
  image_location = image[:location]
259
260
  bucket = image_location.split('/').first
260
261
  image_name = image_location.split('/').last.gsub(/\.manifest\.xml$/, '')
@@ -3,65 +3,80 @@ module Rubber
3
3
 
4
4
  class Base
5
5
 
6
- attr_reader :env
6
+ attr_reader :env, :provider_env, :provider_name
7
7
 
8
- def initialize(env)
8
+ def initialize(env, provider_name)
9
9
  @env = env
10
+ @provider_name = provider_name
11
+ @provider_env = @env.dns_providers[provider_name]
10
12
  end
11
13
 
12
14
  def update(host, ip)
13
15
  if up_to_date(host, ip)
14
16
  puts "IP has not changed for #{host}, not updating dynamic DNS"
15
17
  else
16
- if ! host_exists?(host)
18
+ if find_host_records(:host => host).size == 0
17
19
  puts "Creating dynamic DNS: #{host} => #{ip}"
18
- create_host_record(host, ip)
20
+ create_host_record(:host => host, :data => ip)
19
21
  else
20
22
  puts "Updating dynamic DNS: #{host} => #{ip}"
21
- update_host_record(host, ip)
23
+ update_host_record({:host => host}, {:host => host, :data => ip})
22
24
  end
23
25
  end
24
26
  end
25
27
 
26
28
  def destroy(host)
27
- if host_exists?(host)
29
+ if find_host_records(:host => host).size != 0
28
30
  puts "Destroying dynamic DNS record: #{host}"
29
- destroy_host_record(host)
31
+ destroy_host_record(:host => host)
30
32
  end
31
33
  end
32
34
 
33
- def hostname(host)
34
- "#{host}.#{@env.domain}"
35
- end
36
-
37
35
  def up_to_date(host, ip)
38
- # This queries dns server directly instead of using hosts file
39
- current_ip = nil
40
- Resolv::DNS.open(:nameserver => [nameserver], :search => [], :ndots => 1) do |dns|
41
- current_ip = dns.getaddress(hostname(host)).to_s rescue nil
42
- end
43
- return ip == current_ip
36
+ find_host_records(:host => host).any? {|host| host[:data] == ip}
44
37
  end
45
38
 
46
- def nameserver()
47
- raise "nameserver not implemented"
39
+ def create_host_record(opts = {})
40
+ raise "create_host_record not implemented"
48
41
  end
49
42
 
50
- def host_exists?(host)
51
- raise "host_exists? not implemented"
43
+ def find_host_records(opts = {})
44
+ raise "find_host_records not implemented"
52
45
  end
53
46
 
54
- def create_host_record(host, ip)
55
- raise "create_host_record not implemented"
47
+ def update_host_record(old_opts={}, new_opts={})
48
+ raise "update_host_record not implemented"
56
49
  end
57
50
 
58
- def destroy_host_record(host)
51
+ def destroy_host_record(opts = {})
59
52
  raise "destroy_host_record not implemented"
60
53
  end
61
54
 
62
- def update_host_record(host, ip)
63
- raise "update_host_record not implemented"
55
+ def host_records_equal?(lhs_opts = {}, rhs_opts = {})
56
+ lhs = setup_opts(lhs_opts)
57
+ rhs = setup_opts(rhs_opts)
58
+ [lhs, rhs].each {|h| h.delete(:id); h.delete(:priority) if h[:priority] == 0}
59
+ lhs == rhs
60
+ end
61
+
62
+ def setup_opts(opts, required=[])
63
+ default_opts = {:domain => @env.domain,
64
+ :type => @provider_env['type'] || @provider_env.record_type || 'A',
65
+ :ttl => @provider_env.ttl || 300}
66
+
67
+ if opts.delete(:no_defaults)
68
+ actual_opts = Rubber::Util::symbolize_keys(opts)
69
+ else
70
+ actual_opts = default_opts.merge(Rubber::Util::symbolize_keys(opts))
71
+ end
72
+
73
+ required.each do |r|
74
+ raise "Missing required options: #{r}" unless actual_opts[r]
75
+ end
76
+
77
+ return actual_opts
64
78
  end
79
+
65
80
 
66
81
  end
67
82
 
@@ -4,10 +4,9 @@ module Rubber
4
4
  class Dyndns < Base
5
5
 
6
6
  def initialize(env)
7
- super(env)
8
- @dyndns_env = env.dns_providers.dyndns
9
- @user, @pass = @dyndns_env.user, @dyndns_env.password
10
- @update_url = @dyndns_env.update_url || 'https://members.dyndns.org/nic/update?hostname=%host%&myip=%ip%'
7
+ super(env, 'dyndns')
8
+ @user, @pass = provider_env.user, provider_env.password
9
+ @update_url = provider_env.update_url || 'https://members.dyndns.org/nic/update?hostname=%host%&myip=%ip%'
11
10
  @update_url = @update_url.gsub(/%([^%]+)%/, '#{\1}')
12
11
  end
13
12
 
@@ -15,10 +14,22 @@ module Rubber
15
14
  "ns1.mydyndns.org"
16
15
  end
17
16
 
18
- def host_exists?(host)
17
+ def up_to_date(host, ip)
18
+ # This queries dns server directly instead of using hosts file
19
+ current_ip = nil
20
+ Resolv::DNS.open(:nameserver => [nameserver], :search => [], :ndots => 1) do |dns|
21
+ current_ip = dns.getaddress("#{host}.#{provider_env.domain}").to_s rescue nil
22
+ end
23
+ return ip == current_ip
24
+ end
25
+
26
+ def find_host_records(opts={})
27
+ opts = setup_opts(opts, [:host, :domain])
28
+ hostname = "#{opts[:host]}.#{opts[:domain]}"
19
29
  begin
20
30
  Resolv::DNS.open(:nameserver => [nameserver], :search => [], :ndots => 1) do |dns|
21
- dns.getresource(hostname(host), Resolv::DNS::Resource::IN::A)
31
+ r = dns.getresource(hostname, Resolv::DNS::Resource::IN::A)
32
+ result = [{:host =>host, :data => r.address}]
22
33
  end
23
34
  rescue
24
35
  raise "Domain needs to exist in dyndns as an A record before record can be updated"
@@ -26,16 +37,20 @@ module Rubber
26
37
  return true
27
38
  end
28
39
 
29
- def create_host_record(host, ip)
40
+ def create_host_record(opts={})
30
41
  puts "WARNING: No create record available for dyndns, you need to do so manually"
31
42
  end
32
43
 
33
- def destroy_host_record(host)
44
+ def destroy_host_record(opts={})
34
45
  puts "WARNING: No destroy record available for dyndns, you need to do so manually"
35
46
  end
36
47
 
37
- def update_host_record(host, ip)
38
- host = hostname(host)
48
+ def update_host_record(old_opts={}, new_opts={})
49
+ old_opts = setup_opts(opts, [:host, :domain])
50
+ new_opts = setup_opts(opts, [:data])
51
+
52
+ host = hostname(old_opts[:host])
53
+ ip = new_opts[:data]
39
54
  update_url = eval('%Q{' + @update_url + '}')
40
55
 
41
56
  # This header is required by dyndns.org
@@ -5,15 +5,8 @@ module Rubber
5
5
  class Nettica < Base
6
6
 
7
7
  def initialize(env)
8
- super(env)
9
- @nettica_env = @env.dns_providers.nettica
10
- @client = ::Nettica::Client.new(@nettica_env.user, @nettica_env.password)
11
- @ttl = (@nettica_env.ttl || 300).to_i
12
- @record_type = @nettica_env.record_type || "A"
13
- end
14
-
15
- def nameserver
16
- "dns1.nettica.com"
8
+ super(env, "nettica")
9
+ @client = ::Nettica::Client.new(provider_env.user, provider_env.password)
17
10
  end
18
11
 
19
12
  def check_status(response)
@@ -33,40 +26,91 @@ module Rubber
33
26
  return response
34
27
  end
35
28
 
36
- def host_exists?(host)
37
- domain_info = check_status @client.list_domain(env.domain)
38
- raise "Domain needs to exist in nettica before records can be updated" unless domain_info.record
39
- return domain_info.record.any? { |r| r.hostName == host }
29
+ def find_host_records(opts = {})
30
+ opts = setup_opts(opts, [:host, :domain])
31
+
32
+ result = []
33
+ hn = opts[:host]
34
+ ht = opts[:type]
35
+ hd = opts[:data]
36
+
37
+ domain_info = find_or_create_zone(opts[:domain])
38
+
39
+ domain_info.record.each do |h|
40
+ keep = true
41
+ if hn && h.hostName != hn && hn != '*'
42
+ keep = false
43
+ end
44
+ if ht && h.recordType != ht && ht != '*'
45
+ keep = false
46
+ end
47
+ if hd && h.data != hd
48
+ keep = false
49
+ end
50
+ result << record_to_opts(h) if keep
51
+ end
52
+
53
+ return result
40
54
  end
41
55
 
42
- def create_host_record(host, ip)
43
- new = @client.create_domain_record(env.domain, host, @record_type, ip, @ttl, 0)
44
- check_status @client.add_record(new)
56
+ def create_host_record(opts = {})
57
+ opts = setup_opts(opts, [:host, :data, :domain, :type, :ttl])
58
+ find_or_create_zone(opts[:domain])
59
+ record = opts_to_record(opts)
60
+ check_status @client.add_record(record)
45
61
  end
46
62
 
47
- def destroy_host_record(host)
48
- old_record = check_status(@client.list_domain(env.domain)).record.find {|r| r.hostName == host }
49
- old = @client.create_domain_record(env.domain, host, old_record.recordType, old_record.data, old_record.tTL, old_record.priority)
50
- check_status @client.delete_record(old)
63
+ def destroy_host_record(opts = {})
64
+ find_host_records(opts).each do |h|
65
+ record = opts_to_record(h)
66
+ check_status @client.delete_record(record)
67
+ end
51
68
  end
52
69
 
53
- def update_host_record(host, ip)
54
- old_record = check_status(@client.list_domain(env.domain)).record.find {|r| r.hostName == host }
55
- update_record(host, ip, old_record)
70
+ def update_host_record(old_opts = {}, new_opts = {})
71
+ old_opts = setup_opts(old_opts, [:host, :domain])
72
+ new_opts = setup_opts(new_opts.merge(:no_defaults =>true), [])
73
+ find_host_records(old_opts).each do |h|
74
+ old_record = opts_to_record(h)
75
+ new_record = opts_to_record(h.merge(new_opts))
76
+ check_status @client.update_record(old_record, new_record)
77
+ end
56
78
  end
57
79
 
58
- # update the top level domain record which has an empty hostName
59
- def update_domain_record(ip)
60
- old_record = check_status(@client.list_domain(env.domain)).record.find {|r| r.hostName == '' and r.recordType == 'A' and r.data =~ /\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/}
61
- update_record('', ip, old_record)
80
+ private
81
+
82
+ def find_or_create_zone(domain)
83
+ domain_info = @client.list_domain(domain)
84
+ if domain_info.record
85
+ check_status domain_info
86
+ else
87
+ check_status @client.create_zone(domain)
88
+ domain_info = check_status @client.list_domain(domain)
89
+ raise "Could not create zone in nettica: #{domain}" unless domain_info.record
90
+ end
91
+ return domain_info
62
92
  end
63
93
 
64
- def update_record(host, ip, old_record)
65
- old = @client.create_domain_record(env.domain, host, old_record.recordType, old_record.data, old_record.tTL, old_record.priority)
66
- new = @client.create_domain_record(env.domain, host, @record_type, ip, @ttl, 0)
67
- check_status @client.update_record(old, new)
94
+ def opts_to_record(opts)
95
+ record = @client.create_domain_record(opts[:domain],
96
+ opts[:host],
97
+ opts[:type],
98
+ opts[:data],
99
+ opts[:ttl],
100
+ opts[:priority] || 0)
101
+ return record
68
102
  end
69
103
 
104
+ def record_to_opts(record)
105
+ opts = {}
106
+ opts[:host] = record.hostName || ''
107
+ opts[:domain] = record.domainName
108
+ opts[:type] = record.recordType
109
+ opts[:data] = record.data if record.data
110
+ opts[:ttl] = record.tTL if record.tTL
111
+ opts[:priority] = record.priority if record.priority
112
+ return opts
113
+ end
70
114
  end
71
115
 
72
116
  end
@@ -9,6 +9,11 @@ module Rubber
9
9
  include HTTParty
10
10
  format :xml
11
11
 
12
+ @@zones = {}
13
+ def self.get_zone(domain, provider_env)
14
+ @@zones[domain] ||= Zone.new(provider_env.customer_id, provider_env.email, provider_env.token, domain)
15
+ end
16
+
12
17
  def initialize(customer_id, email, token, domain)
13
18
  self.class.basic_auth email, token
14
19
  self.class.base_uri "https://ns.zerigo.com/accounts/#{customer_id}"
@@ -26,31 +31,49 @@ module Rubber
26
31
  return response
27
32
  end
28
33
 
29
- def hosts
30
- hosts = check_status self.class.get("/zones/#{@zone['id']}/hosts.xml")
31
- return hosts['hosts']
34
+ def create_host(opts)
35
+ host = opts_to_host(opts, new_host())
36
+ check_status self.class.post("/zones/#{@zone['id']}/hosts.xml", :body => {:host => host})
32
37
  end
33
38
 
34
- def host(hostname)
35
- hosts = check_status self.class.get("/zones/#{@zone['id']}/hosts.xml?fqdn=#{hostname}.#{@domain}")
36
- return (hosts['hosts'] || []).first
37
- end
39
+ def find_host_records(opts={})
40
+ result = []
41
+ hn = opts[:host]
42
+ ht = opts[:type]
43
+ hd = opts[:data]
44
+ has_host = hn && hn != '*'
38
45
 
39
- def new_host
40
- check_status(self.class.get("/zones/#{@zone['id']}/hosts/new.xml"))['host']
41
- end
46
+ url = "/zones/#{@zone['id']}/hosts.xml"
47
+ if has_host
48
+ url << "?fqdn="
49
+ url << "#{hn}." if hn.strip.size > 0
50
+ url << "#{@domain}"
51
+ end
52
+ hosts = self.class.get(url)
42
53
 
43
- def create_host(host)
44
- check_status self.class.post("/zones/#{@zone['id']}/hosts.xml", :body => {:host => host})
54
+ # returns 404 on not found, so don't check status
55
+ hosts = check_status hosts unless has_host
56
+
57
+ hosts['hosts'].each do |h|
58
+ keep = true
59
+ if ht && h['host_type'] != ht && ht != '*'
60
+ keep = false
61
+ end
62
+ if hd && h['data'] != hd
63
+ keep = false
64
+ end
65
+ result << host_to_opts(h) if keep
66
+ end if hosts['hosts']
67
+
68
+ return result
45
69
  end
46
70
 
47
- def update_host(host)
48
- host_id = host['id']
71
+ def update_host(host_id, opts)
72
+ host = opts_to_host(opts, new_host())
49
73
  check_status self.class.put("/zones/#{@zone['id']}/hosts/#{host_id}.xml", :body => {:host => host})
50
74
  end
51
75
 
52
- def delete_host(hostname)
53
- host_id = host(hostname)['id']
76
+ def delete_host(host_id)
54
77
  check_status self.class.delete("/zones/#{@zone['id']}/hosts/#{host_id}.xml")
55
78
  end
56
79
 
@@ -62,67 +85,87 @@ module Rubber
62
85
  zones = check_status self.class.get('/zones.xml')
63
86
  @zone = zones["zones"].find {|z| z["domain"] == @domain }
64
87
  end
88
+ if ! @zone
89
+ zone = new_zone()
90
+ zone['domain'] = @domain
91
+ zones = check_status self.class.post('/zones.xml', :body => {:zone => zone})
92
+ @zone = zones['zone']
93
+ end
65
94
  end
66
95
 
67
- def data
96
+ def zone_record
68
97
  return @zone
69
98
  end
70
99
 
71
- protected
100
+ private
72
101
 
73
- def zones()
74
- check_status self.class.get('/zones.xml')
102
+ def new_host
103
+ check_status(self.class.get("/zones/#{@zone['id']}/hosts/new.xml"))['host']
104
+ end
105
+
106
+ def new_zone
107
+ check_status(self.class.get("/zones/new.xml"))['zone']
108
+ end
109
+
110
+ def opts_to_host(opts, host={})
111
+ host['hostname'] = opts[:host]
112
+ host['host_type'] = opts[:type]
113
+ host['data'] = opts[:data] if opts[:data]
114
+ host['ttl'] = opts[:ttl] if opts[:ttl]
115
+ host['priority'] = opts[:priority] if opts[:priority]
116
+ return host
75
117
  end
76
118
 
77
- def zone(domain_name)
78
- zone = zones
79
- return zone
119
+ def host_to_opts(host)
120
+ opts = {}
121
+ opts[:id] = host['id']
122
+ opts[:domain] = @domain
123
+ opts[:host] = host['hostname'] || ''
124
+ opts[:type] = host['host_type']
125
+ opts[:data] = host['data'] if host['data']
126
+ opts[:ttl] = host['ttl'] if host['ttl']
127
+ opts[:priority] = host['priority'] if host['priority']
128
+ return opts
80
129
  end
81
-
82
130
  end
83
131
 
84
132
  class Zerigo < Base
85
133
 
86
134
  def initialize(env)
87
- super(env)
88
- @zerigo_env = env.dns_providers.zerigo
89
- @ttl = (@zerigo_env.ttl || 300).to_i
90
- @record_type = @zerigo_env.record_type || "A"
91
- @zone = Zone.new(@zerigo_env.customer_id, @zerigo_env.email, @zerigo_env.token, env.domain)
135
+ super(env, "zerigo")
92
136
  end
93
137
 
94
- def nameserver
95
- "a.ns.zerigo.net"
96
- end
138
+ def find_host_records(opts = {})
139
+ opts = setup_opts(opts, [:host, :domain])
140
+ zone = Zone.get_zone(opts[:domain], provider_env)
97
141
 
98
- def host_exists?(host)
99
- @zone.host(host)
142
+ zone.find_host_records(opts)
100
143
  end
101
144
 
102
- def create_host_record(hostname, ip)
103
- host = @zone.new_host()
104
- host['host-type'] = @record_type
105
- host['ttl'] = @ttl
106
- host['hostname'] = hostname
107
- host['data'] = ip
108
- @zone.create_host(host)
109
- end
145
+ def create_host_record(opts = {})
146
+ opts = setup_opts(opts, [:host, :data, :domain, :type, :ttl])
147
+ zone = Zone.get_zone(opts[:domain], provider_env)
110
148
 
111
- def destroy_host_record(host)
112
- @zone.delete_host(host)
149
+ zone.create_host(opts)
113
150
  end
114
151
 
115
- def update_host_record(host, ip)
116
- old = @zone.host(host)
117
- old['data'] = ip
118
- @zone.update_host(old)
152
+ def destroy_host_record(opts = {})
153
+ opts = setup_opts(opts, [:host, :domain])
154
+ zone = Zone.get_zone(opts[:domain], provider_env)
155
+
156
+ find_host_records(opts).each do |h|
157
+ zone.delete_host(h[:id])
158
+ end
119
159
  end
120
160
 
121
- # update the top level domain record which has an empty hostName
122
- def update_domain_record(ip)
123
- old = @zone.hosts.find {|h| h['hostname'].nil? }
124
- old['data'] = ip
125
- @zone.update_host(old)
161
+ def update_host_record(old_opts={}, new_opts={})
162
+ old_opts = setup_opts(old_opts, [:host, :domain])
163
+ new_opts = setup_opts(new_opts.merge(:no_defaults =>true), [])
164
+ zone = Zone.get_zone(old_opts[:domain], provider_env)
165
+
166
+ find_host_records(old_opts).each do |h|
167
+ zone.update_host(h[:id], h.merge(new_opts))
168
+ end
126
169
  end
127
170
 
128
171
  end
@@ -32,6 +32,8 @@ module Rubber
32
32
  roles_dir = File.join(@config_root, "role")
33
33
  roles = Dir.entries(roles_dir)
34
34
  roles.delete_if {|d| d =~ /(^\..*)/}
35
+ roles += @items['roles'].keys
36
+ return roles.compact.uniq
35
37
  end
36
38
 
37
39
  def current_host
@@ -41,7 +43,7 @@ module Rubber
41
43
  def current_full_host
42
44
  Socket::gethostname
43
45
  end
44
-
46
+
45
47
  def bind(roles = nil, host = nil)
46
48
  BoundEnv.new(@items, roles, host)
47
49
  end
@@ -95,6 +97,7 @@ module Rubber
95
97
  end
96
98
 
97
99
  def expand_string(val)
100
+ rubber_instances = Rubber::Configuration::rubber_instances
98
101
  while val =~ /\#\{[^\}]+\}/
99
102
  val = eval('%Q{' + val + '}', binding)
100
103
  end
@@ -61,6 +61,83 @@ namespace :rubber do
61
61
  end
62
62
  end
63
63
 
64
+ desc <<-DESC
65
+ Sets up the additional dns records supplied in the dns_records config in rubber.yml
66
+ DESC
67
+ required_task :setup_dns_records do
68
+ records = rubber_env.dns_records
69
+ if records && rubber_env.dns_provider
70
+ provider = Rubber::Dns::get_provider(rubber_env.dns_provider, rubber_env)
71
+
72
+ # collect the round robin records (those with the same host/domain/type)
73
+ rr_records = []
74
+ records.each_with_index do |record, i|
75
+ m = records.find_all {|r| record['host'] == r['host'] && record['domain'] == r['domain'] && record['type'] == r['type']}
76
+ m = m.sort {|a,b| a.object_id <=> b.object_id}
77
+ rr_records << m if m.size > 1 && ! rr_records.include?(m)
78
+ end
79
+
80
+ # simple records are those that aren't round robin ones
81
+ simple_records = records - rr_records.flatten
82
+
83
+ # for each simple record, create or update as necessary
84
+ simple_records.each do |record|
85
+ matching = provider.find_host_records(:host => record['host'], :domain =>record['domain'], :type => record['type'])
86
+ if matching.size > 1
87
+ msg = "Multiple records in dns provider, but not in rubber.yml\n"
88
+ msg << "Round robin records need to be in both, or neither.\n"
89
+ msg << "Please fix manually:\n"
90
+ msg << matching.pretty_inspect
91
+ fatal(msg)
92
+ end
93
+
94
+ record = provider.setup_opts(record)
95
+ if matching.size == 1
96
+ match = matching.first
97
+ if provider.host_records_equal?(record, match)
98
+ logger.info "Simple dns record already up to date: #{record[:host]}.#{record[:domain]}:#{record[:type]} => #{record[:data]}"
99
+ else
100
+ logger.info "Updating simple dns record: #{record[:host]}.#{record[:domain]}:#{record[:type]} => #{record[:data]}"
101
+ provider.update_host_record(match, record)
102
+ end
103
+ else
104
+ logger.info "Creating simple dns record: #{record[:host]}.#{record[:domain]}:#{record[:type]} => #{record[:data]}"
105
+ provider.create_host_record(record)
106
+ end
107
+ end
108
+
109
+ # group round robin records
110
+ rr_records.each do |rr_group|
111
+ host = rr_group.first['host']
112
+ domain = rr_group.first['domain']
113
+ type = rr_group.first['type']
114
+ matching = provider.find_host_records(:host => host, :domain => domain, :type => type)
115
+
116
+ # remove from consideration the local records that are the same as remote ones
117
+ matching.clone.each do |r|
118
+ rr_group.delete_if {|rg| provider.host_records_equal?(r, rg) }
119
+ matching.delete_if {|rg| provider.host_records_equal?(r, rg) }
120
+ end
121
+ if rr_group.size == 0 && matching.size == 0
122
+ logger.info "Round robin dns records already up to date: #{host}.#{domain}:#{type}"
123
+ end
124
+
125
+ # create the local records that don't exist remotely
126
+ rr_group.each do |r|
127
+ r = provider.setup_opts(r)
128
+ logger.info "Creating round robin dns record: #{r[:host]}.#{r[:domain]}:#{r[:type]} => #{r[:data]}"
129
+ provider.create_host_record(r)
130
+ end
131
+
132
+ # remove the remote records that don't exist locally
133
+ matching.each do |r|
134
+ logger.info "Removing round robin dns record: #{r[:host]}.#{r[:domain]}:#{r[:type]} => #{r[:data]}"
135
+ provider.destroy_host_record(r)
136
+ end
137
+ end
138
+ end
139
+ end
140
+
64
141
  desc <<-DESC
65
142
  Sets up aliases for instance hostnames based on contents of instance.yml.
66
143
  Generates /etc/hosts for remote machines and sets hostname on remote instances
@@ -144,11 +221,33 @@ namespace :rubber do
144
221
  desc <<-DESC
145
222
  Install ruby gems defined in the rails environment.rb
146
223
  DESC
147
- after "deploy:symlink", "rubber:install_rails_gems" if Rubber::Util.is_rails?
224
+ after "rubber:config", "rubber:install_rails_gems" if Rubber::Util.is_rails?
148
225
  task :install_rails_gems do
149
226
  sudo "sh -c 'cd #{current_path} && RAILS_ENV=#{RUBBER_ENV} rake gems:install'"
150
227
  end
151
228
 
229
+ desc <<-DESC
230
+ Convenience task for installing your defined set of ruby gems locally.
231
+ DESC
232
+ required_task :install_local_gems do
233
+ fatal("install_local_gems can only be run in development") if RUBBER_ENV != 'development'
234
+ env = rubber_cfg.environment.bind(rubber_cfg.environment.known_roles)
235
+ gems = env['gems']
236
+ expanded_gem_list = []
237
+ gems.each do |gem_spec|
238
+ if gem_spec.is_a?(Array)
239
+ expanded_gem_list << "#{gem_spec[0]}:#{gem_spec[1]}"
240
+ else
241
+ expanded_gem_list << gem_spec
242
+ end
243
+ end
244
+ expanded_gem_list = expanded_gem_list.join(' ')
245
+
246
+ logger.info "Installing gems:#{expanded_gem_list}"
247
+ open("/tmp/gem_helper", "w") {|f| f.write(gem_helper_script)}
248
+ system "sh /tmp/gem_helper install #{expanded_gem_list}"
249
+ end
250
+
152
251
  desc <<-DESC
153
252
  Setup ruby gems sources. Set 'gemsources' in rubber.yml to \
154
253
  be an array of URI strings.
@@ -280,11 +379,61 @@ namespace :rubber do
280
379
  end
281
380
  end
282
381
 
382
+ # Rubygems always installs even if the gem is already installed
383
+ # When providing versions, rubygems fails unless versions are provided for all gems
384
+ # This helper script works around these issues by installing gems only if they
385
+ # aren't already installed, and separates versioned/unversioned into two separate
386
+ # calls to rubygems
387
+ #
388
+ set :gem_helper_script, <<-'ENDSCRIPT'
389
+ ruby - $@ <<-'EOF'
390
+
391
+ gem_cmd = ARGV[0]
392
+ gems = ARGV[1..-1]
393
+ cmd = "gem #{gem_cmd} --no-rdoc --no-ri"
394
+
395
+ to_install = {}
396
+ to_install_ver = {}
397
+ # gem list passed in, possibly with versions, as "gem1 gem2:1.2 gem3"
398
+ gems.each do |gem_spec|
399
+ parts = gem_spec.split(':')
400
+ if parts[1]
401
+ to_install_ver[parts[0]] = parts[1]
402
+ else
403
+ to_install[parts[0]] = true
404
+ end
405
+ end
406
+
407
+ installed = {}
408
+ `gem list --local`.each do |line|
409
+ parts = line.scan(/(.*) \((.*)\)/).first
410
+ next unless parts && parts.size == 2
411
+ installed[parts[0]] = parts[1].split(",")
412
+ end
413
+
414
+ to_install.delete_if {|g, v| installed.has_key?(g) } if gem_cmd == 'install'
415
+ to_install_ver.delete_if {|g, v| installed.has_key?(g) && installed[g].include?(v) }
416
+
417
+ # rubygems can only do asingle versioned gem at a time so we need
418
+ # to do the two groups separately
419
+ # install versioned ones first so unversioned don't pull in a newer version
420
+ to_install_ver.each do |g, v|
421
+ system "#{cmd} #{g} -v #{v}"
422
+ fail "Unable to install versioned gem #{g}:#{v}" if $?.exitstatus > 0
423
+ end
424
+ if to_install.size > 0
425
+ gem_list = to_install.keys.join(' ')
426
+ system "#{cmd} #{gem_list}"
427
+ fail "Unable to install gems" if $?.exitstatus > 0
428
+ end
429
+
430
+ 'EOF'
431
+ ENDSCRIPT
432
+
283
433
  # Helper for installing gems,allows one to respond to prompts
284
434
  def gem_helper(update=false)
285
435
  cmd = update ? "update" : "install"
286
436
 
287
-
288
437
  opts = get_host_options('gems') do |gem_list|
289
438
  expanded_gem_list = []
290
439
  gem_list.each do |gem_spec|
@@ -298,56 +447,7 @@ namespace :rubber do
298
447
  end
299
448
 
300
449
  if opts.size > 0
301
- # Rubygems always installs even if the gem is already installed
302
- # When providing versions, rubygems fails unless versions are provided for all gems
303
- # This helper script works around these issues by installing gems only if they
304
- # aren't already installed, and separates versioned/unversioned into two separate
305
- # calls to rubygems
306
- script = prepare_script 'gem_helper', <<-'ENDSCRIPT'
307
- ruby - $@ <<-'EOF'
308
-
309
- gem_cmd = ARGV[0]
310
- gems = ARGV[1..-1]
311
- cmd = "gem #{gem_cmd} --no-rdoc --no-ri"
312
-
313
- to_install = {}
314
- to_install_ver = {}
315
- # gem list passed in, possibly with versions, as "gem1 gem2:1.2 gem3"
316
- gems.each do |gem_spec|
317
- parts = gem_spec.split(':')
318
- if parts[1]
319
- to_install_ver[parts[0]] = parts[1]
320
- else
321
- to_install[parts[0]] = true
322
- end
323
- end
324
-
325
- installed = {}
326
- `gem list --local`.each do |line|
327
- parts = line.scan(/(.*) \((.*)\)/).first
328
- next unless parts && parts.size == 2
329
- installed[parts[0]] = parts[1].split(",")
330
- end
331
-
332
- to_install.delete_if {|g, v| installed.has_key?(g) } if gem_cmd == 'install'
333
- to_install_ver.delete_if {|g, v| installed.has_key?(g) && installed[g].include?(v) }
334
-
335
- # rubygems can only do asingle versioned gem at a time so we need
336
- # to do the two groups separately
337
- # install versioned ones first so unversioned don't pull in a newer version
338
- to_install_ver.each do |g, v|
339
- system "#{cmd} #{g} -v #{v}"
340
- fail "Unable to install versioned gem #{g}:#{v}" if $?.exitstatus > 0
341
- end
342
- if to_install.size > 0
343
- gem_list = to_install.keys.join(' ')
344
- system "#{cmd} #{gem_list}"
345
- fail "Unable to install gems" if $?.exitstatus > 0
346
- end
347
-
348
- 'EOF'
349
- ENDSCRIPT
350
-
450
+ script = prepare_script('gem_helper', gem_helper_script)
351
451
  sudo "sh #{script} #{cmd} $CAPISTRANO:VAR$", opts do |ch, str, data|
352
452
  handle_gem_prompt(ch, data, str)
353
453
  end
@@ -98,11 +98,70 @@ namespace :rubber do
98
98
  end
99
99
  end
100
100
 
101
+
102
+ desc <<-DESC
103
+ Backup database to given backup directory
104
+ The following arguments affect behavior:
105
+ BACKUP_DIR (required): Directory where backups will be stored
106
+ BACKUP_NAME (required): What to name the backup
107
+ BACKUP_CMD (required): Command used to backup
108
+ BACKUP_AGE (3): Delete rotated logs older than this many days in the past
109
+ DESC
110
+ task :backup do
111
+ dir = get_env('BACKUP_DIR', true)
112
+ name = get_env('BACKUP_NAME', true)
113
+ cmd = get_env('BACKUP_CMD', true)
114
+ age = (get_env('BACKUP_AGE') || 3).to_i
115
+
116
+ time_stamp = Time.now.strftime("%Y-%m-%d_%H-%M")
117
+ FileUtils.mkdir_p(dir)
118
+
119
+ backup_cmd = cmd.gsub(/%([^%]+)%/, '#{\1}')
120
+ backup_cmd = eval('%Q{' + backup_cmd + '}')
121
+
122
+ puts "Backing up with command:"
123
+ sh backup_cmd
124
+ puts "Backup created"
125
+
126
+ s3_prefix = "#{name}/"
127
+ backup_bucket = cloud_provider.backup_bucket
128
+ if backup_bucket
129
+ init_s3
130
+ unless AWS::S3::Bucket.list.find { |b| b.name == backup_bucket }
131
+ AWS::S3::Bucket.create(backup_bucket)
132
+ end
133
+ newest = Dir.entries(dir).sort_by {|f| File.mtime(File.join(dir,f))}.last
134
+ dest = "#{s3_prefix}#{newest}"
135
+ puts "Saving backup to S3: #{backup_bucket}:#{dest}"
136
+ AWS::S3::S3Object.store(dest, open(File.join(dir, newest)), backup_bucket)
137
+ end
138
+
139
+ tdate = Date.today - age
140
+ threshold = Time.local(tdate.year, tdate.month, tdate.day)
141
+ puts "Cleaning backups older than #{age} days"
142
+ Dir["#{dir}/*"].each do |file|
143
+ if File.mtime(file) < threshold
144
+ puts "Deleting #{file}"
145
+ FileUtils.rm_f(file)
146
+ end
147
+ end
148
+
149
+ if backup_bucket
150
+ puts "Cleaning S3 backups older than #{age} days from: #{backup_bucket}:#{s3_prefix}"
151
+ AWS::S3::Bucket.objects(backup_bucket, :prefix => s3_prefix).clone.each do |obj|
152
+ if Time.parse(obj.about["last-modified"]) < threshold
153
+ puts "Deleting #{obj.key}"
154
+ obj.delete
155
+ end
156
+ end
157
+ end
158
+ end
159
+
101
160
  desc <<-DESC
102
161
  Backup database to given backup directory
103
162
  The following arguments affect behavior:
104
163
  BACKUP_DIR (required): Directory where db backups will be stored
105
- BACKUP_AGE (7): Delete rotated logs older than this many days in the past
164
+ BACKUP_AGE (3): Delete rotated logs older than this many days in the past
106
165
  DBUSER (required) User to connect to the db as
107
166
  DBPASS (optional): Pass to connect to the db with
108
167
  DBHOST (required): Host where the db is
@@ -120,15 +179,15 @@ namespace :rubber do
120
179
  pass = nil if pass.strip.size == 0
121
180
  host = get_env('DBHOST', true)
122
181
  name = get_env('DBNAME', true)
123
-
182
+
124
183
  raise "No db_backup_cmd defined in rubber.yml, cannot backup!" unless rubber_env.db_backup_cmd
125
184
  db_backup_cmd = rubber_env.db_backup_cmd.gsub(/%([^%]+)%/, '#{\1}')
126
185
  db_backup_cmd = eval('%Q{' + db_backup_cmd + '}')
127
-
186
+
128
187
  puts "Backing up database with command:"
129
188
  sh db_backup_cmd
130
189
  puts "Created backup: #{backup_file}"
131
-
190
+
132
191
  s3_prefix = "db/"
133
192
  backup_bucket = cloud_provider.backup_bucket
134
193
  if backup_bucket
@@ -150,7 +209,7 @@ namespace :rubber do
150
209
  FileUtils.rm_f(file)
151
210
  end
152
211
  end
153
-
212
+
154
213
  if backup_bucket
155
214
  puts "Cleaning S3 backups older than #{age} days from: #{backup_bucket}:#{s3_prefix}"
156
215
  AWS::S3::Bucket.objects(backup_bucket, :prefix => s3_prefix).clone.each do |obj|
@@ -211,7 +270,6 @@ namespace :rubber do
211
270
 
212
271
  end
213
272
 
214
-
215
273
  def get_env(name, required=false)
216
274
  value = ENV[name]
217
275
  raise("#{name} is required, pass using environment") if required && ! value
data/rails/init.rb ADDED
@@ -0,0 +1,9 @@
1
+ # this file just makes it easy to refer to rubber config
2
+ # variables in your apps's config'
3
+ #
4
+
5
+ require "rubber"
6
+ Rubber::initialize(RAILS_ROOT, RAILS_ENV)
7
+
8
+ ::RUBBER_CONFIG = Rubber::Configuration.rubber_env
9
+ ::RUBBER_INSTANCES = Rubber::Configuration.rubber_instances
@@ -115,4 +115,19 @@ class EnvironmentTest < Test::Unit::TestCase
115
115
  assert_equal 'val5', e.var2.var9, 'env not retrieving right val'
116
116
  end
117
117
 
118
+ def test_instances_in_expansion
119
+ instance = InstanceItem.new('host1', 'domain.com', [RoleItem.new('role1')], '')
120
+ instance.external_ip = "1.2.3.4"
121
+ instances = Instance.new(Tempfile.new('testforinstanceexpansion').path)
122
+ instances.add(instance)
123
+
124
+ File.expects(:exist?).returns(true)
125
+ YAML.expects(:load_file).returns({'var1' =>'"#{rubber_instances.for_role("role1").first.external_ip}"'})
126
+ Rubber::Configuration.expects(:rubber_instances).returns(instances)
127
+ env = Rubber::Configuration::Environment.new(nil)
128
+ e = env.bind()
129
+
130
+ assert_equal "\"1.2.3.4\"", e['var1']
131
+ end
132
+
118
133
  end
data/test/test_helper.rb CHANGED
@@ -2,3 +2,7 @@ $:.unshift "#{File.dirname(__FILE__)}/../lib"
2
2
 
3
3
  require 'rubber'
4
4
  Rubber::initialize(File.dirname(__FILE__), 'test')
5
+
6
+ require 'rubygems'
7
+ require 'mocha'
8
+ require 'pp'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubber
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Conway
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-28 00:00:00 -04:00
12
+ date: 2009-10-06 00:00:00 -04:00
13
13
  default_executable: vulcanize
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -105,6 +105,7 @@ files:
105
105
  - generators/vulcanize/templates/base/config/rubber/common/crontab
106
106
  - generators/vulcanize/templates/base/config/rubber/common/profile.rc
107
107
  - generators/vulcanize/templates/base/config/rubber/deploy-setup.rb
108
+ - generators/vulcanize/templates/base/config/rubber/rubber-dns.yml
108
109
  - generators/vulcanize/templates/base/config/rubber/rubber.yml
109
110
  - generators/vulcanize/templates/base/lib/tasks/rubber.rake
110
111
  - generators/vulcanize/templates/base/script/cron-rake
@@ -238,6 +239,7 @@ files:
238
239
  - lib/rubber/recipes/rubber/volumes.rb
239
240
  - lib/rubber/tasks/rubber.rb
240
241
  - lib/rubber/util.rb
242
+ - rails/init.rb
241
243
  has_rdoc: true
242
244
  homepage: http://github.com/wr0ngway/rubber
243
245
  licenses: []