foreplay 0.0.1 → 0.0.2

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.
@@ -0,0 +1,305 @@
1
+ require 'thor/group'
2
+ require 'yaml'
3
+ require 'net/ssh'
4
+ require 'active_support/inflector'
5
+ require 'active_support/core_ext/object'
6
+ require 'active_support/core_ext/hash'
7
+ require 'colorize'
8
+ require 'foreplay/utility'
9
+
10
+ module Foreplay
11
+ class Deploy < Thor::Group
12
+ include Thor::Actions
13
+
14
+ argument :mode, :type => :string, :required => true
15
+ argument :environment, :type => :string, :required => true
16
+ argument :filters, :type => :hash, :required => false
17
+
18
+ DEFAULTS_KEY = 'defaults'
19
+
20
+ def parse
21
+ # Explain what we're going to do
22
+ puts '%sing %s environment, %s, %s' % [
23
+ mode.capitalize,
24
+ environment.dup.yellow,
25
+ explanatory_text(filters, 'role'),
26
+ explanatory_text(filters, 'server')
27
+ ]
28
+
29
+ config_file = "#{Dir.getwd}/config/foreplay.yml"
30
+
31
+ begin
32
+ config_yml = File.read config_file
33
+ rescue Errno::ENOENT
34
+ terminate "Can't find configuration file #{config_file}.\nPlease run foreplay setup or create the file manually."
35
+ end
36
+
37
+ config_all = YAML.load(config_yml)
38
+ config_env = config_all[environment] || {}
39
+
40
+ # This environment
41
+ terminate("No deployment configuration defined for #{environment} environment.\nCheck #{config_file}") unless config_all.has_key? environment
42
+
43
+ # Establish defaults
44
+ # First the default defaults
45
+ defaults = {
46
+ :name => File.basename(Dir.getwd),
47
+ :environment => environment,
48
+ :env => { 'RAILS_ENV' => environment },
49
+ :port => 50000
50
+ }
51
+
52
+ defaults = Foreplay::Utility::supermerge(defaults, config_all[DEFAULTS_KEY]) if config_all.has_key? DEFAULTS_KEY # Then the global defaults
53
+ defaults = Foreplay::Utility::supermerge(defaults, config_env[DEFAULTS_KEY]) if config_env.has_key? DEFAULTS_KEY # Then the defaults for this environment
54
+
55
+ config_env.each do |role, additional_instructions|
56
+ next if role == DEFAULTS_KEY # 'defaults' is not a role
57
+ next if filters.has_key?('role') && filters['role'] != role # Only deploy to the role we've specified (or all roles if none is specified)
58
+
59
+ instructions = Foreplay::Utility::supermerge(defaults, additional_instructions).symbolize_keys
60
+ instructions[:role] = role
61
+ required_keys = [:name, :environment, :role, :servers, :path, :repository]
62
+ required_keys.each { |key| terminate("Required key #{key} not found in instructions for #{environment} environment.\nCheck #{config_file}") unless instructions.has_key? key }
63
+
64
+ deploy_role instructions
65
+ end
66
+
67
+ puts mode == :deploy ? 'Finished deployment' : 'Deployment configuration check was successful'
68
+ end
69
+
70
+ private
71
+
72
+ def deploy_role instructions
73
+ servers = instructions[:servers]
74
+ preposition = mode == :deploy ? 'to' : 'for'
75
+
76
+ puts "#{mode.capitalize}ing #{instructions[:name].yellow} #{preposition} #{servers.join(', ').yellow} in the #{instructions[:role].dup.yellow} role on the #{environment.dup.yellow} environment..." if servers.length > 1
77
+ servers.each { |server| deploy_to_server server, instructions }
78
+ end
79
+
80
+ def deploy_to_server server, instructions
81
+ name = instructions[:name]
82
+ role = instructions[:role]
83
+ path = instructions[:path]
84
+ repository = instructions[:repository]
85
+ user = instructions[:user]
86
+ port = instructions[:port]
87
+ preposition = mode == :deploy ? 'to' : 'for'
88
+
89
+ instructions[:server] = server
90
+
91
+ puts "#{mode.capitalize}ing #{name.yellow} #{preposition} #{server.yellow} in the #{role.dup.yellow} role on the #{environment.dup.yellow} environment"
92
+
93
+ # Substitute variables in the path
94
+ path.gsub! '%u', user
95
+ path.gsub! '%a', name
96
+
97
+ # Find out which port we're currently running on
98
+ steps = [ { :command => 'mkdir -p .foreplay && touch .foreplay/current_port && cat .foreplay/current_port', :silent => true } ]
99
+
100
+ current_port_string = execute_on_server(steps, instructions).strip!
101
+ puts current_port_string.blank? ? ' No instance is currently deployed' : " Current instance is using port #{current_port_string}"
102
+
103
+ current_port = current_port_string.to_i
104
+
105
+ # Switch ports
106
+ if current_port == port
107
+ current_port = port + 1000
108
+ former_port = port
109
+ else
110
+ current_port = port
111
+ former_port = port + 1000
112
+ end
113
+
114
+ # Contents of .foreman file
115
+ current_service = '%s-%s' % [name, current_port]
116
+ former_service = '%s-%s' % [name, former_port]
117
+
118
+ instructions[:foreman]['app'] = current_service
119
+ instructions[:foreman]['port'] = current_port
120
+ instructions[:foreman]['user'] = user
121
+
122
+ # Commands to execute on remote server
123
+ steps = [
124
+ { :command => "echo #{current_port} > .foreplay/current_port",
125
+ :commentary => "Setting the port for the new instance to #{current_port}" },
126
+ { :command => "mkdir -p #{path} && cd #{path} && rm -rf #{current_port} && git clone #{repository} #{current_port}",
127
+ :commentary => "Cloning repository #{repository}" },
128
+ { :command => "rvm rvmrc trust #{current_port}",
129
+ :commentary => 'Trusting the .rvmrc file for the new instance' },
130
+ { :command => "cd #{current_port}",
131
+ :commentary => 'Configuring the new instance' },
132
+ { :key => :env,
133
+ :delimiter => '=',
134
+ :prefix => '.',
135
+ :commentary => 'Building .env' },
136
+ { :key => :foreman,
137
+ :delimiter => ': ',
138
+ :prefix => '.',
139
+ :commentary => 'Building .foreman' },
140
+ { :key => :database,
141
+ :delimiter => ': ',
142
+ :suffix => '.yml',
143
+ :commentary => 'Building config/database.yml',
144
+ :before => ' ',
145
+ :header => "#{environment}:",
146
+ :path => 'config/' },
147
+ { :command => "bundle install",
148
+ :commentary => 'Using bundler to install the required gems' },
149
+ { :command => "sudo ln -f `which foreman` /usr/bin/foreman",
150
+ :commentary => 'Setting the current version of foreman to be the default' },
151
+ { :command => "sudo foreman export upstart /etc/init",
152
+ :commentary => "Converting #{current_service} to an upstart service" },
153
+ { :command => "sudo start #{current_service} || sudo restart #{current_service}",
154
+ :commentary => 'Starting the service',
155
+ :ignore_error => true },
156
+ { :command => 'sleep 60',
157
+ :commentary => 'Waiting 60s to give service time to start' },
158
+ { :command => "sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{current_port.to_i + 100}",
159
+ :commentary => "Adding firewall rule to direct incoming traffic on port 80 to port #{current_port}" },
160
+ { :command => "sudo iptables -t nat -D PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{former_port.to_i + 100}",
161
+ :commentary => "Removing previous firewall directing traffic to port #{former_port}",
162
+ :ignore_error => true },
163
+ { :command => "sudo iptables-save > /etc/iptables/rules.v4",
164
+ :commentary => "Saving iptables rules to /etc/iptables/rules.v4" },
165
+ { :command => "sudo iptables-save > /etc/iptables.up.rules",
166
+ :commentary => "Saving iptables rules to /etc/iptables.up.rules" },
167
+ { :command => "sudo iptables-save -c | egrep REDIRECT --color=never",
168
+ :ignore_error => true,
169
+ :commentary => "Current firewall NAT configuration:" },
170
+ { :command => "sudo stop #{former_service} || echo 'No previous instance running'",
171
+ :commentary => 'Stopping the previous instance',
172
+ :ignore_error => true },
173
+ ]
174
+
175
+ execute_on_server steps, instructions
176
+ end
177
+
178
+ def execute_on_server steps, instructions
179
+ server = instructions[:server]
180
+ user = instructions[:user]
181
+ password = instructions[:password]
182
+ keyfile = instructions[:keyfile]
183
+ private_key = instructions[:private_key]
184
+
185
+ keyfile.sub! '~', ENV['HOME'] || '/' unless keyfile.blank? # Remote shell won't expand this for us
186
+
187
+ # SSH authentication methods
188
+ options = { :verbose => :warn }
189
+
190
+ if password.blank?
191
+ # If there's no password we must supply a private key
192
+ if private_key.blank?
193
+ terminate('No authentication methods supplied. You must supply a private key, key file or password in the configuration file') if keyfile.blank?
194
+ # Get the key from the key file
195
+ puts " Using private key from #{keyfile}"
196
+ private_key = File.read keyfile
197
+ else
198
+ puts " Using private key from the configuration file"
199
+ end
200
+
201
+ options[:key_data] = [private_key]
202
+ else
203
+ # Use the password supplied
204
+ options[:password] = password
205
+ end
206
+
207
+ # Capture output of last command to return to the calling routine
208
+ output = ''
209
+
210
+ if mode == :deploy
211
+ puts " Connecting to #{server}"
212
+
213
+ # SSH connection
214
+ begin
215
+ Net::SSH.start(server, user, options) do |session|
216
+ puts " Successfully connected to #{server}"
217
+
218
+ session.shell do |sh|
219
+ steps.each do |step|
220
+ # Output from this step
221
+ output = ''
222
+ previous = '' # We don't need or want the final CRLF
223
+
224
+ commands = build_commands step, instructions
225
+
226
+ commands.each do |command|
227
+ process = sh.execute command
228
+
229
+ process.on_output do |p, o|
230
+ previous = o
231
+ output += previous
232
+ end
233
+
234
+ sh.wait!
235
+
236
+ if step[:ignore_error] == true || process.exit_status == 0
237
+ print output.gsub!(/^/, " ") unless step[:silent] == true
238
+ else
239
+ terminate(output)
240
+ end
241
+ end
242
+ end
243
+ end
244
+ end
245
+ rescue SocketError => e
246
+ terminate "There was a problem starting an ssh session on #{server}:\n#{e.message}"
247
+ end
248
+ else
249
+ # Deployment check: just say what we would have done
250
+ steps.each do |step|
251
+ commands = build_commands step, instructions
252
+
253
+ commands.each { |command| puts " #{command}" unless step[:silent] }
254
+ end
255
+ end
256
+
257
+ output
258
+ end
259
+
260
+ def build_commands step, instructions
261
+ puts " #{(step[:commentary] || step[:command]).yellow}" unless step[:silent] == true
262
+
263
+ # Each step can be (1) a command or (2) a series of values to add to a file
264
+ if step.has_key? :key
265
+ # Add values from the config file to a file on the remote machine
266
+ key = step[:key]
267
+ prefix = step[:prefix] || ''
268
+ suffix = step[:suffix] || ''
269
+ path = step[:path] || ''
270
+ before = step[:before] || ''
271
+ delimiter = step[:delimiter] || ''
272
+ after = step[:after] || ''
273
+
274
+ step[:silent] = true
275
+ filename = '%s%s%s%s' % [path, prefix, key, suffix]
276
+
277
+ if step.has_key?(:header)
278
+ commands = ['echo "%s" > %s' % [step[:header], filename]]
279
+ redirect = '>>'
280
+ else
281
+ commands = []
282
+ redirect = '>'
283
+ end
284
+
285
+ instructions[key].each do |k, v|
286
+ commands << 'echo "%s%s%s%s%s" %s %s' % [before, k, delimiter, v, after, redirect, filename]
287
+ redirect = '>>'
288
+ end
289
+
290
+ commands
291
+ else
292
+ # ...or just execute the command specified
293
+ [step[:command]]
294
+ end
295
+ end
296
+
297
+ def explanatory_text(hsh, key)
298
+ hsh.has_key?(key) ? "#{hsh[key].dup.yellow} #{key}" : "all #{key.pluralize}"
299
+ end
300
+
301
+ def terminate(message)
302
+ raise message
303
+ end
304
+ end
305
+ end
@@ -0,0 +1,32 @@
1
+ require 'thor/group'
2
+
3
+ module Foreplay
4
+ class Setup < Thor::Group
5
+ include Thor::Actions
6
+
7
+ class_option :name, :aliases => '-n', :default => File.basename(Dir.getwd)
8
+ class_option :repository, :aliases => '-r'
9
+ class_option :user, :aliases => '-u'
10
+ class_option :password
11
+ class_option :keyfile
12
+ class_option :private_key, :aliases => '-k'
13
+ class_option :port, :aliases => '-p', :default => 50000
14
+ class_option :path, :aliases => '-f'
15
+ class_option :servers, :aliases => '-s', :type => :array
16
+ class_option :db_adapter, :aliases => '-a', :default => 'postgresql'
17
+ class_option :db_encoding, :aliases => '-e', :default => 'utf8'
18
+ class_option :db_pool, :default => 5
19
+ class_option :db_name, :aliases => '-d'
20
+ class_option :db_host, :aliases => '-h'
21
+ class_option :db_user
22
+ class_option :db_password
23
+
24
+ def self.source_root
25
+ File.dirname(__FILE__)
26
+ end
27
+
28
+ def config
29
+ template('setup/foreplay.yml', 'config/foreplay.yml')
30
+ end
31
+ end
32
+ end
@@ -43,8 +43,7 @@
43
43
  #
