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 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
@@ -0,0 +1,3 @@
1
+ # Dollhouse
2
+
3
+ Dollhouse is a way to deploy servers.
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
@@ -0,0 +1,4 @@
1
+ module Dollhouse
2
+ class Server < Struct.new(:name, :instance_type, :os, :snapshot, :callbacks)
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module Dollhouse
2
+ VERSION = "0.1.1" unless defined?(Dollhouse::VERSION)
3
+ end
data/lib/dollhouse.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Dollhouse
2
+ end
3
+
4
+ Dir.glob(File.dirname(__FILE__) + '/core_ext/*.rb') { |f| require f }
5
+ Dir.glob(File.dirname(__FILE__) + '/dollhouse/*.rb') { |f| require f }
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
+