rudy 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,52 +5,36 @@ module Rudy
5
5
  class Metadata < Rudy::Command::Base
6
6
 
7
7
 
8
- # Update Rudy's metadata for a running instance
9
- def update_metadata(instances)
10
- attributes = {
11
- 'role' => role,
12
- 'environment' => environment,
13
- 'instances' => instances
14
- }
15
- puts "Putting #{instances.join(', ')} into #{machine_group}"
16
-
17
- @sdb.store(RUDY_DOMAIN, "group_#{machine_group}", attributes)
18
-
19
- p group_metadata
8
+ # Print Rudy's metadata to STDOUT
9
+ def metadata
10
+ group_metadata.each_pair do |n,h|
11
+ puts n.att(:bright)
12
+ puts h.inspect, ""
13
+ end
20
14
  end
21
15
 
22
- # Print Rudy's metadata to STDOUT
23
- def print_metadata(instances=[])
24
- p group_metadata
25
- #query = "['instances' = '#{instances.first}'] union ['group' = '#{machine}']"
26
- #
27
- # p @sdb.get_attributes(RUDY_DOMAIN, "instances_#{machine}")
28
- # p @sdb.query(RUDY_DOMAIN, query)
16
+ def destroy_metadata_valid?
17
+ false
29
18
  end
30
19
 
31
20
  def destroy_metadata
32
21
  @sdb.domains.destroy(RUDY_DOMAIN)
33
22
  end
34
23
 
35
- def setup
36
- puts "Checking environment..."
37
- check_environment
38
-
39
- puts "Creating SimpleDB domain called #{RUDY_DOMAIN}"
40
- @sdb.domains.create(RUDY_DOMAIN)
41
- end
42
24
 
43
- def info
25
+
26
+ def info
44
27
  domains = @sdb.domains.list[:domains]
45
28
  puts "Domains: #{domains.join(", ")}"
46
29
  end
47
-
30
+
31
+ private
48
32
  def check_environment
49
33
  raise "No Amazon keys provided!" unless has_keys?
50
- raise "No SSH keypairs provided!" unless has_keypairs?
34
+ raise "No SSH keypairs provided!" unless has_keypair?
51
35
  true
52
36
  end
53
-
37
+
54
38
  end
55
39
  end
56
40
  end
