rudy 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,61 @@
1
+
2
+
3
+ module Rudy
4
+ module Command
5
+ class Images < Rudy::Command::Base
6
+
7
+
8
+ def print_images
9
+ @ec2.images.list.each do |img|
10
+ print_image img
11
+ end
12
+ end
13
+
14
+ def create_image
15
+
16
+ if @user != "root"
17
+ puts "This command will be run as root"
18
+ @user = "root" # We need to be root for this operation!
19
+ end
20
+
21
+ raise "No EC2 .pem keys provided" unless has_pem_keys?
22
+ raise "No SSH key provided for #{keypairname}!" unless has_keypair?(keypairname)
23
+ raise "SSH key provided but cannot be found! (#{keypairpath})" unless File.exists?(keypairpath)
24
+
25
+ machine_list = @ec2.instances.list(machine_group)
26
+ machine = machine_list.values.first # NOTE: Only one machine per group, for now...
27
+
28
+ raise "There's no machine running in #{machine_group}" unless machine
29
+ raise "The primary machine in #{machine_group} is not in a running state" unless machine[:aws_state] == 'running'
30
+
31
+ puts "The new image will be based on #{machine_group}_01"
32
+
33
+ unless @account
34
+ puts "Enter your 12 digit Amazon account number:"
35
+ @account = gets.chomp
36
+ end
37
+
38
+ unless @image_name
39
+ puts "Enter the image name:"
40
+ @image_name = gets.chomp
41
+ end
42
+
43
+ unless @bucket_name
44
+ puts "Enter the S3 bucket that will store the image:"
45
+ @bucket_name = gets.chomp
46
+ end
47
+
48
+ scp machine[:dns_name], keypairpath, user, @ec2_cert, "/mnt/"
49
+ scp machine[:dns_name], keypairpath, user, @ec2_private_key, "/mnt/"
50
+
51
+ ssh machine[:dns_name], keypairpath, user, "ec2-bundle-vol -r i386 -p #{@image_name} -k /mnt/pk-*pem -c /mnt/cert*pem -u #{@account}"
52
+ ssh machine[:dns_name], keypairpath, user, "ec2-upload-bundle -b #{@bucket_name} -m /tmp/#{@image_name}.manifest.xml -a #{@access_key} -s #{@secret_key}"
53
+
54
+ # Why did I not use the Ruby API?
55
+ sh "ec2-register -K #{@ec2_private_key} -C #{@ec2_cert} #{@bucket_name}/#{@image_name}.manifest.xml"
56
+ end
57
+
58
+ end
59
+ end
60
+ end
61
+
@@ -0,0 +1,109 @@
1
+
2
+
3
+ module Rudy
4
+ module Command
5
+ class Instances < Rudy::Command::Base
6
+
7
+ def print_instances(filter=nil)
8
+ filter = machine_group if filter.nil? && !@all
9
+ if instance_id?(filter)
10
+ inst = @ec2.instances.get(filter)
11
+ raise "The instance #{filter} does not exist" if inst.empty?
12
+ list = {inst[:aws_instance_id] => inst}
13
+ else
14
+ raise "The security group #{filter} does not exist" if filter && !@ec2.groups.exists?(filter)
15
+ list = @ec2.instances.list(filter)
16
+ if list.empty?
17
+ msg = "There are no instances running"
18
+ msg << " in the group #{filter}" if filter
19
+ raise msg
20
+ end
21
+ end
22
+
23
+ list.each_pair do |id, inst|
24
+ print_instance inst
25
+ end
26
+
27
+ end
28
+
29
+ def destroy_instances(filter=nil)
30
+ raise "No instance ID or group name provided" if filter.nil?
31
+ raise "I will not help you destroy production!" if @environment == "prod" || filter =~ /^prod/
32
+ if @ec2.groups.exists?(filter)
33
+ list = @ec2.instances.list(filter)
34
+ raise "The group #{filter} has no running instances" if list.empty?
35
+ instance = list.keys.first
36
+ else
37
+ instance = filter
38
+ end
39
+ puts "Destroying #{instance}!"
40
+ @ec2.instances.destroy instance
41
+ end
42
+
43
+ def start_instance
44
+ @image ||= machine_image
45
+
46
+ @user = "root"
47
+
48
+ rig = @ec2.instances.list(machine_group)
49
+ raise "There is already an instance running in #{machine_group}" unless rig.empty?
50
+ raise "No SSH key provided for #{keypairname}!" unless has_keypair?(keypairname)
51
+ raise "SSH key provided but cannot be found! (#{keypairpath})" unless File.exists?(keypairpath)
52
+
53
+ user_data = {
54
+ :dbmaster => "localhost",
55
+ :role => @role,
56
+ :env => @environment
57
+ }
58
+
59
+ puts "Starting an instance in #{machine_group}"
60
+ instances = @ec2.instances.create(@image, machine_group.to_s, File.basename(keypairpath), user_data.to_yaml, @zone)
61
+ inst = instances.first
62
+ id, state = inst[:aws_instance_id], inst[:aws_state]
63
+
64
+ if @address
65
+ puts "Associating #{@address} to #{id}"
66
+ @ec2.addresses.associate(id, @address)
67
+ end
68
+
69
+ print "Waiting for #{id} to become available"
70
+
71
+ while @ec2.instances.pending?(id)
72
+ sleep 2
73
+ print '.'
74
+ STDOUT.flush
75
+ end
76
+
77
+ machine = @ec2.instances.get(id)
78
+
79
+ puts " It's up!"
80
+ print "Waiting for SSH daemon at #{machine[:dns_name]}"
81
+ while !Rudy::Utils.service_available?(machine[:dns_name], 22)
82
+ print '.'
83
+ STDOUT.flush
84
+ end
85
+ puts " It's up!"
86
+
87
+ print "Looking for disk metadata for #{machine[:aws_availability_zone]}... "
88
+ disks = Rudy::MetaData::Disk.list(@sdb, machine[:aws_availability_zone], @environment, @role, @position)
89
+
90
+ if disks.empty?
91
+ puts "None"
92
+ else
93
+ puts "#{disks.size} disk(s)."
94
+ disks.each do |disk|
95
+
96
+ do_dirty_disk_volume_deeds(disk, machine)
97
+ end
98
+ end
99
+
100
+ puts
101
+ ssh machine[:dns_name], keypairpath, user, "df -h" # Display current mounts
102
+ puts
103
+ puts "Done!"
104
+ end
105
+
106
+ end
107
+ end
108
+ end
109
+
@@ -0,0 +1,57 @@
1
+
2
+
3
+ module Rudy
4
+ module Command
5
+ class Metadata < Rudy::Command::Base
6
+
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
20
+ end
21
+
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)
29
+ end
30
+
31
+ def destroy_metadata
32
+ @sdb.domains.destroy(RUDY_DOMAIN)
33
+ end
34
+
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
+
43
+ def info
44
+ domains = @sdb.domains.list[:domains]
45
+ puts "Domains: #{domains.join(", ")}"
46
+ end
47
+
48
+ def check_environment
49
+ raise "No Amazon keys provided!" unless has_keys?
50
+ raise "No SSH keypairs provided!" unless has_keypairs?
51
+ true
52
+ end
53
+
54
+ end
55
+ end
56
+ end
57
+
@@ -0,0 +1,43 @@
1
+
2
+
3
+ module Rudy
4
+ module Command
5
+ class Release < Rudy::Command::Base
6
+
7
+
8
+ def create_release
9
+
10
+ if @user != "root"
11
+ #puts "This command will be run as root"
12
+ @user = "root" # We need to be root for this operation!
13
+ end
14
+
15
+ raise "No EC2 .pem keys provided" unless has_pem_keys?
16
+ raise "No SSH key provided for #{keypairname}!" unless has_keypair?(keypairname)
17
+ raise "SSH key provided but cannot be found! (#{keypairpath})" unless File.exists?(keypairpath)
18
+
19
+ raise "The security group #{filter} does not exist" if machine_group && !@ec2.groups.exists?(machine_group)
20
+ list = @ec2.instances.list(machine_group)
21
+ raise "Please start an instance in #{machine_group} before releasing!" if list.empty?
22
+ puts "Creating release from: #{Dir.pwd}"
23
+
24
+ tag = @scm.create_release
25
+
26
+ machine = list.values.first # NOTE: we're assuming there's only one machine
27
+
28
+ #basename = File.basename(@rscripts[keypairname])
29
+ #scp machine[:dns_name], keypairpath, user, @rscripts[keypairname], "/mnt/"
30
+ #ssh machine[:dns_name], keypairpath, user, "chmod 755 /mnt/#{basename} && /mnt/#{basename} #{tag}"
31
+
32
+ @user = "rudy"
33
+ basename = File.basename(@rscripts[keypairname])
34
+
35
+ scp machine[:dns_name], keypairpath, user, @rscripts[keypairname], "~/"
36
+ ssh machine[:dns_name], keypairpath, user, "chmod 755 ~/#{basename} && ~/#{basename} test #{@access_key} #{@secret_key} r1ll1r1ll1"
37
+
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,13 @@
1
+
2
+
3
+ module Rudy
4
+ module Command
5
+ class Volumes < Rudy::Command::Base
6
+
7
+ def print_volumes
8
+ y @ec2.volumes.list
9
+ end
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,142 @@
1
+
2
+
3
+ module Rudy
4
+ class MetaData
5
+ attr_accessor :sdb
6
+
7
+ def initalize(sdb)
8
+ @sdb = sdb
9
+ end
10
+
11
+ end
12
+ class MetaData
13
+ class Disk < Storable
14
+
15
+ field :awsid
16
+
17
+ field :environment
18
+ field :role
19
+ field :path
20
+ field :position
21
+
22
+ field :zone
23
+ field :region
24
+ field :device
25
+ field :backups => Array
26
+ field :size
27
+
28
+ def initialize
29
+ @device = "/dev/sdh"
30
+ @zone = DEFAULT_ZONE
31
+ @region = DEFAULT_REGION
32
+ @backups = []
33
+
34
+ end
35
+
36
+ def name
37
+ Disk.generate_name(@zone, @environment, @role, @position, @path)
38
+ end
39
+
40
+ def valid?
41
+ @zone && @environment && @role && @position && @path
42
+ end
43
+
44
+ def to_query(more=[], remove=[])
45
+ criteria = [:zone, :environment, :role, :position, :path, *more]
46
+ criteria -= [*remove].flatten
47
+ query = []
48
+ criteria.each do |n|
49
+ query << "['#{n}' = '#{self.send(n.to_sym)}']"
50
+ end
51
+ query.join(" intersection ")
52
+ end
53
+
54
+ def to_s
55
+ str = ""
56
+ field_names.each do |key|
57
+ str << sprintf(" %22s: %s#{$/}", key, self.send(key.to_sym))
58
+ end
59
+ str
60
+ end
61
+
62
+ def Disk.generate_name(zon, env, rol, pos, pat, sep=File::SEPARATOR)
63
+ pos = pos.to_s.rjust 2, '0'
64
+ dirs = pat.split sep if pat
65
+ dirs.shift while dirs && (dirs[0].nil? || dirs[0].empty?)
66
+ ["disk", zon, env, rol, pos, *dirs].join(RUDY_DELIM)
67
+ end
68
+
69
+ def Disk.get(sdb, name)
70
+ disk = sdb.get_attributes(RUDY_DOMAIN, name)
71
+ raise "Disk #{name} does not exist!" unless disk && disk.has_key?(:attributes)
72
+ Rudy::MetaData::Disk.from_hash(disk[:attributes])
73
+ end
74
+
75
+ def Disk.destroy(sdb, name)
76
+ disk = Disk.get(sdb, name) # get raises an exception if the disk doesn't exist
77
+ sdb.destroy(RUDY_DOMAIN, name)
78
+ true # wtf: RightAws::SimpleDB doesn't tell us whether it succeeds. We'll assume!
79
+ end
80
+
81
+ def Disk.save(sdb, disk)
82
+ sdb.store(RUDY_DOMAIN, disk.name, disk.to_hash, :replace)
83
+ end
84
+
85
+ def Disk.is_defined?(sdb, disk)
86
+ # We don't care about the path, but we do care about the device
87
+ # which is not part of the disk's name.
88
+ query = disk.to_query(:device, :path)
89
+ !sdb.query_with_attributes(RUDY_DOMAIN, query).empty?
90
+ end
91
+
92
+ def Disk.update_volume(sdb, ec2, disk, machine)
93
+
94
+ disk = Disk.get(sdb, disk) if disk.is_a?(String)
95
+ raise "You must provide a disk name or obect" unless disk.is_a?(Rudy::MetaData::Disk)
96
+
97
+
98
+ # Make sure the volume is still running
99
+ disk.awsid = nil if disk.awsid && !ec2.volumes.exists?(disk.awsid)
100
+
101
+
102
+ # Otherwise we need to start one
103
+ unless disk.awsid
104
+ puts "No active EBS volume found for #{disk.name}"
105
+
106
+ backup = disk.backups.last
107
+ if backup
108
+ puts "We'll use the most recent backup..."
109
+ volume = ec2.volumes.create(disk.zone, disk.size, backup)
110
+ puts "Attaching #{disk.awsid} to #{id} (#{disk.device})"
111
+ ec2.volumes.attach(machine[:aws_instance_id], disk.awsid, disk.device)
112
+ else
113
+ puts "We'll create one from scratch..."
114
+ volume = ec2.volumes.create(disk.zone, disk.size, nil)
115
+ disk.awsid = volume[:aws_id]
116
+ puts "Saving disk metadata"
117
+ Disk.save(sdb, disk)
118
+ end
119
+ puts ""
120
+ end
121
+
122
+ disk
123
+ end
124
+
125
+ def Disk.list(sdb, zon, env=nil, rol=nil, pos=nil)
126
+ query = ''
127
+ query << "['zone' = '#{zon}']" if zon
128
+ query << " intersection ['environment' = '#{env}']" if env
129
+ query << " intersection ['role' = '#{rol}']" if rol
130
+ query << " intersection ['position' = '#{pos}']" if pos
131
+
132
+ list = []
133
+ sdb.query_with_attributes(RUDY_DOMAIN, query).each_pair do |name, hash|
134
+ list << Rudy::MetaData::Disk.from_hash(hash)
135
+ end
136
+ list
137
+ end
138
+ end
139
+
140
+ end
141
+
142
+ end
File without changes
@@ -0,0 +1,57 @@
1
+
2
+ require 'date'
3
+
4
+ module Rudy
5
+ module SCM
6
+ class SVN
7
+ attr_accessor :base_uri
8
+
9
+ def initialize(uri)
10
+ @base_uri = uri
11
+ end
12
+
13
+ def create_release
14
+ raise "There are local changes. Please revert or check them in!" unless everything_checked_in?
15
+ raise "Invalid base URI (#{@base_uri}). Check RUDY_SVN_BASE." unless valid_uri?(@base_uri)
16
+ raise "You must run this command from SVN you want to release from!" unless svn_dir?(Dir.pwd)
17
+
18
+ re = `svn info`.match /^URL:\s+(.+)$/
19
+ release = re[1] if re
20
+
21
+ release_tag = "#{@base_uri}/#{generate_release_tag}"
22
+
23
+ puts "Creating tag: #{release_tag}"
24
+ cmd = "svn copy -m 'Another Release by Rudy!' #{release} #{release_tag}"
25
+
26
+ `#{cmd}`
27
+
28
+ release_tag
29
+ end
30
+
31
+ def generate_release_tag
32
+ now = Time.now
33
+ mon = now.mon.to_s.rjust(2, '0')
34
+ day = now.day.to_s.rjust(2, '0')
35
+ rev = "r01"
36
+ criteria = ['rudy', now.year, mon, day, rev]
37
+ tag = criteria.join(RUDY_DELIM)
38
+ # Keep incrementing the revision number until we find the next one.
39
+ tag.succ! while (valid_uri?("#{@base_uri}/#{tag}"))
40
+ tag
41
+ end
42
+
43
+ def svn_dir?(path)
44
+ (File.exists?(File.join(path, '.svn')))
45
+ end
46
+
47
+ def valid_uri?(uri)
48
+ ret = `svn info #{uri} 2>1&` || '' # Valid SVN URIs will return some info
49
+ (ret =~ /Repository UUID/) ? true : false
50
+ end
51
+
52
+ def everything_checked_in?
53
+ `svn diff . 2>1&` == '' # svn diff should return nothing
54
+ end
55
+ end
56
+ end
57
+ end