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,157 @@
1
+
2
+
3
+ module Rudy::AWS
4
+
5
+ module EC2
6
+
7
+ class Address < Storable
8
+ field :ipaddress
9
+ field :instid
10
+
11
+ def liner_note
12
+ info = self.associated? ? @instid : "available"
13
+ "%s (%s)" % [@ipaddress.to_s.bright, info]
14
+ end
15
+
16
+ def to_s(with_titles=false)
17
+ liner_note
18
+ end
19
+
20
+ def associated?
21
+ !@instid.nil? && !@instid.empty?
22
+ end
23
+ end
24
+
25
+ class Addresses
26
+ include Rudy::AWS::ObjectBase
27
+ include Rudy::AWS::EC2::Base
28
+
29
+
30
+ def create
31
+ ret = @ec2.allocate_address
32
+ return false unless ret && ret['publicIp']
33
+ address = Rudy::AWS::EC2::Address.new
34
+ address.ipaddress = ret['publicIp']
35
+ address
36
+ end
37
+
38
+ def destroy(address)
39
+ address = address.ipaddress if address.is_a?(Rudy::AWS::EC2::Address)
40
+ raise UnknownAddress unless exists?(address)
41
+
42
+ opts ={
43
+ :public_ip => address || raise("No public IP address supplied")
44
+ }
45
+ ret = @ec2.release_address(opts)
46
+ (ret && ret['return'] == 'true')
47
+ end
48
+
49
+
50
+ # Associate an elastic IP to an instance
51
+ def associate(address, instance)
52
+ raise NoInstanceID unless instance
53
+ raise NoAddress unless address
54
+
55
+ address = address.ipaddress if address.is_a?(Rudy::AWS::EC2::Address)
56
+ instance = instance.awsid if instance.is_a?(Rudy::AWS::EC2::Instance)
57
+ raise UnknownAddress unless exists?(address)
58
+ raise AddressAssociated if associated?(address)
59
+
60
+ opts ={
61
+ :instance_id => instance,
62
+ :public_ip => address
63
+ }
64
+ ret = @ec2.associate_address(opts)
65
+ (ret && ret['return'] == 'true')
66
+ end
67
+
68
+ # Disssociate an elastic IP from an instance
69
+ def disassociate(address)
70
+ raise NoAddress unless address
71
+ address = address.ipaddress if address.is_a?(Rudy::AWS::EC2::Address)
72
+ instance = instance.awsid if instance.is_a?(Rudy::AWS::EC2::Instance)
73
+ raise UnknownAddress unless exists?(address)
74
+ raise AddressNotAssociated unless associated?(address)
75
+
76
+ opts ={
77
+ :public_ip => address
78
+ }
79
+ ret = @ec2.disassociate_address(opts)
80
+ (ret && ret['return'] == 'true')
81
+ end
82
+
83
+
84
+
85
+ # Returns a Array of Rudy::AWS::EC2::Address objects.
86
+ def list(addresses=[])
87
+ addresses = list_as_hash(addresses)
88
+ addresses &&= addresses.values
89
+ addresses
90
+ end
91
+
92
+ # Returns a Hash of Rudy::AWS::EC2::Address objects. The key of the IP address.
93
+ def list_as_hash(addresses=[])
94
+ addresses ||= []
95
+ addresses = [addresses].flatten.compact
96
+ alist = @ec2.describe_addresses(:addresses=> addresses)
97
+
98
+ return nil unless alist['addressesSet'].is_a?(Hash)
99
+
100
+ addresses = {}
101
+ alist['addressesSet']['item'].each do |address|
102
+ address = Addresses.from_hash(address)
103
+ addresses[address.ipaddress] = address
104
+ end
105
+
106
+ addresses
107
+ end
108
+
109
+
110
+ def any?
111
+ !list_as_hash.nil?
112
+ end
113
+
114
+ def get(address)
115
+ raise "Address cannot be nil" if address.nil?
116
+ address = address.ipaddress if address.is_a?(Rudy::AWS::EC2::Address)
117
+ (list(address) || []).first
118
+ end
119
+
120
+ def self.from_hash(h)
121
+ # requestId: 5ebcad80-eed9-4221-86f6-8d19d7acffe4
122
+ # addressesSet:
123
+ # item:
124
+ # - publicIp: 75.101.137.7
125
+ # instanceId:
126
+ address = Rudy::AWS::EC2::Address.new
127
+ address.ipaddress = h['publicIp']
128
+ address.instid = h['instanceId'] if h['instanceId'] && !h['instanceId'].empty?
129
+ address
130
+ end
131
+
132
+
133
+ # +address+ is an IP address or Rudy::AWS::EC2::Address object
134
+ # Returns true if the given address is assigned to the current account
135
+ def exists?(address)
136
+ address = address.ipaddress if address.is_a?(Rudy::AWS::EC2::Address)
137
+ list.each do |a|
138
+ return true if a.ipaddress == address
139
+ end
140
+ false
141
+ end
142
+
143
+ # +address+ is an IP address or Rudy::AWS::EC2::Address object
144
+ # Returns true if the given address is associated to an instance
145
+ def associated?(address)
146
+ address = address.ipaddress if address.is_a?(Rudy::AWS::EC2::Address)
147
+ list.each do |a|
148
+ return true if a.ipaddress == address && a.instid
149
+ end
150
+ false
151
+ end
152
+ end
153
+
154
+ end
155
+ end
156
+
157
+
@@ -0,0 +1,301 @@
1
+
2
+ module Rudy::AWS
3
+
4
+ class EC2::Group < Storable
5
+ class Rule < Storable
6
+ field :ports => Range # Port range
7
+ field :protocol => String
8
+
9
+ def to_s(with_title=false)
10
+ if self.ports.first == self.ports.last
11
+ "%s(%s)" % [self.protocol, self.ports.last]
12
+ else
13
+ "%s(%s..%s)" % [self.protocol, self.ports.first, self.ports.last]
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ class EC2::Group < Storable
20
+ field :name => String
21
+ field :description => String
22
+ field :owner_id => String
23
+ field :addresses => Hash # key: address/mask, value Array of Rule object
24
+ field :groups => Hash # key: group, value Array of Rule object
25
+
26
+
27
+ def liner_note
28
+ info = "(authorized accounts: #{@groups.keys.join(', ')})"
29
+ info = '' if @groups.empty?
30
+ "%s %s" % [@name.bright, info]
31
+ end
32
+
33
+
34
+ # Print info about a security group
35
+ #
36
+ # * +group+ is a Rudy::AWS::EC2::Group object
37
+ def to_s(with_title=false)
38
+ lines = [liner_note]
39
+ (self.addresses || {}).each_pair do |address,rules|
40
+ lines << "%18s -> %s" % [address.to_s, rules.collect { |p| p.to_s}.join(', ')]
41
+ end
42
+ lines.join($/)
43
+ end
44
+
45
+ def inspect
46
+ lines = [@name.bright]
47
+ field_names.each do |key|
48
+ next unless self.respond_to?(key)
49
+ next if [:addresses, :groups].member?(key)
50
+ val = self.send(key)
51
+ lines << sprintf(" %12s: %s", key, (val.is_a?(Array) ? val.join(', ') : val))
52
+ end
53
+ @addresses.each_pair do |a,r|
54
+ rules = r.collect { |r| r.to_s }.join(', ') if r
55
+ lines << sprintf(" %12s: %s (%s)", 'address', a.to_s, rules)
56
+ end
57
+ @groups.each_pair do |g,r|
58
+ rules = r.collect { |r| r.to_s }.join(', ')
59
+ lines << sprintf(" %12s: %s (%s)", 'group', g.to_s, rules)
60
+ end
61
+ lines.join($/)
62
+ end
63
+
64
+
65
+ # * +ipaddress+ is a String, ipaddress/mask/protocol
66
+ # * +rule+ is a Rule object
67
+ def add_address(ipaddress, rule)
68
+ return false unless rule.is_a?(Rule)
69
+ @addresses ||= {}
70
+ (@addresses[ipaddress] ||= []) << rule
71
+ rule
72
+ end
73
+
74
+ # * +group+ is a String, accountnum:group
75
+ # * +rule+ is a Rule object
76
+ def add_group(group, rule)
77
+ return false unless rule.is_a?(Rule)
78
+ @groups ||= {}
79
+ (@groups[group] ||= []) << rule
80
+ end
81
+
82
+ end
83
+
84
+
85
+
86
+ module EC2
87
+ class Groups
88
+ include Rudy::AWS::ObjectBase
89
+ include Rudy::AWS::EC2::Base
90
+
91
+ # Create a new EC2 security group
92
+ # Returns list of created groups
93
+ def create(name, desc=nil, addresses=[], ports=[], protocols=[], &each_group)
94
+ desc ||= "Security Group #{name}"
95
+ ret = @ec2.create_security_group(:group_name => name, :group_description => desc)
96
+ return false unless (ret && ret['return'] == 'true')
97
+ authorize(name, addresses, ports, protocols)
98
+ get(name, &each_group)
99
+ end
100
+
101
+ # Delete an EC2 security group
102
+ # Returns true/false whether successful
103
+ def destroy(name, &each_group)
104
+ list(name, &each_group) if each_group
105
+ ret = @ec2.delete_security_group(:group_name => name)
106
+ (ret && ret['return'] == 'true')
107
+ end
108
+
109
+ # Authorize a port/protocol for a specific IP address
110
+ def authorize(name, addresses=[], ports=[], protocols=[], &each_group)
111
+ modify_rules(:authorize, name, addresses, ports, protocols, &each_group)
112
+ end
113
+ alias :authorise :authorize
114
+
115
+ # Revoke a port/protocol for a specific IP address
116
+ # Takes the same arguments as authorize
117
+ def revoke(name, addresses=[], ports=[], protocols=[], &each_group)
118
+ modify_rules(:revoke, name, addresses, ports, protocols, &each_group)
119
+ end
120
+
121
+ def authorize_group(name, gname, owner, &each_group)
122
+ modify_group_rules(:authorize, name, gname, owner, &each_group)
123
+ end
124
+ alias :authorise_group :authorize_group
125
+
126
+ def revoke_group(name, gname, owner, &each_group)
127
+ modify_group_rules(:revoke, name, gname, owner, &each_group)
128
+ end
129
+
130
+ def list(group_names=[], &each_group)
131
+ group_names ||= []
132
+ groups = list_as_hash(group_names, &each_group)
133
+ groups &&= groups.values
134
+ groups
135
+ end
136
+
137
+ # * +group_names+ is a list of security group names to look for. If it's empty, all groups
138
+ # associated to the account will be returned.
139
+ #
140
+ # Returns an Array of Rudy::AWS::EC2::Group objects
141
+ def list_as_hash(group_names=[], &each_group)
142
+ group_names = [group_names].flatten.compact
143
+ glist = @ec2.describe_security_groups(:group_name => group_names) || {}
144
+ return unless glist['securityGroupInfo'].is_a?(Hash)
145
+ groups = {}
146
+ glist['securityGroupInfo']['item'].each do |oldg|
147
+ g = Groups.from_hash(oldg)
148
+ groups[g.name] = g
149
+ end
150
+ groups.each_value { |g| each_group.call(g) } if each_group
151
+ groups
152
+ end
153
+
154
+ def any?
155
+ groups = list || []
156
+ !groups.empty?
157
+ end
158
+
159
+ # * +name+ a string
160
+ def get(name)
161
+ (list([name]) || []).first
162
+ end
163
+
164
+ # +group+ a Rudy::AWS::EC2::Group object
165
+ #def save(group)
166
+ #
167
+ #end
168
+
169
+ # Does the security group +name+ exist?
170
+ def exists?(name)
171
+ begin
172
+ g = list([name.to_s])
173
+ rescue ::EC2::InvalidGroupNotFound
174
+ return false
175
+ end
176
+
177
+ !g.empty?
178
+ end
179
+
180
+
181
+
182
+
183
+ # * +ghash+ is an EC2::Base Security Group Hash. This is the format
184
+ # returned by EC2::Base#describe_security_groups
185
+ #
186
+ # groupName: stage-app
187
+ # groupDescription:
188
+ # ownerId: "207436219441"
189
+ # ipPermissions:
190
+ # item:
191
+ # - ipRanges:
192
+ # item:
193
+ # - cidrIp: 216.19.182.83/32
194
+ # - cidrIp: 24.5.71.201/32
195
+ # - cidrIp: 75.157.176.202/32
196
+ # - cidrIp: 84.28.52.172/32
197
+ # - cidrIp: 87.212.145.201/32
198
+ # - cidrIp: 96.49.129.178/32
199
+ # groups:
200
+ # item:
201
+ # - groupName: default
202
+ # userId: "207436219441"
203
+ # - groupName: stage-app
204
+ # userId: "207436219441"
205
+ # fromPort: "22"
206
+ # toPort: "22"
207
+ # ipProtocol: tcp
208
+ #
209
+ # Returns a Rudy::AWS::EC2::Group object
210
+ def self.from_hash(ghash)
211
+ newg = Rudy::AWS::EC2::Group.new
212
+ newg.name = ghash['groupName']
213
+ newg.description = ghash['groupDescription']
214
+ newg.owner_id = ghash['ownerId']
215
+ newg.addresses = {}
216
+ newg.groups = {}
217
+
218
+ return newg unless ghash['ipPermissions'].is_a?(Hash)
219
+
220
+ ghash['ipPermissions']['item'].each do |oldp|
221
+ newp = Rudy::AWS::EC2::Group::Rule.new
222
+ newp.ports = Range.new(oldp['fromPort'], oldp['toPort'])
223
+ newp.protocol = oldp['ipProtocol']
224
+ if oldp['groups'].is_a?(Hash)
225
+ oldp['groups']['item'].each do |oldpg|
226
+ name = [oldpg['userId'], oldpg['groupName']].join(':') # account_num:name
227
+ newg.add_group(name, newp)
228
+ end
229
+ end
230
+ if oldp['ipRanges'].is_a?(Hash)
231
+ oldp['ipRanges']['item'].each do |olda|
232
+ name = "#{olda['cidrIp']}"
233
+ newg.add_address(name, newp) # ipaddress/mask/protocol
234
+ end
235
+ end
236
+ end
237
+ newg
238
+ end
239
+
240
+
241
+ private
242
+
243
+
244
+ def modify_rules(meth, name, addresses, ports, protocols, &each_group)
245
+ list(name, &each_group) if each_group
246
+
247
+ ports = [[22,22],[80,80],[443,443]] if !ports || ports.empty?
248
+ protocols = ["tcp"] if !protocols || protocols.empty?
249
+ addresses = [Rudy::Utils::external_ip_address] if !addresses || addresses.empty?
250
+
251
+ # Make sure the IP addresses have ranges
252
+ addresses.collect! { |ip| (ip.match /\/\d+/) ? ip : "#{ip}/32" }
253
+ protocols.collect! { |p| p.to_s }
254
+ ret = false
255
+ protocols.each do |protocol|
256
+ addresses.each do |address|
257
+ ports.each do |port|
258
+ port_lo, port_hi = port.is_a?(Array) ? [port[0], port[1]] : [port, port]
259
+ @logger.puts "#{meth} for ports #{port[0]}:#{port[1]} (#{protocol}) for #{addresses.join(', ')}" if @logger
260
+ ret = modify_rule(meth, name, port[0].to_i, (port[1] || port[0]).to_i, protocol, address)
261
+ raise "Unknown error during #{meth}" unless ret
262
+ end
263
+ end
264
+ end
265
+
266
+ ret
267
+ end
268
+
269
+ def modify_rule(meth, name, from_port, to_port, protocol, ipa)
270
+ opts = {
271
+ :group_name => name,
272
+ :ip_protocol => protocol,
273
+ :from_port => from_port,
274
+ :to_port => to_port,
275
+ :cidr_ip => ipa
276
+ }
277
+ ret = @ec2.send("#{meth}_security_group_ingress", opts)
278
+ (ret && ret['return'] == 'true')
279
+ end
280
+
281
+
282
+ def modify_group_rules(meth, name, gname, gowner, &each_group)
283
+ list(name, &each_group) if each_group
284
+ # probably works, needs to be tested
285
+ #gowner &&= gowner.tr!('-', '') # Remove dashes from aws account number
286
+
287
+ opts = {
288
+ :group_name => name,
289
+ :source_security_group_name => gname,
290
+ :source_security_group_owner_id => gowner
291
+ }
292
+ ret = @ec2.send("#{meth}_security_group_ingress", opts)
293
+ (ret && ret['return'] == 'true')
294
+ end
295
+
296
+
297
+
298
+ end
299
+ end
300
+
301
+ end