@@ -0,0 +1,174 @@
1
+
2
+
3
+ module Rudy
4
+ module Command
5
+ class Release < Rudy::Command::Base
6
+
7
+
8
+
9
+
10
+ def release_valid?
11
+
12
+ relroutine = @config.routines.find_deferred(@global.environment, @global.role, :release)
13
+ raise "No release routines defined for #{machine_group}" if relroutine.nil?
14
+
15
+ raise "No EC2 .pem keys provided" unless has_pem_keys?
16
+ raise "No SSH key provided for #{@global.user} in #{machine_group}!" unless has_keypair?
17
+ raise "No SSH key provided for root in #{machine_group}!" unless has_keypair?(:root)
18
+
19
+ @list = @ec2.instances.list(machine_group)
20
+ unless @list.empty?
21
+ msg = "#{machine_group} is in use, probably with another release. #{$/}"
22
+ msg << 'Sort it out and run "rudy shutdown" before continuing.'
23
+ raise msg
24
+ end
25
+
26
+ @scm, @scm_params = find_scm(:release)
27
+
28
+ raise "No SCM defined for release routine" unless @scm
29
+ raise "#{Dir.pwd} is not a working copy" unless @scm.working_copy?(Dir.pwd)
30
+ raise "There are local changes. Please revert or check them in." unless @scm.everything_checked_in?
31
+ raise "Invalid base URI (#{@scm_params[:base]})." unless @scm.valid_uri?(@scm_params[:base])
32
+
33
+ true
34
+ end
35
+
36
+
37
+
38
+ def rerelease_valid?
39
+ relroutine = @config.routines.find_deferred(@global.environment, @global.role, :rerelease)
40
+ raise "No rerelease routines defined for #{machine_group}" if relroutine.nil?
41
+
42
+ raise "No EC2 .pem keys provided" unless has_pem_keys?
43
+ raise "No SSH key provided for #{@global.user} in #{machine_group}!" unless has_keypair?
44
+ raise "No SSH key provided for root in #{machine_group}!" unless has_keypair?(:root)
45
+
46
+ @list = @ec2.instances.list(machine_group)
47
+ if @list.empty?
48
+ msg = "There are no machines running in #{machine_group}. #{$/}"
49
+ msg << 'You must run "rudy release" before you can rerelease.'
50
+ raise msg
51
+ end
52
+
53
+ @scm, @scm_params = find_scm(:rerelease)
54
+
55
+ raise "No SCM defined for release routine" unless @scm
56
+ raise "#{Dir.pwd} is not a working copy" unless @scm.working_copy?(Dir.pwd)
57
+ raise "There are local changes. Please revert or check them in." unless @scm.everything_checked_in?
58
+ raise "Invalid base URI (#{@scm_params[:base]})." unless @scm.valid_uri?(@scm_params[:base])
59
+
60
+ true
61
+ end
62
+
63
+ def rerelease
64
+ puts "Updating release from working copy".att(:bright)
65
+
66
+ tag, revision = @scm.local_info
67
+ puts "tag: #{tag}"
68
+ puts "rev: #{revision}"
69
+
70
+ execute_disk_routines(@list.values, :rerelease)
71
+
72
+ if @scm
73
+
74
+ puts "Running SCM command".att(:bright)
75
+ ssh do |session|
76
+ cmd = "svn #{@scm_params[:command]}"
77
+ puts "#{cmd}"
78
+ session.exec!("cd #{@scm_params[:path]}")
79
+ session.exec!(cmd)
80
+ puts "#{@scm_params[:command]} complete"
81
+ end
82
+
83
+ end
84
+
85
+ execute_routines(@list.values, :rerelease, :after)
86
+
87
+ end
88
+
89
+ # <li>Creates a release tag based on the working copy on your machine</li>
90
+ # <li>Starts a new stage instance</li>
91
+ # <li>Executes release routines</li>
92
+ def release
93
+ # TODO: store metadata about release with local username and hostname
94
+ puts "Creating release from working copy".att(:bright)
95
+
96
+ exit unless are_you_sure?
97
+
98
+ tag = @scm.create_release(@global.local_user, @option.msg)
99
+ puts "Done! (#{tag})"
100
+
101
+ if @option.switch
102
+ puts "Switching working copy to new tag".att(:bright)
103
+ @scm.switch_working_copy(tag)
104
+ end
105
+
106
+ @option.image ||= machine_image
107
+
108
+ switch_user("root")
109
+
110
+ puts "Starting #{machine_group}".att(:bright)
111
+
112
+ instances = @ec2.instances.create(@option.image, machine_group.to_s, File.basename(keypairpath), machine_data.to_yaml, @global.zone)
113
+ inst = instances.first
114
+
115
+ if @option.address ||= machine_address
116
+ puts "Associating #{@option.address} to #{inst[:aws_instance_id]}".att(:bright)
117
+ @ec2.addresses.associate(inst[:aws_instance_id], @option.address)
118
+ end
119
+
120
+ wait_for_machine(inst[:aws_instance_id])
121
+ inst = @ec2.instances.get(inst[:aws_instance_id])
122
+
123
+ #inst = @ec2.instances.list(machine_group).values
124
+
125
+
126
+ execute_disk_routines(inst, :release)
127
+
128
+ if @scm
129
+
130
+ puts "Running SCM command".att(:bright)
131
+ ssh do |session|
132
+ cmd = "svn #{@scm_params[:command]} #{tag} #{@scm_params[:path]}"
133
+ puts "#{cmd}"
134
+ session.exec!(cmd)
135
+ puts "#{@scm_params[:command]} complete"
136
+ end
137
+
138
+ end
139
+
140
+ execute_routines(inst, :release, :after)
141
+
142
+ print_instance inst
143
+
144
+ puts "Done!"
145
+ end
146
+
147
+
148
+ def find_scm(routine)
149
+ env, rol, att = @global.environment, @global.role
150
+
151
+ # Look for the source control engine, checking all known scm values.
152
+ # The available one will look like [environment][role][release][svn]
153
+ params = nil
154
+ scm_name = nil
155
+ SUPPORTED_SCM_NAMES.each do |v|
156
+ scm_name = v
157
+ params = @config.routines.find(env, rol, routine, scm_name)
158
+ break if params
159
+ end
160
+
161
+ if params
162
+ klass = eval "Rudy::SCM::#{scm_name.to_s.upcase}"
163
+ scm = klass.new(:base => params[:base])
164
+ end
165
+
166
+ [scm, params]
167
+
168
+ end
169
+ private :find_scm
170
+
171
+ end
172
+ end
173
+ end
174
+
@@ -4,24 +4,40 @@ module Rudy
4
4
  module Command
