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 +3 -0
- data/Rakefile +28 -0
- data/bin/blimpy +2 -79
- data/features/cli/ssh.feature +39 -0
- data/features/cli/start.feature +37 -15
- data/features/step_definitions/cli_steps.rb +16 -0
- data/features/support/hooks.rb +10 -0
- data/lib/blimpy.rb +2 -0
- data/lib/blimpy/box.rb +82 -21
- data/lib/blimpy/cli.rb +102 -0
- data/lib/blimpy/fleet.rb +65 -15
- data/lib/blimpy/helpers/state.rb +20 -0
- data/lib/blimpy/keys.rb +26 -0
- data/lib/blimpy/version.rb +1 -1
- data/spec/{box_spec.rb → blimpy/box_spec.rb} +14 -26
- data/spec/{engine_spec.rb → blimpy/engine_spec.rb} +0 -0
- data/spec/blimpy/fleet_spec.rb +107 -0
- data/spec/blimpy/helpers/state_spec.rb +38 -0
- data/spec/blimpy/keys_spec.rb +27 -0
- metadata +21 -12
- data/spec/fleet_spec.rb +0 -56
data/.gitignore
CHANGED
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
|
data/features/cli/start.feature
CHANGED
@@ -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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
data/features/support/hooks.rb
CHANGED
data/lib/blimpy.rb
CHANGED
data/lib/blimpy/box.rb
CHANGED
@@ -1,24 +1,39 @@
|
|
1
|
+
require 'blimpy/helpers/state'
|
2
|
+
require 'blimpy/keys'
|
1
3
|
|
2
4
|
module Blimpy
|
3
5
|
class Box
|
4
|
-
|
5
|
-
attr_accessor :image_id, :livery, :group, :name, :tags, :fleet_id
|
6
|
+
include Blimpy::Helpers::State
|
6
7
|
|
7
|
-
|
8
|
-
|
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 =
|
18
|
-
@image_id =
|
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(
|
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
|
-
|
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(
|
83
|
+
File.unlink(state_file)
|
65
84
|
end
|
66
85
|
end
|
67
86
|
|
68
|
-
def
|
69
|
-
File.
|
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
|
80
|
-
|
81
|
-
|
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
|
-
|
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
|
data/lib/blimpy/cli.rb
ADDED
@@ -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
|
data/lib/blimpy/fleet.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
require 'blimpy/helpers/state'
|
1
2
|
|
2
3
|
module Blimpy
|
3
4
|
class Fleet
|
4
|
-
|
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
|
-
|
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.
|
35
|
-
|
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
|
-
|
79
|
+
instances = []
|
46
80
|
Dir["#{Dir.pwd}/.blimpy.d/*.blimp"].each do |d|
|
47
81
|
filename = File.basename(d)
|
48
|
-
|
82
|
+
instance_id = filename.split('.blimp').first
|
83
|
+
instance_data = YAML.load_file(d)
|
84
|
+
instances << [instance_id, instance_data]
|
49
85
|
end
|
50
|
-
|
86
|
+
instances
|
51
87
|
end
|
52
88
|
|
53
89
|
def stop
|
54
|
-
|
55
|
-
|
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
|
+
|
data/lib/blimpy/keys.rb
ADDED
@@ -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
|
data/lib/blimpy/version.rb
CHANGED
@@ -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 ==
|
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(:
|
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
|
-
|
107
|
-
File.should_receive(:open).with(
|
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('
|
132
|
-
File.should_receive(:unlink).with(
|
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
|
-
|
139
|
-
|
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
|
-
|
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
|
147
|
-
|
148
|
-
|
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
|
File without changes
|
@@ -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
|
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-
|
12
|
+
date: 2012-04-28 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: fog
|
16
|
-
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: *
|
24
|
+
version_requirements: *6754760
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: thor
|
27
|
-
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: *
|
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
|
data/spec/fleet_spec.rb
DELETED
@@ -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
|