blimpy 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -15,3 +15,6 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .blimpy.d
19
+ .rvmrc
20
+ Blimpyfile
data/Rakefile CHANGED
@@ -1,2 +1,30 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+ require 'cucumber/rake/task'
5
+
6
+ RSpec::Core::RakeTask.new('spec') do |t|
7
+ t.rspec_opts = '--color --fail-fast'
8
+ end
9
+
10
+
11
+ namespace :cucumber do
12
+ cucumber_opts = '--color --format progress --tags ~@wip'
13
+
14
+ Cucumber::Rake::Task.new('basic') do |t|
15
+ t.cucumber_opts = cucumber_opts + ' --tags ~@slow'
16
+ end
17
+
18
+ Cucumber::Rake::Task.new('integration') do |t|
19
+ t.cucumber_opts = cucumber_opts + ' --tags @slow'
20
+ end
21
+ end
22
+
23
+ desc 'Run the basic test suite'
24
+ task :test => [:spec, :"cucumber:basic"]
25
+
26
+ namespace :test do
27
+ desc 'Run all the tests, including the slow integration tests'
28
+ task :all => [:spec, :'cucumber:basic', :'cucumber:integration']
29
+ end
30
+
data/bin/blimpy CHANGED
@@ -1,88 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'rubygems'
4
- require 'thor'
5
-
6
3
  begin
7
- require 'blimpy'
4
+ require 'blimpy/cli'
8
5
  rescue LoadError
9
6
  $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib/')
10
- require 'blimpy'
11
- end
12
-
13
- module Blimpy
14
- class CLI < Thor
15
- BLIMPFILE = File.join(Dir.pwd, 'Blimpfile')
16
-
17
- no_tasks do
18
- def ensure_blimpfile
19
- unless File.exists? Blimpy::CLI::BLIMPFILE
20
- puts 'Please create a Blimpfile in your current directory'
21
- exit 1
22
- end
23
- end
24
- end
25
-
26
- desc 'start', 'Start up a fleet of blimps'
27
- method_options :"dry-run" => :boolean
28
- def start
29
- ensure_blimpfile
30
- engine = Blimpy::Engine.new
31
- engine.load_file(File.open(BLIMPFILE).read)
32
- puts 'Up, up and away!'
33
-
34
- if options[:'dry-run']
35
- puts 'skipping actually starting the fleet'
36
- exit 0
37
- end
38
-
39
- engine.fleet.start
40
- end
41
-
42
- desc 'list', 'List running blimps'
43
- def list
44
- ensure_blimpfile
45
- blimps = Dir["#{Dir.pwd}/.blimpy.d/*.blimp"]
46
- if blimps.empty?
47
- puts 'No currently running VMs'
48
- exit 0
49
- end
50
-
51
- blimps.each do |blimp|
52
- data = YAML.load_file(blimp)
53
- instance_id = File.basename(blimp)
54
- instance_id = instance_id.split('.blimp').first
55
- puts "#{data['name']} (#{instance_id}) is: online at #{data['dns']}"
56
- end
57
- end
58
-
59
- desc 'destroy', 'Destroy all running blimps'
60
- def destroy
61
- ensure_blimpfile
62
- fleet = Blimpy::Fleet.new
63
- fleet.destroy
64
- end
65
-
66
-
67
- desc 'init', 'Create a skeleton Blimpfile in the current directory'
68
- def init
69
- File.open(File.join(Dir.pwd, 'Blimpfile'), 'w') do |f|
70
- f.write(
71
- """# vim: ft=ruby
72
- # Blimpfile created on #{Time.now}
73
-
74
- Blimpy.fleet do |fleet|
75
- fleet.add do |ship|
76
- ship.name = 'Excelsior'
77
- ship.group = 'default'
78
- end
79
- end
80
- """)
81
- end
82
- end
83
- end
7
+ require 'blimpy/cli'
84
8
  end
85
9
 
86
10
  Blimpy::CLI.start
87
-
88
11
  exit 0
