rubber 1.0.2 → 1.1.0

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