awsborn 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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