sabat-rudy 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. data/CHANGES.txt +188 -0
  2. data/LICENSE.txt +19 -0
  3. data/README.rdoc +118 -0
  4. data/Rakefile +165 -0
  5. data/Rudyfile +184 -0
  6. data/bin/ird +153 -0
  7. data/bin/rudy +232 -0
  8. data/bin/rudy-ec2 +241 -0
  9. data/bin/rudy-s3 +79 -0
  10. data/bin/rudy-sdb +69 -0
  11. data/examples/README.md +10 -0
  12. data/examples/debian-sinatra-passenger/commands.rb +19 -0
  13. data/examples/debian-sinatra-passenger/machines.rb +32 -0
  14. data/examples/debian-sinatra-passenger/routines.rb +30 -0
  15. data/examples/debian-sinatra-thin/commands.rb +17 -0
  16. data/examples/debian-sinatra-thin/machines.rb +35 -0
  17. data/examples/debian-sinatra-thin/routines.rb +72 -0
  18. data/lib/rudy.rb +170 -0
  19. data/lib/rudy/aws.rb +75 -0
  20. data/lib/rudy/aws/ec2.rb +59 -0
  21. data/lib/rudy/aws/ec2/address.rb +157 -0
  22. data/lib/rudy/aws/ec2/group.rb +301 -0
  23. data/lib/rudy/aws/ec2/image.rb +168 -0
  24. data/lib/rudy/aws/ec2/instance.rb +438 -0
  25. data/lib/rudy/aws/ec2/keypair.rb +104 -0
  26. data/lib/rudy/aws/ec2/snapshot.rb +109 -0
  27. data/lib/rudy/aws/ec2/volume.rb +230 -0
  28. data/lib/rudy/aws/ec2/zone.rb +77 -0
  29. data/lib/rudy/aws/s3.rb +60 -0
  30. data/lib/rudy/aws/sdb.rb +312 -0
  31. data/lib/rudy/aws/sdb/error.rb +47 -0
  32. data/lib/rudy/cli.rb +186 -0
  33. data/lib/rudy/cli/aws/ec2/addresses.rb +105 -0
  34. data/lib/rudy/cli/aws/ec2/candy.rb +191 -0
  35. data/lib/rudy/cli/aws/ec2/groups.rb +118 -0
  36. data/lib/rudy/cli/aws/ec2/images.rb +185 -0
  37. data/lib/rudy/cli/aws/ec2/instances.rb +222 -0
  38. data/lib/rudy/cli/aws/ec2/keypairs.rb +53 -0
  39. data/lib/rudy/cli/aws/ec2/snapshots.rb +49 -0
  40. data/lib/rudy/cli/aws/ec2/volumes.rb +104 -0
  41. data/lib/rudy/cli/aws/ec2/zones.rb +22 -0
  42. data/lib/rudy/cli/aws/s3/buckets.rb +49 -0
  43. data/lib/rudy/cli/aws/s3/store.rb +22 -0
  44. data/lib/rudy/cli/aws/sdb/domains.rb +41 -0
  45. data/lib/rudy/cli/candy.rb +19 -0
  46. data/lib/rudy/cli/config.rb +81 -0
  47. data/lib/rudy/cli/disks.rb +58 -0
  48. data/lib/rudy/cli/machines.rb +114 -0
  49. data/lib/rudy/cli/routines.rb +108 -0
  50. data/lib/rudy/config.rb +116 -0
  51. data/lib/rudy/config/objects.rb +148 -0
  52. data/lib/rudy/global.rb +130 -0
  53. data/lib/rudy/guidelines.rb +18 -0
  54. data/lib/rudy/huxtable.rb +373 -0
  55. data/lib/rudy/machines.rb +281 -0
  56. data/lib/rudy/metadata.rb +51 -0
  57. data/lib/rudy/metadata/backup.rb +113 -0
  58. data/lib/rudy/metadata/backups.rb +65 -0
  59. data/lib/rudy/metadata/disk.rb +177 -0
  60. data/lib/rudy/metadata/disks.rb +67 -0
  61. data/lib/rudy/metadata/objectbase.rb +104 -0
  62. data/lib/rudy/mixins.rb +2 -0
  63. data/lib/rudy/mixins/hash.rb +25 -0
  64. data/lib/rudy/routines.rb +318 -0
  65. data/lib/rudy/routines/helper.rb +55 -0
  66. data/lib/rudy/routines/helpers/dependshelper.rb +34 -0
  67. data/lib/rudy/routines/helpers/diskhelper.rb +331 -0
  68. data/lib/rudy/routines/helpers/scmhelper.rb +39 -0
  69. data/lib/rudy/routines/helpers/scripthelper.rb +198 -0
  70. data/lib/rudy/routines/helpers/userhelper.rb +37 -0
  71. data/lib/rudy/routines/passthrough.rb +38 -0
  72. data/lib/rudy/routines/reboot.rb +75 -0
  73. data/lib/rudy/routines/release.rb +50 -0
  74. data/lib/rudy/routines/shutdown.rb +33 -0
  75. data/lib/rudy/routines/startup.rb +36 -0
  76. data/lib/rudy/scm.rb +75 -0
  77. data/lib/rudy/scm/git.rb +217 -0
  78. data/lib/rudy/scm/svn.rb +110 -0
  79. data/lib/rudy/utils.rb +365 -0
  80. data/rudy.gemspec +151 -0
  81. data/support/mailtest +40 -0
  82. data/support/randomize-root-password +45 -0
  83. data/support/rudy-ec2-startup +200 -0
  84. data/support/update-ec2-ami-tools +20 -0
  85. data/test/01_mixins/10_hash_test.rb +25 -0
  86. data/test/10_config/00_setup_test.rb +20 -0
  87. data/test/10_config/30_machines_test.rb +69 -0
  88. data/test/15_scm/00_setup_test.rb +20 -0
  89. data/test/15_scm/20_git_test.rb +61 -0
  90. data/test/20_sdb/00_setup_test.rb +16 -0
  91. data/test/20_sdb/10_domains_test.rb +115 -0
  92. data/test/25_ec2/00_setup_test.rb +29 -0
  93. data/test/25_ec2/10_keypairs_test.rb +41 -0
  94. data/test/25_ec2/20_groups_test.rb +131 -0
  95. data/test/25_ec2/30_addresses_test.rb +38 -0
  96. data/test/25_ec2/40_volumes_test.rb +49 -0
  97. data/test/25_ec2/50_snapshots_test.rb +74 -0
  98. data/test/26_ec2_instances/00_setup_test.rb +28 -0
  99. data/test/26_ec2_instances/10_instances_test.rb +83 -0
  100. data/test/26_ec2_instances/50_images_test.rb +13 -0
  101. data/test/30_sdb_metadata/00_setup_test.rb +21 -0
  102. data/test/30_sdb_metadata/10_disks_test.rb +109 -0
  103. data/test/30_sdb_metadata/20_backups_test.rb +102 -0
  104. data/test/coverage.txt +51 -0
  105. data/test/helper.rb +36 -0
  106. metadata +276 -0