5
5
  class Volumes < Rudy::Command::Base
6
6
 
7
- def destroy_volume(id)
7
+ def destroy_volumes_valid?
8
+ id = @argv.first
8
9
  raise "No volume ID provided" unless id
9
- raise "I will not help you destroy production!" if @environment == "prod"
10
+ raise "I will not help you destroy production!" if @global.environment == "prod"
10
11
  raise "The volume #{id} doesn't exist!" unless @ec2.volumes.exists?(id)
11
- disk = Rudy::MetaData::Disk.from_volume(@sdb, id)
12
+ exit unless are_you_sure? 4
13
+ true
14
+ end
12
15
 
13
- puts "Destroying #{id}!"
14
- @ec2.volumes.destroy id
16
+ def destroy_volumes
17
+ id = @argv.first
18
+ disk = Rudy::MetaData::Disk.find_from_volume(@sdb, id)
19
+
20
+ begin
21
+ puts "Detaching #{id}"
22
+ @ec2.volumes.detach(id)
23
+ sleep 3
15
24
 
16
- if disk
17
- disk.awsid = nil
25
+ puts "Destroying #{id}"
26
+ @ec2.volumes.destroy(id)
27
+
28
+ if disk
29
+ puts "Deleteing metadata for #{disk.name}"
30
+ Rudy::MetaData::Disk.destroy(@sdb, disk)
31
+ end
32
+
33
+ rescue => ex
34
+ puts "Error while detaching volume #{id}: #{ex.message}"
18
35
  end
19
36
 
20
- Rudy::MetaData::Disk.save(@sdb, disk)
21
37
 
22
38
  end
23
39
 
24
- def print_volumes
40
+ def volumes
25
41
  machines = {}
26
42
  volumes = @ec2.volumes.list
27
43
  @ec2.volumes.list.each do |volume|
@@ -38,7 +54,7 @@ module Rudy
38
54
  env = (machine[:aws_groups]) ? machine[:aws_groups] : "Not-attached"
39
55
  puts "Environment: #{env}"
40
56
  hash[:volumes].each do |vol|
41
- disk = Rudy::MetaData::Disk.from_volume(@sdb, vol[:aws_id])
57
+ disk = Rudy::MetaData::Disk.find_from_volume(@sdb, vol[:aws_id])
42
58
  print_volume(vol, disk)
43
59
  end
44
60
  end
