poolparty 1.6.8 → 1.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -14,7 +14,17 @@ require 'config/jeweler' # setup gem configuration
14
14
 
15
15
  task :default => [:test, :cleanup_test]
16
16
  desc "Update vendor directory and run tests"
17
- task :ci => ["poolparty:vendor:setup", "poolparty:vendor:update", :spec, :test]
17
+
18
+ namespace :poolparty do
19
+ namespace :vendor do
20
+ desc "Fetch all the submodules"
21
+ task :submodules do
22
+ `git submodule update`
23
+ end
24
+ end
25
+ end
26
+
27
+ task :vendor => ["poolparty:vendor:submodules"]
18
28
 
19
29
  task :cleanup_test do
20
30
  ::FileUtils.rm_rf "/tmp/poolparty"
@@ -83,4 +93,4 @@ Rake::RDocTask.new do |rd|
83
93
  rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
84
94
  rd.rdoc_dir = "rdoc"
85
95
  # rd.template = "hanaa"
86
- end
96
+ end
@@ -1,4 +1,5 @@
1
1
  ---
2
- :patch: 8
3
2
  :major: 1
4
3
  :minor: 6
4
+ :patch: 9
5
+ :build:
data/bin/cloud CHANGED
@@ -23,6 +23,7 @@ EOS
23
23
  opt :debug, "Debug the output", :type => :boolean, :default => false
24
24
  opt :very_debug, "Set very debug mode on", :type => :boolean, :default => false
25
25
  opt :name, "Name of the working cloud", :type => String, :default => nil
26
+ opt :chef_task, "Name of chef task to execute", :type => String, :default => 'default'
26
27
 
27
28
  before_run do |command|
28
29
  # Setup testing/debugging
@@ -38,9 +39,11 @@ EOS
38
39
  exit
39
40
  end
40
41
 
42
+ pool.chef_step command[:chef_task].to_sym
43
+
41
44
  @loaded_pool = pool
42
45
  @loaded_clouds = command[:name] ? [pool.clouds[command[:name]]] : pool.clouds.map {|name,cld|cld}
43
- if @loaded_clouds.count == 0
46
+ if @loaded_clouds.size == 0
44
47
  puts "No clouds loaded. Check your clouds.rb or -n option"
45
48
  exit
46
49
  end
@@ -24,8 +24,8 @@ EOS
24
24
  n.ssh(command[:command])
25
25
  end
26
26
 
27
- p output
27
+ #p output
28
28
 
29
29
  end
30
30
  end
31
- end
31
+ end
@@ -6,8 +6,8 @@ require 'git-style-binary/command'
6
6
 
7
7
  GitStyleBinary.command do
8
8
  @theme = :short
9
-
10
- version "PoolParty #{$0} command"
9
+
10
+ version "PoolParty #{$0} command"
11
11
  banner <<-EOS
12
12
  Usage: #{$0} #{all_options_string}
13
13
 
@@ -20,7 +20,7 @@ EOS
20
20
  run do |command|
21
21
 
22
22
  @loaded_clouds.each do |cld|
23
-
23
+
24
24
  msg = [
25
25
  "Cloud: #{cld.name}",
26
26
  "----------------------------",
@@ -29,11 +29,12 @@ EOS
29
29
  "Maximum instances: #{cld.maximum_instances}",
30
30
  "Running on: #{cld.cloud_provider.name}",
31
31
  "Keypair: #{cld.keypair.basename}",
32
- "Security group: #{cld.cloud_provider.security_group_names}",
33
- "Availability zones: #{cld.cloud_provider.availability_zones}",
34
- "User: #{cld.user}"
32
+ "Security group: #{cld.cloud_provider.security_group_names.join(', ')}",
33
+ "Availability zones: #{cld.cloud_provider.availability_zones.join(', ')}",
34
+ "User: #{cld.user}",
35
+ "Active recipes: #{cld.chef._recipes(cld.pool.chef_step).join ", " }"
35
36
  ]
36
-
37
+
37
38
  if cld.load_balancers.size > 0
38
39
  load_balancers = cld.cloud_provider.load_balancers.first.running_load_balancers.map {|a| a[:dns_name]}