44
44
  # value Normally defined as Notes
45
45
  # ------------- -------------------- -------------------------------------------------------------------
46
- # name: Global default App name (if omitted then
47
- # Rails.application.class.parent_name.underscore is used)
46
+ # name: Global default App name (if omitted then current folder name is used)
48
47
  # user: Global default The username to connect with (must have SSH permissions)
49
48
  # password: Global default The password to use to connect (not necessary if you've set up SSH
50
49
  # keys - see below)
@@ -54,6 +53,9 @@
54
53
  # path: Global default An absolute path to deploy the app on each server. %a will be
55
54
  # translated to the application name. %u will be translated to the
56
55
  # login user name
56
+ # port: Global default The base port for the web app to listen on. Port 80 will be
57
+ # mapped to this port so it's usually OK to leave this at the
58
+ # default value of 50000.
57
59
  # database: Environment default The database.yml elements to write to the config folder
58
60
  # key: value
59
61
  # servers: [server1, server2, server3]
@@ -64,22 +66,22 @@
64
66
  # key: value
65
67
  #
66
68
  defaults:
67
- name: <%= @name %>
68
- repository: %q{TODO: Add the git repository path}
69
- user: %q{TODO: Add the user to logon to the deployment server}
70
- password: %q{TODO: Add the password for the user on the deployment server}
71
- path: %q{TODO: Add the path to deploy to on the deployment server}
69
+ name: <%= @options.name? ? @options.name : 'TODO Put the app name here' %>
70
+ repository: <%= @options.repository? ? @options.repository : 'TODO Put the git repository path here' %>
71
+ user: <%= @options.user? ? @options.user : 'TODO Put here the user to logon to the deployment server' %><%= @options.password? ? "\n password: #{@options.password}" : '' %><%= @options.keyfile? ? "\n keyfile: #{@options.keyfile}" : '' %><%= @options.private_key? ? "\n private_key: #{@options.private_key}" : '' %>
72
+ path: <%= @options.path? ? @options.path : 'TODO Put here the path to deploy to on the deployment server' %>
73
+ port: <%= @options.port %>
72
74
  production:
