solutious-rudy 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.
- data/CHANGES.txt +75 -0
- data/LICENSE.txt +19 -0
- data/README.rdoc +36 -0
- data/Rakefile +68 -0
- data/bin/rudy +175 -0
- data/bin/rudy-ec2 +108 -0
- data/lib/aws_sdb.rb +3 -0
- data/lib/aws_sdb/error.rb +42 -0
- data/lib/aws_sdb/service.rb +215 -0
- data/lib/console.rb +385 -0
- data/lib/rudy.rb +210 -0
- data/lib/rudy/aws.rb +68 -0
- data/lib/rudy/aws/ec2.rb +304 -0
- data/lib/rudy/aws/s3.rb +3 -0
- data/lib/rudy/aws/simpledb.rb +53 -0
- data/lib/rudy/command/addresses.rb +46 -0
- data/lib/rudy/command/backups.rb +175 -0
- data/lib/rudy/command/base.rb +839 -0
- data/lib/rudy/command/config.rb +77 -0
- data/lib/rudy/command/deploy.rb +12 -0
- data/lib/rudy/command/disks.rb +213 -0
- data/lib/rudy/command/environment.rb +74 -0
- data/lib/rudy/command/groups.rb +61 -0
- data/lib/rudy/command/images.rb +99 -0
- data/lib/rudy/command/instances.rb +85 -0
- data/lib/rudy/command/machines.rb +170 -0
- data/lib/rudy/command/metadata.rb +41 -0
- data/lib/rudy/command/release.rb +174 -0
- data/lib/rudy/command/volumes.rb +66 -0
- data/lib/rudy/config.rb +93 -0
- data/lib/rudy/metadata.rb +26 -0
- data/lib/rudy/metadata/backup.rb +160 -0
- data/lib/rudy/metadata/disk.rb +138 -0
- data/lib/rudy/scm/svn.rb +68 -0
- data/lib/rudy/utils.rb +64 -0
- data/lib/storable.rb +280 -0
- data/lib/tryouts.rb +40 -0
- data/rudy.gemspec +76 -0
- data/support/mailtest +40 -0
- data/support/rudy-ec2-startup +200 -0
- data/tryouts/console_tryout.rb +91 -0
- metadata +188 -0
data/lib/rudy/aws/s3.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
module Rudy::AWS
|
5
|
+
|
6
|
+
|
7
|
+
class SimpleDB
|
8
|
+
class Domains
|
9
|
+
include Rudy::AWS::ObjectBase
|
10
|
+
|
11
|
+
def create(name)
|
12
|
+
@aws.create_domain(name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def destroy(name)
|
16
|
+
@aws.delete_domain(name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def list
|
20
|
+
@aws.list_domains
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy(domain, item, attributes={})
|
25
|
+
@aws.delete_attributes(domain, item, attributes)
|
26
|
+
end
|
27
|
+
|
28
|
+
def store(domain, item, attributes={}, replace=false)
|
29
|
+
@aws.put_attributes(domain, item, attributes, replace)
|
30
|
+
end
|
31
|
+
|
32
|
+
def query(domain, query=nil, max=nil)
|
33
|
+
@aws.query(domain, query, max)
|
34
|
+
end
|
35
|
+
|
36
|
+
def query_with_attributes(domain, query, max=nil)
|
37
|
+
items = {}
|
38
|
+
query(domain, query)[:items].each do |item|
|
39
|
+
items[item] = get_attributes(domain, item)[:attributes]
|
40
|
+
end
|
41
|
+
items
|
42
|
+
end
|
43
|
+
|
44
|
+
def select(query)
|
45
|
+
list = @aws2.select(query) || []
|
46
|
+
list[0]
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_attributes(domain, item, attribute=nil)
|
50
|
+
@aws.get_attributes(domain, item, attribute)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
module Rudy
|
5
|
+
module Command
|
6
|
+
class Addresses < Rudy::Command::Base
|
7
|
+
|
8
|
+
|
9
|
+
def associate_addresses_valid?
|
10
|
+
raise "You have not supplied an IP addresses" unless @argv.address
|
11
|
+
raise "You did not supply an instance ID" unless @argv.instanceid
|
12
|
+
|
13
|
+
@inst = @ec2.instances.get(@argv.instanceid)
|
14
|
+
raise "Instance #{@inst[:aws_instance_id]} does not exist!" unless @inst
|
15
|
+
|
16
|
+
raise "That's not an elastic IP you own!" unless @ec2.addresses.valid?(@argv.address)
|
17
|
+
raise "#{@argv.address} is already associated!" if @ec2.addresses.associated?(@argv.address)
|
18
|
+
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def associate_addresses
|
23
|
+
puts "Associating #{@argv.address} to #{@inst[:aws_groups]}: #{@inst[:dns_name]}"
|
24
|
+
@ec2.addresses.associate(@inst[:aws_instance_id], @argv.address)
|
25
|
+
puts "Done!"
|
26
|
+
puts
|
27
|
+
|
28
|
+
addresses
|
29
|
+
end
|
30
|
+
|
31
|
+
def addresses
|
32
|
+
puts "Elastic IP mappings:"
|
33
|
+
@ec2.addresses.list.each do |address|
|
34
|
+
print "IP: #{address[:public_ip]} "
|
35
|
+
if address[:instance_id]
|
36
|
+
inst = @ec2.instances.get(address[:instance_id])
|
37
|
+
puts "%s: %s %s" % [inst[:aws_groups], inst[:aws_instance_id], inst[:dns_name]]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
puts
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,175 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
module Rudy
|
6
|
+
module Command
|
7
|
+
class Backups < Rudy::Command::Base
|
8
|
+
|
9
|
+
|
10
|
+
def backup
|
11
|
+
criteria = [@global.zone]
|
12
|
+
criteria += [@global.environment, @global.role] unless @option.all
|
13
|
+
|
14
|
+
Rudy::MetaData::Backup.list(@sdb, *criteria).each do |backup|
|
15
|
+
puts "%s (%s)" % [backup.name, backup.awsid]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Check for backups pointing to snapshots that don't exist.
|
20
|
+
def sync_backup
|
21
|
+
unless argv.empty?
|
22
|
+
puts "The disk you specified will be ignored."
|
23
|
+
argv.clear
|
24
|
+
end
|
25
|
+
|
26
|
+
criteria = [@global.zone]
|
27
|
+
criteria += [@global.environment, @global.role] unless @option.all
|
28
|
+
|
29
|
+
puts "Looking for backup metadata with delinquent snapshots..."
|
30
|
+
to_be_deleted = {} # snap-id => backup
|
31
|
+
Rudy::MetaData::Backup.list(@sdb, *criteria).each do |backup|
|
32
|
+
to_be_deleted[backup.awsid] = backup unless @ec2.snapshots.exists?(backup.awsid)
|
33
|
+
end
|
34
|
+
|
35
|
+
if to_be_deleted.empty?
|
36
|
+
puts "All backups are in-sync with snapshots. Nothing to do."
|
37
|
+
return
|
38
|
+
end
|
39
|
+
|
40
|
+
puts
|
41
|
+
puts "These backup metadata will be deleted:"
|
42
|
+
to_be_deleted.each do |snap_id, backup|
|
43
|
+
puts "%s: %s" % [snap_id, backup.name]
|
44
|
+
end
|
45
|
+
|
46
|
+
puts
|
47
|
+
are_you_sure?
|
48
|
+
|
49
|
+
puts
|
50
|
+
puts "Deleting..."
|
51
|
+
to_be_deleted.each do |snap_id, backup|
|
52
|
+
print " -> #{backup.name}... "
|
53
|
+
@sdb.destroy(RUDY_DOMAIN, backup.name)
|
54
|
+
puts "done"
|
55
|
+
end
|
56
|
+
|
57
|
+
puts "Done!"
|
58
|
+
end
|
59
|
+
|
60
|
+
def destroy_backup_valid?
|
61
|
+
raise "No backup specified" if argv.empty?
|
62
|
+
exit unless are_you_sure?(5)
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def destroy_backup
|
67
|
+
name = @argv.first
|
68
|
+
puts "Destroying #{name}"
|
69
|
+
begin
|
70
|
+
backup = Rudy::MetaData::Backup.get(@sdb, name)
|
71
|
+
rescue => ex
|
72
|
+
puts "Error deleteing backup: #{ex.message}"
|
73
|
+
end
|
74
|
+
|
75
|
+
return unless backup
|
76
|
+
|
77
|
+
begin
|
78
|
+
puts " -> deleting snapshot..."
|
79
|
+
@ec2.snapshots.destroy(backup.awsid)
|
80
|
+
rescue => ex
|
81
|
+
puts "Error deleting snapshot: #{ex.message}."
|
82
|
+
puts "Continuing..."
|
83
|
+
ensure
|
84
|
+
puts " -> deleting metadata..."
|
85
|
+
@sdb.destroy(RUDY_DOMAIN, name)
|
86
|
+
end
|
87
|
+
puts "Done."
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_backup
|
91
|
+
diskname = @argv.first
|
92
|
+
|
93
|
+
machine = find_current_machine
|
94
|
+
|
95
|
+
disks = Rudy::MetaData::Disk.list(@sdb, machine[:aws_availability_zone], @global.environment, @global.role, @global.position)
|
96
|
+
raise "The machine #{machine_name} does not have any disk metadata" if disks.empty?
|
97
|
+
|
98
|
+
puts "Machine: #{machine_name}"
|
99
|
+
|
100
|
+
if @option.snapshot
|
101
|
+
raise "You must supply a diskname when using an existing snapshot" unless diskname
|
102
|
+
raise "The snapshot #{@option.snapshot} does not exist" unless @ec2.snapshots.exists?(@option.snapshot)
|
103
|
+
disk = Rudy::MetaData::Disk.get(@sdb, diskname)
|
104
|
+
|
105
|
+
raise "The disk #{diskname} does not exist" unless disk
|
106
|
+
backup = Rudy::MetaData::Backup.new
|
107
|
+
backup.awsid = @option.snapshot
|
108
|
+
backup.time_stamp
|
109
|
+
|
110
|
+
# Populate machine infos
|
111
|
+
[:zone, :environment, :role, :position].each do |n|
|
112
|
+
backup.send("#{n}=", @global.send(n)) if @global.send(n)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Populate disk infos
|
116
|
+
[:path, :size].each do |n|
|
117
|
+
backup.send("#{n}=", disk.send(n)) if disk.send(n)
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
Rudy::MetaData::Backup.save(@sdb, backup)
|
122
|
+
|
123
|
+
puts backup.name
|
124
|
+
|
125
|
+
else
|
126
|
+
volumes = @ec2.instances.volumes(machine[:aws_instance_id])
|
127
|
+
raise "The machine #{machine_name} does not have any volumes attached." if volumes.empty?
|
128
|
+
|
129
|
+
puts "#{disks.size} Disk(s) defined with #{volumes.size} Volume(s) running"
|
130
|
+
|
131
|
+
volumes.each do |volume|
|
132
|
+
print "Volume #{volume[:aws_id]}... "
|
133
|
+
disk = Rudy::MetaData::Disk.find_from_volume(@sdb, volume[:aws_id])
|
134
|
+
backup = Rudy::MetaData::Backup.new
|
135
|
+
|
136
|
+
# TODO: Look for the disk based on the machine
|
137
|
+
raise "No disk associated to volume #{volume[:aws_id]}" unless disk
|
138
|
+
|
139
|
+
backup.volume = volume[:aws_id]
|
140
|
+
|
141
|
+
# Populate machine infos
|
142
|
+
[:zone, :environment, :role, :position].each do |n|
|
143
|
+
backup.send("#{n}=", @global.send(n)) if @global.send(n)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Populate disk infos
|
147
|
+
[:path, :size].each do |n|
|
148
|
+
backup.send("#{n}=", disk.send(n)) if disk.send(n)
|
149
|
+
end
|
150
|
+
|
151
|
+
backup.time_stamp
|
152
|
+
|
153
|
+
raise "There was a problem creating the backup metadata" unless backup.valid?
|
154
|
+
|
155
|
+
snap = @ec2.snapshots.create(volume[:aws_id])
|
156
|
+
|
157
|
+
if !snap || !snap.is_a?(Hash)
|
158
|
+
puts "There was an unknown problem creating #{backup.name}. Continuing with the next volume..."
|
159
|
+
next
|
160
|
+
end
|
161
|
+
|
162
|
+
backup.awsid = snap[:aws_id]
|
163
|
+
|
164
|
+
Rudy::MetaData::Backup.save(@sdb, backup)
|
165
|
+
|
166
|
+
puts backup.name
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,839 @@
|
|
1
|
+
|
2
|
+
module Rudy
|
3
|
+
class UnknownInstance < RuntimeError; end
|
4
|
+
end
|
5
|
+
|
6
|
+
module Rudy
|
7
|
+
module Command
|
8
|
+
class NoCred < RuntimeError; end;
|
9
|
+
|
10
|
+
class Base < Drydock::Command
|
11
|
+
|
12
|
+
attr_reader :scm
|
13
|
+
|
14
|
+
attr_reader :rscripts
|
15
|
+
attr_reader :domains
|
16
|
+
attr_reader :machine_images
|
17
|
+
|
18
|
+
attr_reader :config
|
19
|
+
|
20
|
+
|
21
|
+
def init
|
22
|
+
|
23
|
+
|
24
|
+
raise "PRODUCTION ACCESS IS DISABLED IN DEBUG MODE" if @global.environment == "prod" && Drydock.debug?
|
25
|
+
|
26
|
+
@global.config ||= RUDY_CONFIG_FILE
|
27
|
+
|
28
|
+
unless File.exists?(@global.config)
|
29
|
+
init_config_dir
|
30
|
+
end
|
31
|
+
|
32
|
+
@config = Rudy::Config.new(@global.config, {:verbose => (@global.verbose > 0)} )
|
33
|
+
@config.look_and_load
|
34
|
+
|
35
|
+
raise "There is no machine group configured" if @config.machines.nil?
|
36
|
+
raise "There is no AWS info configured" if @config.awsinfo.nil?
|
37
|
+
|
38
|
+
|
39
|
+
@global.accesskey ||= @config.awsinfo.accesskey || ENV['AWS_ACCESS_KEY']
|
40
|
+
@global.secretkey ||= @config.awsinfo.secretkey || ENV['AWS_SECRET_KEY'] || ENV['AWS_SECRET_ACCESS_KEY']
|
41
|
+
@global.account ||= @config.awsinfo.account || ENV['AWS_ACCOUNT_NUMBER']
|
42
|
+
|
43
|
+
@global.cert ||= @config.awsinfo.cert || ENV['EC2_CERT']
|
44
|
+
@global.privatekey ||= @config.awsinfo.privatekey || ENV['EC2_PRIVATE_KEY']
|
45
|
+
|
46
|
+
@global.cert = File.expand_path(@global.cert || '')
|
47
|
+
@global.privatekey = File.expand_path(@global.privatekey || '')
|
48
|
+
|
49
|
+
@global.region ||= @config.defaults.region || DEFAULT_REGION
|
50
|
+
@global.zone ||= @config.defaults.zone || DEFAULT_ZONE
|
51
|
+
@global.environment ||= @config.defaults.environment || DEFAULT_ENVIRONMENT
|
52
|
+
@global.role ||= @config.defaults.role || DEFAULT_ROLE
|
53
|
+
@global.position ||= @config.defaults.position || DEFAULT_POSITION
|
54
|
+
@global.user ||= @config.defaults.user || DEFAULT_USER
|
55
|
+
|
56
|
+
@global.local_user = ENV['USER'] || :user
|
57
|
+
@global.local_hostname = Socket.gethostname || :host
|
58
|
+
|
59
|
+
|
60
|
+
if @global.verbose > 1
|
61
|
+
puts "GLOBALS:"
|
62
|
+
@global.marshal_dump.each_pair do |n,v|
|
63
|
+
puts "#{n}: #{v}"
|
64
|
+
end
|
65
|
+
["machines", "routines"].each do |type|
|
66
|
+
puts "#{$/*2}#{type.upcase}:"
|
67
|
+
val = @config.send(type).find_deferred(@global.environment, @global.role)
|
68
|
+
puts val.to_hash.to_yaml
|
69
|
+
end
|
70
|
+
puts
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
# TODO: enforce home directory permissions
|
76
|
+
#if File.exists?(RUDY_CONFIG_DIR)
|
77
|
+
# puts "Checking #{check_environment} permissions..."
|
78
|
+
#end
|
79
|
+
|
80
|
+
if has_keys?
|
81
|
+
@ec2 = Rudy::AWS::EC2.new(@global.accesskey, @global.secretkey)
|
82
|
+
@sdb = Rudy::AWS::SimpleDB.new(@global.accesskey, @global.secretkey)
|
83
|
+
#@s3 = Rudy::AWS::SimpleDB.new(@global.accesskey, @global.secretkey)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
protected :init
|
87
|
+
|
88
|
+
def machine_data
|
89
|
+
machine_data = {
|
90
|
+
# Give the machine an identity
|
91
|
+
:zone => @global.zone,
|
92
|
+
:environment => @global.environment,
|
93
|
+
:role => @global.role,
|
94
|
+
:position => @global.position,
|
95
|
+
|
96
|
+
# Add hosts to the /etc/hosts file
|
97
|
+
:hosts => {
|
98
|
+
:dbmaster => "127.0.0.1",
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
machine_data.to_hash
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
# Raises exceptions if the requested user does
|
107
|
+
# not have a valid keypair configured. (See: EC2_KEYPAIR_*)
|
108
|
+
def check_keys
|
109
|
+
raise "No SSH key provided for #{@global.user}! (check #{RUDY_CONFIG_FILE})" unless has_keypair?
|
110
|
+
raise "SSH key provided but cannot be found! (check #{RUDY_CONFIG_FILE})" unless File.exists?(keypairpath)
|
111
|
+
end
|
112
|
+
|
113
|
+
def has_pem_keys?
|
114
|
+
(@global.cert && File.exists?(@global.cert) &&
|
115
|
+
@global.privatekey && File.exists?(@global.privatekey))
|
116
|
+
end
|
117
|
+
|
118
|
+
def has_keys?
|
119
|
+
(@global.accesskey && !@global.accesskey.empty? && @global.secretkey && !@global.secretkey.empty?)
|
120
|
+
end
|
121
|
+
|
122
|
+
def keypairpath(name=nil)
|
123
|
+
name ||= @global.user
|
124
|
+
raise "No default user configured" unless name
|
125
|
+
kp = @config.machines.find(@global.environment, @global.role, :users, name, :keypair2)
|
126
|
+
kp ||= @config.machines.find(@global.environment, :users, name, :keypair)
|
127
|
+
kp ||= @config.machines.find(:users, name, :keypair)
|
128
|
+
kp &&= File.expand_path(kp)
|
129
|
+
kp
|
130
|
+
end
|
131
|
+
def has_keypair?(name=nil)
|
132
|
+
kp = keypairpath(name)
|
133
|
+
(!kp.nil? && File.exists?(kp))
|
134
|
+
end
|
135
|
+
|
136
|
+
# Opens an SSH session.
|
137
|
+
# <li>+host+ the hostname to connect to. Defaults to the machine specified
|
138
|
+
# by @global.environment, @global.role, @global.position.</li>
|
139
|
+
# <li>+b+ a block to execute on the host. Receives |session|</li>
|
140
|
+
#
|
141
|
+
# ssh do |session|
|
142
|
+
# session.exec(cmd)
|
143
|
+
# end
|
144
|
+
#
|
145
|
+
# See Net::SSH
|
146
|
+
#
|
147
|
+
def ssh(host=nil, &b)
|
148
|
+
host ||= machine_hostname
|
149
|
+
raise "No host provided for SSH" unless host
|
150
|
+
raise "No block provided for SSH" unless b
|
151
|
+
|
152
|
+
Net::SSH.start(host, @global.user, :keys => [keypairpath]) do |session|
|
153
|
+
b.call(session)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Secure copy.
|
158
|
+
#
|
159
|
+
# scp do |scp|
|
160
|
+
# # upload a file to a remote server
|
161
|
+
# scp.upload! "/local/path", "/remote/path"
|
162
|
+
#
|
163
|
+
# # upload from an in-memory buffer
|
164
|
+
# scp.upload! StringIO.new("some data to upload"), "/remote/path"
|
165
|
+
#
|
166
|
+
# # run multiple downloads in parallel
|
167
|
+
# d1 = scp.download("/remote/path", "/local/path")
|
168
|
+
# d2 = scp.download("/remote/path2", "/local/path2")
|
169
|
+
# [d1, d2].each { |d| d.wait }
|
170
|
+
# end
|
171
|
+
#
|
172
|
+
def scp(host=nil, &b)
|
173
|
+
host ||= machine_hostname
|
174
|
+
raise "No host provided for scp" unless host
|
175
|
+
raise "No block provided for scp" unless b
|
176
|
+
|
177
|
+
Net::SCP.start(host, @global.user, :keys => [keypairpath]) do |scp|
|
178
|
+
b.call(scp)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# +name+ the name of the remote user to use for the remainder of the command
|
183
|
+
# (or until switched again). If no name is provided, the user will be revert
|
184
|
+
# to whatever it was before the previous switch.
|
185
|
+
def switch_user(name=nil)
|
186
|
+
if name == nil && @switch_user_previous
|
187
|
+
@global.user = @switch_user_previous
|
188
|
+
elsif @global.user != name
|
189
|
+
puts "Remote commands will be run as #{name} user"
|
190
|
+
@switch_user_previous = @global.user
|
191
|
+
@global.user = name
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Returns a hash of info for the requested machine. If the requested machine
|
196
|
+
# is not running, it will raise an exception.
|
197
|
+
def find_current_machine
|
198
|
+
find_machine(machine_group)
|
199
|
+
end
|
200
|
+
|
201
|
+
def find_machine(group)
|
202
|
+
machine_list = @ec2.instances.list(group)
|
203
|
+
machine = machine_list.values.first # NOTE: Only one machine per group, for now...
|
204
|
+
raise "There's no machine running in #{group}" unless machine
|
205
|
+
raise "The primary machine in #{group} is not in a running state" unless machine[:aws_state] == 'running'
|
206
|
+
machine
|
207
|
+
end
|
208
|
+
|
209
|
+
def machine_hostname(group=nil)
|
210
|
+
group ||= machine_group
|
211
|
+
find_machine(group)[:dns_name]
|
212
|
+
end
|
213
|
+
|
214
|
+
def machine_group
|
215
|
+
[@global.environment, @global.role].join(RUDY_DELIM)
|
216
|
+
end
|
217
|
+
|
218
|
+
def machine_image
|
219
|
+
ami = @config.machines.find_deferred(@global.environment, @global.role, :ami)
|
220
|
+
raise "There is no AMI configured for #{machine_group}" unless ami
|
221
|
+
ami
|
222
|
+
end
|
223
|
+
|
224
|
+
def machine_address
|
225
|
+
@config.machines.find_deferred(@global.environment, @global.role, :address)
|
226
|
+
end
|
227
|
+
|
228
|
+
# TODO: fix machine_group to include zone
|
229
|
+
def machine_name
|
230
|
+
[@global.zone, machine_group, @global.position].join(RUDY_DELIM)
|
231
|
+
end
|
232
|
+
|
233
|
+
def instance_id?(id=nil)
|
234
|
+
(id && id[0,2] == "i-")
|
235
|
+
end
|
236
|
+
|
237
|
+
def image_id?(id=nil)
|
238
|
+
(id && id[0,4] == "ami-")
|
239
|
+
end
|
240
|
+
|
241
|
+
def volume_id?(id=nil)
|
242
|
+
(id && id[0,4] == "vol-")
|
243
|
+
end
|
244
|
+
|
245
|
+
def snapshot_id?(id=nil)
|
246
|
+
(id && id[0,5] == "snap-")
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
def wait_for_machine(id)
|
251
|
+
|
252
|
+
print "Waiting for #{id} to become available"
|
253
|
+
STDOUT.flush
|
254
|
+
|
255
|
+
while @ec2.instances.pending?(id)
|
256
|
+
sleep 2
|
257
|
+
print '.'
|
258
|
+
STDOUT.flush
|
259
|
+
end
|
260
|
+
|
261
|
+
machine = @ec2.instances.get(id)
|
262
|
+
|
263
|
+
puts " It's up!\a\a" # with bells
|
264
|
+
print "Waiting for SSH daemon at #{machine[:dns_name]}"
|
265
|
+
STDOUT.flush
|
266
|
+
|
267
|
+
while !Rudy::Utils.service_available?(machine[:dns_name], 22)
|
268
|
+
print '.'
|
269
|
+
STDOUT.flush
|
270
|
+
end
|
271
|
+
puts " It's up!\a\a\a"
|
272
|
+
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
def device_to_path(machine, device)
|
277
|
+
# /dev/sdr 10321208 154232 9642688 2% /rilli/app
|
278
|
+
dfoutput = ssh_command(machine[:dns_name], keypairpath, @global.user, "df #{device} | tail -1").chomp
|
279
|
+
dfvals = dfoutput.scan(/(#{device}).+\s(.+?)$/).flatten # ["/dev/sdr", "/rilli/app"]
|
280
|
+
dfvals.last
|
281
|
+
end
|
282
|
+
|
283
|
+
# +action+ is one of: :shutdown, :start, :deploy
|
284
|
+
# +machine+ is a right_aws machine instance hash
|
285
|
+
def execute_disk_routines(machines, action)
|
286
|
+
machines = [machines] unless machines.is_a?( Array)
|
287
|
+
|
288
|
+
puts "Running #{action.to_s.capitalize} DISK routines".att(:bright)
|
289
|
+
|
290
|
+
disks = @config.machines.find_deferred(@global.environment, @global.role, :disks)
|
291
|
+
routines = @config.routines.find(@global.environment, @global.role, action, :disks)
|
292
|
+
|
293
|
+
unless routines
|
294
|
+
puts "No #{action} disk routines."
|
295
|
+
return
|
296
|
+
end
|
297
|
+
|
298
|
+
switch_user("root")
|
299
|
+
|
300
|
+
machines.each do |machine|
|
301
|
+
|
302
|
+
unless machine[:aws_instance_id]
|
303
|
+
puts "Machine given has no instance ID. Skipping disks."
|
304
|
+
return
|
305
|
+
end
|
306
|
+
|
307
|
+
unless machine[:dns_name]
|
308
|
+
puts "Machine given has no DNS name. Skipping disks."
|
309
|
+
return
|
310
|
+
end
|
311
|
+
|
312
|
+
if routines.destroy
|
313
|
+
disk_paths = routines.destroy.keys
|
314
|
+
vols = @ec2.instances.volumes(machine[:aws_instance_id]) || []
|
315
|
+
puts "No volumes to destroy for (#{machine[:aws_instance_id]})" if vols.empty?
|
316
|
+
vols.each do |vol|
|
317
|
+
disk = Rudy::MetaData::Disk.find_from_volume(@sdb, vol[:aws_id])
|
318
|
+
if disk
|
319
|
+
this_path = disk.path
|
320
|
+
else
|
321
|
+
puts "No disk metadata for volume #{vol[:aws_id]}. Going old school..."
|
322
|
+
this_path = device_to_path(machine, vol[:aws_device])
|
323
|
+
end
|
324
|
+
|
325
|
+
dconf = disks[this_path]
|
326
|
+
|
327
|
+
unless dconf
|
328
|
+
puts "#{this_path} is not defined for this machine. Check your machines config."
|
329
|
+
next
|
330
|
+
end
|
331
|
+
|
332
|
+
if disk_paths.member?(this_path)
|
333
|
+
|
334
|
+
unless disks.has_key?(this_path)
|
335
|
+
puts "#{this_path} is not defined as a machine disk. Skipping..."
|
336
|
+
next
|
337
|
+
end
|
338
|
+
|
339
|
+
begin
|
340
|
+
puts "Unmounting #{this_path}..."
|
341
|
+
ssh_command machine[:dns_name], keypairpath, @global.user, "umount #{this_path}"
|
342
|
+
sleep 3
|
343
|
+
rescue => ex
|
344
|
+
puts "Error while unmounting #{this_path}: #{ex.message}"
|
345
|
+
puts ex.backtrace if Drydock.debug?
|
346
|
+
puts "We'll keep going..."
|
347
|
+
end
|
348
|
+
|
349
|
+
begin
|
350
|
+
|
351
|
+
if @ec2.volumes.attached?(disk.awsid)
|
352
|
+
puts "Detaching #{vol[:aws_id]}"
|
353
|
+
@ec2.volumes.detach(vol[:aws_id])
|
354
|
+
sleep 3 # TODO: replace with something like wait_for_machine
|
355
|
+
end
|
356
|
+
|
357
|
+
puts "Destroying #{this_path} (#{vol[:aws_id]})"
|
358
|
+
if @ec2.volumes.available?(disk.awsid)
|
359
|
+
@ec2.volumes.destroy(vol[:aws_id])
|
360
|
+
else
|
361
|
+
puts "Volume is still attached (maybe a web server of database is running?)"
|
362
|
+
end
|
363
|
+
|
364
|
+
if disk
|
365
|
+
puts "Deleteing metadata for #{disk.name}"
|
366
|
+
Rudy::MetaData::Disk.destroy(@sdb, disk)
|
367
|
+
end
|
368
|
+
|
369
|
+
rescue => ex
|
370
|
+
puts "Error while detaching volume #{vol[:aws_id]}: #{ex.message}"
|
371
|
+
puts ex.backtrace if Drydock.debug?
|
372
|
+
puts "Continuing..."
|
373
|
+
end
|
374
|
+
|
375
|
+
end
|
376
|
+
puts
|
377
|
+
|
378
|
+
end
|
379
|
+
|
380
|
+
end
|
381
|
+
|
382
|
+
|
383
|
+
if routines.mount
|
384
|
+
disk_paths = routines.mount.keys
|
385
|
+
vols = @ec2.instances.volumes(machine[:aws_instance_id]) || []
|
386
|
+
puts "No volumes to mount for (#{machine[:aws_instance_id]})" if vols.empty?
|
387
|
+
vols.each do |vol|
|
388
|
+
disk = Rudy::MetaData::Disk.find_from_volume(@sdb, vol[:aws_id])
|
389
|
+
if disk
|
390
|
+
this_path = disk.path
|
391
|
+
else
|
392
|
+
puts "No disk metadata for volume #{vol[:aws_id]}. Going old school..."
|
393
|
+
this_path = device_to_path(machine, vol[:aws_device])
|
394
|
+
end
|
395
|
+
|
396
|
+
next unless disk_paths.member?(this_path)
|
397
|
+
|
398
|
+
dconf = disks[this_path]
|
399
|
+
|
400
|
+
unless dconf
|
401
|
+
puts "#{this_path} is not defined for this machine. Check your machines config."
|
402
|
+
next
|
403
|
+
end
|
404
|
+
|
405
|
+
|
406
|
+
begin
|
407
|
+
unless @ec2.instances.attached_volume?(machine[:aws_instance_id], vol[:aws_device])
|
408
|
+
puts "Attaching #{vol[:aws_id]} to #{machine[:aws_instance_id]}".att(:bright)
|
409
|
+
@ec2.volumes.attach(machine[:aws_instance_id], vol[:aws_id],vol[:aws_device])
|
410
|
+
sleep 3
|
411
|
+
end
|
412
|
+
|
413
|
+
puts "Mounting #{this_path} to #{vol[:aws_device]}".att(:bright)
|
414
|
+
ssh_command machine[:dns_name], keypairpath, @global.user, "mkdir -p #{this_path} && mount -t ext3 #{vol[:aws_device]} #{this_path}"
|
415
|
+
|
416
|
+
sleep 1
|
417
|
+
rescue => ex
|
418
|
+
puts "There was an error mounting #{this_path}: #{ex.message}"
|
419
|
+
puts ex.backtrace if Drydock.debug?
|
420
|
+
end
|
421
|
+
puts
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
|
426
|
+
|
427
|
+
if routines.restore
|
428
|
+
|
429
|
+
routines.restore.each_pair do |path,props|
|
430
|
+
from = props[:from] || "unknown"
|
431
|
+
unless from.to_s == "backup"
|
432
|
+
puts "Sorry! You can currently only restore from backup. Check your routines config."
|
433
|
+
next
|
434
|
+
end
|
435
|
+
|
436
|
+
begin
|
437
|
+
puts "Restoring disk for #{path}"
|
438
|
+
|
439
|
+
dconf = disks[path]
|
440
|
+
|
441
|
+
unless dconf
|
442
|
+
puts "#{path} is not defined for this machine. Check your machines config."
|
443
|
+
next
|
444
|
+
end
|
445
|
+
|
446
|
+
zon = props[:zone] || @global.zone
|
447
|
+
env = props[:environment] || @global.environment
|
448
|
+
rol = props[:role] || @global.role
|
449
|
+
pos = props[:position] || @global.position
|
450
|
+
puts "Looking for backup from #{zon}-#{env}-#{rol}-#{pos}"
|
451
|
+
backup = find_most_recent_backup(zon, env, rol, pos, path)
|
452
|
+
|
453
|
+
unless backup
|
454
|
+
puts "No backups found"
|
455
|
+
next
|
456
|
+
end
|
457
|
+
|
458
|
+
puts "Found: #{backup.name}".att(:bright)
|
459
|
+
|
460
|
+
disk = Rudy::MetaData::Disk.new
|
461
|
+
disk.path = path
|
462
|
+
[:region, :zone, :environment, :role, :position].each do |n|
|
463
|
+
disk.send("#{n}=", @global.send(n)) if @global.send(n)
|
464
|
+
end
|
465
|
+
|
466
|
+
disk.device = dconf[:device]
|
467
|
+
size = (backup.size.to_i > dconf[:size].to_i) ? backup.size : dconf[:size]
|
468
|
+
disk.size = size.to_i
|
469
|
+
|
470
|
+
|
471
|
+
if Rudy::MetaData::Disk.is_defined?(@sdb, disk)
|
472
|
+
puts "The disk #{disk.name} already exists."
|
473
|
+
puts "You probably need to define when to destroy the disk."
|
474
|
+
puts "Skipping..."
|
475
|
+
next
|
476
|
+
end
|
477
|
+
|
478
|
+
if @ec2.instances.attached_volume?(machine[:aws_instance_id], disk.device)
|
479
|
+
puts "Skipping disk for #{disk.path} (device #{disk.device} is in use)"
|
480
|
+
next
|
481
|
+
end
|
482
|
+
|
483
|
+
# NOTE: It's important to use Caesars' hash syntax b/c the disk property
|
484
|
+
# "size" conflicts with Hash#size which is what we'll get if there's no
|
485
|
+
# size defined.
|
486
|
+
unless disk.size.kind_of?(Integer)
|
487
|
+
puts "Skipping disk for #{disk.path} (size not defined)"
|
488
|
+
next
|
489
|
+
end
|
490
|
+
|
491
|
+
if disk.path.nil?
|
492
|
+
puts "Skipping disk for #{disk.path} (no path defined)"
|
493
|
+
next
|
494
|
+
end
|
495
|
+
|
496
|
+
unless disk.valid?
|
497
|
+
puts "Skipping #{disk.name} (not enough info)"
|
498
|
+
next
|
499
|
+
end
|
500
|
+
|
501
|
+
puts "Creating volume... (from #{backup.awsid})".att(:bright)
|
502
|
+
volume = @ec2.volumes.create(@global.zone, disk.size, backup.awsid)
|
503
|
+
|
504
|
+
puts "Attaching #{volume[:aws_id]} to #{machine[:aws_instance_id]}".att(:bright)
|
505
|
+
@ec2.volumes.attach(machine[:aws_instance_id], volume[:aws_id], disk.device)
|
506
|
+
sleep 3
|
507
|
+
|
508
|
+
puts "Mounting #{disk.device} to #{disk.path}".att(:bright)
|
509
|
+
ssh_command machine[:dns_name], keypairpath, @global.user, "mkdir -p #{disk.path} && mount -t ext3 #{disk.device} #{disk.path}"
|
510
|
+
|
511
|
+
puts "Creating disk metadata for #{disk.name}"
|
512
|
+
disk.awsid = volume[:aws_id]
|
513
|
+
Rudy::MetaData::Disk.save(@sdb, disk)
|
514
|
+
|
515
|
+
sleep 1
|
516
|
+
rescue => ex
|
517
|
+
puts "There was an error creating #{path}: #{ex.message}"
|
518
|
+
puts ex.backtrace if Drydock.debug?
|
519
|
+
if disk
|
520
|
+
puts "Removing metadata for #{disk.name}"
|
521
|
+
Rudy::MetaData::Disk.destroy(@sdb, disk)
|
522
|
+
end
|
523
|
+
end
|
524
|
+
puts
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
|
529
|
+
|
530
|
+
if routines.create
|
531
|
+
routines.create.each_pair do |path,props|
|
532
|
+
|
533
|
+
begin
|
534
|
+
puts "Creating disk for #{path}"
|
535
|
+
|
536
|
+
dconf = disks[path]
|
537
|
+
|
538
|
+
unless dconf
|
539
|
+
puts "#{path} is not defined for this machine. Check your machines config."
|
540
|
+
next
|
541
|
+
end
|
542
|
+
|
543
|
+
disk = Rudy::MetaData::Disk.new
|
544
|
+
disk.path = path
|
545
|
+
[:region, :zone, :environment, :role, :position].each do |n|
|
546
|
+
disk.send("#{n}=", @global.send(n)) if @global.send(n)
|
547
|
+
end
|
548
|
+
[:device, :size].each do |n|
|
549
|
+
disk.send("#{n}=", dconf[n]) if dconf.has_key?(n)
|
550
|
+
end
|
551
|
+
|
552
|
+
if Rudy::MetaData::Disk.is_defined?(@sdb, disk)
|
553
|
+
puts "The disk #{disk.name} already exists."
|
554
|
+
puts "You probably need to define when to destroy the disk."
|
555
|
+
puts "Skipping..."
|
556
|
+
next
|
557
|
+
end
|
558
|
+
|
559
|
+
if @ec2.instances.attached_volume?(machine[:aws_instance_id], disk.device)
|
560
|
+
puts "Skipping disk for #{disk.path} (device #{disk.device} is in use)"
|
561
|
+
next
|
562
|
+
end
|
563
|
+
|
564
|
+
# NOTE: It's important to use Caesars' hash syntax b/c the disk property
|
565
|
+
# "size" conflicts with Hash#size which is what we'll get if there's no
|
566
|
+
# size defined.
|
567
|
+
unless disk.size.kind_of?(Integer)
|
568
|
+
puts "Skipping disk for #{disk.path} (size not defined)"
|
569
|
+
next
|
570
|
+
end
|
571
|
+
|
572
|
+
if disk.path.nil?
|
573
|
+
puts "Skipping disk for #{disk.path} (no path defined)"
|
574
|
+
next
|
575
|
+
end
|
576
|
+
|
577
|
+
unless disk.valid?
|
578
|
+
puts "Skipping #{disk.name} (not enough info)"
|
579
|
+
next
|
580
|
+
end
|
581
|
+
|
582
|
+
puts "Creating volume... (#{disk.size}GB in #{@global.zone})".att(:bright)
|
583
|
+
volume = @ec2.volumes.create(@global.zone, disk.size)
|
584
|
+
|
585
|
+
puts "Attaching #{volume[:aws_id]} to #{machine[:aws_instance_id]}".att(:bright)
|
586
|
+
@ec2.volumes.attach(machine[:aws_instance_id], volume[:aws_id], disk.device)
|
587
|
+
sleep 6
|
588
|
+
|
589
|
+
puts "Creating the filesystem (mkfs.ext3 -F #{disk.device})".att(:bright)
|
590
|
+
ssh_command machine[:dns_name], keypairpath, @global.user, "mkfs.ext3 -F #{disk.device}"
|
591
|
+
sleep 3
|
592
|
+
|
593
|
+
puts "Mounting #{disk.device} to #{disk.path}".att(:bright)
|
594
|
+
ssh_command machine[:dns_name], keypairpath, @global.user, "mkdir -p #{disk.path} && mount -t ext3 #{disk.device} #{disk.path}"
|
595
|
+
|
596
|
+
puts "Creating disk metadata for #{disk.name}"
|
597
|
+
disk.awsid = volume[:aws_id]
|
598
|
+
Rudy::MetaData::Disk.save(@sdb, disk)
|
599
|
+
|
600
|
+
sleep 1
|
601
|
+
rescue => ex
|
602
|
+
puts "There was an error creating #{path}: #{ex.message}"
|
603
|
+
if disk
|
604
|
+
puts "Removing metadata for #{disk.name}"
|
605
|
+
Rudy::MetaData::Disk.destroy(@sdb, disk)
|
606
|
+
end
|
607
|
+
end
|
608
|
+
puts
|
609
|
+
end
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
def find_most_recent_backup(zon, env, rol, pos, path)
|
615
|
+
criteria = [zon, env, rol, pos, path]
|
616
|
+
(Rudy::MetaData::Backup.list(@sdb, *criteria) || []).first
|
617
|
+
end
|
618
|
+
|
619
|
+
def execute_routines(machines, action, before_or_after)
|
620
|
+
machines = [machines] unless machines.is_a?( Array)
|
621
|
+
config = @config.routines.find_deferred(@global.environment, @global.role, :config) || {}
|
622
|
+
config[:global] = @global.marshal_dump
|
623
|
+
config[:global].reject! { |n,v| n == :cert || n == :privatekey }
|
624
|
+
|
625
|
+
# The config file contains settings from ~/.rudy/config
|
626
|
+
#
|
627
|
+
# routines do
|
628
|
+
# config do
|
629
|
+
# end
|
630
|
+
# end
|
631
|
+
#
|
632
|
+
config_file = "#{action}-config.yaml"
|
633
|
+
tf = Tempfile.new(config_file)
|
634
|
+
write_to_file(tf.path, config.to_hash.to_yaml, 'w')
|
635
|
+
puts "Running #{action.to_s.capitalize} #{before_or_after.to_s.upcase} routines".att(:bright)
|
636
|
+
machines.each do |machine|
|
637
|
+
|
638
|
+
rscripts = @config.routines.find_deferred(@global.environment, @global.role, action, before_or_after) || []
|
639
|
+
rscripts = [rscripts] unless rscripts.is_a?(Array)
|
640
|
+
|
641
|
+
puts "No scripts defined." if !rscripts || rscripts.empty?
|
642
|
+
|
643
|
+
rscripts.each do |rscript|
|
644
|
+
user, script = rscript.shift
|
645
|
+
|
646
|
+
switch_user(user) # scp and ssh will run as this user
|
647
|
+
|
648
|
+
puts "Transfering #{config_file}..."
|
649
|
+
scp do |scp|
|
650
|
+
scp.upload!(tf.path, "~/#{config_file}") do |ch, name, sent, total|
|
651
|
+
"#{name}: #{sent}/#{total}"
|
652
|
+
end
|
653
|
+
end
|
654
|
+
ssh do |session|
|
655
|
+
puts "Running #{script}...".att(:bright)
|
656
|
+
session.exec!("chmod 700 ~/#{config_file}")
|
657
|
+
session.exec!("chmod 700 #{script}")
|
658
|
+
puts session.exec!("#{script}")
|
659
|
+
|
660
|
+
puts "Removing remote copy of #{config_file}..."
|
661
|
+
session.exec!("rm ~/#{config_file}")
|
662
|
+
end
|
663
|
+
puts $/
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
tf.delete # remove local copy of config_file
|
668
|
+
#switch_user # return to the requested user
|
669
|
+
end
|
670
|
+
|
671
|
+
# Print a default header to the screen for every command.
|
672
|
+
# +cmd+ is the name of the command current running.
|
673
|
+
def print_header(cmd=nil)
|
674
|
+
title = "RUDY v#{Rudy::VERSION}" unless @global.quiet
|
675
|
+
now_utc = Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")
|
676
|
+
criteria = []
|
677
|
+
[:zone, :environment, :role, :position].each do |n|
|
678
|
+
val = @global.send(n)
|
679
|
+
next unless val
|
680
|
+
criteria << "#{n.to_s.slice(0,1).att :normal}:#{val.att :bright}"
|
681
|
+
end
|
682
|
+
puts '%s -- %s UTC' % [title, now_utc] unless @global.quiet
|
683
|
+
puts '[%s]' % criteria.join(" ") unless @global.quiet
|
684
|
+
|
685
|
+
puts unless @global.quiet
|
686
|
+
|
687
|
+
if (@global.environment == "prod")
|
688
|
+
msg = without_indent %q(
|
689
|
+
=======================================================
|
690
|
+
=======================================================
|
691
|
+
!!!!!!!!! YOU ARE PLAYING WITH PRODUCTION !!!!!!!!!
|
692
|
+
=======================================================
|
693
|
+
=======================================================)
|
694
|
+
puts msg.colour(:red).bgcolour(:white).att(:bright), $/ unless @global.quiet
|
695
|
+
|
696
|
+
end
|
697
|
+
|
698
|
+
if Rudy.in_situ?
|
699
|
+
msg = %q(============ THIS IS EC2 ============)
|
700
|
+
puts msg.colour(:blue).bgcolour(:white).att(:bright), $/ unless @global.quiet
|
701
|
+
end
|
702
|
+
|
703
|
+
end
|
704
|
+
|
705
|
+
def print_footer
|
706
|
+
|
707
|
+
end
|
708
|
+
|
709
|
+
|
710
|
+
|
711
|
+
|
712
|
+
def group_metadata(env=@global.environment, role=@global.role)
|
713
|
+
query = "['environment' = '#{env}'] intersection ['role' = '#{role}']"
|
714
|
+
@sdb.query_with_attributes(RUDY_DOMAIN, query)
|
715
|
+
end
|
716
|
+
|
717
|
+
private
|
718
|
+
# Print info about a running instance
|
719
|
+
# +inst+ is a hash
|
720
|
+
def print_instance(inst)
|
721
|
+
puts '-'*60
|
722
|
+
puts "Instance: #{inst[:aws_instance_id].att(:bright)} (AMI: #{inst[:aws_image_id]})"
|
723
|
+
[:aws_state, :dns_name, :private_dns_name, :aws_availability_zone, :aws_launch_time, :ssh_key_name].each do |key|
|
724
|
+
printf(" %22s: %s#{$/}", key, inst[key]) if inst[key]
|
725
|
+
end
|
726
|
+
printf(" %22s: %s#{$/}", 'aws_groups', inst[:aws_groups].join(', '))
|
727
|
+
puts
|
728
|
+
end
|
729
|
+
|
730
|
+
def print_image(img)
|
731
|
+
puts '-'*60
|
732
|
+
puts "Image: #{img[:aws_id].att(:bright)}"
|
733
|
+
img.each_pair do |key, value|
|
734
|
+
printf(" %22s: %s#{$/}", key, value) if value
|
735
|
+
end
|
736
|
+
puts
|
737
|
+
end
|
738
|
+
|
739
|
+
def print_disk(disk, backups=[])
|
740
|
+
puts '-'*60
|
741
|
+
puts "Disk: #{disk.name.att(:bright)}"
|
742
|
+
puts disk.to_s
|
743
|
+
puts "#{backups.size} most recent backups:", backups.collect { |back| "#{back.nice_time} (#{back.awsid})" }
|
744
|
+
puts
|
745
|
+
end
|
746
|
+
|
747
|
+
|
748
|
+
def print_volume(vol, disk)
|
749
|
+
puts '-'*60
|
750
|
+
puts "Volume: #{vol[:aws_id].att(:bright)} (disk: #{disk.name if disk})"
|
751
|
+
vol.each_pair do |key, value|
|
752
|
+
printf(" %22s: %s#{$/}", key, value) if value
|
753
|
+
end
|
754
|
+
puts
|
755
|
+
end
|
756
|
+
|
757
|
+
# Print info about a a security group
|
758
|
+
# +group+ is an OpenStruct
|
759
|
+
def print_group(group)
|
760
|
+
puts '-'*60
|
761
|
+
puts "%12s: %s" % ['GROUP', group[:aws_group_name].att(:bright)]
|
762
|
+
puts
|
763
|
+
|
764
|
+
group_ip = {}
|
765
|
+
group[:aws_perms].each do |perm|
|
766
|
+
(group_ip[ perm[:cidr_ips] ] ||= []) << "#{perm[:protocol]}/#{perm[:from_port]}-#{perm[:to_port]}"
|
767
|
+
end
|
768
|
+
|
769
|
+
puts "%22s %s" % ["source address/mask", "protocol/ports (from, to)"]
|
770
|
+
|
771
|
+
|
772
|
+
group_ip.each_pair do |ip, perms|
|
773
|
+
puts "%22s %s" % [ip, perms.shift]
|
774
|
+
perms.each do |perm|
|
775
|
+
puts "%22s %s" % ['', perm]
|
776
|
+
end
|
777
|
+
puts
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
781
|
+
def init_config_dir
|
782
|
+
unless File.exists?(RUDY_CONFIG_DIR)
|
783
|
+
puts "Creating #{RUDY_CONFIG_DIR}"
|
784
|
+
Dir.mkdir(RUDY_CONFIG_DIR, 0700)
|
785
|
+
end
|
786
|
+
|
787
|
+
unless File.exists?(RUDY_CONFIG_FILE)
|
788
|
+
puts "Creating #{RUDY_CONFIG_FILE}"
|
789
|
+
rudy_config = without_indent %Q{
|
790
|
+
# Amazon Web Services
|
791
|
+
# Account access indentifiers.
|
792
|
+
awsinfo do
|
793
|
+
account ""
|
794
|
+
accesskey ""
|
795
|
+
secretkey ""
|
796
|
+
privatekey "~/path/2/pk-xxxx.pem"
|
797
|
+
cert "~/path/2/cert-xxxx.pem"
|
798
|
+
end
|
799
|
+
|
800
|
+
# Machine Configuration
|
801
|
+
# Specify your private keys here. These can be defined globally
|
802
|
+
# or by environment and role like in machines.rb.
|
803
|
+
machines do
|
804
|
+
ami "ami-0734d36e" # gentoo-m1.small-v5
|
805
|
+
users do
|
806
|
+
root :keypair => "path/2/root-private-key"
|
807
|
+
end
|
808
|
+
end
|
809
|
+
|
810
|
+
# Routine Configuration
|
811
|
+
# Define stuff here that you don't want to be stored in version control.
|
812
|
+
routines do
|
813
|
+
config do
|
814
|
+
# ...
|
815
|
+
end
|
816
|
+
end
|
817
|
+
|
818
|
+
# Global Defaults
|
819
|
+
# Define the values to use unless otherwise specified on the command-line.
|
820
|
+
defaults do
|
821
|
+
region "us-east-1"
|
822
|
+
zone "us-east-1b"
|
823
|
+
environment "stage"
|
824
|
+
role "app"
|
825
|
+
position "01"
|
826
|
+
user ENV['USER']
|
827
|
+
end
|
828
|
+
}
|
829
|
+
write_to_file(RUDY_CONFIG_FILE, rudy_config, 'w')
|
830
|
+
end
|
831
|
+
|
832
|
+
#puts "Creating SimpleDB domain called #{RUDY_DOMAIN}"
|
833
|
+
#@sdb.domains.create(RUDY_DOMAIN)
|
834
|
+
end
|
835
|
+
end
|
836
|
+
end
|
837
|
+
end
|
838
|
+
|
839
|
+
|