@@ -0,0 +1,93 @@
1
+
2
+
3
+ module Rudy
4
+ require 'caesars'
5
+
6
+ class AWSInfo < Caesars
7
+ def valid?
8
+ (!account.nil? && !accesskey.nil? && !secretkey.nil?) &&
9
+ (!account.empty? && !accesskey.empty? && !secretkey.empty?)
10
+ end
11
+ end
12
+
13
+ class Defaults < Caesars
14
+ end
15
+
16
+
17
+ class Routines < Caesars
18
+
19
+ def create(*args, &b)
20
+ hash_handler(:create, *args, &b)
21
+ end
22
+ def destroy(*args, &b)
23
+ hash_handler(:destroy, *args, &b)
24
+ end
25
+ def restore(*args, &b)
26
+ hash_handler(:restore, *args, &b)
27
+ end
28
+ def mount(*args, &b)
29
+ hash_handler(:mount, *args, &b)
30
+ end
31
+
32
+ #
33
+ # Force the specified keyword to always be treated as a hash.
34
+ # Example:
35
+ #
36
+ # startup do
37
+ # disks do
38
+ # create "/path/2" # Available as hash: [action][disks][create][/path/2] == {}
39
+ # create "/path/4" do # Available as hash: [action][disks][create][/path/4] == {size => 14}
40
+ # size 14
41
+ # end
42
+ # end
43
+ # end
44
+ #
45
+ def hash_handler(caesars_meth, *args, &b)
46
+ # TODO: Move to caesars
47
+ return @caesars_properties[caesars_meth] if @caesars_properties.has_key?(caesars_meth) && args.empty? && b.nil?
48
+ return nil if args.empty? && b.nil?
49
+ return method_missing(caesars_meth, *args, &b) if args.empty?
50
+
51
+ caesars_name = args.shift
52
+
53
+ prev = @caesars_pointer
54
+ @caesars_pointer[caesars_meth] ||= Caesars::Hash.new
55
+ hash = Caesars::Hash.new
56
+ @caesars_pointer = hash
57
+ b.call if b
58
+ @caesars_pointer = prev
59
+ @caesars_pointer[caesars_meth][caesars_name] = hash
60
+ @caesars_pointer = prev
61
+ end
62
+ end
63
+
64
+ class Machines < Caesars
65
+ end
66
+
67
+ class Config < Caesars::Config
68
+ dsl Rudy::AWSInfo::DSL
69
+ dsl Rudy::Defaults::DSL
70
+ dsl Rudy::Routines::DSL
71
+ dsl Rudy::Machines::DSL
72
+
73
+ def postprocess
74
+ # TODO: give caesar attributes setter methods
75
+ self.awsinfo.cert &&= File.expand_path(self.awsinfo.cert)
76
+ self.awsinfo.privatekey &&= File.expand_path(self.awsinfo.privatekey)
77
+
78
+ end
79
+
80
+ def look_and_load
81
+ cwd = Dir.pwd
82
+ # Rudy looks for configs in all these locations
83
+ @paths += Dir.glob(File.join('/etc', 'rudy', '*.rb')) || []
84
+ @paths += Dir.glob(File.join(cwd, 'config', 'rudy', '*.rb')) || []
85
+ @paths += Dir.glob(File.join(cwd, '.rudy', '*.rb')) || []
86
+ refresh
87
+ end
88
+
89
+
90
+ end
91
+ end
92
+
93
+
@@ -50,7 +50,7 @@ module Rudy
50
50
  end
51
51
 
52
52
  def valid?
53
- @zone && @environment && @role && @position && @path && @date && @time && @sec
53
+ @zone && @environment && @role && @position && @path && @date && @time && @second
54
54
  end
55
55
 
56
56
  def time_stamp
@@ -26,9 +26,6 @@ module Rudy
26
26
  field :size
27
27
 
28
28
  def initialize
29
- @device = "/dev/sdh"
30
- @zone = DEFAULT_ZONE
31
- @region = DEFAULT_REGION
32
29
  @backups = []
33
30
  @rtype = @@rtype.to_s
34
31
  @raw_volume = false
@@ -46,7 +43,7 @@ module Rudy
46
43
  end
47
44
 
48
45
  def valid?
49
- @zone && @environment && @role && @position && @path
46
+ @zone && @environment && @role && @position && @path && @size && @device
50
47
  end
51
48
 
