foreplay 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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