rudy 0.3.2 → 0.4.0

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