rudy 0.2
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 +21 -0
- data/LICENSE.txt +19 -0
- data/README.rdoc +32 -0
- data/Rakefile +68 -0
- data/bin/rudy +258 -0
- data/lib/drydock.rb +525 -0
- data/lib/rudy.rb +102 -0
- data/lib/rudy/aws.rb +65 -0
- data/lib/rudy/aws/ec2.rb +197 -0
- data/lib/rudy/aws/s3.rb +3 -0
- data/lib/rudy/aws/simpledb.rb +48 -0
- data/lib/rudy/command/addresses.rb +41 -0
- data/lib/rudy/command/base.rb +275 -0
- data/lib/rudy/command/commit.rb +10 -0
- data/lib/rudy/command/disks.rb +61 -0
- data/lib/rudy/command/environment.rb +95 -0
- data/lib/rudy/command/groups.rb +59 -0
- data/lib/rudy/command/images.rb +61 -0
- data/lib/rudy/command/instances.rb +109 -0
- data/lib/rudy/command/metadata.rb +57 -0
- data/lib/rudy/command/release.rb +43 -0
- data/lib/rudy/command/volumes.rb +13 -0
- data/lib/rudy/metadata/disk.rb +142 -0
- data/lib/rudy/metadata/environment.rb +0 -0
- data/lib/rudy/scm/svn.rb +57 -0
- data/lib/rudy/utils.rb +65 -0
- data/lib/storable.rb +268 -0
- data/rudy.gemspec +52 -0
- data/support/rudy-ec2-startup +166 -0
- metadata +87 -0
data/lib/rudy.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
|
2
|
+
require 'right_aws'
|
3
|
+
require 'stringio'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
require 'storable'
|
8
|
+
|
9
|
+
require 'rudy/aws'
|
10
|
+
require 'rudy/scm/svn'
|
11
|
+
require 'rudy/utils'
|
12
|
+
require 'rudy/command/base'
|
13
|
+
|
14
|
+
# Autoload Command and MetaData classes
|
15
|
+
begin
|
16
|
+
Dir.glob(File.join(RUDY_LIB, 'rudy', 'command', "*.rb")).each do |path|
|
17
|
+
require path
|
18
|
+
end
|
19
|
+
Dir.glob(File.join(RUDY_LIB, 'rudy', 'metadata', "*.rb")).each do |path|
|
20
|
+
require path
|
21
|
+
end
|
22
|
+
rescue LoadError => ex
|
23
|
+
puts "Error: #{ex.message}"
|
24
|
+
exit 1
|
25
|
+
end
|
26
|
+
|
27
|
+
module Rudy #:nodoc:
|
28
|
+
RUDY_DOMAIN = ENV['RUDY_DOMAIN'] || "rudy_state"
|
29
|
+
RUDY_DELIM = ENV['RUDY_DELIM'] || '-'
|
30
|
+
DEFAULT_REGION = ENV['EC2_DEFAULT_REGION'] || 'us-east-1'
|
31
|
+
DEFAULT_ZONE = ENV['EC2_DEFAULT_ZONE'] || 'us-east-1b'
|
32
|
+
|
33
|
+
module VERSION #:nodoc:
|
34
|
+
MAJOR = 0.freeze unless defined? MAJOR
|
35
|
+
MINOR = 2.freeze unless defined? MINOR
|
36
|
+
TINY = 0.freeze unless defined? TINY
|
37
|
+
def self.to_s
|
38
|
+
[MAJOR, MINOR, TINY].join('.')
|
39
|
+
end
|
40
|
+
def self.to_f
|
41
|
+
self.to_s.to_f
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
# Capture STDOUT or STDERR to prevent it from being printed.
|
49
|
+
#
|
50
|
+
# capture(:stdout) do
|
51
|
+
# ...
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
def capture(stream)
|
55
|
+
raise "We can only capture STDOUT or STDERR" unless stream == :stdout || stream == :stderr
|
56
|
+
|
57
|
+
# I'm using this to trap the annoying right_aws "peer certificate" warning.
|
58
|
+
# TODO: discover source of annoying right_aws warning and give it a hiding.
|
59
|
+
begin
|
60
|
+
stream = stream.to_s
|
61
|
+
eval "$#{stream} = StringIO.new"
|
62
|
+
yield
|
63
|
+
result = eval("$#{stream}").read
|
64
|
+
ensure
|
65
|
+
eval("$#{stream} = #{stream.upcase}")
|
66
|
+
end
|
67
|
+
|
68
|
+
result
|
69
|
+
end
|
70
|
+
|
71
|
+
def you_are_sure?
|
72
|
+
print "Are you sure? "
|
73
|
+
STDIN.gets =~ /^y|yes|ya$/i
|
74
|
+
end
|
75
|
+
def sh(command, chdir=false)
|
76
|
+
prevdir = Dir.pwd
|
77
|
+
Dir.chdir chdir if chdir
|
78
|
+
puts command if @verbose > 0
|
79
|
+
system(command)
|
80
|
+
Dir.chdir prevdir if chdir
|
81
|
+
end
|
82
|
+
|
83
|
+
# TODO: Net::SSH
|
84
|
+
def ssh(host, keypair, user, command, chdir=false, verbose=false)
|
85
|
+
cmd = "ssh -q -i #{keypair} #{user}@#{host} '"
|
86
|
+
cmd += "cd #{chdir} && " if chdir
|
87
|
+
cmd += " #{command}'"
|
88
|
+
puts cmd if verbose
|
89
|
+
system(cmd)
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def scp(host, keypair, user, local_path, remote_path, verbose=false)
|
94
|
+
cmd = "scp -i #{keypair} #{local_path} #{user}@#{host}:#{remote_path}"
|
95
|
+
puts cmd if verbose
|
96
|
+
system(cmd)
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
|
data/lib/rudy/aws.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
module Rudy
|
6
|
+
module AWS
|
7
|
+
|
8
|
+
module ObjectBase
|
9
|
+
attr_accessor :aws
|
10
|
+
def initialize(aws_connection)
|
11
|
+
@aws = aws_connection
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class EC2
|
16
|
+
@@logger = StringIO.new
|
17
|
+
|
18
|
+
attr_reader :instances
|
19
|
+
attr_reader :images
|
20
|
+
attr_reader :addresses
|
21
|
+
attr_reader :groups
|
22
|
+
attr_reader :volumes
|
23
|
+
attr_reader :aws
|
24
|
+
|
25
|
+
def initialize(access_key, secret_key)
|
26
|
+
@aws = RightAws::Ec2.new(access_key, secret_key, {:logger => Logger.new(@@logger)})
|
27
|
+
@instances = Rudy::AWS::EC2::Instances.new(@aws)
|
28
|
+
@images = Rudy::AWS::EC2::Images.new(@aws)
|
29
|
+
@groups = Rudy::AWS::EC2::Groups.new(@aws)
|
30
|
+
@addresses = Rudy::AWS::EC2::Addresses.new(@aws)
|
31
|
+
@volumes = Rudy::AWS::EC2::Volumes.new(@aws)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
class S3
|
37
|
+
@@logger = StringIO.new
|
38
|
+
|
39
|
+
attr_reader :aws
|
40
|
+
|
41
|
+
def initialize(access_key, secret_key)
|
42
|
+
@aws = RightAws::S3.new(access_key, secret_key, {:logger => Logger.new(@@logger)})
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class SimpleDB
|
47
|
+
@@logger = StringIO.new
|
48
|
+
|
49
|
+
attr_reader :domains
|
50
|
+
attr_reader :aws
|
51
|
+
|
52
|
+
def initialize(access_key, secret_key)
|
53
|
+
@aws = RightAws::SdbInterface.new(access_key, secret_key, {:logger => Logger.new(@@logger)})
|
54
|
+
@domains = Rudy::AWS::SimpleDB::Domains.new(@aws)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
require 'rudy/aws/simpledb'
|
60
|
+
require 'rudy/aws/ec2'
|
61
|
+
require 'rudy/aws/s3'
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/lib/rudy/aws/ec2.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
|
2
|
+
module Rudy::AWS
|
3
|
+
|
4
|
+
class EC2
|
5
|
+
class Images
|
6
|
+
include Rudy::AWS::ObjectBase
|
7
|
+
|
8
|
+
# Returns an array of hashes:
|
9
|
+
# {:aws_architecture=>"i386", :aws_owner=>"105148267242", :aws_id=>"ami-6fe40dd5",
|
10
|
+
# :aws_image_type=>"machine", :aws_location=>"bucket-name/your-image.manifest.xml",
|
11
|
+
# :aws_kernel_id=>"aki-a71cf9ce", :aws_state=>"available", :aws_ramdisk_id=>"ari-a51cf9cc",
|
12
|
+
# :aws_is_public=>false}
|
13
|
+
def list
|
14
|
+
@aws.describe_images_by_owner('self') || []
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
class Volumes
|
19
|
+
include Rudy::AWS::ObjectBase
|
20
|
+
|
21
|
+
def list
|
22
|
+
list = @aws.describe_volumes() || []
|
23
|
+
list.select { |v| v[:aws_status] != "deleting" }
|
24
|
+
end
|
25
|
+
|
26
|
+
def attach(inst_id, vol_id, device)
|
27
|
+
@aws.attach_volume(vol_id, inst_id, device)
|
28
|
+
end
|
29
|
+
|
30
|
+
def create(zone, size, snapshot=nil)
|
31
|
+
@aws.create_volume(snapshot, size, zone)
|
32
|
+
end
|
33
|
+
|
34
|
+
def exists?(id)
|
35
|
+
list.each do |v|
|
36
|
+
return true if v[:aws_id] === id
|
37
|
+
end
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
class Instances
|
44
|
+
include Rudy::AWS::ObjectBase
|
45
|
+
|
46
|
+
def destroy(*list)
|
47
|
+
begin
|
48
|
+
@aws.terminate_instances(list.flatten)
|
49
|
+
rescue RightAws::AwsError => ex
|
50
|
+
raise UnknownInstance.new
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def attached_volume?(id, device)
|
55
|
+
list = volumes(id)
|
56
|
+
list.each do |v|
|
57
|
+
return true if v[:aws_device] == device
|
58
|
+
end
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
def volumes(id)
|
63
|
+
list = @aws.describe_volumes() || []
|
64
|
+
list.select { |v| v[:aws_status] != "deleting" && v[:aws_instance_id] === id }
|
65
|
+
end
|
66
|
+
|
67
|
+
def create(ami, group, keypair_name, user_data, zone)
|
68
|
+
@aws.run_instances(ami, 1, 1, [group], keypair_name, user_data, 'public', nil, nil, nil, zone)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Creates a list of running instance IDs which are in a security group
|
72
|
+
# that matches +filter+.
|
73
|
+
# Returns a hash. The keys are instance IDs and the values are a hash
|
74
|
+
# of attributes associated to that instance.
|
75
|
+
def list(filter='.')
|
76
|
+
filter = filter.to_s.downcase.tr('_|-', '.') # treat dashes, underscores as one
|
77
|
+
# Returns an array of hashes with the following keys:
|
78
|
+
# :aws_image_id, :aws_reason, :aws_state_code, :aws_owner, :aws_instance_id, :aws_reservation_id
|
79
|
+
# :aws_state, :dns_name, :ssh_key_name, :aws_groups, :private_dns_name, :aws_instance_type,
|
80
|
+
# :aws_launch_time, :aws_availability_zone :aws_kernel_id, :aws_ramdisk_id
|
81
|
+
instances = @aws.describe_instances || []
|
82
|
+
running_instances = {}
|
83
|
+
instances.each do |inst|
|
84
|
+
if inst[:aws_state] != "terminated" && (inst[:aws_groups].to_s =~ /#{filter}/)
|
85
|
+
running_instances[inst[:aws_instance_id]] = inst
|
86
|
+
end
|
87
|
+
end
|
88
|
+
running_instances
|
89
|
+
end
|
90
|
+
|
91
|
+
def get(inst_id)
|
92
|
+
instance = {}
|
93
|
+
list.each_pair do |id, hash|
|
94
|
+
next unless inst_id == id
|
95
|
+
instance = hash
|
96
|
+
end
|
97
|
+
instance
|
98
|
+
end
|
99
|
+
|
100
|
+
def running?(inst_id)
|
101
|
+
inst = get(inst_id)
|
102
|
+
(inst && inst[:aws_state] == "running")
|
103
|
+
end
|
104
|
+
|
105
|
+
def pending?(inst_id)
|
106
|
+
inst = get(inst_id)
|
107
|
+
(inst && inst[:aws_state] == "pending")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class Groups
|
112
|
+
include Rudy::AWS::ObjectBase
|
113
|
+
|
114
|
+
|
115
|
+
# +list+ is a list of security groups to look for. If it's empty, all groups
|
116
|
+
# associated to the account will be returned.
|
117
|
+
# right_aws returns an array of hashes
|
118
|
+
# :aws_group_name => "default-1",
|
119
|
+
# :aws_owner => "000000000888",
|
120
|
+
# :aws_description => "Default allowing SSH, HTTP, and HTTPS ingress",
|
121
|
+
# :aws_perms => [{:owner => "000000000888", :group => "default"},
|
122
|
+
# {:owner => "000000000888", :group => "default-1"},
|
123
|
+
# {:to_port => "-1", :protocol => "icmp", :from_port => "-1", :cidr_ips => "0.0.0.0/0"}]
|
124
|
+
# ]
|
125
|
+
def list(list=[])
|
126
|
+
glist = @aws.describe_security_groups(list) || []
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
# Create a new EC2 security group
|
131
|
+
# Returns true/false whether successful
|
132
|
+
def create(name, desc='')
|
133
|
+
@aws.create_security_group(name, desc)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Delete an EC2 security group
|
137
|
+
# Returns true/false whether successful
|
138
|
+
def destroy(name)
|
139
|
+
@aws.delete_security_group(name)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Modify an EC2 security group
|
143
|
+
# Returns true/false whether successful
|
144
|
+
def modify(name, from_port, to_port, protocol='tcp', ipa='0.0.0.0/0')
|
145
|
+
@aws.authorize_security_group_IP_ingress(name, from_port, to_port, protocol, ipa)
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
# Does the security group +name+ exist?
|
150
|
+
def exists?(name)
|
151
|
+
begin
|
152
|
+
g = list([name.to_s])
|
153
|
+
|
154
|
+
rescue RightAws::AwsError => ex
|
155
|
+
# Ignore (it raises an exception when the list contains an unknown group name)
|
156
|
+
ensure
|
157
|
+
g ||= []
|
158
|
+
end
|
159
|
+
|
160
|
+
!g.empty?
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
class Addresses
|
166
|
+
include Rudy::AWS::ObjectBase
|
167
|
+
|
168
|
+
# Returns and array of hashes:
|
169
|
+
# [{:instance_id=>"i-d630cbbf", :public_ip=>"75.101.1.140"},
|
170
|
+
# {:instance_id=>nil, :public_ip=>"75.101.1.141"}]
|
171
|
+
def list
|
172
|
+
@aws.describe_addresses || []
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
# Associate an elastic IP to an instance
|
177
|
+
def associate(instance, address)
|
178
|
+
@aws.associate_address(instance, address)
|
179
|
+
end
|
180
|
+
|
181
|
+
def valid?(address)
|
182
|
+
list.each do |a|
|
183
|
+
return true if a[:public_ip] == address
|
184
|
+
end
|
185
|
+
false
|
186
|
+
end
|
187
|
+
|
188
|
+
def associated?(address)
|
189
|
+
list.each do |a|
|
190
|
+
return true if a[:public_ip] == address && a[:instance_id]
|
191
|
+
end
|
192
|
+
false
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
data/lib/rudy/aws/s3.rb
ADDED
@@ -0,0 +1,48 @@
|
|
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 get_attributes(domain, item, attribute=nil)
|
45
|
+
@aws.get_attributes(domain, item, attribute)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
module Rudy
|
5
|
+
module Command
|
6
|
+
class Addresses < Rudy::Command::Base
|
7
|
+
|
8
|
+
def associate_address(address)
|
9
|
+
raise "You did not supply an instance ID" unless instance
|
10
|
+
inst = @ec2.instances.get(instance)
|
11
|
+
raise "Instance #{inst[:aws_instance_id]} is not running!" unless inst
|
12
|
+
|
13
|
+
raise "You have not supplied an IP addresses" unless address
|
14
|
+
raise "That's not an elastic IP you own!" unless @ec2.addresses.valid?(address)
|
15
|
+
raise "#{address} is already associated!" if @ec2.addresses.associated?(address)
|
16
|
+
|
17
|
+
puts "Associating #{address} to #{inst[:aws_groups]}: #{inst[:dns_name]}"
|
18
|
+
@ec2.addresses.associate(inst[:aws_instance_id], address)
|
19
|
+
puts "Done!"
|
20
|
+
puts
|
21
|
+
|
22
|
+
print_addresses
|
23
|
+
end
|
24
|
+
|
25
|
+
def print_addresses
|
26
|
+
puts "Elastic IP mappings:"
|
27
|
+
@ec2.addresses.list.each do |address|
|
28
|
+
print "IP: #{address[:public_ip]} "
|
29
|
+
if address[:instance_id]
|
30
|
+
inst = @ec2.instances.get(address[:instance_id])
|
31
|
+
puts "%s: %s %s" % [inst[:aws_groups], inst[:aws_instance_id], inst[:dns_name]]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
puts
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|