@@ -0,0 +1,39 @@
1
+ Feature: SSH into a named VM
2
+ In order to directly access running VMs
3
+ As a Blimpy user
4
+ I should be able to run `blimpy ssh <name>` and be logged in
5
+
6
+ Scenario: SSHing with an invalid name
7
+ Given I have the Blimpfile:
8
+ """
9
+ Blimpy.fleet do |f|
10
+ f.add do |host|
11
+ host.name = 'Cucumber Host'
12
+ end
13
+ end
14
+ """
15
+ When I run `blimpy ssh Gherkins`
16
+ Then the exit status should be 1
17
+ And the output should contain:
18
+ """
19
+ Could not find blimp named "Gherkins"
20
+ """
21
+
22
+
23
+ # This test is really frustrating and I can't get aruba and the ssh code
24
+ # to work together here :-/
25
+ @slow @destroy @wip
26
+ Scenario: SSHing into a remote host should work
27
+ Given I have the Blimpfile:
28
+ """
29
+ Blimpy.fleet do |f|
30
+ f.add do |host|
31
+ host.group = 'Simple'
32
+ host.name = 'Cucumber Host'
33
+ end
34
+ end
35
+ """
36
+ When I ssh into the machine
37
+ And I type "hostname -f"
38
+ And I type "exit"
39
+ Then the output should contain the right DNS info
@@ -9,19 +9,41 @@ Feature: Start a VM or cluster of VMs in the cloud
9
9
  """
10
10
  And the exit status should be 1
11
11
 
12
- Scenario: With a simple Blimpfile
13
- Given I have the Blimpfile:
14
- """
15
- Blimpy.fleet do |f|
16
- f.add do |host|
17
- host.group = 'Simple'
18
- host.name = 'Cucumber Host'
19
- end
12
+ Scenario: dry-run start with a simple Blimpfile
13
+ Given I have the Blimpfile:
14
+ """
15
+ Blimpy.fleet do |f|
16
+ f.add do |host|
17
+ host.group = 'Simple'
18
+ host.name = 'Cucumber Host'
19
+ end
20
+ end
21
+ """
22
+ When I run `blimpy start --dry-run`
23
+ Then the exit status should be 0
24
+ And the output should contain:
25
+ """
26
+ Up, up and away!
27
+ """
28
+
29
+ @slow @destroy
30
+ Scenario: start with a functional Blimpfile
31
+ Given I have the Blimpfile:
32
+ """
33
+ Blimpy.fleet do |f|
34
+ f.add do |host|
35
+ host.group = 'Simple'
36
+ host.name = 'Cucumber Host'
20
37
  end
21
- """
22
- When I run `blimpy start --dry-run`
23
- Then the exit status should be 0
24
- And the output should contain:
25
- """
26
- Up, up and away!
27
- """
38
+ end
39
+ """
40
+ When I run `blimpy start`
41
+ Then the exit status should be 0
42
+ And the output should contain:
43
+ """
44
+ Up, up and away!
45
+ """
46
+ And the output should contain:
47
+ """
48
+ online at:
49
+ """
@@ -26,7 +26,23 @@ Given /^I have a single VM running$/ do
26
26
  end
27
27
  end
28
28
 
29
+ When /^I ssh into the machine$/ do
30
+ step %{I run `blimpy start`}
31
+ step %{I run `blimpy ssh "Cucumber Host" -o StrictHostKeyChecking=no` interactively}
32
+ end
33
+
29
34
  Then /^the output should list the VM$/ do
30
35
  expected = 'Cucumber host (0xdeadbeef) is: online at foo.bar'
31
36
  assert_partial_output(expected, all_output)
32
37
  end
38
+
39
+ Then /^the output should contain the right DNS info$/ do
40
+ terminate_processes!
41
+ internal_name = nil
42
+ Dir["#{@tempdir}/.blimpy.d/*.blimp"].each do |filename|
43
+ data = YAML.load_file(filename)
44
+ internal_name = data['internal_dns']
45
+ break unless internal_name.nil?
46
+ end
47
+ step %{the output should contain "#{internal_name}"}
48
+ end
@@ -6,6 +6,16 @@ Before do
6
6
  @dirs = [@tempdir]
7
7
  end
8
8
 
9
+ Before '@slow' do
10
+ @aruba_timeout_seconds = 90
11
+ end
12
+
13
+ After '@destroy' do |scenario|
14
+ Dir.chdir(@tempdir) do
15
+ `blimpy destroy`
16
+ end
17
+ end
18
+
9
19
  After do |scenario|
10
20
  Dir.chdir(@cwd)
11
21
 
@@ -19,4 +19,6 @@ module Blimpy
19
19
  end
20
20
  class BoxValidationError < Exception
21
21
  end
22
+ class SSHKeyNotFoundError < Exception
23
+ end
22
24
  end
@@ -1,24 +1,39 @@
1
+ require 'blimpy/helpers/state'
2
+ require 'blimpy/keys'
1
3
 
2
4
  module Blimpy
3
5
  class Box
4
- attr_reader :allowed_regions, :region, :server
5
- attr_accessor :image_id, :livery, :group, :name, :tags, :fleet_id
6
+ include Blimpy::Helpers::State
6
7
 
7
- def self.from_instance_id(an_id)
8
- server = Fog::Compute[:aws].servers.get(an_id)
8
+ # Default to US West (Oregon)
9
+ DEFAULT_REGION = 'us-west-2'
10
+ # Default to 10.04 64-bit
11
+ DEFAULT_IMAGE_ID = 'ami-ec0b86dc'
12
+
13
+ attr_reader :allowed_regions, :region
14
+ attr_accessor :image_id, :livery, :group
15
+ attr_accessor :name, :tags, :fleet_id, :username
16
+
17
+ def self.from_instance_id(an_id, data)
18
+ region = data['region'] || DEFAULT_REGION
19
+ fog = Fog::Compute.new(:provider => 'AWS', :region => region)
20
+ server = fog.servers.get(an_id)
9
21
  if server.nil?
10
22
  return nil
11
23
  end
12
- self.new(server)
24
+ box = self.new(server)
25
+ box.name = data['name']
26
+ box
13
27
  end
14
28
 
15
29
  def initialize(server=nil)
16
30
  @allowed_regions = ['us-west-1', 'us-west-2', 'us-east-1']
17
- @region = 'us-west-2' # Default to US West (Oregon) for now
18
- @image_id = 'ami-349b495d' # Default to Ubuntu 10.04 LTS (64bit)
31
+ @region = DEFAULT_REGION
32
+ @image_id = DEFAULT_IMAGE_ID
19
33
  @livery = nil
20
34
  @group = nil
21
35
  @name = 'Unnamed Box'
36
+ @username = 'ubuntu'
22
37
  @tags = {}
23
38
  @server = server
24
39
  @fleet_id = 0
@@ -38,18 +53,16 @@ module Blimpy
38
53
  end
39
54
 
40
55
  def online!
41
- File.open(File.join(state_dir, state_file), 'a') do |f|
56
+ File.open(state_file, 'a') do |f|
42
57
  f.write("dns: #{@server.dns_name}\n")
58
+ f.write("internal_dns: #{@server.private_dns_name}\n")
43
59
  end
44
60
  end
45
61
 
46
62
  def start
47
- ensure_state_dir
63
+ ensure_state_folder
48
64
  @server = create_host
49
-
50
- File.open(File.join(state_dir, state_file), 'w') do |f|
51
- f.write("name: #{@name}\n")
52
- end
65
+ write_state_file
53
66
  end
54
67
 
55
68
  def stop
@@ -58,35 +71,83 @@ module Blimpy
58
71
  end
59
72
  end
60
73
 
74
+ def resume
75
+ unless @server.nil?
76
+ @server.start
77
+ end
78
+ end
79
+
61
80
  def destroy
62
81
  unless @server.nil?
63
82
  @server.destroy
64
- File.unlink(File.join(state_dir, state_file))
83
+ File.unlink(state_file)
65
84
  end
66
85
  end
67
86
 
68
- def state_dir
69
- File.join(Dir.pwd, '.blimpy.d')
87
+ def write_state_file
88
+ File.open(state_file, 'w') do |f|
89
+ f.write("name: #{@name}\n")
90
+ f.write("region: #{@region}\n")
91
+ end
70
92
  end
71
93
 
94
+
72
95
  def state_file
73
96
  if @server.nil?
74
97
  raise Exception, "I can't make a state file without a @server!"
75
98
  end
76
- "#{@server.id}.blimp"
99
+ File.join(state_folder, "#{@server.id}.blimp")
77
100
  end
78
101
 
79
- def ensure_state_dir
80
- unless File.exist? state_dir
81
- Dir.mkdir(state_dir)
102
+ def wait_for_state(until_state, &block)
103
+ if @server.nil?
104
+ return
105
+ end
106
+
107
+ @server.wait_for do
108
+ block.call
109
+ state == until_state
110
+ end
111
+ end
112
+
113
+ def dns_name
114
+ return @server.dns_name unless @server.nil?
115
+ 'no name'
116
+ end
117
+
118
+ def internal_dns_name
119
+ return @server.private_dns_name unless @server.nil?
120
+ 'no name'
121
+ end
122
+
123
+ def ssh_into
124
+ start = Time.now.to_i
125
+ print "..making sure #{@name} is online"
126
+ begin
127
+ TCPSocket.new(dns_name, 22)
128
+ rescue Errno::ECONNREFUSED
129
+ if (Time.now.to_i - start) < 30
130
+ print '.'
131
+ retry
132
+ end
82
133
  end
134
+ puts
135
+ ::Kernel.exec('ssh', '-l', username, dns_name, *ARGV[2..-1])
83
136
  end
84
137
 
85
138
  private
86
139
 
87
140
  def create_host
88
141
  tags = @tags.merge({:Name => @name, :CreatedBy => 'Blimpy', :BlimpyFleetId => @fleet_id})
89
- Fog::Compute[:aws].servers.create(:image_id => @image_id, :region => @region, :tags => tags)
142
+ if @fog.nil?
143
+ @fog = Fog::Compute.new(:provider => 'AWS', :region => @region)
144
+ end
145
+
146
+ Blimpy::Keys.import_key(@fog)
147
+ @fog.servers.create(:image_id => @image_id,
148
+ :key_name => Blimpy::Keys.key_name,
149
+ :groups => [@group],
150
+ :tags => tags)
90
151
  end
91
152
  end
92
153
  end
@@ -0,0 +1,102 @@
1
+ require 'rubygems'
2
+ require 'thor'
3
+
4
+ require 'blimpy'
5
+ require 'blimpy/engine'
6
+
7
+ module Blimpy
8
+ class CLI < Thor
9
+ BLIMPFILE = File.join(Dir.pwd, 'Blimpfile')
10
+
11
+ no_tasks do
12
+ def ensure_blimpfile
13
+ unless File.exists? Blimpy::CLI::BLIMPFILE
14
+ puts 'Please create a Blimpfile in your current directory'
15
+ exit 1
16
+ end
17
+ end
18
+ end
19
+
20
+ desc 'start', 'Start up a fleet of blimps'
21
+ method_options :"dry-run" => :boolean
22
+ def start
23
+ ensure_blimpfile
24
+ engine = Blimpy::Engine.new
25
+ engine.load_file(File.open(BLIMPFILE).read)
26
+ puts 'Up, up and away!'
27
+
28
+ if options[:'dry-run']
29
+ puts 'skipping actually starting the fleet'
30
+ exit 0
31
+ end
32
+
33
+ engine.fleet.start
34
+ end
35
+
36
+ desc 'list', 'List running blimps'
37
+ def list
38
+ ensure_blimpfile
39
+ blimps = Dir["#{Dir.pwd}/.blimpy.d/*.blimp"]
40
+ if blimps.empty?
41
+ puts 'No currently running VMs'
42
+ exit 0
43
+ end
44
+
45
+ blimps.each do |blimp|
46
+ data = YAML.load_file(blimp)
47
+ instance_id = File.basename(blimp)
48
+ instance_id = instance_id.split('.blimp').first
49
+ puts "#{data['name']} (#{instance_id}) is: online at #{data['dns']}"
50
+ end
51
+ end
52
+
53
+ desc 'destroy', 'Destroy all running blimps'
54
+ def destroy
55
+ ensure_blimpfile
56
+ fleet = Blimpy::Fleet.new
57
+ fleet.destroy
58
+ end
59
+
60
+ desc 'stop', 'Stop the running blimps'
61
+ def stop
62
+ ensure_blimpfile
63
+ fleet = Blimpy::Fleet.new
64
+ fleet.stop
65
+ end
66
+
67
+ desc 'init', 'Create a skeleton Blimpfile in the current directory'
68
+ def init
69
+ File.open(File.join(Dir.pwd, 'Blimpfile'), 'w') do |f|
70
+ f.write(
71
+ """# vim: ft=ruby
72
+ # Blimpfile created on #{Time.now}
73
+
74
+ Blimpy.fleet do |fleet|
75
+ fleet.add do |ship|
76
+ ship.name = 'Excelsior'
77
+ ship.group = 'default'
78
+ end
79
+ end
80
+ """)
81
+ end
82
+ end
83
+
84
+ desc 'ssh BLIMP_NAME', 'Log into a running blimp'
85
+ def ssh(name, *args)
86
+ ensure_blimpfile
87
+ fleet = Blimpy::Fleet.new
88
+ box = nil
89
+ fleet.members.each do |instance_id, data|
90
+ box_name = data['name']
91
+ next unless box_name == name
92
+ box = Blimpy::Box.from_instance_id(instance_id, data)
93
+ end
94
+ if box.nil?
95
+ puts "Could not find blimp named \"#{name}\""
96
+ exit 1
97
+ end
98
+
99
+ box.ssh_into
100
+ end
101
+ end
102
+ end
@@ -1,11 +1,13 @@
1
+ require 'blimpy/helpers/state'
1
2
 
2
3
  module Blimpy
3
4
  class Fleet
4
- attr_reader :hosts
5
+ include Blimpy::Helpers::State
6
+
7
+ attr_reader :hosts, :id
5
8
 
6
9
  def initialize
7
10
  @hosts = []
8
- @servers = []
9
11
  @id = Time.now.utc.to_i
10
12
  end
11
13
 
@@ -19,49 +21,97 @@ module Blimpy
19
21
  block.call(box)
20
22
  end
21
23
 
24
+ def state_file
25
+ File.join(state_folder, 'manifest')
26
+ end
27
+
28
+ def save!
29
+ File.open(state_file, 'w') do |f|
30
+ f.write("id=#{id}\n")
31
+ end
32
+ end
33
+
34
+ def resume(instances)
35
+ boxes = []
36
+ print '>> Resuming: '
37
+ instances.each do |instance_id, instance_data|
38
+ print "#{instance_data['name']},"
39
+ box = Blimpy::Box.from_instance_id(instance_id, instance_data)
40
+ box.resume
41
+ boxes << box
42
+ end
43
+
44
+ boxes.each do |box|
45
+ box.wait_for_state('running') { print '.' }
46
+ end
47
+ puts
48
+ end
49
+
22
50
  def start
51
+ instances = members
52
+ unless instances.empty?
53
+ return resume(instances)
54
+ end
55
+
23
56
  # Make sure all our hosts are valid first!
24
57
  @hosts.each do |host|
25
58
  host.validate!
26
59
  end
27
60
 
61
+ puts '>> Starting:'
28
62
  @hosts.each do |host|
29
- @servers << host.start
63
+ puts "..#{host.name}"
64
+ host.start
30
65
  end
31
66
 
32
67
  @hosts.each do |host|
33
68
  print ">> #{host.name} "
34
- host.server.wait_for do
35
- print '.'
36
- ready?
37
- end
38
- print ".. online at: #{host.server.dns_name}"
69
+ host.wait_for_state('running') { print '.' }
70
+ print ".. online at: #{host.dns_name}"
39
71
  host.online!
40
72
  puts
41
73
  end
74
+
75
+ save!
42
76
  end
43
77
 
44
78
  def members
45
- instance_ids = []
79
+ instances = []
46
80
  Dir["#{Dir.pwd}/.blimpy.d/*.blimp"].each do |d|
47
81
  filename = File.basename(d)
48
- instance_ids << filename.split('.blimp').first
82
+ instance_id = filename.split('.blimp').first
83
+ instance_data = YAML.load_file(d)
84
+ instances << [instance_id, instance_data]
49
85
  end
50
- instance_ids
86
+ instances
51
87
  end
52
88
 
53
89
  def stop
54
- members.each do |instance_id|
55
- box = Blimpy::Box.from_instance_id(instance_id)
90
+ print '>> Stopping: '
91
+ boxes = []
92
+
93
+ members.each do |instance_id, instance_data|
94
+ box = Blimpy::Box.from_instance_id(instance_id, instance_data)
95
+ print "#{instance_data['name']},"
56
96
  box.stop
97
+ boxes << box
57
98
  end
99
+
100
+ boxes.each do |box|
101
+ box.wait_for_state('stopped') { print '.' }
102
+ end
103
+ puts
58
104
  end
59
105
 
60
106
  def destroy
61
- members.each do |instance_id|
62
- box = Blimpy::Box.from_instance_id(instance_id)
107
+ members.each do |instance_id, instance_data|
108
+ box = Blimpy::Box.from_instance_id(instance_id, instance_data)
63
109
  box.destroy
64
110
  end
111
+
112
+ if File.exists? state_file
113
+ File.unlink(state_file)
114
+ end
65
115
  end
66
116
  end
67
117
  end
@@ -0,0 +1,20 @@
1
+ module Blimpy
2
+ module Helpers
3
+ module State
4
+ def state_folder
5
+ File.join(Dir.pwd, '.blimpy.d')
6
+ end
7
+
8
+ def ensure_state_folder
9
+ unless File.exist? state_folder
10
+ Dir.mkdir(state_folder)
11
+ end
12
+ end
13
+
14
+ def state_file
15
+ raise NotImplementedError, '#state_file should be implemented in a consumer'
16
+ end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,26 @@
1
+ require 'fog'
2
+ require 'socket'
3
+
4
+ module Blimpy
5
+ module Keys
6
+ def self.import_key(fog)
7
+ filename = File.expand_path('~/.ssh/id_rsa.pub')
8
+ unless File.exists? filename
9
+ filename = File.expand_path('~/.ssh/id_dsa.pub')
10
+ unless File.exists? filename
11
+ raise Blimpy::SSHKeyNotFoundError, 'Expected either ~/.ssh/id_rsa.pub or ~/.ssh/id_dsa.pub but found neither'
12
+ end
13
+ end
14
+
15
+ material = File.open(filename, 'r').read
16
+ begin
17
+ fog.import_key_pair(key_name, material)
18
+ rescue Fog::Compute::AWS::Error => e
19
+ end
20
+ end
21
+
22
+ def self.key_name
23
+ "Blimpy-#{ENV['USER']}@#{Socket.gethostname}"
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module Blimpy
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Blimpy::Box do
4
4
  describe '#image_id' do
5
5
  it 'should be the Ubuntu 10.04 AMI ID by default' do
6
- subject.image_id.should == 'ami-349b495d'
6
+ subject.image_id.should == Blimpy::Box::DEFAULT_IMAGE_ID
7
7
  end
8
8
  end
9
9
 
@@ -99,17 +99,16 @@ describe Blimpy::Box do
99
99
  before :each do
100
100
  # Mocking out #create_host so we don't actually create EC2 instance
101
101
  Blimpy::Box.any_instance.should_receive(:create_host).and_return(server)
102
- Blimpy::Box.any_instance.should_receive(:ensure_state_dir).and_return(true)
102
+ Blimpy::Box.any_instance.should_receive(:ensure_state_folder).and_return(true)
103
103
  end
104
104
 
105
105
  it 'should create a state file' do
106
- path = File.join(subject.state_dir, "#{server_id}.blimp")
107
- File.should_receive(:open).with(path, 'w')
106
+ subject.stub(:state_file).and_return('fake-state-file')
107
+ File.should_receive(:open).with('fake-state-file', 'w')
108
108
  subject.start
109
109
  end
110
110
  end
111
111
 
112
-
113
112
  describe '#stop' do
114
113
  before :each do
115
114
  server.should_receive(:stop)
@@ -121,6 +120,7 @@ describe Blimpy::Box do
121
120
  subject.stop
122
121
  end
123
122
  end
123
+
124
124
  describe '#destroy' do
125
125
  before :each do
126
126
  server.should_receive(:destroy)
@@ -128,36 +128,24 @@ describe Blimpy::Box do
128
128
  subject { Blimpy::Box.new(server) }
129
129
 
130
130
  it 'should remove its state file' do
131
- subject.should_receive(:state_file).and_return('foo')
132
- File.should_receive(:unlink).with(File.join(subject.state_dir, 'foo'))
131
+ subject.should_receive(:state_file).and_return('fake-state-file')
132
+ File.should_receive(:unlink).with('fake-state-file')
133
133
  subject.destroy
134
134
  end
135
135
  end
136
- end
137
136
 
138
- describe '#ensure_state_dir' do
139
- let(:path) { File.join(Dir.pwd, '.blimpy.d') }
140
-
141
- context 'if ./.blimpy.d does not exist' do
137
+ describe '#from_instance_id' do
138
+ let(:fog) { double('Fog::Compute') }
142
139
  before :each do
143
- File.should_receive(:exist?).with(path).and_return(false)
140
+ fog.stub_chain(:servers, :get).and_return(server)
141
+ Fog::Compute.should_receive(:new).and_return(fog)
144
142
  end
145
143
 
146
- it 'should create directory' do
147
- Dir.should_receive(:mkdir).with(path)
148
- subject.ensure_state_dir
144
+ it 'should create a new Box instance' do
145
+ result = Blimpy::Box.from_instance_id('someid', {})
146
+ result.should be_instance_of Blimpy::Box
149
147
  end
150
- end
151
148
 
152
- context 'if ./blimpy.d does exist' do
153
- before :each do
154
- File.should_receive(:exist?).with(path).and_return(true)
155
- end
156
-
157
- it 'should create directory' do
158
- Dir.should_receive(:mkdir).with(path).never
159
- subject.ensure_state_dir
160
- end
161
149
  end
162
150
  end
163
151
  end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+
3
+ describe Blimpy::Fleet do
4
+ describe '#hosts' do
5
+ it 'should be an Array' do
6
+ subject.hosts.should be_instance_of Array
7
+ subject.hosts.size.should == 0
8
+ end
9
+ end
10
+
11
+ describe '#add' do
12
+ it 'should return false if no Box was properly added' do
13
+ subject.add.should == false
14
+ end
15
+
16
+ it 'should pass a Box instance to the block' do
17
+ invoked_block = false
18
+ subject.add do |box|
19
+ invoked_block = true
20
+ box.should be_instance_of Blimpy::Box
21
+ end
22
+ invoked_block.should be true
23
+ end
24
+
25
+ context 'with a block' do
26
+ before :each do
27
+ subject.add do |b|
28
+ @box = b
29
+ end
30
+ end
31
+
32
+ it 'should add the box the fleet' do
33
+ @box.should_not be nil
34
+ subject.hosts.should include(@box)
35
+ end
36
+ end
37
+ end
38
+
39
+ describe '#save!' do
40
+ let(:manifest) { 'fake-manifest' }
41
+ let(:manifest_file) do
42
+ fd = mock('Manifest File Descriptor')
43
+ fd
44
+ end
45
+
46
+ before :each do
47
+ subject.should_receive(:state_file).and_return(manifest)
48
+ end
49
+
50
+ it 'should save the fleet id' do
51
+ fleet_id = 1337
52
+ subject.should_receive(:id).and_return(fleet_id)
53
+ File.should_receive(:open).with(manifest, 'w').and_yield(manifest_file)
54
+ manifest_file.should_receive(:write).with("id=#{fleet_id}\n")
55
+ subject.save!
56
+ end
57
+ end
58
+
59
+ describe '#state_file' do
60
+ it 'should return a file named manifest' do
61
+ subject.should_receive(:state_folder).and_return('fake-state-folder')
62
+ subject.state_file.should == 'fake-state-folder/manifest'
63
+ end
64
+ end
65
+
66
+ context 'group operations' do
67
+ let(:members) do
68
+ members = []
69
+ members << [0xdeadbeef, {}]
70
+ members
71
+ end
72
+ let(:box) do
73
+ box = double('Blimpy::Box')
74
+ box.stub(:stop)
75
+ box.stub(:destroy)
76
+ box.stub(:wait_for_state)
77
+ box
78
+ end
79
+
80
+ before :each do
81
+ Blimpy::Box.should_receive(:from_instance_id).with(0xdeadbeef, {}).and_return(box)
82
+ subject.should_receive(:members).and_return(members)
83
+ # Stub out output methods, this will keep our output clean in RSpec
84
+ subject.stub(:print)
85
+ subject.stub(:puts)
86
+ end
87
+
88
+ describe '#stop' do
89
+ it 'should run stop' do
90
+ subject.stop
91
+ end
92
+ end
93
+
94
+ describe '#destroy' do
95
+ it 'should run destroy' do
96
+ subject.destroy
97
+ end
98
+ end
99
+
100
+ describe '#start' do
101
+ it 'should invoke resume' do
102
+ box.should_receive(:resume)
103
+ subject.start
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+ require 'blimpy/helpers/state'
3
+
4
+ describe Blimpy::Helpers::State do
5
+ include Blimpy::Helpers::State
6
+
7
+ describe '#state_folder' do
8
+ it 'should be .blimpy.d in the working directory' do
9
+ pwd = '/fake-pwd'
10
+ Dir.should_receive(:pwd).and_return(pwd)
11
+ state_folder.should == "#{pwd}/.blimpy.d"
12
+ end
13
+ end
14
+
15
+ describe '#state_file' do
16
+ it 'should raise an error since it must be defined by consumer classes' do
17
+ expect {
18
+ state_file
19
+ }.to raise_error(NotImplementedError)
20
+ end
21
+ end
22
+
23
+ describe '#ensure_state_folder' do
24
+ it 'should make the dir if it doesn\'t exist' do
25
+ File.should_receive(:exist?).and_return(false)
26
+ Dir.should_receive(:mkdir)
27
+ ensure_state_folder
28
+ end
29
+
30
+ it 'should not make the dir if it exists' do
31
+ File.should_receive(:exist?).and_return(true)
32
+ Dir.should_receive(:mkdir).never
33
+ ensure_state_folder
34
+ end
35
+ end
36
+ end
37
+
38
+
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ require 'blimpy/keys'
3
+
4
+ describe Blimpy::Keys do
5
+ describe '#import_key' do
6
+ context 'with no SSH keys' do
7
+ it 'should raise a SSHKeyNotFoundError' do
8
+ File.stub(:exists?).and_return(false)
9
+ expect {
10
+ subject.import_key(nil)
11
+ }.to raise_error(Blimpy::SSHKeyNotFoundError)
12
+ end
13
+ end
14
+ end
15
+
16
+ describe '#key_name' do
17
+ before :each do
18
+ ENV['USER'] = 'tester'
19
+ end
20
+
21
+ it do
22
+ hostname = 'rspec'
23
+ Socket.should_receive(:gethostname).and_return(hostname)
24
+ subject.key_name.should == "Blimpy-tester@#{hostname}"
25
+ end
26
+ end
27
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blimpy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-23 00:00:00.000000000Z
12
+ date: 2012-04-28 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fog
16
- requirement: &6750880 !ruby/object:Gem::Requirement
16
+ requirement: &6754760 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *6750880
24
+ version_requirements: *6754760
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: thor
27
- requirement: &6747240 !ruby/object:Gem::Requirement
27
+ requirement: &6754100 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *6747240
35
+ version_requirements: *6754100
36
36
  description: Blimpy is a tool for managing a fleet of machines in the CLOUD!
37
37
  email:
38
38
  - tyler@monkeypox.org
@@ -50,19 +50,25 @@ files:
50
50
  - blimpy.gemspec
51
51
  - features/cli/init.feature
52
52
  - features/cli/list.feature
53
+ - features/cli/ssh.feature
53
54
  - features/cli/start.feature
54
55
  - features/step_definitions/cli_steps.rb
55
56
  - features/support/env.rb
56
57
  - features/support/hooks.rb
57
58
  - lib/blimpy.rb
58
59
  - lib/blimpy/box.rb
60
+ - lib/blimpy/cli.rb
59
61
  - lib/blimpy/engine.rb
60
62
  - lib/blimpy/fleet.rb
63
+ - lib/blimpy/helpers/state.rb
64
+ - lib/blimpy/keys.rb
61
65
  - lib/blimpy/version.rb
66
+ - spec/blimpy/box_spec.rb
67
+ - spec/blimpy/engine_spec.rb
68
+ - spec/blimpy/fleet_spec.rb
69
+ - spec/blimpy/helpers/state_spec.rb
70
+ - spec/blimpy/keys_spec.rb
62
71
  - spec/blimpy_spec.rb
63
- - spec/box_spec.rb
64
- - spec/engine_spec.rb
65
- - spec/fleet_spec.rb
66
72
  - spec/spec_helper.rb
67
73
  homepage: https://github.com/rtyler/blimpy
68
74
  licenses: []
@@ -91,12 +97,15 @@ summary: Ruby + CLOUD = Blimpy
91
97
  test_files:
92
98
  - features/cli/init.feature
93
99
  - features/cli/list.feature
100
+ - features/cli/ssh.feature
94
101
  - features/cli/start.feature
95
102
  - features/step_definitions/cli_steps.rb
96
103
  - features/support/env.rb
97
104
  - features/support/hooks.rb
105
+ - spec/blimpy/box_spec.rb
106
+ - spec/blimpy/engine_spec.rb
107
+ - spec/blimpy/fleet_spec.rb
108
+ - spec/blimpy/helpers/state_spec.rb
109
+ - spec/blimpy/keys_spec.rb
98
110
  - spec/blimpy_spec.rb
99
- - spec/box_spec.rb
100
- - spec/engine_spec.rb
101
- - spec/fleet_spec.rb
102
111
  - spec/spec_helper.rb
@@ -1,56 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Blimpy::Fleet do
4
- describe '#hosts' do
5
- it 'should be an Array' do
6
- subject.hosts.should be_instance_of Array
7
- subject.hosts.size.should == 0
8
- end
9
- end
10
-
11
- describe '#add' do
12
- it 'should return false if no Box was properly added' do
13
- subject.add.should == false
14
- end
15
-
16
- it 'should pass a Box instance to the block' do
17
- invoked_block = false
18
- subject.add do |box|
19
- invoked_block = true
20
- box.should be_instance_of Blimpy::Box
21
- end
22
- invoked_block.should be true
23
- end
24
-
25
- context 'with a block' do
26
- before :each do
27
- subject.add do |b|
28
- @box = b
29
- end
30
- end
31
-
32
- it 'should add the box the fleet' do
33
- @box.should_not be nil
34
- subject.hosts.should include(@box)
35
- end
36
- end
37
- end
38
-
39
-
40
- context 'group operations' do
41
- before :each do
42
- subject.should_receive(:members).and_return([])
43
- end
44
- describe '#stop' do
45
- it 'should run stop' do
46
- subject.stop
47
- end
48
- end
49
-
50
- describe '#destroy' do
51
- it 'should run destroy' do
52
- subject.destroy
53
- end
54
- end
55
- end
56
- end