dollhouse 0.1.1
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/LICENSE +20 -0
- data/README.md +3 -0
- data/bin/dollhouse +55 -0
- data/lib/core_ext/hash.rb +21 -0
- data/lib/dollhouse/cloud_adapter.rb +21 -0
- data/lib/dollhouse/config_loader.rb +49 -0
- data/lib/dollhouse/deployment.rb +39 -0
- data/lib/dollhouse/dollhouse.rb +21 -0
- data/lib/dollhouse/instances.rb +33 -0
- data/lib/dollhouse/manual_config.rb +42 -0
- data/lib/dollhouse/online_server.rb +70 -0
- data/lib/dollhouse/rackspace_cloud_adapter.rb +101 -0
- data/lib/dollhouse/remote_server.rb +101 -0
- data/lib/dollhouse/server.rb +4 -0
- data/lib/dollhouse/version.rb +3 -0
- data/lib/dollhouse.rb +5 -0
- metadata +227 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Glen Maddern
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/bin/dollhouse
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib'))
|
4
|
+
|
5
|
+
require 'dollhouse'
|
6
|
+
require 'main'
|
7
|
+
|
8
|
+
Main do
|
9
|
+
mode 'deploy' do
|
10
|
+
argument('deployment') { required }
|
11
|
+
argument('prefix') { optional }
|
12
|
+
def run
|
13
|
+
Dollhouse.launch_from(Dir.pwd)
|
14
|
+
Dollhouse.initiate_deployment(params['deployment'].value.to_sym, :prefix => params['prefix'].value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
mode 'exec' do
|
19
|
+
argument('server_name') { required }
|
20
|
+
argument('cmd') { required }
|
21
|
+
def run
|
22
|
+
Dollhouse.launch_from(Dir.pwd)
|
23
|
+
Dollhouse.instances[params['server_name'].value].instance_eval params['cmd'].value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
mode 'destroy' do
|
28
|
+
argument('server_name') { required }
|
29
|
+
def run
|
30
|
+
Dollhouse.launch_from(Dir.pwd)
|
31
|
+
Dollhouse.cloud_adapter.destroy params['server_name'].value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
mode 'list' do
|
36
|
+
def run
|
37
|
+
Dollhouse.launch_from(Dir.pwd)
|
38
|
+
p Dollhouse.cloud_adapter.list
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
mode 'run' do
|
43
|
+
argument('server_name') { required }
|
44
|
+
argument('task_name') { required }
|
45
|
+
def run
|
46
|
+
Dollhouse.launch_from(Dir.pwd)
|
47
|
+
Dollhouse.instances[params['server_name'].value].run_task(params['task_name'].value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Print the useage message by default.
|
52
|
+
def run
|
53
|
+
help!
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Hash
|
2
|
+
def map_keys &blk
|
3
|
+
map_keys_with_values { |k, v| blk.call(k) }
|
4
|
+
end
|
5
|
+
|
6
|
+
def map_keys_with_values &blk
|
7
|
+
result = {}
|
8
|
+
each { |k, v| result[blk.call(k, v)] = v }
|
9
|
+
result
|
10
|
+
end
|
11
|
+
|
12
|
+
def map_values &blk
|
13
|
+
map_values_with_keys { |k, v| blk.call(v) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def map_values_with_keys &blk
|
17
|
+
result = {}
|
18
|
+
each { |k, v| result[k] = blk.call(k, v) }
|
19
|
+
result
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Dollhouse
|
2
|
+
class CloudAdapter
|
3
|
+
def initialize
|
4
|
+
raise NotImplementedError, %Q{
|
5
|
+
Must implement the following methods:
|
6
|
+
|
7
|
+
boot_new_server(name, callback, opts)
|
8
|
+
- name is the unique id for the server in this cloud
|
9
|
+
- callback is a lambda to be fired_off when that server comes online
|
10
|
+
- generally, opts has :machine_id, :ram_size, :data_center, :backup_image_id
|
11
|
+
|
12
|
+
execute(server_name, cmd, opts)
|
13
|
+
|
14
|
+
write_file(server_name, path, content, opts)
|
15
|
+
|
16
|
+
list
|
17
|
+
|
18
|
+
} unless [:boot_new_server, :execute, :write_file, :list].all? { |m| self.class.method_defined? m }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
def deployment name, &blk
|
2
|
+
Dollhouse::Deployment.register(Dollhouse::DeployBuilder[name].tap { |d| d.instance_eval(&blk) }.to_deployment)
|
3
|
+
end
|
4
|
+
|
5
|
+
module Dollhouse
|
6
|
+
class DeployBuilder < Struct.new(:name)
|
7
|
+
def to_deployment
|
8
|
+
Deployment.new(name, servers.map(&:to_server))
|
9
|
+
end
|
10
|
+
|
11
|
+
def server server_name, &blk
|
12
|
+
servers << ServerBuilder[server_name].tap { |s| s.instance_eval(&blk) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def servers
|
16
|
+
@servers ||= []
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ServerBuilder < Struct.new(:name)
|
21
|
+
def instance_type t
|
22
|
+
@instance_type = t
|
23
|
+
end
|
24
|
+
|
25
|
+
def os o
|
26
|
+
@os = o
|
27
|
+
end
|
28
|
+
|
29
|
+
def from_latest_snapshot snapshot_name
|
30
|
+
@snapshot = snapshot_name
|
31
|
+
end
|
32
|
+
|
33
|
+
def first_boot &blk
|
34
|
+
callbacks[:first_boot] = blk
|
35
|
+
end
|
36
|
+
|
37
|
+
def task name, &blk
|
38
|
+
callbacks[name.to_s] = blk
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_server
|
42
|
+
Server[name, @instance_type, @os, @snapshot, callbacks]
|
43
|
+
end
|
44
|
+
|
45
|
+
def callbacks
|
46
|
+
@callbacks ||= {}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Dollhouse
|
2
|
+
class Deployment
|
3
|
+
attr_accessor :name, :servers
|
4
|
+
|
5
|
+
def initialize name, servers
|
6
|
+
@name = name
|
7
|
+
@servers = servers
|
8
|
+
end
|
9
|
+
|
10
|
+
def initiate opts
|
11
|
+
servers.each do |server|
|
12
|
+
cloud_name = [opts[:prefix], server.name].compact.join("-")
|
13
|
+
Dollhouse.cloud_adapter.boot_new_server cloud_name,
|
14
|
+
lambda { server_online(cloud_name, server) },
|
15
|
+
{:instance_type => server.instance_type, :os => server.os, :snapshot => server.snapshot}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def server_online cloud_name, server
|
20
|
+
online_server = OnlineServer[cloud_name, self.name, server.name, :running]
|
21
|
+
Dollhouse.instances.server_came_online online_server
|
22
|
+
online_server.bootstrap
|
23
|
+
online_server.instance_eval &server.callbacks[:first_boot]
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.[](deployment)
|
27
|
+
raise "Unknown deployment #{deployment}" unless all.has_key? deployment.to_s
|
28
|
+
all[deployment.to_s]
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.register deployment
|
32
|
+
all.merge! deployment.name.to_s => deployment
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.all
|
36
|
+
@all ||= {}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Dollhouse
|
2
|
+
def self.launch_from(dir)
|
3
|
+
self.root = dir
|
4
|
+
# may need something cleverer in the future
|
5
|
+
require dir + '/config/dollhouse/config.rb'
|
6
|
+
Dir.glob(dir + '/config/dollhouse/auth.rb') { |f| require f } #optional require
|
7
|
+
Dir.glob(dir + '/config/dollhouse/deployments/*.rb') { |f| require f }
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.initiate_deployment(deployment, opts = {})
|
11
|
+
Deployment[deployment].initiate(opts)
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_accessor :root, :cloud_adapter, :instances
|
16
|
+
|
17
|
+
def instances
|
18
|
+
@instances ||= Instances.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Dollhouse
|
2
|
+
class Instances
|
3
|
+
def initialize
|
4
|
+
require 'yaml'
|
5
|
+
end
|
6
|
+
|
7
|
+
def server_came_online online_server
|
8
|
+
online_servers[online_server.name_in_cloud] = online_server
|
9
|
+
save!
|
10
|
+
end
|
11
|
+
|
12
|
+
def [] name
|
13
|
+
online_servers[name] or raise "Don't have any record of a server called #{name.inspect}!"
|
14
|
+
end
|
15
|
+
|
16
|
+
def online_servers
|
17
|
+
@online_servers = if File.exists? "#{Dollhouse.root}/config/dollhouse/instances/servers.yml"
|
18
|
+
YAML::load_file("#{Dollhouse.root}/config/dollhouse/instances/servers.yml") or {}
|
19
|
+
else
|
20
|
+
{}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def save!
|
27
|
+
FileUtils.mkdir_p(Dollhouse.root + '/config/dollhouse/instances')
|
28
|
+
File.open(Dollhouse.root + '/config/dollhouse/instances/servers.yml', 'w') { |f|
|
29
|
+
YAML::dump(@online_servers, f)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Dollhouse
|
2
|
+
class ManualConfig < CloudAdapter
|
3
|
+
def execute(name, cmd, opts = {})
|
4
|
+
#nasty, but sudo_password isn't valid for starting a connection
|
5
|
+
sudo_password = opts.delete(:sudo_password)
|
6
|
+
ssh_conn(Dollhouse.instances[name].ip, opts[:user] || 'root', opts) do
|
7
|
+
p "Executing: #{cmd}"
|
8
|
+
exec cmd, {:sudo_password => sudo_password}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def boot_new_server(name, callback, opts)
|
13
|
+
raise "You can't, you fool!"
|
14
|
+
end
|
15
|
+
|
16
|
+
def list
|
17
|
+
Dollhouse.instances.online_servers.values.select { |i| i.status == :running }
|
18
|
+
end
|
19
|
+
|
20
|
+
def write_file(name, path, content, opts)
|
21
|
+
ssh_conn(Dollhouse.instances[name].ip, opts[:user] || 'root', opts) do
|
22
|
+
write_file(path) do |out|
|
23
|
+
out.puts content
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def ssh_conn(host, user, opts, &blk)
|
31
|
+
#nasty, but sudo_password isn't valid for starting a connection
|
32
|
+
opts.delete(:sudo_password)
|
33
|
+
ssh_conns[[host, user, opts]].instance_eval(&blk)
|
34
|
+
end
|
35
|
+
|
36
|
+
def ssh_conns
|
37
|
+
@ssh_conns ||= Hash.new { |h, (host, user, opts)|
|
38
|
+
h[[host, user, opts]] = RemoteServer.new(host, user, {:forward_agent => true}.merge(opts))
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Dollhouse
|
2
|
+
class OnlineServer < Struct.new(:name_in_cloud, :deployment_name, :server_name, :status, :ip)
|
3
|
+
attr_accessor :user, :password
|
4
|
+
|
5
|
+
def bootstrap
|
6
|
+
Dollhouse.cloud_adapter.execute(name_in_cloud, %Q{bash -c "`wget -O- babushka.me/up/hard`"}, default_opts)
|
7
|
+
end
|
8
|
+
|
9
|
+
def babushka taskname, vars = {}
|
10
|
+
# not used yet, but this makes sense. --defaults (or headless) is the default!
|
11
|
+
if vars == :no_defaults
|
12
|
+
Dollhouse.cloud_adapter.execute(name_in_cloud, "babushka '#{taskname}'", default_opts)
|
13
|
+
else
|
14
|
+
if !vars.empty?
|
15
|
+
write_file(".babushka/vars/#{taskname}", {
|
16
|
+
:vars => vars.map_keys(&:to_s).map_values { |v| {:value => v} }
|
17
|
+
}.to_yaml)
|
18
|
+
end
|
19
|
+
Dollhouse.cloud_adapter.execute(name_in_cloud, "babushka '#{taskname}' --defaults", default_opts)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def shell cmd, opts = {}
|
24
|
+
Dollhouse.cloud_adapter.execute(name_in_cloud, cmd, default_opts.merge(opts))
|
25
|
+
end
|
26
|
+
|
27
|
+
def write_file path, content, opts = {}
|
28
|
+
Dollhouse.cloud_adapter.write_file(name_in_cloud, path, content, default_opts.merge(opts))
|
29
|
+
end
|
30
|
+
|
31
|
+
def destroy
|
32
|
+
Dollhouse.cloud_adapter.destroy(name_in_cloud)
|
33
|
+
end
|
34
|
+
|
35
|
+
def as user, opts = {}, &blk
|
36
|
+
old_user = self.user
|
37
|
+
old_password = self.password
|
38
|
+
self.user = user
|
39
|
+
self.password = opts[:password]
|
40
|
+
instance_eval(&blk)
|
41
|
+
self.user = old_user
|
42
|
+
self.password = old_password
|
43
|
+
end
|
44
|
+
|
45
|
+
def take_snapshot name
|
46
|
+
Dollhouse.cloud_adapter.take_snapshot(name_in_cloud, name + "-" + Time.now.strftime("%Y%M%d-%H%M%S"))
|
47
|
+
end
|
48
|
+
|
49
|
+
def server
|
50
|
+
deployment.servers.find { |s| s.name == server_name }
|
51
|
+
end
|
52
|
+
|
53
|
+
def deployment
|
54
|
+
Deployment[self.deployment_name]
|
55
|
+
end
|
56
|
+
|
57
|
+
def run_task task_name
|
58
|
+
instance_eval &server.callbacks[task_name]
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def default_opts
|
64
|
+
opts = {}
|
65
|
+
opts.merge!({:user => user}) if user
|
66
|
+
opts.merge!({:sudo_password => password}) if password
|
67
|
+
opts
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Dollhouse
|
2
|
+
class RackspaceCloudAdapter < CloudAdapter
|
3
|
+
FLAVORS = {"256mb" => 1, "512mb" => 2, "1024mb" => 3, "2048mb" => 4}
|
4
|
+
IMAGES = {"Ubuntu 10.04" => 49}
|
5
|
+
|
6
|
+
def conn
|
7
|
+
require 'fog'
|
8
|
+
@conn ||= Fog::Rackspace::Servers.new(
|
9
|
+
:rackspace_api_key => Auth::Rackspace::API_KEY,
|
10
|
+
:rackspace_username => Auth::Rackspace::USERNAME
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def boot_new_server(name, callback, opts)
|
15
|
+
flavor_num = FLAVORS[opts[:instance_type]]
|
16
|
+
raise "Unknown instance_type of #{opts[:instance_type].inspect}. Permitted values are #{FLAVORS.keys.inspect}" unless flavor_num
|
17
|
+
image_num = if opts[:snapshot]
|
18
|
+
image = conn.images.select { |i| i.created_at && i.name =~ Regexp.new(Regexp.escape(opts[:snapshot])) }.sort_by { |i| i.created_at }.last
|
19
|
+
raise "Snapshot name of #{opts[:snapshot]} doesn't match any images!" unless image
|
20
|
+
puts "Booting from image: #{image.inspect}"
|
21
|
+
image.id
|
22
|
+
else
|
23
|
+
raise "Unknown os of #{opts[:os].inspect}. Permitted values are #{IMAGES.keys.inspect}" unless IMAGES[opts[:os]]
|
24
|
+
IMAGES[opts[:os]]
|
25
|
+
end
|
26
|
+
|
27
|
+
puts "Booting server #{name}"
|
28
|
+
server = conn.servers.create(:flavor_id => flavor_num, :image_id => image_num, :name => name)
|
29
|
+
|
30
|
+
puts "Server booted: #{server.inspect}"
|
31
|
+
# make this asynch soon
|
32
|
+
server.wait_for { ready? }
|
33
|
+
puts "Server #{name} online. Adding our private key"
|
34
|
+
server.public_key_path = Auth::KEYPAIR + ".pub"
|
35
|
+
server.setup
|
36
|
+
puts "Done. Ready to go!"
|
37
|
+
|
38
|
+
callback.call
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute(name, cmd, opts = {})
|
42
|
+
#nasty, but sudo_password isn't valid for starting a connection
|
43
|
+
sudo_password = opts.delete(:sudo_password)
|
44
|
+
ssh_conn(name, opts) do
|
45
|
+
p "Executing: #{cmd}"
|
46
|
+
exec cmd, {:sudo_password => sudo_password}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_file(name, path, content, opts = {})
|
51
|
+
ssh_conn(name, opts) do
|
52
|
+
write_file(path) do |out|
|
53
|
+
out.puts content
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def destroy name
|
59
|
+
server = get_server name
|
60
|
+
puts "Killing server #{server.inspect}"
|
61
|
+
server.destroy
|
62
|
+
puts "Done."
|
63
|
+
end
|
64
|
+
|
65
|
+
def take_snapshot name, snapshot_name
|
66
|
+
response = conn.create_image get_server(name).id, 'name' => snapshot_name
|
67
|
+
puts "Created image: #{response.body['image'].inspect}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def list
|
71
|
+
conn.servers
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def ssh_conn(name, opts, &blk)
|
77
|
+
#nasty, but sudo_password isn't valid for starting a connection
|
78
|
+
opts.delete(:sudo_password)
|
79
|
+
ssh_conns[[name, opts]].instance_eval(&blk)
|
80
|
+
end
|
81
|
+
|
82
|
+
def ssh_conns
|
83
|
+
@ssh_conns ||= Hash.new { |h, (name, opts)|
|
84
|
+
puts "Establishing connection to #{name}:"
|
85
|
+
#change this to use instances.yml, or something
|
86
|
+
server = get_server name
|
87
|
+
host = server.addresses['public'].first
|
88
|
+
user = opts[:user] || 'root'
|
89
|
+
puts "Connecting to #{host} as #{user}..."
|
90
|
+
|
91
|
+
h[[name, opts]] = RemoteServer.new(host, user, {:forward_agent => true}.merge(opts))
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def get_server name
|
96
|
+
server = conn.servers.find { |s| s.name == name }
|
97
|
+
raise "Can't find server #{name}" if server.nil?
|
98
|
+
server
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# Let it be known, dgoodlad is a profase.
|
2
|
+
|
3
|
+
require 'net/ssh'
|
4
|
+
require 'net/sftp'
|
5
|
+
require 'tempfile'
|
6
|
+
|
7
|
+
module Dollhouse
|
8
|
+
class RemoteServer
|
9
|
+
class FailedRemoteCommand < Exception;
|
10
|
+
end
|
11
|
+
|
12
|
+
include Net::SSH::Prompt
|
13
|
+
|
14
|
+
attr_reader :ssh, :host, :user, :init_opts
|
15
|
+
|
16
|
+
def initialize(host, user, opts = {})
|
17
|
+
begin
|
18
|
+
@ssh = Net::SSH.start(host, user, opts)
|
19
|
+
rescue Net::SSH::HostKeyMismatch => exception
|
20
|
+
exception.remember_host!
|
21
|
+
sleep 0.2
|
22
|
+
retry
|
23
|
+
end
|
24
|
+
|
25
|
+
@host = host
|
26
|
+
@user = user
|
27
|
+
@init_opts = opts
|
28
|
+
end
|
29
|
+
|
30
|
+
# Write to a remote file at _path_.
|
31
|
+
def write_file(path)
|
32
|
+
exec "mkdir -p #{File.dirname(path).gsub(/ /, "\\ ")}"
|
33
|
+
Tempfile.open(File.basename(path)) do |f|
|
34
|
+
yield f
|
35
|
+
f.flush
|
36
|
+
remote_path = %Q{"#{@user}@#{host}:#{path.gsub(/ /, "\\ ")}"}
|
37
|
+
puts "Writing to #{remote_path}"
|
38
|
+
`scp '#{f.path}' #{remote_path}`
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def exec(command, opts = {})
|
43
|
+
channel = @ssh.open_channel do |ch|
|
44
|
+
ch.request_pty(:term => 'xterm-color') do |ch, success|
|
45
|
+
raise "Failed to get a PTY!" unless success
|
46
|
+
|
47
|
+
output = ''
|
48
|
+
status_code = nil
|
49
|
+
|
50
|
+
puts "Executing:\n#{command}"
|
51
|
+
|
52
|
+
ch.exec(command) do |ch, success|
|
53
|
+
raise "Failed to start execution!" unless success
|
54
|
+
|
55
|
+
ch.on_data do |ch, data|
|
56
|
+
# could loop badly
|
57
|
+
if opts[:sudo_password] && data =~ /\[sudo\] password for/
|
58
|
+
print data
|
59
|
+
puts "*SUDO PASSWORD AUTOMATICALLY SENT*"
|
60
|
+
ch.send_data("#{opts[:sudo_password]}\n")
|
61
|
+
elsif data =~ /[Pp]assword.+:/
|
62
|
+
ch.send_data("#{prompt(data, false)}\n")
|
63
|
+
elsif data =~ /continue connecting \(yes\/no\)\?/
|
64
|
+
ch.send_data("#{prompt(data, true)}\n")
|
65
|
+
else
|
66
|
+
print data
|
67
|
+
end
|
68
|
+
|
69
|
+
output << data
|
70
|
+
end
|
71
|
+
|
72
|
+
ch.on_extended_data do |ch, data|
|
73
|
+
print data
|
74
|
+
end
|
75
|
+
|
76
|
+
ch.on_request('exit-status') do |ch, data|
|
77
|
+
status_code = data.read_long
|
78
|
+
end
|
79
|
+
end
|
80
|
+
ch.wait
|
81
|
+
|
82
|
+
unless status_code.zero?
|
83
|
+
raise FailedRemoteCommand, "Status code: #{status_code}"
|
84
|
+
end
|
85
|
+
|
86
|
+
result = [status_code.zero?, output, status_code]
|
87
|
+
block_given? ? yield(result) : result
|
88
|
+
end
|
89
|
+
end
|
90
|
+
channel.wait
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_environment(var)
|
94
|
+
@ssh.exec!("echo $#{var}").strip
|
95
|
+
end
|
96
|
+
|
97
|
+
def connected_as_root?
|
98
|
+
@ssh.exec!("id") =~ /^uid=0\(root\)/
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/dollhouse.rb
ADDED
metadata
ADDED
@@ -0,0 +1,227 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dollhouse
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Glen Maddern
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-28 00:00:00 +11:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: main
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 55
|
30
|
+
segments:
|
31
|
+
- 4
|
32
|
+
- 2
|
33
|
+
- 0
|
34
|
+
version: 4.2.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: fog
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 43
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 2
|
49
|
+
- 30
|
50
|
+
version: 0.2.30
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: net-ssh
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 33
|
62
|
+
segments:
|
63
|
+
- 2
|
64
|
+
- 0
|
65
|
+
- 23
|
66
|
+
version: 2.0.23
|
67
|
+
type: :runtime
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: net-sftp
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 7
|
78
|
+
segments:
|
79
|
+
- 2
|
80
|
+
- 0
|
81
|
+
- 4
|
82
|
+
version: 2.0.4
|
83
|
+
type: :runtime
|
84
|
+
version_requirements: *id004
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: ruby-debug
|
87
|
+
prerelease: false
|
88
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 49
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
- 10
|
97
|
+
- 3
|
98
|
+
version: 0.10.3
|
99
|
+
type: :runtime
|
100
|
+
version_requirements: *id005
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
name: bundler
|
103
|
+
prerelease: false
|
104
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
hash: 17
|
110
|
+
segments:
|
111
|
+
- 1
|
112
|
+
- 0
|
113
|
+
- 3
|
114
|
+
version: 1.0.3
|
115
|
+
type: :development
|
116
|
+
version_requirements: *id006
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: cucumber
|
119
|
+
prerelease: false
|
120
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
hash: 61
|
126
|
+
segments:
|
127
|
+
- 0
|
128
|
+
- 9
|
129
|
+
- 3
|
130
|
+
version: 0.9.3
|
131
|
+
type: :development
|
132
|
+
version_requirements: *id007
|
133
|
+
- !ruby/object:Gem::Dependency
|
134
|
+
name: rake
|
135
|
+
prerelease: false
|
136
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ~>
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
hash: 49
|
142
|
+
segments:
|
143
|
+
- 0
|
144
|
+
- 8
|
145
|
+
- 7
|
146
|
+
version: 0.8.7
|
147
|
+
type: :development
|
148
|
+
version_requirements: *id008
|
149
|
+
- !ruby/object:Gem::Dependency
|
150
|
+
name: rspec
|
151
|
+
prerelease: false
|
152
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ~>
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
hash: 15
|
158
|
+
segments:
|
159
|
+
- 2
|
160
|
+
- 0
|
161
|
+
- 0
|
162
|
+
version: 2.0.0
|
163
|
+
type: :development
|
164
|
+
version_requirements: *id009
|
165
|
+
description: Dollhouse is a way to deploy servers. It is designed to be used with Babushka.
|
166
|
+
email: glen.maddern@gmail.com
|
167
|
+
executables:
|
168
|
+
- dollhouse
|
169
|
+
extensions: []
|
170
|
+
|
171
|
+
extra_rdoc_files: []
|
172
|
+
|
173
|
+
files:
|
174
|
+
- bin/dollhouse
|
175
|
+
- lib/core_ext/hash.rb
|
176
|
+
- lib/dollhouse/cloud_adapter.rb
|
177
|
+
- lib/dollhouse/config_loader.rb
|
178
|
+
- lib/dollhouse/deployment.rb
|
179
|
+
- lib/dollhouse/dollhouse.rb
|
180
|
+
- lib/dollhouse/instances.rb
|
181
|
+
- lib/dollhouse/manual_config.rb
|
182
|
+
- lib/dollhouse/online_server.rb
|
183
|
+
- lib/dollhouse/rackspace_cloud_adapter.rb
|
184
|
+
- lib/dollhouse/remote_server.rb
|
185
|
+
- lib/dollhouse/server.rb
|
186
|
+
- lib/dollhouse/version.rb
|
187
|
+
- lib/dollhouse.rb
|
188
|
+
- LICENSE
|
189
|
+
- README.md
|
190
|
+
has_rdoc: true
|
191
|
+
homepage: http://github.com/geelen/dollhouse
|
192
|
+
licenses: []
|
193
|
+
|
194
|
+
post_install_message:
|
195
|
+
rdoc_options: []
|
196
|
+
|
197
|
+
require_paths:
|
198
|
+
- lib
|
199
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
200
|
+
none: false
|
201
|
+
requirements:
|
202
|
+
- - ">="
|
203
|
+
- !ruby/object:Gem::Version
|
204
|
+
hash: 3
|
205
|
+
segments:
|
206
|
+
- 0
|
207
|
+
version: "0"
|
208
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
209
|
+
none: false
|
210
|
+
requirements:
|
211
|
+
- - ">="
|
212
|
+
- !ruby/object:Gem::Version
|
213
|
+
hash: 23
|
214
|
+
segments:
|
215
|
+
- 1
|
216
|
+
- 3
|
217
|
+
- 6
|
218
|
+
version: 1.3.6
|
219
|
+
requirements: []
|
220
|
+
|
221
|
+
rubyforge_project: dollhouse
|
222
|
+
rubygems_version: 1.3.7
|
223
|
+
signing_key:
|
224
|
+
specification_version: 3
|
225
|
+
summary: A place to manage your babushkas.
|
226
|
+
test_files: []
|
227
|
+
|