foreplay 0.0.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/.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