@@ -0,0 +1,104 @@
1
+
2
+ module Rudy::AWS
3
+ module EC2
4
+
5
+ class KeyPair < Storable
6
+
7
+ field :name
8
+ field :fingerprint
9
+ field :private_key
10
+
11
+ def liner_note
12
+ "%-20s %s" % [self.name.bright, self.fingerprint]
13
+ end
14
+
15
+ def to_s(titles=false)
16
+ str = titles ? "%-20s %s#{$/}" % ['name', 'fingerprint'] : ""
17
+ str << liner_note
18
+ end
19
+
20
+ def public_key
21
+ return unless @private_key
22
+ k = Rye::Key.new(@private_key)
23
+ k.public_key.to_ssh2
24
+ end
25
+
26
+ end
27
+
28
+ class KeyPairs
29
+ include Rudy::AWS::ObjectBase
30
+ include Rudy::AWS::EC2::Base
31
+
32
+ def create(name)
33
+ raise "No name provided" unless name
34
+ ret = @ec2.create_keypair(:key_name => name)
35
+ self.class.from_hash(ret)
36
+ end
37
+
38
+ def destroy(name)
39
+ name = name.name if name.is_a?(Rudy::AWS::EC2::KeyPair)
40
+ raise "No name provided" unless name.is_a?(String)
41
+ ret = @ec2.delete_keypair(:key_name => name)
42
+ (ret && ret['return'] == 'true') # BUG? Always returns true
43
+ end
44
+
45
+ def list(*names)
46
+ keypairs = list_as_hash(names)
47
+ keypairs &&= keypairs.values
48
+ keypairs
49
+ end
50
+
51
+ def list_as_hash(*names)
52
+ names = names.flatten
53
+ klist = @ec2.describe_keypairs(:key_name => names)
54
+ return unless klist['keySet'].is_a?(Hash)
55
+ keypairs = {}
56
+ klist['keySet']['item'].each do |oldkp|
57
+ kp = self.class.from_hash(oldkp)
58
+ keypairs[kp.name] = kp
59
+ end
60
+ keypairs
61
+ end
62
+
63
+ def self.from_hash(h)
64
+ # keyName: test-c5g4v3pe
65
+ # keyFingerprint: 65:d0:ce:e7:6a:b0:88:4a:9c:c7:2d:b8:33:0c:fd:3b:c8:0f:0a:3c
66
+ # keyMaterial: |-
67
+ # -----BEGIN RSA PRIVATE KEY-----
68
+ #
69
+ keypair = Rudy::AWS::EC2::KeyPair.new
70
+ keypair.fingerprint = h['keyFingerprint']
71
+ keypair.name = h['keyName']
72
+ keypair.private_key = h['keyMaterial']
73
+ keypair
74
+ end
75
+
76
+ def any?
77
+ keypairs = list || []
78
+ !keypairs.empty?
79
+ end
80
+
81
+ def get(name)
82
+ keypairs = list(name) || []
83
+ return if keypairs.empty?
84
+ keypairs.first
85
+ end
86
+
87
+ def exists?(name)
88
+ return false unless name
89
+ kp = get(name) rescue nil
90
+ !kp.nil?
91
+ end
92
+
93
+ end
94
+
95
+ class Keypairs #:nodoc:
96
+ def initialize(*args)
97
+ raise "Oops! The correct class uses a capital 'P': Rudy::AWS::EC2::KeyPairs"
98
+ end
99
+ end
100
+
101
+ end
102
+ end
103
+
104
+
@@ -0,0 +1,109 @@
1
+
2
+
3
+ module Rudy::AWS
4
+ module EC2
5
+ class Snapshot < Storable
6
+ @@sformat = "%s <- %10s; %s"
7
+
8
+ field :awsid
9
+ field :progress
10
+ field :created
11
+ field :volid
12
+ field :status
13
+
14
+ def liner_note
15
+ t = Time.parse(@created)
16
+ info = t.strftime("%Y-%m-%d %H:%M:%S")
17
+ "%s (%s)" % [(self.awsid || '').bright, info]
18
+ end
19
+
20
+ def to_s(with_title=false)
21
+ @@sformat % [liner_note, @volid, @status]
22
+ end
23
+
24
+ def inspect
25
+ lines = []
26
+ lines << liner_note
27
+ field_names.each do |key|
28
+ next unless self.respond_to?(key)
29
+ val = self.send(key)
30
+ lines << sprintf(" %22s: %s", key, (val.is_a?(Array) ? val.join(', ') : val))
31
+ end
32
+ lines.join($/)
33
+ end
34
+
35
+ def completed?
36
+ self.status && self.status == 'completed'
37
+ end
38
+
39
+ end
40
+
41
+ class Snapshots
42
+ include Rudy::AWS::ObjectBase
43
+ include Rudy::AWS::EC2::Base
44
+
45
+ def list(snap_id=[])
46
+ snapshots = list_as_hash(snap_id)
47
+ if snapshots
48
+ snapshots = snapshots.values.sort { |a,b| a.created <=> b.created }
49
+ end
50
+ snapshots
51
+ end
52
+ def list_as_hash(snap_id=[])
53
+ snap_id = [snap_id].flatten.compact
54
+ slist = @ec2.describe_snapshots(:snapshot_id => snap_id)
55
+ return unless slist['snapshotSet'].is_a?(Hash)
56
+ snapshots = {}
57
+ slist['snapshotSet']['item'].each do |snap|
58
+ kp = self.class.from_hash(snap)
59
+ snapshots[kp.awsid] = kp
60
+ end
61
+ snapshots
62
+ end
63
+
64
+ def create(vol_id)
65
+ vol_id = (vol_id.is_a?(Rudy::AWS::EC2::Volume)) ? vol_id.awsid : vol_id
66
+ shash = @ec2.create_snapshot(:volume_id => vol_id)
67
+ snap = Snapshots.from_hash(shash)
68
+ snap
69
+ end
70
+
71
+ def destroy(snap_id)
72
+ ret = @ec2.delete_snapshot(:snapshot_id => snap_id)
73
+ (ret && ret['return'] == 'true')
74
+ end
75
+
76
+
77
+ def Snapshots.from_hash(h)
78
+ #snapshotSet:
79
+ # item:
80
+ # - snapshotId: snap-5493653d
81
+ # volumeId: vol-0836d761
82
+ # status: completed
83
+ # startTime: "2009-03-29T20:15:10.000Z"
84
+ # progress: 100%
85
+ vol = Rudy::AWS::EC2::Snapshot.new
86
+ vol.awsid = h['snapshotId']
87
+ vol.volid = h['volumeId']
88
+ vol.created = h['startTime']
89
+ vol.progress = h['progress']
90
+ vol.status = h['status']
91
+ vol
92
+ end
93
+
94
+ def any?
95
+ !(list_as_hash || {}).empty?
96
+ end
97
+
98
+ def get(snap_id)
99
+ list(snap_id).first || nil
100
+ end
101
+
102
+ def exists?(id)
103
+ !get(snap_id).nil?
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,230 @@
1
+
2
+
3
+ module Rudy::AWS
4
+ module EC2
5
+ class Volume < Storable
6
+ @@sformat = "%s %10s;%4sGB; %s " # cram the terabyte
7
+
8
+ field :awsid
9
+ field :status
10
+ field :size
11
+ field :snapid
12
+ field :zone
13
+ field :create_time
14
+ field :attach_time
15
+ field :instid
16
+ field :device
17
+
18
+ def liner_note
19
+ info = attached? ? "attached to #{@instid}" : @status
20
+ "%s (%s)" % [(self.awsid || '').bright, info]
21
+ end
22
+
23
+ def to_s(with_title=false)
24
+ line = @@sformat % [liner_note, @zone, @size, @device]
25
+ line << " <- %s" % [@snapid] if @snapid
26
+ line
27
+ end
28
+
29
+ def inspect
30
+ lines = [liner_note]
31
+ field_names.each do |n|
32
+ lines << sprintf(" %12s: %s", n, self.send(n)) if self.send(n)
33
+ end
34
+ lines.join($/)
35
+ end
36
+
37
+ # Alias for status
38
+ def state
39
+ status
40
+ end
41
+
42
+ def available?; (status && status == "available"); end
43
+ def creating?; (status && status == "creating"); end
44
+ def deleting?; (status && status == "deleting"); end
45
+ def attached?; (status && status == "attached"); end
46
+ def in_use?; (status && status == "in-use"); end
47
+
48
+ end
49
+
50
+
51
+ class Volumes
52
+ include Rudy::AWS::ObjectBase
53
+ include Rudy::AWS::EC2::Base
54
+
55
+ unless defined?(KNOWN_STATES)
56
+ KNOWN_STATES = [:available, :creating, :deleting, :attached, :detaching].freeze
57
+ end
58
+
59
+ # * +size+ the number of GB
60
+ def create(size, zone, snapid=nil)
61
+ opts = {
62
+ :availability_zone => zone.to_s,
63
+ :size => (size || 1).to_s
64
+ }
65
+
66
+ opts[:snapshot_id] = snapid if snapid
67
+
68
+ # "status"=>"creating",
69
+ # "size"=>"1",
70
+ # "snapshotId"=>nil,
71
+ # "requestId"=>"d42ff744-48b5-4f47-a3f0-7aba57a13eb9",
72
+ # "availabilityZone"=>"us-east-1b",
73
+ # "createTime"=>"2009-03-17T20:10:48.000Z",
74
+ # "volumeId"=>"vol-48826421"
75
+ vol = execute_request({}) { @ec2.create_volume(opts) }
76
+
77
+ # TODO: use a waiter?
78
+ #Rudy.waiter(1, 30) do
79
+ # ret = @@ec2.volumes.available?(volume.awsid)
80
+ #end
81
+
82
+ reqid = vol['requestId']
83
+ Volumes.from_hash(vol) || nil
84
+ end
85
+
86
+ def destroy(vol_id)
87
+ vol_id = Volumes.get_vol_id(vol_id)
88
+ raise VolumeNotAvailable, vol_id unless available?(vol_id)
89
+ ret = execute_request({}) { @ec2.delete_volume(:volume_id => vol_id) }
90
+ (ret['return'] == 'true')
91
+ end
92
+
93
+ def attach(vol_id, inst_id, device)
94
+ vol_id = Volumes.get_vol_id(vol_id)
95
+ inst_id = inst_id.is_a?(Rudy::AWS::EC2::Instance) ? inst_id.awsid : inst_id
96
+ raise NoVolumeID unless vol_id
97
+ raise VolumeAlreadyAttached, vol_id if attached?(vol_id)
98
+ raise NoInstanceID unless inst_id
99
+ raise NoDevice unless device
100
+
101
+ opts = {
102
+ :volume_id => vol_id,
103
+ :instance_id => inst_id,
104
+ :device => device.to_s # Solaris devices are numbers
105
+ }
106
+ ret = execute_request(false) { @ec2.attach_volume(opts) }
107
+ (ret['status'] == 'attaching')
108
+ end
109
+
110
+ def detach(vol_id)
111
+ vol_id = Volumes.get_vol_id(vol_id)
112
+ raise NoVolumeID unless vol_id
113
+ raise VolumeNotAttached, vol_id unless attached?(vol_id)
114
+ ret = execute_request({}) { @ec2.detach_volume(:volume_id => vol_id) }
115
+ (ret['status'] == 'detaching')
116
+ end
117
+
118
+
119
+ def list(state=nil, vol_id=[])
120
+ list_as_hash(state, vol_id).values
121
+ end
122
+
123
+ def list_as_hash(state=nil, vol_id=[])
124
+ state &&= state.to_sym
125
+ state = nil if state == :any
126
+ # A nil state is fine, but we don't want an unknown one!
127
+ raise UnknownState, state if state && !Volumes.known_state?(state)
128
+
129
+ opts = {
130
+ :volume_id => vol_id ? [vol_id].flatten : []
131
+ }
132
+
133
+ vlist = execute_request({}) { @ec2.describe_volumes(opts) }
134
+
135
+ volumes = {}
136
+ return volumes unless vlist['volumeSet'].is_a?(Hash)
137
+ vlist['volumeSet']['item'].each do |vol|
138
+ v = Volumes.from_hash(vol)
139
+ next if state && v.state != state.to_s
140
+ volumes[v.awsid] = v
141
+ end
142
+ volumes
143
+ end
144
+
145
+ def any?(state=nil,vol_id=[])
146
+ !list(state, vol_id).nil?
147
+ end
148
+
149
+ def exists?(vol_id)
150
+ vol_id = Volumes.get_vol_id(vol_id)
151
+ vol = get(vol_id)
152
+ !vol.nil?
153
+ end
154
+
155
+ def get(vol_id)
156
+ vol_id = Volumes.get_vol_id(vol_id)
157
+ list(:any, vol_id).first rescue nil
158
+ end
159
+
160
+ # deleting?, available?, etc...
161
+ %w[deleting available attached in-use].each do |state|
162
+ define_method("#{state.tr('-', '_')}?") do |vol_id|
163
+ vol_id = Volumes.get_vol_id(vol_id)
164
+ return false unless vol_id
165
+ vol = get(vol_id)
166
+ (vol && vol.status == state)
167
+ end
168
+ end
169
+
170
+ # Creates a Rudy::AWS::EC2::Volume object from:
171
+ #
172
+ # volumeSet:
173
+ # item:
174
+ # - status: available
175
+ # size: "1"
176
+ # snapshotId:
177
+ # availabilityZone: us-east-1b
178
+ # attachmentSet:
179
+ # createTime: "2009-03-17T20:10:48.000Z"
180
+ # volumeId: vol-48826421
181
+ # attachmentSet:
182
+ # item:
183
+ # - attachTime: "2009-03-17T21:49:54.000Z"
184
+ # status: attached
185
+ # device: /dev/sdh
186
+ # instanceId: i-956af3fc
187
+ # volumeId: vol-48826421
188
+ #
189
+ # requestId: 8fc30e5b-a9c3-4fe0-a979-0f71e639a7c7
190
+ #
191
+ def self.from_hash(h)
192
+ vol = Rudy::AWS::EC2::Volume.new
193
+ vol.status = h['status']
194
+ vol.size = h['size']
195
+ vol.snapid = h['snapshotId']
196
+ vol.zone = h['availabilityZone']
197
+ vol.awsid = h['volumeId']
198
+ vol.create_time = h['createTime']
199
+ if h['attachmentSet'].is_a?(Hash)
200
+ item = h['attachmentSet']['item'].first
201
+ vol.status = item['status'] # Overwrite "available status". Possibly a bad idea.
202
+ vol.device = item['device']
203
+ vol.attach_time = item['attachTime']
204
+ vol.instid = item['instanceId']
205
+ end
206
+ vol
207
+ end
208
+
209
+ # * +vol_id+ is a String or Rudy::AWS::EC2::Volume
210
+ # Returns the volume ID
211
+ def self.get_vol_id(vol_id)
212
+ (vol_id.is_a?(Rudy::AWS::EC2::Volume)) ? vol_id.awsid : vol_id
213
+ end
214
+
215
+ # Is +state+ a known EC2 volume state? See: KNOWN_STATES
216
+ def self.known_state?(state)
217
+ return false unless state
218
+ state &&= state.to_sym
219
+ KNOWN_STATES.member?(state)
220
+ end
221
+
222
+ end
223
+ end
224
+ end
225
+
226
+
227
+ class Rudy::AWS::EC2::Volumes
228
+
229
+
230
+ end
@@ -0,0 +1,77 @@
1
+
2
+ module Rudy::AWS
3
+ module EC2
4
+
5
+ class Zone < Storable
6
+
7
+ field :name
8
+ field :region
9
+ field :state
10
+
11
+ def liner_note
12
+ "%-10s %9s %s" % [self.name, self.region, self.state]
13
+ end
14
+
15
+ def to_s(titles=false)
16
+ str = titles ? "%-20s %s#{$/}" % ['name', 'region', 'state'] : ""
17
+ str << liner_note
18
+ end
19
+
20
+ end
21
+
22
+ class Zones
23
+ include Rudy::AWS::ObjectBase
24
+ include Rudy::AWS::EC2::Base
25
+
26
+ def list(*names)
27
+ zones = list_as_hash(names)
28
+ zones &&= zones.values
29
+ zones
30
+ end
31
+
32
+ def list_as_hash(*names)
33
+ names = names.flatten
34
+ zlist = @ec2.describe_availability_zones(:zone_name => names)
35
+ return unless zlist['availabilityZoneInfo'].is_a?(Hash)
36
+ zones = {}
37
+ zlist['availabilityZoneInfo']['item'].each do |zhash|
38
+ zon = Zones.from_hash(zhash)
39
+ zones[zon.name] = zon
40
+ end
41
+ zones
42
+ end
43
+
44
+ def self.from_hash(h)
45
+ zone = Rudy::AWS::EC2::Zone.new
46
+ zone.name = h['zoneName']
47
+ zone.region = h['regionName']
48
+ zone.state = h['zoneState']
49
+ zone
50
+ end
51
+
52
+ def any?
53
+ zones = list || []
54
+ !zones.empty?
55
+ end
56
+
57
+ def get(name)
58
+ zones = list(name) || []
59
+ return if zones.empty?
60
+ zones.first
61
+ end
62
+
63
+ def zone?(name)
64
+ begin
65
+ kp = get(name)
66
+ kp.is_a?(Rudy::AWS::EC2::Zone)
67
+ rescue => ex
68
+ false
69
+ end
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
76
+
77
+