blimpy 0.0.1 → 0.1.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/.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