awsborn 0.6.0 → 0.7.0

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/README.mdown CHANGED
@@ -182,6 +182,11 @@ Awsborn integrates with [Chef Solo](http://github.com/opscode/chef). An example
182
182
  server :log_b, :zone => :eu_west_1b, :disk => {:sdf => "vol-aa57b8c3"}, :ip => 'log-b'
183
183
  end
184
184
 
185
+ cluster :test do
186
+ domain 'releware.net'
187
+ server :test, :zone => :eu_west_1b, :disk => {:sdf => "vol-abababab"}, :ip => 'log-test'
188
+ end
189
+
185
190
  def chef_dna
186
191
  {
187
192
  :user => "lumberjack",
@@ -232,6 +237,7 @@ Other rake tasks include:
232
237
  * `rake chef` - Run chef on all servers, or the ones specified with `host=name1,name2`.
233
238
  * `rake chef:debug` - Ditto, but with chef's log level set to `debug`.
234
239
  * `rake start` - Start all servers (or host=name1,name2) but don't run `chef`.
240
+ * `rake start cluster=test` - Start the servers in the named cluster. Default is the first unnamed cluster.
235
241
 
236
242
 
237
243
  ## Bugs and surprising features
@@ -271,6 +277,25 @@ Other rake tasks include:
271
277
 
272
278
  ## Changes
273
279
 
280
+ ### New in 0.7.0
281
+
282
+ * Allow several security groups per server.
283
+ * Specify `individual_security_group true` to add a unique-ish security group named _`ServerClass servername`_.
284
+ This security group is created on the fly if it doesn't exist.
285
+ * Server clusters can have names.
286
+
287
+ ### New in 0.6.0
288
+
289
+ * Uses `right_aws` version 2.
290
+
291
+ ### New in 0.5.4, 0.5.5
292
+
293
+ * Run `rake list` to see a list of running servers.
294
+
295
+ ### New in 0.5.2, 0.5.3
296
+
297
+ * Run `rake upgrade_chef host=foo` to upgrade chef on server `foo`.
298
+
274
299
  ### New in 0.5.1
275
300
 
276
301
  * When awsborn uploads cookbooks and configuration, it looks for a `cookbooks`
data/Rakefile CHANGED
@@ -13,8 +13,8 @@ begin
13
13
  gem.add_dependency "right_aws", ">= 2.1.0"
14
14
  gem.add_dependency "json_pure", ">= 1.2.3"
15
15
  gem.add_dependency "rake"
16
- gem.add_development_dependency "rspec", ">= 1.2.9"
17
- gem.add_development_dependency "webmock", ">= 0.9.1"
16
+ gem.add_development_dependency "rspec", ">= 2.6.0"
17
+ gem.add_development_dependency "webmock", ">= 1.3.0"
18
18
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
19
  end
20
20
  Jeweler::GemcutterTasks.new
@@ -22,16 +22,14 @@ rescue LoadError
22
22
  puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
23
23
  end
24
24
 
25
- require 'spec/rake/spectask'
26
- Spec::Rake::SpecTask.new(:spec) do |spec|
27
- spec.libs << 'lib' << 'spec'
28
- spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ require 'rspec/core/rake_task'
26
+ RSpec::Core::RakeTask.new do |t|
27
+ t.rspec_opts = ["-c", "-f progress", "-r ./spec/spec_helper.rb"]
28
+ t.pattern = 'spec/**/*_spec.rb'
29
29
  end
30
30
 
31
- Spec::Rake::SpecTask.new(:rcov) do |spec|
32
- spec.libs << 'lib' << 'spec'
33
- spec.pattern = 'spec/**/*_spec.rb'
34
- spec.rcov = true
31
+ RSpec::Core::RakeTask.new(:rcov) do |t|
32
+ t.rcov_opts = %q[--exclude "spec"]
35
33
  end
36
34
 
37
35
  task :spec => :check_dependencies
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.0
1
+ 0.7.0
data/lib/awsborn/ec2.rb CHANGED
@@ -2,9 +2,16 @@ module Awsborn
2
2
  class Ec2
3
3
  extend Forwardable
4
4
  def_delegators :Awsborn, :logger
5
-
5
+
6
6
  attr_accessor :instance_id
7
7
 
8
+ class << self
9
+ def endpoint_for_zone (zone)
10
+ zone = zone.to_s.sub(/[a-z]$/,'').tr('_','-')
11
+ "https://#{zone}.ec2.amazonaws.com"
12
+ end
13
+ end
14
+
8
15
  def connection
9
16
  unless @connection
10
17
  env_ec2_url = ENV['EC2_URL']
@@ -18,21 +25,14 @@ module Awsborn
18
25
  end
19
26
  end
20
27
  @connection
21
- end
22
-
28
+ end
29
+
23
30
  def initialize (zone)
24
- @endpoint = case zone
25
- when :eu_west_1a, :eu_west_1b
26
- 'https://eu-west-1.ec2.amazonaws.com/'
27
- when :us_east_1a, :us_east_1b
28
- 'https://us-east-1.ec2.amazonaws.com'
29
- else
30
- 'https://ec2.amazonaws.com'
31
- end
31
+ @endpoint = self.class.endpoint_for_zone(zone)
32
32
  end
33
33
 
34
34
  KeyPair = Struct.new :name, :path
35
-
35
+
36
36
  def generate_key_pair
37
37
  time = Time.now
38
38
  key_name = "temp_key_#{time.to_i}_#{time.usec}"
@@ -71,12 +71,12 @@ module Awsborn
71
71
  self.instance_id = response[:aws_instance_id]
72
72
  response
73
73
  end
74
-
74
+
75
75
  def get_console_output
76
76
  output = connection.get_console_output(instance_id)
77
77
  output[:aws_output]
78
78
  end
79
-
79
+
80
80
  def attach_volume (volume, device)
81
81
  connection.attach_volume(volume, instance_id, device)
82
82
  end
@@ -84,7 +84,7 @@ module Awsborn
84
84
  def monitoring?
85
85
  %w[enabled pending].include?(describe_instance[:monitoring_state])
86
86
  end
87
-
87
+
88
88
  def monitor
89
89
  logger.debug "Activating monitoring for #{instance_id}"
90
90
  connection.monitor_instances(instance_id)
@@ -95,5 +95,11 @@ module Awsborn
95
95
  connection.unmonitor_instances(instance_id)
96
96
  end
97
97
 
98
+ def create_security_group_if_missing (group_name, description = "Created by Awsborn")
99
+ unless connection.describe_security_groups.detect { |group| group[:aws_group_name] == group_name }
100
+ connection.create_security_group(group_name, description)
101
+ end
102
+ end
103
+
98
104
  end
99
105
  end
data/lib/awsborn/rake.rb CHANGED
@@ -17,11 +17,6 @@ module Awsborn
17
17
  #
18
18
  module Rake
19
19
 
20
- def default_cluster #:nodoc:
21
- default_klass = Awsborn::Server.children.first
22
- default_klass.clusters.first
23
- end
24
-
25
20
  desc "Default: Start all servers (if needed) and deploy with chef."
26
21
  task :all => [:start, "chef:run"]
27
22
  task :default => :all
@@ -31,13 +26,13 @@ module Awsborn
31
26
 
32
27
  desc "Start all servers (or host=name1,name2) but don't run chef."
33
28
  task :start do |t,args|
34
- default_cluster.launch get_hosts(args)
29
+ cluster(args).launch get_hosts(args)
35
30
  end
36
31
 
37
32
  desc "Update .ssh/known_hosts with data from all servers (or host=host1,host2)"
38
33
  task :update_known_hosts do |t,args|
39
34
  hosts = get_hosts(args)
40
- default_cluster.each do |server|
35
+ cluster(args).each do |server|
41
36
  server.running? && server.update_known_hosts if hosts.nil? || hosts.include?(server.name.to_s)
42
37
  end
43
38
  end
@@ -48,7 +43,7 @@ module Awsborn
48
43
  namespace :chef do
49
44
  task :run => [:check_syntax] do |t,args|
50
45
  hosts = get_hosts(args)
51
- default_cluster.each do |server|
46
+ cluster(args).each do |server|
52
47
  next unless hosts.nil? || hosts.include?(server.name.to_s)
53
48
  puts framed("Running chef on '#{server.name}'")
54
49
  server.cook
@@ -79,8 +74,8 @@ module Awsborn
79
74
  end
80
75
 
81
76
  desc "List running servers"
82
- task :list do
83
- running = default_cluster.select { |server| server.running? }
77
+ task :list do |t,args|
78
+ running = cluster(args).select { |server| server.running? }
84
79
  max_name_length = running.map { |server| server.name.to_s.size }.max
85
80
  running.each do |server|
86
81
  h = server.describe_instance
@@ -92,7 +87,7 @@ module Awsborn
92
87
  desc "Update chef on the server"
93
88
  task :update_chef do |t,args|
94
89
  hosts = get_hosts(args)
95
- default_cluster.each do |server|
90
+ cluster(args).each do |server|
96
91
  next if hosts && ! hosts.include?(server.name.to_s)
97
92
  puts framed("Updating chef on server #{server.name}")
98
93
  # Include excplicit path to avoid rvm
@@ -129,6 +124,12 @@ EOH
129
124
  def framed (message) #:nodoc:
130
125
  '*' * (4 + message.length) + "\n* #{message} *\n" + '*' * (4 + message.length)
131
126
  end
127
+
128
+ def cluster (args) #:nodoc:
129
+ name = args[:c] || args[:cluster] || 'cluster 1'
130
+ Awsborn::ServerCluster.clusters.detect { |c| c.name == name } || raise("Could not find cluster named '#{name}'")
131
+ end
132
+
132
133
  end
133
134
  end
134
135
  end
@@ -8,11 +8,8 @@ module Awsborn
8
8
  end
9
9
 
10
10
  class << self
11
- attr_accessor :logger, :children, :clusters
12
- def inherited (klass)
13
- @children ||= []
14
- @children << klass
15
- end
11
+ attr_accessor :logger
12
+
16
13
  # Set image_id. Examples:
17
14
  # image_id 'ami-123123'
18
15
  # image_id 'ami-123123', :sudo_user => 'ubuntu'
@@ -29,9 +26,13 @@ module Awsborn
29
26
  @instance_type
30
27
  end
31
28
  def security_group (*args)
32
- @security_group = args.first unless args.empty?
29
+ @security_group = args unless args.empty?
33
30
  @security_group
34
31
  end
32
+ def individual_security_group (*args)
33
+ @individual_security_group = args.first unless args.empty?
34
+ @individual_security_group
35
+ end
35
36
  def keys (*args)
36
37
  @keys = args unless args.empty?
37
38
  @keys
@@ -49,10 +50,8 @@ module Awsborn
49
50
  @monitor
50
51
  end
51
52
 
52
- def cluster (&block)
53
- @clusters ||= []
54
- @clusters << ServerCluster.build(self, &block)
55
- @clusters.last
53
+ def cluster (name = ServerCluster.next_name, &block)
54
+ ServerCluster.build(self, name, &block)
56
55
  end
57
56
  def logger
58
57
  @logger ||= Awsborn.logger
@@ -171,6 +170,7 @@ module Awsborn
171
170
  end
172
171
 
173
172
  def cook
173
+ raise "#{host_name} not running" unless running?
174
174
  upload_cookbooks
175
175
  run_chef
176
176
  end
@@ -185,7 +185,7 @@ module Awsborn
185
185
  File.open("config/dna.json", "w") { |f| f.write(chef_dna.to_json) }
186
186
  system "rsync -rL --delete --exclude '.*' ./ root@#{host_name}:#{Awsborn.remote_chef_path}"
187
187
  ensure
188
- File.delete("config/dna.json")
188
+ FileUtils.rm_f("config/dna.json")
189
189
  File.delete("cookbooks") if temp_link
190
190
  end
191
191
 
@@ -202,7 +202,7 @@ module Awsborn
202
202
  begin :accessors
203
203
  attr_accessor :name, :logger
204
204
  def host_name= (string)
205
- logger.debug "Setting host_name of #{name} to #{string}"
205
+ logger.debug "Setting host_name of #{name} to #{string.inspect}"
206
206
  @host_name = string
207
207
  end
208
208
  def host_name
@@ -244,7 +244,15 @@ module Awsborn
244
244
  @options[:instance_type] || self.class.instance_type
245
245
  end
246
246
  def security_group
247
- @options[:security_group] || self.class.security_group
247
+ groups = @options[:security_group] || self.class.security_group || []
248
+ groups.each { |group_name| ec2.create_security_group_if_missing(group_name) }
249
+ if self.class.individual_security_group
250
+ group_name = "#{self.class.name} #{name}"
251
+ ec2.create_security_group_if_missing(group_name, "#{group_name} private security group")
252
+ groups + [group_name]
253
+ else
254
+ groups
255
+ end
248
256
  end
249
257
  def sudo_user
250
258
  @options[:sudo_user] || self.class.sudo_user
@@ -283,6 +291,9 @@ module Awsborn
283
291
  def describe_instance
284
292
  @describe_instance ||= ec2.describe_instance
285
293
  end
294
+ def cluster_name
295
+ ServerCluster.cluster_for(self).name
296
+ end
286
297
  end
287
298
 
288
299
  AVAILABILITY_ZONES = %w[
@@ -290,6 +301,7 @@ module Awsborn
290
301
  us-west-1a us-west-1b
291
302
  eu-west-1a eu-west-1b
292
303
  ap-southeast-1a ap-southeast-1b
304
+ ap-northeast-1a ap-northeast-1b
293
305
  ]
294
306
  INSTANCE_TYPES_32_BIT = %w[m1.small c1.medium t1.micro]
295
307
  INSTANCE_TYPES_64_BIT = %w[m1.large m1.xlarge m2.xlarge m2.2xlarge m2.4xlarge c1.xlarge cc1.4xlarge t1.micro]
@@ -2,15 +2,39 @@ module Awsborn
2
2
  class ServerCluster
3
3
  include Enumerable
4
4
 
5
- def self.build (klass, &block)
6
- cluster = new(klass)
5
+ attr_accessor :name
6
+
7
+ def self.build (klass, name, &block)
8
+ cluster = new(klass, name)
7
9
  block.bind(cluster, 'cluster').call
8
10
  cluster
9
11
  end
10
12
 
11
- def initialize (klass)
13
+ def self.clusters
14
+ @clusters ||= []
15
+ end
16
+
17
+ def self.cluster_for (instance)
18
+ clusters.each do |cluster|
19
+ return cluster if cluster.detect { |i| i == instance }
20
+ end
21
+ end
22
+
23
+ def self.next_name
24
+ @next_name_counter ||= 1
25
+ old_names = clusters.map { |c| c.name }
26
+ begin
27
+ next_name = "cluster #{@next_name_counter}"
28
+ @next_name_counter += 1
29
+ end while old_names.include?(next_name)
30
+ next_name
31
+ end
32
+
33
+ def initialize (klass, name)
12
34
  @klass = klass
35
+ @name = name.to_s
13
36
  @instances = []
37
+ self.class.clusters << self
14
38
  end
15
39
 
16
40
  def domain (*args)
data/spec/ec2_spec.rb ADDED
@@ -0,0 +1,12 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Awsborn::Ec2 do
4
+ context ".endpoint_for_zone" do
5
+ it "should have endpoints for zones in five regions" do
6
+ Awsborn::Ec2.endpoint_for_zone(:eu_west_1a).should == 'https://eu-west-1.ec2.amazonaws.com'
7
+ Awsborn::Ec2.endpoint_for_zone("eu_west_1b").should == 'https://eu-west-1.ec2.amazonaws.com'
8
+ Awsborn::Ec2.endpoint_for_zone(:us_west_1b).should == 'https://us-west-1.ec2.amazonaws.com'
9
+ Awsborn::Ec2.endpoint_for_zone(:us_east_1b).should == 'https://us-east-1.ec2.amazonaws.com'
10
+ end
11
+ end
12
+ end
data/spec/server_spec.rb CHANGED
@@ -13,6 +13,7 @@ end
13
13
 
14
14
  describe Awsborn::Server do
15
15
  before(:each) do
16
+ Awsborn.verbose = false
16
17
  @server = SampleServer.new :sample, :zone => :eu_west_1a, :disk => {:sdf => "vol-aaaaaaaa"}
17
18
  end
18
19
 
@@ -54,4 +55,33 @@ describe Awsborn::Server do
54
55
  end
55
56
 
56
57
  end
58
+
59
+ context ".security_group" do
60
+ class SecureServer < Awsborn::Server
61
+ instance_type :m1_small
62
+ image_id 'ami-2fc2e95b'
63
+ security_group 'Common', 'Other'
64
+ individual_security_group true
65
+ end
66
+
67
+ before do
68
+ @server = SecureServer.new :sample, :zone => :eu_west_1a, :disk => {:sdf => "vol-aaaaaaaa"}
69
+ end
70
+ it "allows multiple security groups" do
71
+ key_pair = mock(:key_pair)
72
+ key_pair.should_receive(:name).and_return('fake')
73
+ ec2 = mock(:ec2)
74
+ ec2.should_receive(:instance_id).and_return('i-asdf')
75
+ ec2.should_receive(:launch_instance).with do |image_id, options|
76
+ image_id.should == 'ami-2fc2e95b'
77
+ options[:group_ids].should == ['Common', 'Other', 'SecureServer sample']
78
+ end
79
+ ec2.should_receive(:create_security_group_if_missing).exactly(3).times
80
+ @server.stub(:ec2).and_return(ec2)
81
+ @server.should_receive(:instance_running?).and_return(true)
82
+ @server.should_receive(:aws_dns_name).and_return('asdf')
83
+ @server.launch_instance(key_pair)
84
+ end
85
+ end
86
+
57
87
  end
data/spec/spec_helper.rb CHANGED
@@ -1,14 +1,9 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__))
2
2
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
3
  require 'awsborn'
