foreplay 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,34 @@
1
+ *.rbc
2
+ *.sassc
3
+ .sass-cache
4
+ capybara-*.html
5
+ .rspec
6
+ /.bundle
7
+ /vendor/bundle
8
+ /log/*
9
+ /tmp/*
10
+ /db/*.sqlite3
11
+ /public/system/*
12
+ /coverage/
13
+ /spec/tmp/*
14
+ **.orig
15
+ rerun.txt
16
+ pickle-email-*.html
17
+
18
+ *.gem
19
+ .bundle
20
+ .config
21
+ .yardoc
22
+ Gemfile.lock
23
+ InstalledFiles
24
+ _yardoc
25
+ coverage
26
+ doc/
27
+ lib/bundler/man
28
+ pkg
29
+ rdoc
30
+ spec/reports
31
+ test/tmp
32
+ test/version_tmp
33
+ tmp
34
+ config
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in foreplay.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Dominic Sayers
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # Foreplay
2
+
3
+ Foreplay: deploying Rails projects to Ubuntu using Foreman
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'foreplay'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install foreplay
18
+
19
+ ## Usage
20
+
21
+ ENV=production rake foreplay:push
22
+
23
+ or
24
+
25
+ ENV=production ROLE=web rake foreplay:push
26
+
27
+ You can set the environment variables `ENV` and `ROLE` elsewhere if you wish.
28
+ If `ROLE` is not defined then we deploy all roles.
29
+
30
+ ### How it works
31
+
32
+ Foreplay does this:
33
+
34
+ 1. Opens an SSH connection to the deloyment target
35
+ 2. Grabs a copy of your code from the repository
36
+ 3. Builds a `.env` file, a `.foreman` file and a `database.yml` file
37
+ 4. Does a `bundle install`
38
+ 5. Uses `foreman` to create an Upstart service (`foreman export`) for your app
39
+ 6. Launches the app
40
+ 7. Directs incoming traffic on port 80 to your app
41
+ 8. If there's a previous instance of the app running, Foreplay shuts it down gracefully after it has switched `iptables` to the new instance
42
+
43
+ There should be little or no downtime. If the app is b0rked then you can easily switch back to the previous instance: the Upstart service is still configured.
44
+
45
+ ### foreplay.yml
46
+
47
+ Format:
48
+
49
+ ```YAML
50
+ defaults: # global defaults for all environments
51
+ name: # app name (if omitted then Rails.application.class.parent_name.underscore is used)
52
+ servers: [server1, server2, server3] # which servers to deploy the app on
53
+ user: # The username to connect with (must have SSH permissions)
54
+ password: # The password to use to connect (not necessary if you've set up SSH keys)
55
+ keyfile: # ...or a file containing a private key that allows the named user access to the server
56
+ key: # ...or a private key that allows the named user access to the server
57
+ path: # absolute path to deploy the app on each server. %s will be translated to the application name
58
+ database: # the database.yml elements to write to the config folder
59
+ env: # contents of the .env file
60
+ key: value # will go into the .env file as key=value
61
+ foreman: # contents of the .foreman file
62
+ key: value # will go into the .foreman file as key: value
63
+ production: # deployment configuration for the production environment
64
+ defaults: # defaults for all roles in this environment (structure same as global defaults)
65
+ role1: # settings for the a particular role (e.g. web, worker, etc.) (structure same as global defaults)
66
+ ```
67
+
68
+ ### Environment
69
+
70
+ Settings for the `.env` files and `.foreman` files in specific sections will add to the defaults specified earlier. `.env` files will get a `RAILS_ENV=environment` entry (where `environment` is as specified in `foreplay.yml`). You can override this by adding a different `RAILS_ENV` setting to this configuration here.
71
+
72
+ The first instance of the first entry in `Procfile` that is instantiated by your Foreman concurrency settings will
73
+ be started on port 50100 or 51100 and the external port 80 will be mapped to this port by `iptables`. You cannot
74
+ configure the ports yourself. As an example, if your `Procfile` has a `web` entry on the first line and at
75
+ least one `web` instance is configured in the `.foreman` concurrency setting then the first instance of your `web`
76
+ process will be available to the outside world on port 80.
77
+
78
+ ### Path
79
+
80
+ You can use `%u` in the path. This will be substituted with the `user` value. You can use `%a` in the path. This will be substituted with the app's `name`
81
+
82
+ Example:
83
+
84
+ user: fred
85
+ name: myapp
86
+ path: /home/%u/apps/%a
87
+
88
+ ### Dependencies
89
+
90
+ ```ruby
91
+ gem 'foreman'
92
+ gem 'net-ssh-shell'
93
+ ```
94
+
95
+ You can constrain this to whatever groups you use for initiating deployments, e.g.
96
+
97
+ ```ruby
98
+ group :development, :test do
99
+ gem 'foreman'
100
+ gem 'net-ssh-shell'
101
+ end
102
+ ```
103
+
104
+ ## Contributing
105
+
106
+ 1. Fork it
107
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
108
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
109
+ 4. Push to the branch (`git push origin my-new-feature`)
110
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/foreplay ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'foreplay/cli'
3
+ Foreplay::CLI.start
@@ -0,0 +1,66 @@
1
+ Feature: Config
2
+ In order to configure Foreplay
3
+ As a CLI
4
+ I want to be as usable as possible
5
+
6
+ Scenario: Check configuration
7
+ When I run `foreplay check`
8
+ Then the output should contain "OK"
9
+
10
+ Scenario: Check configuration parameters - invalid parameter
11
+ When I run `foreplay check --invalid xyz`
12
+ Then the output should contain:
13
+ """
14
+ ERROR: foreplay check was called with arguments ["--invalid", "xyz"]
15
+ Usage: "foreplay check".
16
+ """
17
+
18
+ Scenario: Check configuration parameters - short invalid parameter
19
+ When I run `foreplay check -x xyz`
20
+ Then the output should contain:
21
+ """
22
+ ERROR: foreplay check was called with arguments ["-x", "xyz"]
23
+ Usage: "foreplay check".
24
+ """
25
+
26
+ Scenario: Check configuration parameters - environment parameter
27
+ When I run `foreplay check --environment production`
28
+ Then the output should contain "Checking configuration for"
29
+ And the output should contain "production environment"
30
+ And the output should contain "all roles"
31
+ And the output should contain "all servers"
32
+
33
+ Scenario: Check configuration parameters - role parameter
34
+ When I run `foreplay check --role worker`
35
+ Then the output should contain "Checking configuration for"
36
+ And the output should contain "all environments"
37
+ And the output should contain "worker role"
38
+ And the output should contain "all servers"
39
+
40
+ Scenario: Check configuration parameters - server parameter
41
+ When I run `foreplay check --server worker.example.com`
42
+ Then the output should contain "Checking configuration for"
43
+ And the output should contain "all environments"
44
+ And the output should contain "all roles"
45
+ And the output should contain "worker.example.com server"
46
+
47
+ Scenario: Check configuration parameters - short environment parameter
48
+ When I run `foreplay check -e production`
49
+ Then the output should contain "Checking configuration for"
50
+ And the output should contain "production environment"
51
+ And the output should contain "all roles"
52
+ And the output should contain "all servers"
53
+
54
+ Scenario: Check configuration parameters - short role parameter
55
+ When I run `foreplay check -r worker`
56
+ Then the output should contain "Checking configuration for"
57
+ And the output should contain "all environments"
58
+ And the output should contain "worker role"
59
+ And the output should contain "all servers"
60
+
61
+ Scenario: Check configuration parameters - short server parameter
62
+ When I run `foreplay check -s worker.example.com`
63
+ Then the output should contain "Checking configuration for"
64
+ And the output should contain "all environments"
65
+ And the output should contain "all roles"
66
+ And the output should contain "worker.example.com server"
@@ -0,0 +1,97 @@
1
+ Feature: Setup
2
+ In order to setup Foreplay
3
+ As a CLI user
4
+ I want to be able to create the config scaffold
5
+
6
+ Scenario: Setup
7
+ When I run `foreplay setup`
8
+ Then the following files should exist:
9
+ | config/foreplay.yml |
10
+ And the file "config/foreplay.yml" should contain:
11
+ """
12
+ # Format:
13
+ #
14
+ # There is a section for each environment that you will deploy to, plus a section that defines global default
15
+ # values for all environments, like this:
16
+ #
17
+ # defaults:
18
+ # ...
19
+ # production:
20
+ # ...
21
+ # staging:
22
+ # ...
23
+ #
24
+ # Within each section you can define the server roles for that environment: web, worker, database etc. (the
25
+ # names of these roles are up to you). You can also define environment-level defaults that apply to all roles.
26
+ # Like this:
27
+ #
28
+ # production:
29
+ # defaults:
30
+ # ...
31
+ # web:
32
+ # ...
33
+ # worker:
34
+ # ...
35
+ # scheduler:
36
+ # ...
37
+ # database:
38
+ # ...
39
+ # staging:
40
+ # defaults:
41
+ # ...
42
+ # web:
43
+ # ...
44
+ # worker:
45
+ # ...
46
+ # scheduler:
47
+ # ...
48
+ # database:
49
+ # ...
50
+ #
51
+ # Within each role section you can define how the deployment is configured for the servers in that role.
52
+ # Some of these values will normally be defined as a default, some will be specific to a particular role.
53
+ # The values you can configure are as follows:
54
+ #
55
+ # value Normally defined as Notes
56
+ # ------------- -------------------- -------------------------------------------------------------------
57
+ # name: Global default App name (if omitted then
58
+ # Rails.application.class.parent_name.underscore is used)
59
+ # user: Global default The username to connect with (must have SSH permissions)
60
+ # password: Global default The password to use to connect (not necessary if you've set up SSH
61
+ # keys - see below)
62
+ # keyfile: Global default A file containing a private key that allows the named user access
63
+ # to the server, or...
64
+ # key: Global default A private key that allows the named user access to the server
65
+ # path: Global default An absolute path to deploy the app on each server. %a will be
66
+ # translated to the application name. %u will be translated to the
67
+ # login user name
68
+ # database: Environment default The database.yml elements to write to the config folder
69
+ # key: value
70
+ # servers: [server1, server2, server3]
71
+ # Role level Which servers to deploy the app on
72
+ # env: Role level Contents of the .env file
73
+ # key: value Values will go into the .env file as key=value
74
+ # foreman: Role level Contents of the .foreman file
75
+ # key: value
76
+ #
77
+ defaults:
78
+ name: %q{TODO: Add the app name}
79
+ repository: %q{TODO: Add the git repository path}
80
+ user: %q{TODO: Add the user to logon to the deployment server}
81
+ password: %q{TODO: Add the password for the user on the deployment server}
82
+ path: %q{TODO: Add the path to deploy to on the deployment server}
83
+ production:
84
+ defaults:
85
+ database:
86
+ adapter: postgresql
87
+ encoding: utf8
88
+ database: %q{TODO: Add the database name}
89
+ pool: 5
90
+ host: %q{TODO: Add the database host name}
91
+ username: %q{TODO: Add the database user}
92
+ password: %q{TODO: Add the database user's password}
93
+ web:
94
+ servers: [%q{TODO: Add the name of the production web server}]
95
+ foreman:
96
+ concurrency: 'web=1,worker=0,scheduler=0'
97
+ """
@@ -0,0 +1 @@
1
+ require 'aruba/cucumber'
data/foreplay.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'foreplay/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "foreplay"
7
+ spec.version = Foreplay::VERSION
8
+ spec.authors = ["Xenapto"]
9
+ spec.email = ["developers@xenapto.com"]
10
+ spec.description = %q{Deploying Rails projects to Ubuntu using Foreman}
11
+ spec.summary = %q{Example: foreplay push to production}
12
+ spec.homepage = "https://github.com/Xenapto/foreplay"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency 'activesupport'
21
+ spec.add_dependency 'colorize'
22
+ spec.add_dependency 'hashie'
23
+ spec.add_dependency 'foreman'
24
+ spec.add_dependency 'net-ssh-shell'
25
+ spec.add_dependency 'thor'
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.3"
28
+ spec.add_development_dependency "rake"
29
+ spec.add_development_dependency "rspec", "~> 2.6"
30
+ spec.add_development_dependency "cucumber"
31
+ spec.add_development_dependency "aruba"
32
+ spec.add_development_dependency "gem-release"
33
+ end
data/foreplay.rake ADDED
@@ -0,0 +1,28 @@
1
+ # Syntax:
2
+ #
3
+ # rake ENV=production [ROLE=web] foreplay:command
4
+ #
5
+ # You can set the environment variables ENV and ROLE elsewhere if you wish.
6
+ # If ROLE is not defined then we deploy all roles.
7
+ #
8
+ # Dependencies:
9
+ #
10
+ # gem 'net-ssh-shell'
11
+ #
12
+ # You can constrain this to whatever group you use for initiating deployments, e.g.
13
+ #
14
+ # group :development do
15
+ # gem 'net-ssh-shell'
16
+ # end
17
+
18
+ namespace :foreplay do
19
+ desc 'Push app to deployment targets'
20
+ task :push => :environment do
21
+ Foreplay::push
22
+ end
23
+
24
+ desc 'Check deployment configuration'
25
+ task :check => :environment do
26
+ Foreplay::push true
27
+ end
28
+ end
data/foreplay.rb ADDED
@@ -0,0 +1,246 @@
1
+ # encoding: utf-8
2
+ require 'yaml'
3
+ require 'net/ssh'
4
+
5
+ class Foreplay
6
+ class << self
7
+ def push check_only = false
8
+ environment = ENV['ENV']
9
+ config_file = "#{Rails.root}/config/foreplay.yml"
10
+ config_all = YAML.load(File.read(config_file))
11
+
12
+ # This environment
13
+ raise RuntimeError, "No deployment environment defined. Set the ENV environment variable." if environment.blank?
14
+ raise RuntimeError, "No deployment configuration defined for #{environment} environment. Check #{config_file}" unless config_all.has_key? environment
15
+ config = config_all[environment]
16
+
17
+ # Establish defaults
18
+ # First the default defaults
19
+ defaults = {
20
+ 'name' => Rails.application.class.parent_name.underscore,
21
+ 'environment' => environment,
22
+ 'env' => { 'RAILS_ENV' => environment }
23
+ }
24
+
25
+ defaults = deep_merge_with_arrays(defaults, config_all['defaults']) if config_all.has_key? 'defaults' # Then the global defaults
26
+ defaults = deep_merge_with_arrays(defaults, config['defaults']) if config.has_key? 'defaults' # Then the defaults for this environment
27
+
28
+ config.each do |role, additional_instructions|
29
+ next if role == 'defaults' # 'defaults' is not a role
30
+ next unless ENV['ROLE'].blank? || ENV['ROLE'] == role # Only deploy to the role we've specified (or all roles if none is specified)
31
+
32
+ instructions = deep_merge_with_arrays(defaults, additional_instructions).symbolize_keys
33
+ instructions[:role] = role
34
+ required_keys = [:name, :environment, :role, :servers, :path, :repository]
35
+
36
+ required_keys.each { |key| raise RuntimeError, "Required key #{key} not found in instructions for #{environment} environment. Check #{config_file}" unless instructions.has_key? key }
37
+
38
+ deploy_role instructions unless check_only
39
+ end
40
+
41
+ puts check_only ? 'Deployment configuration check was successful' : 'Finished deployment'
42
+ end
43
+
44
+ def deploy_role instructions
45
+ servers = instructions[:servers]
46
+ puts "Deploying #{instructions[:name]} to #{servers.join(', ')} for the #{instructions[:role]} role in the #{instructions[:environment]} environment..." if servers.length > 1
47
+ servers.each { |server| deploy_to_server server, instructions }
48
+ end
49
+
50
+ def deploy_to_server server, instructions
51
+ name = instructions[:name]
52
+ environment = instructions[:environment]
53
+ role = instructions[:role]
54
+ path = instructions[:path]
55
+ repository = instructions[:repository]
56
+ user = instructions[:user]
57
+
58
+ instructions[:server] = server
59
+
60
+ puts "Deploying #{name} to #{server} for the #{role} role in the #{environment} environment"
61
+
62
+ # Substitute variables in the path
63
+ path.sub! '%u', user
64
+ path.sub! '%a', name
65
+
66
+ # Find out which port we're currently running on
67
+ steps = [ { :command => 'mkdir -p .foreplay && touch .foreplay/current_port && cat .foreplay/current_port', :silent => true } ]
68
+
69
+ current_port = execute_on_server(steps, instructions).strip!
70
+ puts "Current instance is using port #{current_port}"
71
+
72
+ # Switch ports
73
+ if current_port == '50000'
74
+ current_port = '51000'
75
+ former_port = '50000'
76
+ else
77
+ current_port = '50000'
78
+ former_port = '51000'
79
+ end
80
+
81
+ # Contents of .foreman file
82
+ current_service = '%s-%s' % [name, current_port]
83
+ former_service = '%s-%s' % [name, former_port]
84
+
85
+ instructions[:foreman]['app'] = current_service
86
+ instructions[:foreman]['port'] = current_port
87
+ instructions[:foreman]['user'] = user
88
+
89
+ # Commands to execute on remote server
90
+ steps = [
91
+ { :command => "echo #{current_port} > .foreplay/current_port" },
92
+ { :command => "mkdir -p #{path} && cd #{path} && rm -rf #{current_port} && git clone #{repository} #{current_port}",
93
+ :commentary => "Cloning repository #{repository}" },
94
+ { :command => "rvm rvmrc trust #{current_port}" },
95
+ { :command => "cd #{current_port}" },
96
+ { :key => :env,
97
+ :delimiter => '=',
98
+ :prefix => '.',
99
+ :commentary => 'Building .env' },
100
+ { :key => :foreman,
101
+ :delimiter => ': ',
102
+ :prefix => '.',
103
+ :commentary => 'Building .foreman' },
104
+ { :key => :database,
105
+ :delimiter => ': ',
106
+ :suffix => '.yml',
107
+ :commentary => 'Building config/database.yml',
108
+ :before => ' ',
109
+ :header => "#{environment}:",
110
+ :path => 'config/' },
111
+ { :command => "bundle install" },
112
+ { :command => "sudo ln -f `which foreman` /usr/bin/foreman" },
113
+ { :command => "sudo foreman export upstart /etc/init" },
114
+ { :command => "sudo start #{current_service} || sudo restart #{current_service}",
115
+ :ignore_error => true },
116
+ { :command => 'sleep 60',
117
+ :commentary => 'Waiting 60s to give service time to start' },
118
+ { :command => "sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{current_port.to_i + 100}",
119
+ :commentary => "Adding firewall rule to direct incoming traffic on port 80 to port #{current_port}" },
120
+ { :command => "sudo iptables -t nat -D PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{former_port.to_i + 100}",
121
+ :commentary => "Removing previous firewall directing traffic to port #{former_port}",
122
+ :ignore_error => true },
123
+ { :command => "sudo iptables -t nat -L | grep REDIRECT",
124
+ :ignore_error => true,
125
+ :commentary => "Current firewall NAT configuration:" },
126
+ { :command => "sudo stop #{former_service} || echo 'No previous instance running'",
127
+ :ignore_error => true },
128
+ ]
129
+
130
+ execute_on_server steps, instructions
131
+ end
132
+
133
+ def execute_on_server steps, instructions
134
+ server = instructions[:server]
135
+ user = instructions[:user]
136
+ password = instructions[:password]
137
+ keyfile = instructions[:keyfile]
138
+ key = instructions[:key]
139
+
140
+ keyfile.sub! '~' , ENV['HOME'] # Remote shell won't expand this for us
141
+
142
+ # SSH authentication methods
143
+ options = { :verbose => :warn }
144
+
145
+ if password.blank?
146
+ # If there's no password we must supply a private key
147
+ if key.blank?
148
+ raise RuntimeError, "No authentication methods supplied. You must supply a private key, key file or password in the configuration file" if keyfile.blank?
149
+ # Get the key from the key file
150
+ puts "Using private key from #{keyfile}"
151
+ key = File.read keyfile
152
+ else
153
+ puts "Using private key from the configuration file"
154
+ end
155
+
156
+ options[:key_data] = [key]
157
+ else
158
+ # Use the password supplied
159
+ options[:password] = password
160
+ end
161
+
162
+ # Capture output of last command to return to the calling routine
163
+ output = ''
164
+
165
+ # SSH connection
166
+ Net::SSH.start(server, user, options) do |ssh|
167
+ puts "Successfully connected to #{server}"
168
+
169
+ ssh.shell do |sh|
170
+ steps.each do |step|
171
+ # Output from this step
172
+ output = ''
173
+ previous = '' # We don't need or want the final CRLF
174
+
175
+ puts step[:commentary] || step[:command] unless step[:silent] == true
176
+
177
+ # Each step can be (1) a command or (2) a series of values to add to a file
178
+ if step.has_key? :key
179
+ step[:silent] = true
180
+
181
+ # Add values from the config file to a file on the remote machine
182
+ key = step[:key]
183
+ prefix = step[:prefix] || ''
184
+ suffix = step[:suffix] || ''
185
+ path = step[:path] || ''
186
+ before = step[:before] || ''
187
+ delimiter = step[:delimiter] || ''
188
+ after = step[:after] || ''
189
+
190
+ filename = '%s%s%s%s' % [path, prefix, key, suffix]
191
+ commands = step.has_key?(:header) ? ['echo "%s" >> %s' % [step[:header], filename]] : []
192
+
193
+ instructions[key].each { |k, v| commands << 'echo "%s%s%s%s%s" >> %s' % [before, k, delimiter, v, after, filename] }
194
+ else
195
+ # ...or just execute the command specified
196
+ commands = [step[:command]]
197
+ end
198
+
199
+ commands.each do |command|
200
+ process = sh.execute command
201
+
202
+ process.on_output do |p, o|
203
+ previous = o
204
+ output += previous
205
+ end
206
+
207
+ sh.wait!
208
+
209
+ if step[:ignore_error] == true || process.exit_status == 0
210
+ print "#{output}" unless step[:silent] == true
211
+ else
212
+ raise RuntimeError, output
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ output
220
+ end
221
+
222
+ # Returns a new hash with +hash+ and +other_hash+ merged recursively, including arrays.
223
+ #
224
+ # h1 = { x: { y: [4,5,6] }, z: [7,8,9] }
225
+ # h2 = { x: { y: [7,8,9] }, z: 'xyz' }
226
+ # h1.deep_merge_with_arrays(h2)
227
+ # #=> {:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]}
228
+ def deep_merge_with_arrays(hash, other_hash)
229
+ new_hash = hash.deep_dup
230
+
231
+ other_hash.each_pair do |k,v|
232
+ tv = new_hash[k]
233
+
234
+ if tv.is_a?(Hash) && v.is_a?(Hash)
235
+ new_hash[k] = deep_merge_with_arrays(tv, v)
236
+ elsif tv.is_a?(Array) || v.is_a?(Array)
237
+ new_hash[k] = Array.wrap(tv) + Array.wrap(v)
238
+ else
239
+ new_hash[k] = v
240
+ end
241
+ end
242
+
243
+ new_hash
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,23 @@
1
+ require 'thor'
2
+ require 'foreplay'
3
+ require 'foreplay/generators/setup'
4
+
5
+ module Foreplay
6
+ class CLI < Thor
7
+ desc 'check', 'Checks if configuration is OK'
8
+
9
+ method_option :environment, :aliases => "-e"
10
+ method_option :role, :aliases => "-r"
11
+ method_option :server, :aliases => "-s"
12
+
13
+ def check
14
+ puts Foreplay::Config.check options
15
+ end
16
+
17
+ desc 'setup', 'Create the Foreplay config file'
18
+
19
+ def setup
20
+ Foreplay::Generators::Setup.start
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ require 'active_support/inflector'
2
+ require 'colorize'
3
+ require 'hashie/mash'
4
+
5
+ module Foreplay
6
+ class Config
7
+ def self.check *args
8
+ options = Hashie::Mash[args.first]
9
+
10
+ # Explain what we're going to do
11
+ environments = explanatory_text options.environment, 'environment'
12
+ roles = explanatory_text options.role, 'role'
13
+ servers = explanatory_text options.server, 'server'
14
+ puts 'Checking configuration for %s, %s, %s' % [environments, roles, servers]
15
+
16
+ 'Not finished'
17
+ end
18
+
19
+ private
20
+
21
+ def self.explanatory_text(value, singular_word)
22
+ value.nil? ? "all #{singular_word.pluralize}" : "#{value.dup.yellow} #{singular_word}"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,85 @@
1
+ # Format:
2
+ #
3
+ # There is a section for each environment that you will deploy to, plus a section that defines global default
4
+ # values for all environments, like this:
5
+ #
6
+ # defaults:
7
+ # ...
8
+ # production:
9
+ # ...
10
+ # staging:
11
+ # ...
12
+ #
13
+ # Within each section you can define the server roles for that environment: web, worker, database etc. (the
14
+ # names of these roles are up to you). You can also define environment-level defaults that apply to all roles.
15
+ # Like this:
16
+ #
17
+ # production:
18
+ # defaults:
19
+ # ...
20
+ # web:
21
+ # ...
22
+ # worker:
23
+ # ...
24
+ # scheduler:
25
+ # ...
26
+ # database:
27
+ # ...
28
+ # staging:
29
+ # defaults:
30
+ # ...
31
+ # web:
32
+ # ...
33
+ # worker:
34
+ # ...
35
+ # scheduler:
36
+ # ...
37
+ # database:
38
+ # ...
39
+ #
40
+ # Within each role section you can define how the deployment is configured for the servers in that role.
41
+ # Some of these values will normally be defined as a default, some will be specific to a particular role.
42
+ # The values you can configure are as follows:
43
+ #
44
+ # value Normally defined as Notes
45
+ # ------------- -------------------- -------------------------------------------------------------------
46
+ # name: Global default App name (if omitted then
47
+ # Rails.application.class.parent_name.underscore is used)
48
+ # user: Global default The username to connect with (must have SSH permissions)
49
+ # password: Global default The password to use to connect (not necessary if you've set up SSH
50
+ # keys - see below)
51
+ # keyfile: Global default A file containing a private key that allows the named user access
52
+ # to the server, or...
53
+ # key: Global default A private key that allows the named user access to the server
54
+ # path: Global default An absolute path to deploy the app on each server. %a will be
55
+ # translated to the application name. %u will be translated to the
56
+ # login user name
57
+ # database: Environment default The database.yml elements to write to the config folder
58
+ # key: value
59
+ # servers: [server1, server2, server3]
60
+ # Role level Which servers to deploy the app on
61
+ # env: Role level Contents of the .env file
62
+ # key: value Values will go into the .env file as key=value
63
+ # foreman: Role level Contents of the .foreman file
64
+ # key: value
65
+ #
66
+ 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}
72
+ production:
73
+ defaults:
74
+ 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}
82
+ web:
83
+ servers: [%q{TODO: Add the name of the production web server}]
84
+ foreman:
85
+ concurrency: 'web=1,worker=0,scheduler=0'
@@ -0,0 +1,20 @@
1
+ require 'thor/group'
2
+
3
+ module Foreplay
4
+ module Generators
5
+ class Setup < Thor::Group
6
+ include Thor::Actions
7
+
8
+ Rails ||= nil
9
+
10
+ def self.source_root
11
+ File.dirname(__FILE__)
12
+ end
13
+
14
+ def create_config_file
15
+ @name = Rails.nil? ? '%q{TODO: Add the app name}' : Rails.application.class.parent_name.underscore
16
+ template('foreplay.yml', 'config/foreplay.yml')
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Foreplay
2
+ VERSION = "0.0.1"
3
+ end
data/lib/foreplay.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "foreplay/version"
2
+ require "foreplay/config"
3
+
4
+ module Foreplay
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,7 @@
1
+ require 'foreplay'
2
+
3
+ describe Foreplay::Config do
4
+ it "should check the config" do
5
+ Foreplay::Config.check.should eql('OK')
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,268 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: foreplay
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Xenapto
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: colorize
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: hashie
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: foreman
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: net-ssh-shell
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: thor
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: bundler
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: '1.3'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: '1.3'
126
+ - !ruby/object:Gem::Dependency
127
+ name: rake
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: rspec
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ~>
148
+ - !ruby/object:Gem::Version
149
+ version: '2.6'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ~>
156
+ - !ruby/object:Gem::Version
157
+ version: '2.6'
158
+ - !ruby/object:Gem::Dependency
159
+ name: cucumber
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ - !ruby/object:Gem::Dependency
175
+ name: aruba
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ! '>='
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ type: :development
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ - !ruby/object:Gem::Dependency
191
+ name: gem-release
192
+ requirement: !ruby/object:Gem::Requirement
193
+ none: false
194
+ requirements:
195
+ - - ! '>='
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ type: :development
199
+ prerelease: false
200
+ version_requirements: !ruby/object:Gem::Requirement
201
+ none: false
202
+ requirements:
203
+ - - ! '>='
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ description: Deploying Rails projects to Ubuntu using Foreman
207
+ email:
208
+ - developers@xenapto.com
209
+ executables:
210
+ - foreplay
211
+ extensions: []
212
+ extra_rdoc_files: []
213
+ files:
214
+ - .gitignore
215
+ - Gemfile
216
+ - LICENSE.txt
217
+ - README.md
218
+ - Rakefile
219
+ - bin/foreplay
220
+ - features/config.feature
221
+ - features/generator.feature
222
+ - features/support/setup.rb
223
+ - foreplay.gemspec
224
+ - foreplay.rake
225
+ - foreplay.rb
226
+ - lib/foreplay.rb
227
+ - lib/foreplay/cli.rb
228
+ - lib/foreplay/config.rb
229
+ - lib/foreplay/generators/foreplay.yml
230
+ - lib/foreplay/generators/setup.rb
231
+ - lib/foreplay/version.rb
232
+ - spec/foreplay_spec.rb
233
+ homepage: https://github.com/Xenapto/foreplay
234
+ licenses:
235
+ - MIT
236
+ post_install_message:
237
+ rdoc_options: []
238
+ require_paths:
239
+ - lib
240
+ required_ruby_version: !ruby/object:Gem::Requirement
241
+ none: false
242
+ requirements:
243
+ - - ! '>='
244
+ - !ruby/object:Gem::Version
245
+ version: '0'
246
+ segments:
247
+ - 0
248
+ hash: -929256133810576486
249
+ required_rubygems_version: !ruby/object:Gem::Requirement
250
+ none: false
251
+ requirements:
252
+ - - ! '>='
253
+ - !ruby/object:Gem::Version
254
+ version: '0'
255
+ segments:
256
+ - 0
257
+ hash: -929256133810576486
258
+ requirements: []
259
+ rubyforge_project:
260
+ rubygems_version: 1.8.25
261
+ signing_key:
262
+ specification_version: 3
263
+ summary: ! 'Example: foreplay push to production'
264
+ test_files:
265
+ - features/config.feature
266
+ - features/generator.feature
267
+ - features/support/setup.rb
268
+ - spec/foreplay_spec.rb