52
49
  def to_query(more=[], remove=[])
@@ -76,14 +73,14 @@ module Rudy
76
73
 
77
74
  def Disk.get(sdb, name)
78
75
  disk = sdb.get_attributes(RUDY_DOMAIN, name)
79
-
80
- raise "Disk #{name} does not exist!" unless disk && disk.has_key?(:attributes)
76
+ return nil unless disk && disk.has_key?(:attributes) && !disk[:attributes].empty?
77
+ # raise "Disk #{name} does not exist!" unless
81
78
  Rudy::MetaData::Disk.from_hash(disk[:attributes])
82
79
  end
83
80
 
84
- def Disk.destroy(sdb, name)
85
- disk = Disk.get(sdb, name) # get raises an exception if the disk doesn't exist
86
- sdb.destroy(RUDY_DOMAIN, name)
81
+ def Disk.destroy(sdb, disk)
82
+ disk = Disk.get(sdb, disk) if disk.is_a?(String) # get raises an exception if the disk doesn't exist
83
+ sdb.destroy(RUDY_DOMAIN, disk.name)
87
84
  true # wtf: RightAws::SimpleDB doesn't tell us whether it succeeds. We'll assume!
88
85
  end
89
86
 
@@ -98,7 +95,7 @@ module Rudy
98
95
  !sdb.query_with_attributes(RUDY_DOMAIN, query).empty?
99
96
  end
100
97
 
101
- def Disk.from_volume(sdb, vol_id)
98
+ def Disk.find_from_volume(sdb, vol_id)
102
99
  query = "['awsid' = '#{vol_id}']"
103
100
  res = sdb.query_with_attributes(RUDY_DOMAIN, query)
104
101
  if res.empty?
@@ -108,47 +105,15 @@ module Rudy
108
105
  end
109
106
  end
110
107
 
111
- def Disk.update_volume(sdb, ec2, disk, machine)
112
-
113
- disk = Disk.get(sdb, disk) if disk.is_a?(String)
114
- raise "You must provide a disk name or obect" unless disk.is_a?(Rudy::MetaData::Disk)
115
-
116
-
117
- # Make sure the volume is still running
118
- disk.awsid = nil if disk.awsid && !ec2.volumes.exists?(disk.awsid)
119
-
120
-
121
- # Otherwise we need to start one
122
- unless disk.awsid
123
- puts "No active EBS volume found for #{disk.name}"
124
-
125
- # TODO: pull actual backups
126
- backups = Rudy::MetaData::Backup.for_disk(sdb, disk, 2)
127
-
128
- if backups.is_a?(Array) && !backups.empty?
129
- backup = backups.first
130
- if ec2.snapshots.exists?(backup.awsid)
131
- puts "We'll use the most recent backup (#{backup.awsid})..."
132
- volume = ec2.volumes.create(disk.zone, disk.size, backup.awsid)
133
- else
134
- puts "The backup refers to a snapshot that doesn't exist."
135
- puts backup.name, backup.awsid
136
- puts "You need to delete this backup metadata before continuing."
137
- exit 1
138
- end
139
- else
140
- puts "We'll create one from scratch..."
141
- volume = ec2.volumes.create(disk.zone, disk.size, nil)
142
- disk.raw_volume = true
143
- end
144
-
145
- puts "Saving disk metadata"
146
- disk.awsid = volume[:aws_id]
147
- Disk.save(sdb, disk)
148
- puts ""
108
+
109
+ def Disk.find_from_path(sdb, path)
110
+ query = "['path' = '#{path}']"
111
+ res = sdb.query_with_attributes(RUDY_DOMAIN, query)
112
+ if res.empty?
113
+ nil
114
+ else
115
+ disk = Rudy::MetaData::Disk.from_hash(res.values.first)
149
116
  end
150
-
151
- disk
152
117
  end
153
118
 
154
119
  def Disk.list(sdb, zon, env=nil, rol=nil, pos=nil)