39
40
  msg << "Load balancers: #{load_balancers.join("\n\t\t\t")}"
@@ -45,10 +46,10 @@ EOS
45
46
 
46
47
  msg << available.join("\n\t\t\t")
47
48
  end
48
-
49
+
49
50
  puts msg.flatten
50
-
51
+
51
52
  end
52
-
53
+
53
54
  end
54
55
  end
@@ -108,7 +108,7 @@ module CloudProviders
108
108
  rsync_opts += %q% --rsync-path="sudo rsync"% unless user=="root"
109
109
  rsync_opts += %q% --exclude=.svn --exclude=.git --exclude=.cvs %
110
110
  cmd_string = "rsync -L -e 'ssh #{ssh_options}' #{rsync_opts} #{opts[:source]} #{user}@#{host}:#{destination_path}"
111
- out = system_run(cmd_string)
111
+ out = system_run(cmd_string, :quiet => true)
112
112
  out
113
113
  end
114
114
 
@@ -137,7 +137,9 @@ module CloudProviders
137
137
  while (chunk = stdin.readpartial(opts[:sysread]))
138
138
  buf << chunk
139
139
  unless chunk.nil? || chunk.empty?
140
- $stdout.write(chunk) #if debugging? || verbose?
140
+ if not opts[:quiet]
141
+ $stdout.write(chunk) #if debugging? || verbose?
142
+ end
141
143
  end
142
144
  end
143
145
  err = stderr.readlines
@@ -10,37 +10,39 @@ rescue LoadError
10
10
  EOM
11
11
  end
12
12
 
13
+ require 'pp'
14
+
13
15
  module CloudProviders
14
16
  class Ec2 < CloudProvider
15
17
  # Set the aws keys from the environment, or load from /etc/poolparty/env.yml if the environment variable is not set
16
18
  def self.default_access_key
17
19
  ENV['EC2_ACCESS_KEY'] || load_keys_from_file[:access_key] || load_keys_from_credential_file[:access_key]
18
20
  end
19
-
21
+
20
22
  def self.default_secret_access_key
21
23
  ENV['EC2_SECRET_KEY'] || load_keys_from_file[:secret_access_key] || load_keys_from_credential_file[:secret_access_key]
22
24
  end
23
-
25
+
24
26
  def self.default_private_key
25
27
  ENV['EC2_PRIVATE_KEY'] || load_keys_from_file[:private_key]
26
28
  end
27
-
29
+
28
30
  def self.default_cert
29
31
  ENV['EC2_CERT'] || load_keys_from_file[:cert]
30
32
  end
31
-
33
+
32
34
  def self.default_user_id
33
35
  ENV['EC2_USER_ID'] || load_keys_from_file[:user_id]
34
36
  end
35
-
37
+
36
38
  def self.default_ec2_url
37
39
  ENV['EC2_URL'] || load_keys_from_file[:ec2_url]
38
40
  end
39
-
41
+
40
42
  def self.default_s3_url
41
43
  ENV['S3_URL'] || load_keys_from_file[:s3_url]
42
44
  end
43
-
45
+
44
46
  def self.default_cloud_cert
45
47
  ENV['CLOUD_CERT'] || ENV['EUCALYPTUS_CERT'] || load_keys_from_file[:cloud_cert]
46
48
  end
@@ -48,7 +50,7 @@ module CloudProviders
48
50
  def self.default_credential_file
49
51
  ENV['AWS_CREDENTIAL_FILE'] || load_keys_from_file[:credential_file]
50
52
  end
51
-
53
+
52
54
  # Load the yaml file containing keys. If the file does not exist, return an empty hash
53
55
  def self.load_keys_from_file(filename="#{ENV["HOME"]}/.poolparty/aws", caching=true)
54
56
  return @aws_yml if @aws_yml && caching==true
@@ -71,8 +73,8 @@ module CloudProviders
71
73
  }
72
74
  return {:access_key => @access_key, :secret_access_key => @secret_access_key}
73
75
  end