4
- require 'spec'
5
- require 'spec/autorun'
4
+ require 'rspec'
6
5
 
7
6
  require 'rubygems'
8
7
  require 'webmock/rspec'
9
- include WebMock
8
+ include WebMock::API
10
9
  WebMock.disable_net_connect!
11
-
12
- Spec::Runner.configure do |config|
13
-
14
- end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: awsborn
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
4
+ hash: 3
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 6
8
+ - 7
9
9
  - 0
10
- version: 0.6.0
10
+ version: 0.7.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - David Vrensk
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-05-18 00:00:00 +02:00
18
+ date: 2011-06-15 00:00:00 +02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -72,12 +72,12 @@ dependencies:
72
72
  requirements:
73
73
  - - ">="
74
74
  - !ruby/object:Gem::Version
75
- hash: 13
75
+ hash: 23
76
76
  segments:
77
- - 1
78
77
  - 2
79
- - 9
80
- version: 1.2.9
78
+ - 6
79
+ - 0
80
+ version: 2.6.0
81
81
  type: :development
82
82
  version_requirements: *id004
83
83
  - !ruby/object:Gem::Dependency
@@ -88,12 +88,12 @@ dependencies:
88
88
  requirements:
89
89
  - - ">="
90
90
  - !ruby/object:Gem::Version
91
- hash: 57
91
+ hash: 27
92
92
  segments:
93
- - 0
94
- - 9
95
93
  - 1
96
- version: 0.9.1
94
+ - 3
95
+ - 0
96
+ version: 1.3.0
97
97
  type: :development
98
98
  version_requirements: *id005
99
99
  description: Awsborn lets you define and launch a server cluster on Amazon EC2.
@@ -125,6 +125,7 @@ files:
125
125
  - lib/awsborn/rake.rb
126
126
  - lib/awsborn/server.rb
127
127
  - lib/awsborn/server_cluster.rb
128
+ - spec/ec2_spec.rb
128
129
  - spec/server_spec.rb
129
130
  - spec/spec.opts
130
131
  - spec/spec_helper.rb
@@ -163,5 +164,6 @@ signing_key:
163
164
  specification_version: 3
164
165
  summary: Awsborn defines servers as instances with a certain disk volume, which makes it easy to restart missing servers.
165
166
  test_files:
167
+ - spec/ec2_spec.rb
166
168
  - spec/server_spec.rb
167
169
  - spec/spec_helper.rb