73
75
  defaults:
74
76
  database:
75
- adapter: postgresql
76
- encoding: utf8
77
- database: %q{TODO: Add the database name}
78
- pool: 5
79
- host: %q{TODO: Add the database host name}
80
- username: %q{TODO: Add the database user}
81
- password: %q{TODO: Add the database user's password}
77
+ adapter: <%= @options.db_adapter %>
78
+ encoding: <%= @options.db_encoding %>
79
+ database: <%= @options.db_name? ? @options.db_name : 'TODO Put the database name here' %>
80
+ pool: <%= @options.db_pool %>
81
+ host: <%= @options.db_host? ? @options.db_host : 'TODO Put here the database host name' %>
82
+ username: <%= @options.db_user? ? @options.db_user : 'TODO Put here the database user' %>
83
+ password: <%= @options.db_password? ? @options.db_password : 'TODO Put here the database user\'s password' %>
82
84
  web:
83
- servers: [%q{TODO: Add the name of the production web server}]
85
+ servers: <%= @options.servers? ? @options.servers : '[TODO Put here the name or names of the production web servers]' %>
84
86
  foreman:
85
87
  concurrency: 'web=1,worker=0,scheduler=0'
@@ -0,0 +1,29 @@
1
+ module Foreplay
2
+ class Utility
3
+ # Returns a new hash with +hash+ and +other_hash+ merged recursively, including arrays.
4
+ #
5
+ # h1 = { x: { y: [4,5,6] }, z: [7,8,9] }
6
+ # h2 = { x: { y: [7,8,9] }, z: 'xyz' }
7
+ # h1.supermerge(h2)
8
+ # #=> {:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]}
9
+ def self.supermerge(hash, other_hash)
10
+ raise 'supermerge only works if you pass two hashes' unless hash.is_a?(Hash) && other_hash.is_a?(Hash)
11
+
12
+ new_hash = hash.deep_dup.with_indifferent_access
13
+
14
+ other_hash.each_pair do |k,v|
15
+ tv = new_hash[k]
16
+
17
+ if tv.is_a?(Hash) && v.is_a?(Hash)
18
+ new_hash[k] = Foreplay::Utility::supermerge(tv, v)
19
+ elsif tv.is_a?(Array) || v.is_a?(Array)
20
+ new_hash[k] = Array.wrap(tv) + Array.wrap(v)
21
+ else
22
+ new_hash[k] = v
23
+ end
24
+ end
25
+
26
+ new_hash
27
+ end
28
+ end
29
+ end
@@ -1,3 +1,3 @@
1
1
  module Foreplay
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,153 @@
1
+ require 'spec_helper'
2
+ require 'foreplay'
3
+ require 'net/ssh/shell'
4
+
5
+ describe Foreplay::Deploy do
6
+ let (:ssh) { double(Net::SSH) }
7
+ let (:session) { double(Net::SSH::Connection::Session) }
8
+ let (:shell) { double(Net::SSH::Shell) }
9
+ let (:process) { double(Net::SSH::Shell::Process) }
10
+
11
+ before :each do
12
+ # Setup foreplay
13
+ `rm -f config/foreplay.yml`
14
+ `foreplay setup -r git@github.com:Xenapto/foreplay.git -s web.example.com -f apps/%a -u fred --password trollope`
15
+
16
+ # Stub all the things
17
+ Net::SSH.stub(:start).and_yield(session)
18
+ session.stub(:shell).and_yield(shell)
19
+ shell.stub(:execute).and_return(process)
20
+ shell.stub(:wait!).and_return(false)
21
+ process.stub(:on_output).and_yield(process, "output message\n")
22
+ process.stub(:exit_status).and_return(0)
23
+ end
24
+
25
+ it "should complain on check if there's no config file" do
26
+ `rm -f config/foreplay.yml`
27
+ expect { Foreplay::Deploy.start([:check, 'production', '']) }.to raise_error(RuntimeError, /.*Please run foreplay setup or create the file manually.*/)
28
+ end
29
+
30
+ it "should complain on deploy if there's no config file" do
31
+ `rm -f config/foreplay.yml`
32
+ expect { Foreplay::Deploy.start([:deploy, 'production', '']) }.to raise_error(RuntimeError, /.*Please run foreplay setup or create the file manually.*/)
33
+ end
34
+
35
+ it "should complain if there are no authentication methods defined in the config file" do
36
+ `rm -f config/foreplay.yml`
37
+ `foreplay setup -r git@github.com:Xenapto/foreplay.git -s web.example.com -f apps/%a -u fred --password ""`
38
+ expect { Foreplay::Deploy.start([:deploy, 'production', '']) }.to raise_error(RuntimeError, /.*No authentication methods supplied. You must supply a private key, key file or password in the configuration file*/)
39
+ end
40
+
41
+ it "should complain if the private keyfile defined in the config file doesn't exist" do
42
+ `rm -f config/foreplay.yml`
43
+ `foreplay setup -r git@github.com:Xenapto/foreplay.git -s web.example.com -f apps/%a -u fred --keyfile "/home/fred/no-such-file"`
44
+ expect { Foreplay::Deploy.start([:deploy, 'production', '']) }.to raise_error(Errno::ENOENT, /.*No such file or directory - \/home\/fred\/no-such-file*/)
45
+ end
46
+
47
+ it "should terminate if a remote process exits with a non-zero status" do
48
+ process.stub(:exit_status).and_return(1)
49
+ expect { Foreplay::Deploy.start([:deploy, 'production', '']) }.to raise_error(RuntimeError, /.*output message*/)
50
+ end
51
+
52
+ it "should terminate if a connection can't be established with the remote server" do
53
+ Net::SSH.unstub(:start)
54
+ expect { Foreplay::Deploy.start([:deploy, 'production', '']) }.to raise_error(RuntimeError, /.*There was a problem starting an ssh session on web.example.com*/)
55
+ end
56
+
57
+ it "should check the config" do
58
+ output = <<OUTPUT
59
+ Checking \e[0;33;49mproduction\e[0m environment, all roles, all servers
60
+ Checking \e[0;33;49mforeplay\e[0m for \e[0;33;49mweb.example.com\e[0m in the \e[0;33;49mweb\e[0m role on the \e[0;33;49mproduction\e[0m environment
61
+ No instance is currently deployed
62
+ \e[0;33;49mSetting the port for the new instance to 50000\e[0m
63
+ echo 50000 > .foreplay/current_port
64
+ \e[0;33;49mCloning repository git@github.com:Xenapto/foreplay.git\e[0m
65
+ mkdir -p apps/foreplay && cd apps/foreplay && rm -rf 50000 && git clone git@github.com:Xenapto/foreplay.git 50000
66
+ \e[0;33;49mTrusting the .rvmrc file for the new instance\e[0m
67
+ rvm rvmrc trust 50000
68
+ \e[0;33;49mConfiguring the new instance\e[0m
69
+ cd 50000
70
+ \e[0;33;49mBuilding .env\e[0m
71
+ \e[0;33;49mBuilding .foreman\e[0m
72
+ \e[0;33;49mBuilding config/database.yml\e[0m
73
+ \e[0;33;49mUsing bundler to install the required gems\e[0m
74
+ bundle install
75
+ \e[0;33;49mSetting the current version of foreman to be the default\e[0m
76
+ sudo ln -f `which foreman` /usr/bin/foreman
77
+ \e[0;33;49mConverting foreplay-50000 to an upstart service\e[0m
78
+ sudo foreman export upstart /etc/init
79
+ \e[0;33;49mStarting the service\e[0m
80
+ sudo start foreplay-50000 || sudo restart foreplay-50000
81
+ \e[0;33;49mWaiting 60s to give service time to start\e[0m
82
+ sleep 60
83
+ \e[0;33;49mAdding firewall rule to direct incoming traffic on port 80 to port 50000\e[0m
84
+ sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 50100
85
+ \e[0;33;49mRemoving previous firewall directing traffic to port 51000\e[0m
86
+ sudo iptables -t nat -D PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 51100
87
+ \e[0;33;49mSaving iptables rules to /etc/iptables/rules.v4\e[0m
88
+ sudo iptables-save > /etc/iptables/rules.v4
89
+ \e[0;33;49mSaving iptables rules to /etc/iptables.up.rules\e[0m
90
+ sudo iptables-save > /etc/iptables.up.rules
91
+ \e[0;33;49mCurrent firewall NAT configuration:\e[0m
92
+ sudo iptables-save -c | egrep REDIRECT --color=never
93
+ \e[0;33;49mStopping the previous instance\e[0m
94
+ sudo stop foreplay-51000 || echo 'No previous instance running'
95
+ Deployment configuration check was successful
96
+ OUTPUT
97
+
98
+ output.split("\n").reverse.each { |line| $stdout.should_receive(:puts).with(line) }
99
+ Foreplay::Deploy.start [:check, 'production', '']
100
+ end
101
+
102
+ it "should deploy to the environment" do
103
+ Net::SSH.should_receive(:start).with('web.example.com', 'fred', { :verbose => :warn, :password => 'trollope' }).and_yield(session)
104
+
105
+ [
106
+ 'mkdir -p .foreplay && touch .foreplay/current_port && cat .foreplay/current_port',
107
+ 'echo 50000 > .foreplay/current_port',
108
+ 'mkdir -p apps/foreplay && cd apps/foreplay && rm -rf 50000 && git clone git@github.com:Xenapto/foreplay.git 50000',
109
+ 'rvm rvmrc trust 50000',
110
+ 'cd 50000',
111
+ 'echo "RAILS_ENV=production" > .env',
112
+ 'echo "concurrency: web=1,worker=0,scheduler=0" > .foreman',
113
+ 'echo "app: foreplay-50000" >> .foreman',
114
+ 'echo "port: 50000" >> .foreman',
115
+ 'echo "user: fred" >> .foreman',
116
+ 'echo "production:" > config/database.yml',
117
+ 'echo " adapter: postgresql" >> config/database.yml',
118
+ 'echo " encoding: utf8" >> config/database.yml',
119
+ 'echo " database: TODO Put the database name here" >> config/database.yml',
120
+ 'echo " pool: 5" >> config/database.yml',
121
+ 'echo " host: TODO Put here the database host name" >> config/database.yml',
122
+ 'echo " username: TODO Put here the database user" >> config/database.yml',
123
+ 'echo " password: TODO Put here the database user\'s password" >> config/database.yml',
124
+ 'bundle install',
125
+ 'sudo ln -f `which foreman` /usr/bin/foreman',
126
+ 'sudo foreman export upstart /etc/init',
127
+ 'sudo start foreplay-50000 || sudo restart foreplay-50000',
128
+ 'sleep 60',
129
+ 'sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 50100',
130
+ 'sudo iptables -t nat -D PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 51100',
131
+ 'sudo iptables-save > /etc/iptables/rules.v4',
132
+ 'sudo iptables-save > /etc/iptables.up.rules',
133
+ 'sudo iptables-save -c | egrep REDIRECT --color=never',
134
+ 'sudo stop foreplay-51000 || echo \'No previous instance running\''
135
+ ].each do |command|
136
+ shell.should_receive(:execute).with(command).and_return(process)
137
+ end
138
+
139
+ Foreplay::Deploy.start [:deploy, 'production', '']
140
+ end
141
+
142
+ it "should use another port if there's already an installed instance" do
143
+ process.stub(:on_output).and_yield(process, "50000\n")
144
+ shell.should_receive(:execute).with('echo 51000 > .foreplay/current_port').and_return(process)
145
+ Foreplay::Deploy.start [:deploy, 'production', '']
146
+ end
147
+
148
+ it "should use the private key provided in the config file" do
149
+ `rm -f config/foreplay.yml`
150
+ `foreplay setup -r git@github.com:Xenapto/foreplay.git -s web.example.com -f apps/%a -u fred -k "top secret private key"`
151
+ Foreplay::Deploy.start([:deploy, 'production', ''])
152
+ end
153
+ end