solutious-rudy 0.4.0

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