solutious-rudy 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. data/CHANGES.txt +8 -9
  2. data/README.rdoc +48 -7
  3. data/Rakefile +102 -7
  4. data/Rudyfile +28 -0
  5. data/bin/ird +162 -0
  6. data/bin/rudy +287 -93
  7. data/lib/annoy.rb +227 -0
  8. data/lib/aws_sdb/service.rb +1 -1
  9. data/lib/console.rb +20 -4
  10. data/lib/escape.rb +305 -0
  11. data/lib/rudy.rb +265 -125
  12. data/lib/rudy/aws.rb +61 -26
  13. data/lib/rudy/aws/ec2.rb +20 -296
  14. data/lib/rudy/aws/ec2/address.rb +121 -0
  15. data/lib/rudy/aws/ec2/group.rb +241 -0
  16. data/lib/rudy/aws/ec2/image.rb +46 -0
  17. data/lib/rudy/aws/ec2/instance.rb +407 -0
  18. data/lib/rudy/aws/ec2/keypair.rb +92 -0
  19. data/lib/rudy/aws/ec2/snapshot.rb +87 -0
  20. data/lib/rudy/aws/ec2/volume.rb +234 -0
  21. data/lib/rudy/aws/simpledb.rb +33 -15
  22. data/lib/rudy/cli.rb +142 -0
  23. data/lib/rudy/cli/addresses.rb +85 -0
  24. data/lib/rudy/cli/backups.rb +175 -0
  25. data/lib/rudy/{command → cli}/config.rb +18 -13
  26. data/lib/rudy/cli/deploy.rb +12 -0
  27. data/lib/rudy/cli/disks.rb +125 -0
  28. data/lib/rudy/cli/domains.rb +17 -0
  29. data/lib/rudy/cli/groups.rb +77 -0
  30. data/lib/rudy/{command → cli}/images.rb +18 -6
  31. data/lib/rudy/cli/instances.rb +142 -0
  32. data/lib/rudy/cli/keypairs.rb +47 -0
  33. data/lib/rudy/cli/manager.rb +51 -0
  34. data/lib/rudy/{command → cli}/release.rb +10 -10
  35. data/lib/rudy/cli/routines.rb +80 -0
  36. data/lib/rudy/cli/volumes.rb +121 -0
  37. data/lib/rudy/command/addresses.rb +62 -39
  38. data/lib/rudy/command/backups.rb +60 -170
  39. data/lib/rudy/command/disks-old.rb +322 -0
  40. data/lib/rudy/command/disks.rb +5 -209
  41. data/lib/rudy/command/domains.rb +34 -0
  42. data/lib/rudy/command/groups.rb +105 -48
  43. data/lib/rudy/command/instances.rb +263 -70
  44. data/lib/rudy/command/keypairs.rb +149 -0
  45. data/lib/rudy/command/manager.rb +65 -0
  46. data/lib/rudy/command/volumes.rb +110 -49
  47. data/lib/rudy/config.rb +90 -70
  48. data/lib/rudy/config/objects.rb +67 -0
  49. data/lib/rudy/huxtable.rb +253 -0
  50. data/lib/rudy/metadata/backup.rb +23 -48
  51. data/lib/rudy/metadata/disk.rb +79 -68
  52. data/lib/rudy/metadata/machine.rb +34 -0
  53. data/lib/rudy/routines.rb +54 -0
  54. data/lib/rudy/routines/disk_handler.rb +190 -0
  55. data/lib/rudy/routines/release.rb +15 -0
  56. data/lib/rudy/routines/script_runner.rb +65 -0
  57. data/lib/rudy/routines/shutdown.rb +42 -0
  58. data/lib/rudy/routines/startup.rb +48 -0
  59. data/lib/rudy/utils.rb +57 -2
  60. data/lib/storable.rb +11 -5
  61. data/lib/sysinfo.rb +274 -0
  62. data/rudy.gemspec +84 -20
  63. data/support/randomize-root-password +45 -0
  64. data/support/rudy-ec2-startup +5 -5
  65. data/support/update-ec2-ami-tools +20 -0
  66. data/test/05_config/00_setup_test.rb +24 -0
  67. data/test/05_config/30_machines_test.rb +69 -0
  68. data/test/20_sdb/00_setup_test.rb +31 -0
  69. data/test/20_sdb/10_domains_test.rb +113 -0
  70. data/test/25_ec2/00_setup_test.rb +34 -0
  71. data/test/25_ec2/10_keypairs_test.rb +33 -0
  72. data/test/25_ec2/20_groups_test.rb +139 -0
  73. data/test/25_ec2/30_addresses_test.rb +35 -0
  74. data/test/25_ec2/40_volumes_test.rb +46 -0
  75. data/test/25_ec2/50_snapshots_test.rb +69 -0
  76. data/test/26_ec2_instances/00_setup_test.rb +33 -0
  77. data/test/26_ec2_instances/10_instances_test.rb +81 -0
  78. data/test/26_ec2_instances/50_images_test.rb +13 -0
  79. data/test/30_sdb_metadata/00_setup_test.rb +28 -0
  80. data/test/30_sdb_metadata/10_disks_test.rb +99 -0
  81. data/test/30_sdb_metadata/20_backups_test.rb +102 -0
  82. data/test/50_commands/00_setup_test.rb +11 -0
  83. data/test/50_commands/10_keypairs_test.rb +79 -0
  84. data/test/50_commands/20_groups_test.rb +77 -0
  85. data/test/50_commands/40_volumes_test.rb +55 -0
  86. data/test/50_commands/50_instances_test.rb +110 -0
  87. data/test/coverage.txt +51 -0
  88. data/test/helper.rb +35 -0
  89. data/tryouts/disks.rb +55 -0
  90. data/tryouts/nested_methods.rb +36 -0
  91. data/tryouts/session_tryout.rb +48 -0
  92. metadata +94 -25
  93. data/bin/rudy-ec2 +0 -108
  94. data/lib/rudy/command/base.rb +0 -839
  95. data/lib/rudy/command/deploy.rb +0 -12
  96. data/lib/rudy/command/environment.rb +0 -74
  97. data/lib/rudy/command/machines.rb +0 -170
  98. data/lib/rudy/command/metadata.rb +0 -41
  99. data/lib/rudy/metadata.rb +0 -26