74
-
75
-
76
+
77
+
76
78
  default_options(
77
79
  :instance_type => 'm1.small',
78
80
  :availability_zones => ["us-east-1a"],
@@ -84,7 +86,7 @@ module CloudProviders
84
86
  :secret_access_key => default_secret_access_key,
85
87
  :ec2_url => default_ec2_url,
86
88
  :s3_url => default_s3_url,
87
- :credential_file => default_credential_file,
89
+ :credential_file => default_credential_file,
88
90
  :min_count => 1,
89
91
  :max_count => 1,
90
92
  :user_data => '',
@@ -109,6 +111,7 @@ module CloudProviders
109
111
  puts " maximum_instances: #{maximum_instances}"
110
112
  puts " security_groups: #{security_group_names.join(", ")}"
111
113
  puts " using keypair: #{keypair}"
114
+ puts " with user_data #{user_data.to_s[0..100]}"
112
115
  puts " user: #{user}\n"
113
116
 
114
117
  security_groups.each do |sg|
@@ -152,7 +155,7 @@ module CloudProviders
152
155
  puts " autoscaler: #{a.name}"
153
156
  puts "-----> The autoscaling groups will launch the instances"
154
157
  a.run
155
-
158
+
156
159
  progress_bar_until("Waiting for autoscaler to launch instances") do
157
160
  reset!
158
161
  running_nodes = nodes.select {|n| n.running? }
@@ -161,8 +164,8 @@ module CloudProviders
161
164
  reset!
162
165
  end
163
166
  end
164
-
165
- from_ports = security_groups.map {|a| a.authorizes.map {|t| t.from_port.to_i }.flatten }.flatten
167
+
168
+ from_ports = security_groups.map {|a| a.authorizes.map {|t| t.from_port.to_i }.flatten }.flatten
166
169
  if from_ports.include?(22)
167
170
  progress_bar_until("Waiting for the instances to be accessible by ssh") do
168
171
  running_nodes = nodes.select {|n| n.running? }
@@ -172,20 +175,20 @@ module CloudProviders
172
175
  accessible_count == running_nodes.size
173
176
  end
174
177
  end
175
-
178
+
176
179
  assign_elastic_ips
177
180
  cleanup_ssh_known_hosts!
178
181
  puts "Attaching EBS volumes"
179
182
  assign_ebs_volumes # Assign EBS volumes
180
183
  end
181
-
184
+
182
185
  def teardown
183
186
  puts "------ Tearing down and cleaning up #{cloud.name} cloud"
184
187
  unless autoscalers.empty?
185
188
  puts "Tearing down autoscalers"
186
189
  end
187
190
  end
188
-
191
+
189
192
  def expand_by(num=1)
190
193
  e = Ec2Instance.run!({
191
194
  :image_id => image_id,
@@ -208,7 +211,7 @@ module CloudProviders
208
211
  end
209
212
  all_nodes.detect {|n| n.instance_id == e.instance_id }
210
213
  end
211
-
214
+
212
215
  def decoded_user_data
213
216
  if user_data
214
217
  if File.file?(user_data)
@@ -218,13 +221,13 @@ module CloudProviders
218
221
  end
219
222
  end
220
223
  end
221
-
224
+
222
225
  def wait_for_node(instance)
223
226
  reset!
224
227
  inst = all_nodes.detect {|n| n.instance_id == instance.instance_id }
225
228
  inst.running? if inst
226
229
  end
227
-
230
+
228
231
  def contract_by(num=1)
229
232
  raise RuntimeError, "Contracting instances by #{num} will lower the number of instances below specified minimum" unless nodes.size - num > minimum_instances
230
233
  num.times do |i|
@@ -235,9 +238,9 @@ module CloudProviders
235
238
  end
236
239
  reset!
237
240
  end
238
-
241
+
239
242
  def bootstrap_nodes!(tmp_path=nil)
240
- unless security_groups.map {|a| a.authorizes.map {|t| t.from_port.to_i }.flatten }.flatten.include?(22)
243
+ unless security_groups.map {|a| a.authorizes.map {|t| t.from_port.to_i }.flatten }.flatten.include?(22)
241
244
  warn "Cloud security_groups are not authorized for ssh. Cannot bootstrap."
242
245
  return
243
246
  end
@@ -250,18 +253,18 @@ module CloudProviders
250
253
  node.run_chef!
251
254
  end
252
255
  end
253
-
256
+
254
257
  def configure_nodes!(tmp_path=nil)
255
258
  # removed duplicated code (now configure_nodes! invokes
256
259
  # node.bootstrap_chef!, while old version did not, but I believe
257
260
  # this is harmless)
258
- bootstrap_nodes!(tmp_path)
261
+ bootstrap_nodes!(tmp_path)
259
262
 
260
263
  ebs_volume_groups.each do |vol_grp|
261
264
  vol_grp.verify_attachments nodes
262
265
  end
263
266
  end
264
-
267
+
265
268
  def assign_elastic_ips
266
269
  unless elastic_ips.empty?
267
270
  unused_elastic_ip_addresses = ElasticIp.unused_elastic_ips(self).map {|i| i.public_ip }
@@ -299,11 +302,27 @@ module CloudProviders
299
302
  def nodes
300
303
  all_nodes.select {|i| i.in_service? }#describe_instances.select {|i| i.in_service? && security_groups.include?(i.security_groups) }
301
304
  end
302
-
305
+
306
+ # === Description
307
+ #
308
+ # Return all the security groups of the instance that are prefixed with #poolparty.
309
+ #
310
+ # These are special security groups used only for tagging
311
+ #
312
+ # === Parameters
313
+ # instance - An ec2 instance as returned from describe_instances
314
+ def tags instance
315
+ instance.groupSet.item.collect{|g| g.groupId }.select {|s| s.start_with? "#poolparty"}
316
+ end
317
+
303
318
  def all_nodes
304
- @nodes ||= describe_instances.select {|i| security_group_names.include?(i.security_groups) }.sort {|a,b| DateTime.parse(a.launchTime) <=> DateTime.parse(b.launchTime)}
319
+ @nodes ||= describe_instances.select { |i|
320
+ !(security_group_names & tags(i)).empty?
321
+ }.sort {|a,b|
322
+ DateTime.parse(a.launchTime) <=> DateTime.parse(b.launchTime)
323
+ }
305
324
  end
306
-
325
+
307
326
  # Describe instances
308
327
  # Describe the instances that are available on this cloud
309
328
  # @params id (optional) if present, details about the instance
@@ -318,15 +337,15 @@ module CloudProviders
318
337
  end
319
338
  end.flatten
320
339
  rescue AWS::InvalidClientTokenId => e # AWS credentials invalid
321
- puts "Error contacting AWS: #{e}"
322
- raise e
340
+ puts "Error contacting AWS: #{e}"
341
+ raise e
323
342
  rescue Exception => e
324
343
  []
325
344
  end
326
345
  end
327
-
346
+
328
347
  # Extras!
329
-
348
+
330
349
  def block_device_mapping(o=[], given_name=cloud.proper_name )
331
350
  @mappings ||= o
332
351
  end
@@ -357,10 +376,10 @@ module CloudProviders
357
376
  @ec2 ||= begin
358
377
  AWS::EC2::Base.new( :access_key_id => access_key, :secret_access_key => secret_access_key )
359
378
  rescue AWS::ArgumentError => e # AWS credentials missing?
360
- puts "Error contacting AWS: #{e}"
361
- raise e
379
+ puts "Error contacting AWS: #{e}"
380
+ raise e
362
381
  rescue Exception => e
363
- puts "Generic error #{e.class}: #{e}"
382
+ puts "Generic error #{e.class}: #{e}"
364
383
  end
365
384
  end
366
385
 
@@ -398,7 +417,7 @@ module CloudProviders
398
417
  @ebs_volume_groups ||= []
399
418
  end
400
419
 
401
- # dsl method for EBS volumes. E.G.:
420
+ # dsl method for EBS volumes. E.G.:
402
421
  # ebs_volumes do
403
422
  # volumes "vol-001248ff", "vol-01ff4b85" # use existing volumes, not mandatory
404
423
  # device "/dev/sdf"
@@ -412,7 +431,7 @@ module CloudProviders
412
431
  def assign_ebs_volumes
413
432
  ebs_volume_groups.each{|ebs_volume_group| ebs_volume_group.attach(nodes)}
414
433
  end
415
-
434
+
416
435
  def rds_instances
417
436
  @rds_instances ||= []
418
437
  end
@@ -447,13 +466,13 @@ module CloudProviders
447
466
  # Read credentials from credential_file if one exists
448
467
  def credential_file(file=nil)
449
468
  unless file.nil?
450
- dsl_options[:credential_file]=file
469
+ dsl_options[:credential_file]=file
451
470
  dsl_options.merge!(Ec2.load_keys_from_credential_file(file))
452
471
  else
453
472
  fetch(:credential_file)
454
473
  end
455
474
  end
456
-
475
+
457
476
  private
458
477
  # Helper to get the options with self as parent
459
478
  def sub_opts
@@ -1,6 +1,6 @@
1
1
  module CloudProviders
2
2
  class Ec2Instance < RemoteInstance
3
-
3
+
4
4
  default_options(
5
5
  :security_groups => [],
6
6
  :private_ip => nil,
@@ -15,35 +15,41 @@ module CloudProviders
15
15
  :instance_initiated_shutdown_behavior => nil,
16
16
  :subnet_id => nil
17
17
  )
18
-
18
+
19
19
  def initialize(raw_response={})
20
20
  @raw_response = raw_response
21
- self.instance_id = raw_response["instanceId"] rescue nil
22
- self.security_groups = raw_response.groupSet.item[0].groupId rescue nil
23
- self.image_id = raw_response["imageId"] rescue nil
24
- self.private_ip = raw_response["privateIpAddress"] rescue nil
25
- self.dns_name = raw_response["dnsName"] rescue nil
26
- self.instance_type = raw_response["instanceType"] rescue nil
27
- self.public_ip = raw_response["ipAddress"] rescue nil
28
- self.key_name = raw_response["keyName"] rescue nil
29
- self.launch_time = raw_response["launchTime"] rescue nil
30
- self.availability_zones = raw_response["placement"]["availabilityZone"] rescue nil
31
- self.status = raw_response["instanceState"]["name"] rescue nil
32
- self.block_device_mapping = raw_response["blockDeviceMapping"] rescue nil
33
- self.disable_api_termination = raw_response["disableApiTermination"] rescue nil
34
- self.instance_initiated_shutdown_behavior = raw_response["instance_initiated_shutdown_behavior"] rescue nil
35
- self.subnet_id = raw_response["subnetId"] rescue nil
21
+ self.instance_id = raw_response["instanceId"] rescue nil
22
+ self.security_groups = raw_response.groupSet.item.map{|sg| sg.groupId }.sort rescue nil
23
+ self.image_id = raw_response["imageId"] rescue nil
24
+ self.private_ip = raw_response["privateIpAddress"] rescue nil
25
+ self.dns_name = raw_response["dnsName"] rescue nil
26
+ self.instance_type = raw_response["instanceType"] rescue nil
27
+ self.public_ip = raw_response["ipAddress"] rescue nil
28
+ self.key_name = raw_response["keyName"] rescue nil
29
+ self.launch_time = raw_response["launchTime"] rescue nil
30
+ self.availability_zones = raw_response["placement"]["availabilityZone"] rescue nil
31
+ self.status = raw_response["instanceState"]["name"] rescue nil
32
+ self.block_device_mapping = raw_response["blockDeviceMapping"] rescue nil
33
+ self.subnet_id = raw_response["subnetId"] rescue nil
34
+ # disable_api_termination and instance_initiated_shutdown_behavior don't currently get returned in the request -- you'd need to later call describe_instance_attribute
35
+ self.disable_api_termination = raw_response["disableApiTermination"] rescue nil
36
+ self.instance_initiated_shutdown_behavior = raw_response["instanceInitiatedShutdownBehavior"] rescue nil
36
37
  super
37
38
  end
38
-
39
+
39
40
  def keypair(n=nil)
40
- @keypair ||= Keypair.new(self.key_name)
41
+ return @keypair if @keypair
42
+ @keypair = (cloud.keypair.basename == self.key_name) ? cloud.keypair : Keypair.new(self.key_name, cloud.keypair.extra_paths)
41
43
  end
42
-
44
+
45
+ def security_group_names
46
+ security_groups.map{|a| a.to_s }
47
+ end
48
+
43
49
  def zone
44
50
  availability_zones.first
45
51
  end
46
-
52
+
47
53
  def reachable?
48
54
  ping_port self.public_ip, 22
49
55
  end
@@ -56,38 +62,42 @@ module CloudProviders
56
62
  in_service? and
57
63
  keypair and keypair.exists?
58
64
  end
59
-
65
+
60
66
  def in_service?
61
67
  running?
62
68
  end
63
-
69
+
64
70
  def run!
65
- r = cloud_provider.ec2.run_instances(:image_id => image_id,
66
- :min_count => min_count,
67
- :max_count => max_count,
68
- :key_name => keypair.basename,
69
- :security_group => cloud.security_group_names,
70
- :user_data => user_data,
71
- :instance_type => instance_type,
72
- :availability_zone => availability_zone,
73
- :block_device_mapping => block_device_mapping,
74
- :base64_encoded => true)
71
+ r = cloud_provider.ec2.run_instances(
72
+ :image_id => image_id,
73
+ :min_count => min_count,
74
+ :max_count => max_count,
75
+ :key_name => keypair.basename,
76
+ :security_group => cloud.security_group_names,
77
+ :user_data => user_data,
78
+ :instance_type => instance_type,
79
+ :availability_zone => availability_zone,
80
+ :block_device_mapping => block_device_mapping,
81
+ :disable_api_termination => disable_api_termination,
82
+ :instance_initiated_shutdown_behavior => instance_initiated_shutdown_behavior,
83
+ :base64_encoded => true)
75
84
  r.instancesSet.item.map do |i|
76
85
  inst_options = i.merge(r.merge(:cloud => cloud)).merge(cloud.cloud_provider.dsl_options)
77
86
  Ec2Instance.new(inst_options)
78
87
  end.first
79
88
  end
80
89
  def self.run!(hsh); new(hsh).run!; end
81
-
90
+
82
91
  def terminate!
83
92
  cloud_provider.ec2.terminate_instances(:instance_id => [self.instance_id])
84
93
  cloud_provider.reset!
85
94
  end
86
95
  def self.terminate!(hsh={}); new(hsh).terminate!; end
87
-
96
+
88
97
  # list of directories and files to exclude when bundling an instance
89
98
  def rsync_excludes(array_of_abs_paths_to_exclude=nil)
90
- array_of_abs_paths_to_exclude ||= %w( /sys
99
+ array_of_abs_paths_to_exclude ||= %w[
100
+ /sys
91
101
  /proc
92
102
  /dev/pts
93
103
  /dev
@@ -99,8 +109,8 @@ module CloudProviders
99
109
  /etc/ssh/moduli
100
110
  /etc/udev/rules.d/70-persistent-net.rules
101
111
  /etc/udev/rules.d/z25_persistent-net.rules
102
- )
103
- array_of_abs_paths_to_exclude.inject(''){|str, path| str<<"--exclude=#{path}"; str}
112
+ ]
113
+ array_of_abs_paths_to_exclude.inject(''){|str, path| str << "--exclude=#{path}" ; str}
104
114
  end
105
115
 
106
116
  # create an image file and copy this instance to the image file.
@@ -138,7 +148,7 @@ module CloudProviders
138
148
  scp ec2cert, "/mnt/bundle/"
139
149
  scp cert, "/mnt/bundle/"
140
150
  arch = self[:instanceType].match(/m1\.small|c1\.medium/) ? 'i386' : 'x86_64'
141
- image = img ? img : make_image(opts)
151
+ image = img ? img : make_image(opts)
142
152
  ssh "ec2-bundle-image #{image} -d /mnt/bundle -u #{self[:ownerId]} -k /mnt/bundle/pk-*.pem -c /tmp/cert-*.pem"
143
153
  manifest = "/mnt/bundle/#{opts[:prefix]}.manifest.xml"
144
154
  ssh "ec2-upload-bundle -a #{access_key} -s #{secret_access_key} -m #{manifest}"
@@ -146,6 +156,6 @@ module CloudProviders
146
156
  ami = ami_str.grep(/ami-\w*/).first
147
157
  return ami
148
158
  end
149
-
159
+
150
160
  end
151
161
  end