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.
@@ -0,0 +1,210 @@
1
+
2
+ #
3
+ # No Ruby 1.9.1 support. Only 1.8.x for now :[
4
+ unless RUBY_VERSION < "1.9"
5
+ puts "Sorry! We're using the right_aws gem and it doesn't support Ruby 1.9 (md5 error)."
6
+ exit 1
7
+ end
8
+
9
+
10
+ begin
11
+ require 'digest/md5'
12
+ require 'right_aws'
13
+ require 'stringio'
14
+ require 'ostruct'
15
+ require 'yaml'
16
+ require 'socket'
17
+ require 'tempfile'
18
+
19
+ require 'console'
20
+ require 'storable'
21
+
22
+ require 'net/ssh'
23
+ require 'net/ssh/gateway'
24
+ require 'net/ssh/multi'
25
+ require 'net/scp'
26
+
27
+ rescue LoadError => ex
28
+ puts "Problem requiring: #{ex.message}"
29
+ exit 1
30
+ end
31
+
32
+
33
+
34
+ module Rudy #:nodoc:
35
+ RUDY_DOMAIN = "rudy_state"
36
+ RUDY_DELIM = '-'
37
+
38
+ RUDY_CONFIG_DIR = File.join(ENV['HOME'] || ENV['USERPROFILE'], '.rudy')
39
+ RUDY_CONFIG_FILE = File.join(RUDY_CONFIG_DIR, 'config')
40
+
41
+ DEFAULT_REGION = 'us-east-1'
42
+ DEFAULT_ZONE = 'us-east-1b'
43
+ DEFAULT_ENVIRONMENT = 'stage'
44
+ DEFAULT_ROLE = 'app'
45
+ DEFAULT_POSITION = '01'
46
+
47
+ DEFAULT_USER = 'rudy'
48
+
49
+ SUPPORTED_SCM_NAMES = [:svn, :git]
50
+
51
+ module VERSION #:nodoc:
52
+ MAJOR = 0.freeze unless defined? MAJOR
53
+ MINOR = 4.freeze unless defined? MINOR
54
+ TINY = 0.freeze unless defined? TINY
55
+ def self.to_s
56
+ [MAJOR, MINOR, TINY].join('.')
57
+ end
58
+ def self.to_f
59
+ self.to_s.to_f
60
+ end
61
+ end
62
+
63
+ # Determine if we're running directly on EC2 or
64
+ # "some other machine". We do this by checking if
65
+ # the file /etc/ec2/instance-id exists. This
66
+ # file is written by /etc/init.d/rudy-ec2-startup.
67
+ # NOTE: Is there a way to know definitively that this is EC2?
68
+ # We could make a request to the metadata IP addresses.
69
+ def self.in_situ?
70
+ File.exists?('/etc/ec2/instance-id')
71
+ end
72
+ end
73
+
74
+ require 'rudy/aws'
75
+ require 'rudy/config'
76
+ require 'rudy/metadata'
77
+ require 'rudy/utils'
78
+ require 'rudy/command/base'
79
+
80
+ # Require Command, MetaData, and SCM classes
81
+ begin
82
+ # TODO: Use autoload
83
+ Dir.glob(File.join(RUDY_LIB, 'rudy', '{command,metadata,scm}', "*.rb")).each do |path|
84
+ require path
85
+ end
86
+ rescue LoadError => ex
87
+ puts "Error: #{ex.message}"
88
+ exit 1
89
+ end
90
+
91
+
92
+ # Capture STDOUT or STDERR to prevent it from being printed.
93
+ #
94
+ # capture(:stdout) do
95
+ # ...
96
+ # end
97
+ #
98
+ def capture(stream)
99
+ #raise "We can only capture STDOUT or STDERR" unless stream == :stdout || stream == :stderr
100
+
101
+ # I'm using this to trap the annoying right_aws "peer certificate" warning.
102
+ # TODO: discover source of annoying right_aws warning and give it a hiding.
103
+ begin
104
+ stream = stream.to_s
105
+ eval "$#{stream} = StringIO.new"
106
+ yield
107
+ result = eval("$#{stream}").read
108
+ ensure
109
+ eval("$#{stream} = #{stream.upcase}")
110
+ end
111
+
112
+ result
113
+ end
114
+
115
+
116
+ def write_to_file(filename, content, type)
117
+ type = (type == :append) ? 'a' : 'w'
118
+ f = File.open(filename,type)
119
+ f.puts content
120
+ f.close
121
+ end
122
+
123
+ def are_you_sure?(len=3)
124
+ if Drydock.debug?
125
+ puts 'DEBUG: skipping "are you sure" check'
126
+ return true
127
+ end
128
+
129
+ if STDIN.tty? # Only ask a question if there's a human
130
+ challenge = strand len
131
+ STDOUT.print "Are you sure? To continue type \"#{challenge}\": "
132
+ STDOUT.flush
133
+ if ((STDIN.gets || "").gsub(/["']/, '') =~ /^#{challenge}$/)
134
+ true
135
+ else
136
+ puts "Nothing changed"
137
+ exit 0
138
+ end
139
+ else
140
+ true
141
+ end
142
+ end
143
+
144
+ #
145
+ # Generates a string of random alphanumeric characters
146
+ # These are used as IDs throughout the system
147
+ def strand( len=8, safe=true )
148
+ chars = ("a".."z").to_a + ("0".."9").to_a
149
+ chars = [("a".."h").to_a, "j", "k", "m", "n", ("p".."z").to_a, ("2".."9").to_a].flatten if safe
150
+ newpass = ""
151
+ 1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
152
+ newpass
153
+ end
154
+
155
+ def sh(command, chdir=false, verbose=false)
156
+ prevdir = Dir.pwd
157
+ Dir.chdir chdir if chdir
158
+ puts command if verbose
159
+ system(command)
160
+ Dir.chdir prevdir if chdir
161
+ end
162
+
163
+ def ssh_command(host, keypair, user, command=false, printonly=false, verbose=false)
164
+ #puts "CONNECTING TO #{host}..."
165
+ cmd = "ssh -i #{keypair} #{user}@#{host} "
166
+ cmd += " '#{command}'" if command
167
+ puts cmd if verbose
168
+ return cmd if printonly
169
+ # backticks returns STDOUT
170
+ # exec replaces current process (it's just like running ssh)
171
+ # -- UPDATE -- Some problem with exec. "Operation not supported"
172
+ # using system (http://www.mail-archive.com/mongrel-users@rubyforge.org/msg02018.html)
173
+ (command) ? `#{cmd}` : Kernel.system(cmd)
174
+ end
175
+
176
+
177
+ def scp_command(host, keypair, user, paths, to_path, to_local=false, verbose=false, printonly=false)
178
+
179
+ paths = [paths] unless paths.is_a?(Array)
180
+ from_paths = ""
181
+ if to_local
182
+ paths.each do |path|
183
+ from_paths << "#{user}@#{host}:#{path} "
184
+ end
185
+ puts "Copying FROM remote TO this machine", $/
186
+
187
+ else
188
+ to_path = "#{user}@#{host}:#{to_path}"
189
+ from_paths = paths.join(' ')
190
+ puts "Copying FROM this machine TO remote", $/
191
+ end
192
+
193
+
194
+ cmd = "scp -r -i #{keypair} #{from_paths} #{to_path}"
195
+
196
+ puts cmd if verbose
197
+ printonly ? (puts cmd) : system(cmd)
198
+ end
199
+
200
+
201
+ # Returns +str+ with the average leading indentation removed.
202
+ # Useful for keeping inline codeblocks spaced with code.
203
+ def without_indent(str)
204
+ lines = str.split($/)
205
+ lspaces = (lines.inject(0) {|total,line| total += (line.scan(/^\s+/).first || '').size } / lines.size) + 1
206
+ lines.collect { |line| line.gsub(/^\s{#{lspaces}}/, '') }.join($/)
207
+ end
208
+
209
+
210
+
@@ -0,0 +1,68 @@
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 :snapshots
24
+ attr_reader :aws
25
+
26
+ def initialize(access_key, secret_key)
27
+ @aws = RightAws::Ec2.new(access_key, secret_key, {:logger => Logger.new(@@logger)})
28
+ @instances = Rudy::AWS::EC2::Instances.new(@aws)
29
+ @images = Rudy::AWS::EC2::Images.new(@aws)
30
+ @groups = Rudy::AWS::EC2::Groups.new(@aws)
31
+ @addresses = Rudy::AWS::EC2::Addresses.new(@aws)
32
+ @snapshots = Rudy::AWS::EC2::Snapshots.new(@aws)
33
+ @volumes = Rudy::AWS::EC2::Volumes.new(@aws)
34
+ end
35
+
36
+ end
37
+
38
+ class S3
39
+ @@logger = StringIO.new
40
+
41
+ attr_reader :aws
42
+
43
+ def initialize(access_key, secret_key)
44
+ @aws = RightAws::S3.new(access_key, secret_key, {:logger => Logger.new(@@logger)})
45
+ end
46
+ end
47
+
48
+ class SimpleDB
49
+ @@logger = StringIO.new
50
+
51
+ attr_reader :domains
52
+ attr_reader :aws
53
+
54
+ def initialize(access_key, secret_key)
55
+ @aws = RightAws::SdbInterface.new(access_key, secret_key, {:logger => Logger.new(@@logger)})
56
+ @aws2 = AwsSdb::Service.new(:access_key_id => access_key, :secret_access_key => secret_key, :logger => Logger.new(@@logger))
57
+ @domains = Rudy::AWS::SimpleDB::Domains.new(@aws)
58
+ end
59
+
60
+ end
61
+
62
+ require 'rudy/aws/simpledb'
63
+ require 'rudy/aws/ec2'
64
+ require 'rudy/aws/s3'
65
+
66
+ end
67
+
68
+ end
@@ -0,0 +1,304 @@
1
+
2
+ module Rudy::AWS
3
+
4
+ class EC2
5
+ class UserData
6
+
7
+ end
8
+
9
+ class Images
10
+ include Rudy::AWS::ObjectBase
11
+
12
+ # Returns an array of hashes:
13
+ # {:aws_architecture=>"i386", :aws_owner=>"105148267242", :aws_id=>"ami-6fe40dd5",
14
+ # :aws_image_type=>"machine", :aws_location=>"bucket-name/your-image.manifest.xml",
15
+ # :aws_kernel_id=>"aki-a71cf9ce", :aws_state=>"available", :aws_ramdisk_id=>"ari-a51cf9cc",
16
+ # :aws_is_public=>false}
17
+ def list
18
+ @aws.describe_images_by_owner('self') || []
19
+ end
20
+
21
+ # +id+ AMI ID to deregister (ami-XXXXXXX)
22
+ # Returns true when successful. Otherwise throws an exception.
23
+ def deregister(id)
24
+ @aws.deregister_image(id)
25
+ end
26
+
27
+ # +path+ the S3 path to the manifest (bucket/file.manifest.xml)
28
+ # Returns the AMI ID when successful, otherwise throws an exception.
29
+ def register(path)
30
+ @aws.register_image(path)
31
+ end
32
+ end
33
+ class Snapshots
34
+ include Rudy::AWS::ObjectBase
35
+
36
+ def list
37
+ @aws.describe_snapshots || []
38
+ end
39
+
40
+ def create(vol_id)
41
+ @aws.create_snapshot(vol_id)
42
+ end
43
+
44
+ def destroy(snap_id)
45
+ @aws.delete_snapshot(snap_id)
46
+ end
47
+
48
+ def exists?(id)
49
+ list.each do |v|
50
+ return true if v[:aws_id] === id
51
+ end
52
+ false
53
+ end
54
+
55
+ end
56
+
57
+ class Volumes
58
+ include Rudy::AWS::ObjectBase
59
+
60
+ # [{:aws_device=>"/dev/sdr",
61
+ # :aws_attachment_status=>"attached",
62
+ # :snapshot_id=>nil,
63
+ # :aws_id=>"vol-6811f601",
64
+ # :aws_attached_at=>Wed Mar 11 07:06:44 UTC 2009,
65
+ # :aws_status=>"in-use",
66
+ # :aws_instance_id=>"i-0b2ab662",
67
+ # :aws_created_at=>Tue Mar 10 18:55:18 UTC 2009,
68
+ # :zone=>"us-east-1b",
69
+ # :aws_size=>10}]
70
+ def list
71
+ list = @aws.describe_volumes() || []
72
+ list.select { |v| v[:aws_status] != "deleting" }
73
+ end
74
+
75
+ def attach(inst_id, vol_id, device)
76
+ @aws.attach_volume(vol_id, inst_id, device)
77
+ end
78
+
79
+ def detach(vol_id)
80
+ @aws.detach_volume(vol_id)
81
+ end
82
+
83
+ def create(zone, size, snapshot=nil)
84
+ @aws.create_volume(snapshot, size, zone)
85
+ end
86
+
87
+ def destroy(vol_id)
88
+ @aws.delete_volume(vol_id)
89
+ end
90
+
91
+ def exists?(id)
92
+ list.each do |v|
93
+ return true if v[:aws_id] === id
94
+ end
95
+ false
96
+ end
97
+
98
+ def get(vol_id)
99
+ list = @aws.describe_volumes(vol_id) || []
100
+ list.first
101
+ end
102
+
103
+ def deleting?(vol_id)
104
+ return false unless vol_id
105
+ vol = get(vol_id)
106
+ (vol && vol[:aws_status] == "deleting")
107
+ end
108
+
109
+ def available?(vol_id)
110
+ return false unless vol_id
111
+ vol = get(vol_id)
112
+ (vol && vol[:aws_status] == "available")
113
+ end
114
+
115
+ def attached?(vol_id)
116
+ return false unless vol_id
117
+ vol = get(vol_id)
118
+ (vol && vol[:aws_status] == "in-use")
119
+ end
120
+
121
+ end
122
+
123
+ class Instances
124
+ include Rudy::AWS::ObjectBase
125
+
126
+ def destroy(*list)
127
+ begin
128
+ @aws.terminate_instances(list.flatten)
129
+ #rescue RightAws::AwsError => ex
130
+ # raise UnknownInstance.new
131
+ end
132
+ end
133
+
134
+ def restart(*list)
135
+ @aws.reboot_instances(list.flatten)
136
+ end
137
+
138
+ def attached_volume?(id, device)
139
+ list = volumes(id)
140
+ list.each do |v|
141
+ return true if v[:aws_device] == device
142
+ end
143
+ false
144
+ end
145
+
146
+ def volumes(id)
147
+ list = @aws.describe_volumes() || []
148
+ list.select { |v| v[:aws_status] != "deleting" && v[:aws_instance_id] === id }
149
+ end
150
+
151
+ def device_volume(id, device)
152
+ volumes.select { |v| v[:aws_device] === device }
153
+ end
154
+
155
+ def create(ami, group, keypair_name, user_data, zone)
156
+ @aws.run_instances(ami, 1, 1, [group], keypair_name, user_data, 'public', nil, nil, nil, zone)
157
+ end
158
+
159
+ # Creates a list of running instance IDs which are in a security group
160
+ # that matches +filter+.
161
+ # Returns a hash. The keys are instance IDs and the values are a hash
162
+ # of attributes associated to that instance.
163
+ # {:aws_state_code=>"16",
164
+ # :private_dns_name=>"domU-12-31-38-00-51-F1.compute-1.internal",
165
+ # :aws_instance_type=>"m1.small",
166
+ # :aws_reason=>"",
167
+ # :ami_launch_index=>"0",
168
+ # :aws_owner=>"207436219441",
169
+ # :aws_launch_time=>"2009-03-11T06:55:00.000Z",
170
+ # :aws_kernel_id=>"aki-a71cf9ce",
171
+ # :ssh_key_name=>"rilli-sexytime",
172
+ # :aws_reservation_id=>"r-66f5710f",
173
+ # :aws_state=>"running",
174
+ # :aws_ramdisk_id=>"ari-a51cf9cc",
175
+ # :aws_instance_id=>"i-0b2ab662",
176
+ # :aws_groups=>["rudydev-app"],
177
+ # :aws_availability_zone=>"us-east-1b",
178
+ # :aws_image_id=>"ami-daca2db3",
179
+ # :aws_product_codes=>[],
180
+ # :dns_name=>"ec2-67-202-9-30.compute-1.amazonaws.com"}
181
+ def list(filter='.')
182
+ filter = filter.to_s.downcase.tr('_|-', '.') # treat dashes, underscores as one
183
+ # Returns an array of hashes with the following keys:
184
+ # :aws_image_id, :aws_reason, :aws_state_code, :aws_owner, :aws_instance_id, :aws_reservation_id
185
+ # :aws_state, :dns_name, :ssh_key_name, :aws_groups, :private_dns_name, :aws_instance_type,
186
+ # :aws_launch_time, :aws_availability_zone :aws_kernel_id, :aws_ramdisk_id
187
+ instances = @aws.describe_instances || []
188
+ running_instances = {}
189
+ instances.each do |inst|
190
+ if inst[:aws_state] != "terminated" && (inst[:aws_groups].to_s =~ /#{filter}/)
191
+ running_instances[inst[:aws_instance_id]] = inst
192
+ end
193
+ end
194
+ running_instances
195
+ end
196
+
197
+ def get(inst_id)
198
+ # This is ridiculous. Send inst_id to describe volumes
199
+ instance = {}
200
+ list.each_pair do |id, hash|
201
+ next unless inst_id == id
202
+ instance = hash
203
+ end
204
+ instance
205
+ end
206
+
207
+ def running?(inst_id)
208
+ inst = get(inst_id)
209
+ (inst && inst[:aws_state] == "running")
210
+ end
211
+
212
+ def pending?(inst_id)
213
+ inst = get(inst_id)
214
+ (inst && inst[:aws_state] == "pending")
215
+ end
216
+ end
217
+
218
+ class Groups
219
+ include Rudy::AWS::ObjectBase
220
+
221
+
222
+ # +list+ is a list of security groups to look for. If it's empty, all groups
223
+ # associated to the account will be returned.
224
+ # right_aws returns an array of hashes
225
+ # :aws_group_name => "default-1",
226
+ # :aws_owner => "000000000888",
227
+ # :aws_description => "Default allowing SSH, HTTP, and HTTPS ingress",
228
+ # :aws_perms => [{:owner => "000000000888", :group => "default"},
229
+ # {:owner => "000000000888", :group => "default-1"},
230
+ # {:to_port => "-1", :protocol => "icmp", :from_port => "-1", :cidr_ips => "0.0.0.0/0"}]
231
+ # ]
232
+ def list(list=[])
233
+ glist = @aws.describe_security_groups(list) || []
234
+
235
+ end
236
+
237
+ # Create a new EC2 security group
238
+ # Returns true/false whether successful
239
+ def create(name, desc=nil)
240
+ @aws.create_security_group(name, desc || "Group #{name}")
241
+ end
242
+
243
+ # Delete an EC2 security group
244
+ # Returns true/false whether successful
245
+ def destroy(name)
246
+ @aws.delete_security_group(name)
247
+ end
248
+
249
+ # Modify an EC2 security group
250
+ # Returns true/false whether successful
251
+ def modify(name, from_port, to_port, protocol='tcp', ipa='0.0.0.0/0')
252
+ @aws.authorize_security_group_IP_ingress(name, from_port, to_port, protocol, ipa)
253
+ end
254
+
255
+
256
+ # Does the security group +name+ exist?
257
+ def exists?(name)
258
+ begin
259
+ g = list([name.to_s])
260
+
261
+ rescue RightAws::AwsError => ex
262
+ # Ignore (it raises an exception when the list contains an unknown group name)
263
+ ensure
264
+ g ||= []
265
+ end
266
+
267
+ !g.empty?
268
+ end
269
+
270
+ end
271
+
272
+ class Addresses
273
+ include Rudy::AWS::ObjectBase
274
+
275
+ # Returns and array of hashes:
276
+ # [{:instance_id=>"i-d630cbbf", :public_ip=>"75.101.1.140"},
277
+ # {:instance_id=>nil, :public_ip=>"75.101.1.141"}]
278
+ def list
279
+ @aws.describe_addresses || []
280
+ end
281
+
282
+
283
+ # Associate an elastic IP to an instance
284
+ def associate(instance, address)
285
+ @aws.associate_address(instance, address)
286
+ end
287
+
288
+ def valid?(address)
289
+ list.each do |a|
290
+ return true if a[:public_ip] == address
291
+ end
292
+ false
293
+ end
294
+
295
+ def associated?(address)
296
+ list.each do |a|
297
+ return true if a[:public_ip] == address && a[:instance_id]
298
+ end
299
+ false
300
+ end
301
+ end
302
+ end
303
+
304
+ end