@@ -0,0 +1,34 @@
1
+
2
+
3
+ module Rudy
4
+ module MetaData
5
+ class Machine < Storable
6
+
7
+ @@rtype = 'machine'
8
+
9
+ field :rtype
10
+ field :awsid
11
+
12
+ field :environment
13
+ field :role
14
+ field :path
15
+ field :position
16
+
17
+ field :zone
18
+ field :region
19
+
20
+ def initialize
21
+ @rtype = @@rtype.to_s
22
+ end
23
+
24
+ def rtype
25
+ @@rtype.to_s
26
+ end
27
+
28
+ def rtype=(val)
29
+ end
30
+
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,54 @@
1
+
2
+
3
+ module Rudy
4
+ module Routines
5
+ class Base
6
+ include Rudy::Huxtable
7
+
8
+ # Examples:
9
+ #
10
+ # def before
11
+ # end
12
+ # def before_local
13
+ # end
14
+ # def svn
15
+ # end
16
+ #
17
+
18
+
19
+ #
20
+ #
21
+ def execute
22
+ raise "Override this method"
23
+ end
24
+
25
+
26
+
27
+ # We grab the appropriate routines config and check the paths
28
+ # against those defined for the matching machine group.
29
+ # Disks that appear in a routine but not the machine will be
30
+ # removed and a warning printed. Otherwise, the routines config
31
+ # is merged on top of the machine config and that's what we return.
32
+ def fetch_routine(action)
33
+ raise "No configuration" unless @config
34
+ raise "No globals" unless @global
35
+
36
+ disk_definitions = @config.machines.find_deferred(@global.environment, @global.role, :disks)
37
+ routine = @config.routines.find(@global.environment, @global.role, action)
38
+ routine.disks.each_pair do |raction,disks|
39
+ disks.each_pair do |path, props|
40
+ routine.disks[raction][path] = disk_definitions[path].merge(props) if disk_definitions.has_key?(path)
41
+ unless disk_definitions.has_key?(path)
42
+ @logger.puts "#{path} is not defined. Check your #{action} routines config.".color(:red)
43
+ routine.disks[raction].delete(path)
44
+ end
45
+ end
46
+ end
47
+ routine
48
+ end
49
+
50
+
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,190 @@
1
+
2
+
3
+ module Rudy::Routines
4
+ class DiskHandler
5
+ include Rudy::Huxtable # @config, @global come from here
6
+
7
+
8
+ # +machine+ is a Rudy::AWS::EC2::Instance object
9
+ # +routines+ is a Hash config contain the disk routines.
10
+ def execute(machine, routines)
11
+
12
+ @logger.puts "Running DISK routines".bright
13
+
14
+
15
+ unless routines
16
+ @logger.puts "No #{action} disk routines."
17
+ return
18
+ end
19
+
20
+ unless machine.awsid
21
+ @logger.puts "Machine given has no instance ID. Skipping disk routines."
22
+ return
23
+ end
24
+
25
+ unless machine.dns_name_public
26
+ @logger.puts "Machine given has no DNS name: #{machine.awsid}. Skipping disk routines."
27
+ return
28
+ end
29
+
30
+ # The order is important. We could be destroying and recreating
31
+ # a disk on the same machine.
32
+ destroy(machine, routines.destroy) if routines.destroy
33
+ mount(machine, routines.mount) if routines.mount
34
+ restore(machine, routines.restore) if routines.restore
35
+ create(machine, routines.create) if routines.create
36
+
37
+ end
38
+
39
+
40
+ def create(machine, disk_routine)
41
+
42
+ disk_routine.each_pair do |path,props|
43
+
44
+ begin
45
+ puts "Creating disk for #{path}"
46
+
47
+ disk = Rudy::MetaData::Disk.new
48
+ disk.path = path
49
+ [:region, :zone, :environment, :role, :position].each do |n|
50
+ disk.send("#{n}=", @global.send(n)) if @global.send(n)
51
+ end
52
+ [:device, :size].each do |n|
53
+ disk.send("#{n}=", props[n]) if props.has_key?(n)
54
+ end
55
+
56
+
57
+ puts "Creating volume... (#{disk.size}GB in #{@global.zone})".bright
58
+ volume = @ec2.volumes.create(disk.size, @global.zone)
59
+
60
+ puts "Attaching #{volume[:aws_id]} to #{machine.awsid}".bright
61
+ @ec2.volumes.attach(machine.awsid, volume[:aws_id], disk.device)
62
+ sleep 6
63
+
64
+ puts "Creating the filesystem (mkfs.ext3 -F #{disk.device})".bright
65
+ ssh_command machine.dns_name_public, keypairpath, @global.user, "mkfs.ext3 -F #{disk.device}"
66
+ sleep 3
67
+
68
+ puts "Mounting #{disk.device} to #{disk.path}".bright
69
+ ssh_command machine.dns_name_public, keypairpath, @global.user, "mkdir -p #{disk.path} && mount -t ext3 #{disk.device} #{disk.path}"
70
+
71
+ puts "Creating disk metadata for #{disk.name}"
72
+ disk.awsid = volume[:aws_id]
73
+ Rudy::MetaData::Disk.save(@sdb, disk)
74
+
75
+ sleep 1
76
+ rescue => ex
77
+ @logger.puts "There was an error creating #{path}: #{ex.message}".color(:red)
78
+ @logger.puts ex.backtrace
79
+ # NOTE: This isn't necessary right? B/c saving happens last so if there
80
+ # is an exception, the disk metadata would never be saved.
81
+ #if disk
82
+ # puts "Removing metadata for #{disk.name}"
83
+ # Rudy::MetaData::Disk.destroy(@sdb, disk)
84
+ #end
85
+ end
86
+ puts
87
+ end
88
+ end
89
+
90
+
91
+
92
+ def destroy(machine, disk_routine)
93
+ disk_paths = disk_routine.keys
94
+
95
+ vols = @ec2.instances.volumes(machine.awsid) || []
96
+ puts "No volumes to destroy for (#{machine.awsid})" if vols.empty?
97
+ vols.each do |vol|
98
+ disk = Rudy::MetaData::Disk.find_from_volume(@sdb, vol[:aws_id])
99
+ if disk
100
+ this_path = disk.path
101
+ else
102
+ puts "No disk metadata for volume #{vol[:aws_id]}. Going old school..."
103
+ this_path = device_to_path(machine, vol[:aws_device])
104
+ end
105
+
106
+ if disk_paths.member?(this_path)
107
+
108
+
109
+ begin
110
+ puts "Unmounting #{this_path}..."
111
+ ssh_command machine.dns_name_public, keypairpath, @global.user, "umount #{this_path}"
112
+ sleep 3
113
+ rescue => ex
114
+ puts "Error while unmounting #{this_path}: #{ex.message}"
115
+ puts ex.backtrace if Drydock.debug?
116
+ puts "We'll keep going..."
117
+ end
118
+
119
+ begin
120
+
121
+ if @ec2.volumes.attached?(disk.awsid)
122
+ puts "Detaching #{vol[:aws_id]}"
123
+ @ec2.volumes.detach(vol[:aws_id])
124
+ sleep 3 # TODO: replace with something like wait_for_machine
125
+ end
126
+
127
+ puts "Destroying #{this_path} (#{vol[:aws_id]})"
128
+ if @ec2.volumes.available?(disk.awsid)
129
+ @ec2.volumes.destroy(vol[:aws_id])
130
+ else
131
+ puts "Volume is still attached (maybe a web server of database is running?)"
132
+ end
133
+
134
+ if disk
135
+ puts "Deleteing metadata for #{disk.name}"
136
+ Rudy::MetaData::Disk.destroy(@sdb, disk)
137
+ end
138
+
139
+ rescue => ex
140
+ puts "Error while detaching volume #{vol[:aws_id]}: #{ex.message}"
141
+ puts ex.backtrace if Drydock.debug?
142
+ puts "Continuing..."
143
+ end
144
+
145
+ end
146
+ puts
147
+
148
+ end
149
+
150
+ end
151
+
152
+
153
+ def mount(machine, disk_routine)
154
+ disk_paths = disk_routine.keys
155
+ vols = @ec2.instances.volumes(machine.awsid) || []
156
+ puts "No volumes to mount for (#{machine.awsid})" if vols.empty?
157
+ vols.each do |vol|
158
+ disk = Rudy::MetaData::Disk.find_from_volume(@sdb, vol[:aws_id])
159
+ if disk
160
+ this_path = disk.path
161
+ else
162
+ puts "No disk metadata for volume #{vol[:aws_id]}. Going old school..."
163
+ this_path = device_to_path(machine, vol[:aws_device])
164
+ end
165
+
166
+ next unless disk_paths.member?(this_path)
167
+
168
+ begin
169
+ unless @ec2.instances.attached_volume?(machine.awsid, vol[:aws_device])
170
+ puts "Attaching #{vol[:aws_id]} to #{machine.awsid}".bright
171
+ @ec2.volumes.attach(machine.awsid, vol[:aws_id],vol[:aws_device])
172
+ sleep 3
173
+ end
174
+
175
+ puts "Mounting #{this_path} to #{vol[:aws_device]}".bright
176
+ ssh_command machine.dns_name_public, keypairpath, @global.user, "mkdir -p #{this_path} && mount -t ext3 #{vol[:aws_device]} #{this_path}"
177
+
178
+ sleep 1
179
+ rescue => ex
180
+ puts "There was an error mounting #{this_path}: #{ex.message}"
181
+ puts ex.backtrace if Drydock.debug?
182
+ end
183
+ puts
184
+ end
185
+ end
186
+
187
+
188
+
189
+ end
190
+ end
@@ -0,0 +1,15 @@
1
+ module Rudy
2
+ module Routines
3
+ class Release < Rudy::Routines::Base
4
+
5
+
6
+ def execute()
7
+
8
+ end
9
+
10
+
11
+
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,65 @@
1
+
2
+ require 'tempfile'
3
+
4
+ module Rudy::Routines
5
+ class ScriptRunner
6
+ include Rudy::Huxtable
7
+
8
+ def execute(instance, routine, before_or_after)
9
+ return false unless instance
10
+
11
+ rscripts = @config.routines.find_deferred(@global.environment, @global.role, routine, before_or_after) || []
12
+ rscripts &&= [rscripts].flatten # Make sure it's an Array
13
+ if !rscripts || rscripts.empty?
14
+ @logger.puts "No scripts defined."
15
+ return
16
+ end
17
+
18
+ # The config file will contain settings from ~/.rudy/config
19
+ script_config = @config.routines.find_deferred(@global.environment, @global.role, :config) || {}
20
+ script_config[:global] = @global.marshal_dump
21
+ script_config[:global].reject! { |n,v| n == :cert || n == :privatekey }
22
+ script_config_filename = "#{routine}_config.yaml"
23
+
24
+ tf = Tempfile.new(script_config_filename)
25
+ Rudy::Utils.write_to_file(tf.path, script_config.to_hash.to_yaml, 'w')
26
+
27
+ rscripts.each do |rscript|
28
+ user, script = rscript.shift
29
+
30
+ @logger.puts "User: #{user} (#{user_keypairpath(user)})"
31
+ begin
32
+ Net::SCP.start(instance.dns_name_public, user, :keys => [user_keypairpath(user)]) do |scp|
33
+ scp.upload!(tf.path, "~/#{script_config_filename}") do |ch, name, sent, total|
34
+ "#{name}: #{sent}/#{total}"
35
+ end
36
+ end
37
+ rescue => ex
38
+ raise "Error transfering #{script_config_filename}: #{ex.message} "
39
+ end
40
+
41
+ begin
42
+ Net::SSH.start(instance.dns_name_public, user, :keys => [user_keypairpath(user)]) do |session|
43
+
44
+ puts "Running #{script}...".bright
45
+ session.exec!("chmod 700 ~/#{script_config_filename}")
46
+ session.exec!("chmod 700 #{script}")
47
+ puts session.exec!("#{script}")
48
+
49
+ puts "Removing remote copy of #{script_filename}..."
50
+ session.exec!("rm ~/#{script_config_filename}")
51
+ end
52
+ rescue => ex
53
+ raise "Error executing #{script}: #{ex.message}"
54
+ end
55
+ end
56
+
57
+
58
+ tf.delete # remove local copy of config_file
59
+ #switch_user # return to the requested user
60
+ end
61
+
62
+
63
+
64
+ end
65
+ end
@@ -0,0 +1,42 @@
1
+
2
+
3
+ module Rudy
4
+ module Routines
5
+ class Shutdown < Rudy::Routines::Base
6
+
7
+ def shutdown
8
+ routine = fetch_routine(:shutdown)
9
+ rmach = Rudy::Instances.new(:config => @config, :global => @global)
10
+ rmach.destroy do
11
+ @logger.puts $/, "Running BEFORE scripts...", $/
12
+ #instances.each { |inst| @script_runner.execute(inst, :shutdown, :before) }
13
+
14
+ @logger.puts $/, "Running DISK routines...", $/
15
+ routine.disks.each_pair do |action,disks|
16
+
17
+ unless @rdisks.respond_to?(action)
18
+ @logger.puts("Skipping unknown action: #{action}").color(:blue)
19
+ next
20
+ end
21
+
22
+ disks.each_pair do |path,props|
23
+ props[:path] = path
24
+ begin
25
+ @rdisks.send(action, instance, props)
26
+ rescue => ex
27
+ @logger.puts ex.message
28
+ @logger.puts "Continuing..."
29
+ @logger.puts ex.backtrace if debug?
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+
42
+
@@ -0,0 +1,48 @@
1
+
2
+
3
+ module Rudy
4
+ module Routines
5
+ class Startup < Rudy::Routines::Base
6
+
7
+
8
+ def startup(opts={})
9
+
10
+ routine = fetch_routine(:startup)
11
+ rdisks = Rudy::Disks.new(:config => @config, :global => @global)
12
+
13
+
14
+ rmach = Rudy::Instances.new(:config => @config, :global => @global)
15
+
16
+ # TODO: .list for debugging, .create for actual use
17
+ instances = rmach.list(opts) do |instance| # Rudy::AWS::EC2::Instance objects
18
+ puts '-'*60
19
+ puts "Instance: #{instance.awsid.bright} (AMI: #{instance.ami})"
20
+ puts instance.to_s
21
+
22
+ @logger.puts("Running DISK routines")
23
+ routine.disks.each_pair do |action,disks|
24
+
25
+ unless rdisks.respond_to?(action)
26
+ @logger.puts("Skipping unknown action: #{action}").color(:blue)
27
+ next
28
+ end
29
+
30
+ disks.each_pair do |path,props|
31
+ props[:path] = path
32
+ puts path
33
+ begin
34
+ rdisks.send(action, instance, props)
35
+ rescue => ex
36
+ @logger.puts "Continuing..."
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ instances
43
+ end
44
+
45
+
46
+ end
47
+ end
48
+ end