capistrano-puma-a 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/CHANGELOG.md +40 -0
- data/CONTRIBUTORS.md +45 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +128 -0
- data/Rakefile +1 -0
- data/capistrano-puma-a.gemspec +24 -0
- data/lib/capistrano/puma.rb +2 -0
- data/lib/capistrano/puma/jungle.rb +2 -0
- data/lib/capistrano/puma/monit.rb +2 -0
- data/lib/capistrano/puma/nginx.rb +1 -0
- data/lib/capistrano/puma/version.rb +5 -0
- data/lib/capistrano/puma/workers.rb +2 -0
- data/lib/capistrano/tasks/jungle.rake +81 -0
- data/lib/capistrano/tasks/monit.rake +72 -0
- data/lib/capistrano/tasks/nginx.rake +25 -0
- data/lib/capistrano/tasks/puma.rake +197 -0
- data/lib/capistrano/tasks/workers.rake +38 -0
- data/lib/capistrano/templates/nginx_conf.erb +82 -0
- data/lib/capistrano/templates/puma-deb.erb +336 -0
- data/lib/capistrano/templates/puma-rpm.erb +328 -0
- data/lib/capistrano/templates/puma.rb.erb +48 -0
- data/lib/capistrano/templates/puma_monit.conf.erb +7 -0
- data/lib/capistrano/templates/run-puma.erb +9 -0
- data/lib/capistrano3-puma.rb +0 -0
- data/lib/generators/capistrano/nginx_puma/USAGE +9 -0
- data/lib/generators/capistrano/nginx_puma/config_generator.rb +21 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1a88362a8db972f708d5fb507140a5a6c4a0c214
|
4
|
+
data.tar.gz: 07574feef309763f8376affb71e4dd35ae437e29
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 05306a6bfa0e12e4e8a5c1352a404e7c707c37971dd30aef61131949691977eb5cae33041aee5f32763e4e9df3198addb7d044887d52a35bb711a422fb267776
|
7
|
+
data.tar.gz: 9b3094c815ed1d850fda379c2a209398ad2e164a2f56e4fba5d3ac0b0e1cacf3af0b711ca9287c0e733daf5d5da7ff835f4316f5ae4c22fe0fc71afd1962629d
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
## Changelog
|
2
|
+
- 2.0.0:
|
3
|
+
- Require puma 3.4+
|
4
|
+
- Require Capistrano 3.5+
|
5
|
+
- Require capistrano-bundler
|
6
|
+
|
7
|
+
- 1.2.0: add support for puma user for puma user @mcb & @seuros
|
8
|
+
- 1.1.0: Set :puma_preload_app to false; Reload Monit after uploading any monit configuration; Always refresh Gemfile @rafaelgoulart @suhailpatel @sime
|
9
|
+
- 1.0.0: Add activate control app @askagirl
|
10
|
+
- 0.8.5: Fix smart_restart task to check if puma preloads app
|
11
|
+
- 0.8.4: Allow patch method (Nginx template) @lonre
|
12
|
+
- 0.8.2: Start task creates a conf file if none exists @stevemadere
|
13
|
+
- 0.8.1: Fixed nginx task @hnatt, support for prune_bundler @behe
|
14
|
+
- 0.8.0: Some changes
|
15
|
+
- 0.7.0: added Nginx template generator @dfang
|
16
|
+
- 0.6.1: added :puma_default_hooks, you can turn off the automatic hooks by setting it false
|
17
|
+
- 0.6.0: Remove `daemonize true` from default puma.rb file. Explicitly pass `--daemon` flag when needed.
|
18
|
+
- 0.5.1: Added worker_timeout option
|
19
|
+
- 0.5.0: Bugs fixes
|
20
|
+
- 0.4.2: Fix monit template to support chruby
|
21
|
+
- 0.4.1: Fix puma jungle (debian)
|
22
|
+
- 0.4.0: Multi-bind support
|
23
|
+
- 0.3.7: Dependency bug fix
|
24
|
+
- 0.3.5: Fixed a prehistoric bug
|
25
|
+
- 0.3.4: I don't remember what i did here
|
26
|
+
- 0.3.3: Puma jungle start fix
|
27
|
+
- 0.3.2: Tag option support (require puma 2.8.2+)
|
28
|
+
- 0.3.1: Typo fix
|
29
|
+
- 0.3.0: Initial support for puma signals
|
30
|
+
- 0.2.2: Application pre-loading is optional now (set puma_preload_app to false to turn it off)
|
31
|
+
- 0.2.1: Tasks are run within rack context
|
32
|
+
- 0.2.0: Support for puma `ActiveRecord::Base.establish_connection` on
|
33
|
+
boot
|
34
|
+
- 0.1.3: Capistrano 3.1 support
|
35
|
+
- 0.1.2: Gemfile are refreshed between deploys now
|
36
|
+
- 0.1.1: Initial support for Monit and configuration override added.
|
37
|
+
- 0.1.0: Phased restart will be used if puma is in cluster mode
|
38
|
+
- 0.0.9: puma.rb location changed to shared_path root. puma:check moved to after deploy:check
|
39
|
+
- 0.0.8: puma.rb is automatically generated if not present. Fixed RVM issue.
|
40
|
+
- 0.0.7: Gem pushed to rubygems as capistrano3-puma. Support of Redhat based OS for Jungle init script.
|
data/CONTRIBUTORS.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
## Contributors
|
2
|
+
|
3
|
+
Abdelkader Boudih
|
4
|
+
André Arko
|
5
|
+
Ariel Zerahia
|
6
|
+
ayaya
|
7
|
+
Bin Huang
|
8
|
+
Bryan Liles
|
9
|
+
Claudio Poli
|
10
|
+
Cyril Rohr
|
11
|
+
dfang
|
12
|
+
Fritz Lee
|
13
|
+
Hnat Kubov
|
14
|
+
Ivan Schneider
|
15
|
+
James-Hendrickson
|
16
|
+
Jens Hausherr
|
17
|
+
Jeremy Rottman
|
18
|
+
Jesse Cooke
|
19
|
+
Johan Lind
|
20
|
+
Jordan Hollinger
|
21
|
+
Jun Lin
|
22
|
+
Kamil Giszczak
|
23
|
+
Kevin Collignon
|
24
|
+
Konstantin Papkovskiy
|
25
|
+
Kyle Decot
|
26
|
+
Lisa Hagemann
|
27
|
+
Lonre Wang
|
28
|
+
marshall-lee
|
29
|
+
Matias De Santi
|
30
|
+
Michael C. Beck
|
31
|
+
Molfar
|
32
|
+
msbrigna
|
33
|
+
Neil Bartley
|
34
|
+
Peter
|
35
|
+
Ponomarev Nikolay
|
36
|
+
Rafael Goulart
|
37
|
+
RavWar
|
38
|
+
ruohan.chen
|
39
|
+
Ruslan
|
40
|
+
Sergey Ponomarev
|
41
|
+
Shane O'Grady
|
42
|
+
Simon Males
|
43
|
+
Steve Madere
|
44
|
+
Suhail Patel
|
45
|
+
Suraj Shirvankar
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013-2016 Abdelkader Boudih
|
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,128 @@
|
|
1
|
+
# Capistrano::Puma
|
2
|
+
|
3
|
+
A clone of [https://github.com/seuros/capistrano-puma]. If you don't know what
|
4
|
+
this does, please use that version instead.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'capistrano3-puma', github: "seuros/capistrano-puma"
|
11
|
+
|
12
|
+
or:
|
13
|
+
|
14
|
+
gem 'capistrano3-puma' , group: :development
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
```ruby
|
22
|
+
# Capfile
|
23
|
+
|
24
|
+
require 'capistrano/puma'
|
25
|
+
require 'capistrano/puma/workers' # if you want to control the workers (in cluster mode)
|
26
|
+
require 'capistrano/puma/jungle' # if you need the jungle tasks
|
27
|
+
require 'capistrano/puma/monit' # if you need the monit tasks
|
28
|
+
require 'capistrano/puma/nginx' # if you want to upload a nginx site template
|
29
|
+
```
|
30
|
+
|
31
|
+
### Config
|
32
|
+
|
33
|
+
To list available tasks use `cap -T`
|
34
|
+
|
35
|
+
To upload puma config use:
|
36
|
+
```ruby
|
37
|
+
cap puma:config
|
38
|
+
```
|
39
|
+
By default the file located in `shared/puma.config`
|
40
|
+
|
41
|
+
|
42
|
+
Ensure that `tmp/pids` and ` tmp/sockets log` are shared (via `linked_dirs`):
|
43
|
+
|
44
|
+
`This step is mandatory before deploying, otherwise puma server won't start`
|
45
|
+
|
46
|
+
### Nginx
|
47
|
+
|
48
|
+
To upload a nginx site config (eg. /etc/nginx/sites-enabled/) use:
|
49
|
+
```ruby
|
50
|
+
cap puma:nginx_config
|
51
|
+
```
|
52
|
+
|
53
|
+
To customize these two templates locally before uploading use:
|
54
|
+
```
|
55
|
+
rails g capistrano:nginx_puma:config
|
56
|
+
```
|
57
|
+
|
58
|
+
if your nginx server configuration is not located in `/etc/nginx`, you may need to customize:
|
59
|
+
```ruby
|
60
|
+
set :nginx_sites_available_path, "/etc/nginx/sites-available"
|
61
|
+
set :nginx_sites_enabled_path, "/etc/nginx/sites-enabled"
|
62
|
+
```
|
63
|
+
|
64
|
+
By default, `nginx_config` will be executed with `:web` role. But you can assign it to a different role:
|
65
|
+
```ruby
|
66
|
+
set :puma_nginx, :foo
|
67
|
+
```
|
68
|
+
or define a standalone one:
|
69
|
+
```ruby
|
70
|
+
role :puma_nginx, %w{root@example.com}
|
71
|
+
```
|
72
|
+
|
73
|
+
### Jungle
|
74
|
+
|
75
|
+
For Jungle tasks (beta), these options exist:
|
76
|
+
```ruby
|
77
|
+
set :puma_jungle_conf, '/etc/puma.conf'
|
78
|
+
set :puma_run_path, '/usr/local/bin/run-puma'
|
79
|
+
```
|
80
|
+
|
81
|
+
### Multi bind
|
82
|
+
|
83
|
+
Multi-bind can be set with an array in the puma_bind variable
|
84
|
+
```ruby
|
85
|
+
set :puma_bind, %w(tcp://0.0.0.0:9292 unix:///tmp/puma.sock)
|
86
|
+
```
|
87
|
+
* Listening on tcp://0.0.0.0:9220
|
88
|
+
* Listening on unix:///tmp/puma.sock
|
89
|
+
|
90
|
+
### Active Record
|
91
|
+
|
92
|
+
For ActiveRecord the following line to your deploy.rb
|
93
|
+
```ruby
|
94
|
+
set :puma_init_active_record, true
|
95
|
+
```
|
96
|
+
|
97
|
+
### Other configs
|
98
|
+
|
99
|
+
Configurable options, shown here with defaults: Please note the configuration options below are not required unless you are trying to override a default setting, for instance if you are deploying on a host on which you do not have sudo or root privileges and you need to restrict the path. These settings go in the deploy.rb file.
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
set :puma_user, fetch(:user)
|
103
|
+
set :puma_rackup, -> { File.join(current_path, 'config.ru') }
|
104
|
+
set :puma_state, "#{shared_path}/tmp/pids/puma.state"
|
105
|
+
set :puma_pid, "#{shared_path}/tmp/pids/puma.pid"
|
106
|
+
set :puma_bind, "unix://#{shared_path}/tmp/sockets/puma.sock" #accept array for multi-bind
|
107
|
+
set :puma_default_control_app, "unix://#{shared_path}/tmp/sockets/pumactl.sock"
|
108
|
+
set :puma_conf, "#{shared_path}/puma.rb"
|
109
|
+
set :puma_access_log, "#{shared_path}/log/puma_access.log"
|
110
|
+
set :puma_error_log, "#{shared_path}/log/puma_error.log"
|
111
|
+
set :puma_role, :app
|
112
|
+
set :puma_env, fetch(:rack_env, fetch(:rails_env, 'production'))
|
113
|
+
set :puma_threads, [0, 16]
|
114
|
+
set :puma_workers, 0
|
115
|
+
set :puma_worker_timeout, nil
|
116
|
+
set :puma_init_active_record, false
|
117
|
+
set :puma_preload_app, false
|
118
|
+
set :puma_plugins, [] #accept array of plugins
|
119
|
+
set :nginx_use_ssl, false
|
120
|
+
```
|
121
|
+
|
122
|
+
## Contributing
|
123
|
+
|
124
|
+
1. Fork it
|
125
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
126
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
127
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
128
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'capistrano/puma/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'capistrano-puma-a'
|
8
|
+
spec.version = Capistrano::Puma::VERSION
|
9
|
+
spec.authors = ['Abdelkader Boudih']
|
10
|
+
spec.email = ['Terminale@gmail.com']
|
11
|
+
spec.description = %q{A clone of seuros/capistrano-puma used here for version bump}
|
12
|
+
spec.summary = %q{Puma integration for Capistrano}
|
13
|
+
spec.homepage = 'https://github.com/seuros/capistrano-puma'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.required_ruby_version = '>= 1.9.3'
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split($/)
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'capistrano', '~> 3.5'
|
22
|
+
spec.add_dependency 'capistrano-bundler', '~> 1.1'
|
23
|
+
spec.add_dependency 'puma' , '~> 3.4'
|
24
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
load File.expand_path('../../tasks/nginx.rake', __FILE__)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
namespace :load do
|
2
|
+
task :defaults do
|
3
|
+
set :puma_jungle_conf, '/etc/puma.conf'
|
4
|
+
set :puma_run_path, '/usr/local/bin/run-puma'
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
namespace :puma do
|
10
|
+
namespace :jungle do
|
11
|
+
|
12
|
+
desc 'Install Puma jungle'
|
13
|
+
task :install do
|
14
|
+
on roles(fetch(:puma_role)) do |role|
|
15
|
+
@role = role
|
16
|
+
template_puma 'run-puma', "#{fetch(:tmp_dir)}/run-puma", role
|
17
|
+
execute "chmod +x #{fetch(:tmp_dir)}/run-puma"
|
18
|
+
sudo "mv #{fetch(:tmp_dir)}/run-puma #{fetch(:puma_run_path)}"
|
19
|
+
if test '[ -f /etc/redhat-release ]'
|
20
|
+
#RHEL flavor OS
|
21
|
+
rhel_install
|
22
|
+
elsif test '[ -f /etc/lsb-release ]'
|
23
|
+
#Debian flavor OS
|
24
|
+
debian_install
|
25
|
+
else
|
26
|
+
#Some other OS
|
27
|
+
error 'This task is not supported for your OS'
|
28
|
+
end
|
29
|
+
sudo "touch #{fetch(:puma_jungle_conf)}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def debian_install
|
35
|
+
template_puma 'puma-deb', "#{fetch(:tmp_dir)}/puma", @role
|
36
|
+
execute "chmod +x #{fetch(:tmp_dir)}/puma"
|
37
|
+
sudo "mv #{fetch(:tmp_dir)}/puma /etc/init.d/puma"
|
38
|
+
sudo 'update-rc.d -f puma defaults'
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def rhel_install
|
43
|
+
template_puma 'puma-rpm', "#{fetch(:tmp_dir)}/puma" , @role
|
44
|
+
execute "chmod +x #{fetch(:tmp_dir)}/puma"
|
45
|
+
sudo "mv #{fetch(:tmp_dir)}/puma /etc/init.d/puma"
|
46
|
+
sudo 'chkconfig --add puma'
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
desc 'Setup Puma config and install jungle script'
|
51
|
+
task :setup do
|
52
|
+
invoke 'puma:config'
|
53
|
+
invoke 'puma:jungle:install'
|
54
|
+
invoke 'puma:jungle:add'
|
55
|
+
end
|
56
|
+
|
57
|
+
desc 'Add current project to the jungle'
|
58
|
+
task :add do
|
59
|
+
on roles(fetch(:puma_role)) do|role|
|
60
|
+
sudo "/etc/init.d/puma add '#{current_path}' #{fetch(:puma_user, role.user)}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
desc 'Remove current project from the jungle'
|
65
|
+
task :remove do
|
66
|
+
on roles(fetch(:puma_role)) do
|
67
|
+
sudo "/etc/init.d/puma remove '#{current_path}'"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
%w[start stop restart status].each do |command|
|
72
|
+
desc "#{command} puma"
|
73
|
+
task command do
|
74
|
+
on roles(fetch(:puma_role)) do
|
75
|
+
sudo "service puma #{command} #{current_path}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
namespace :load do
|
2
|
+
task :defaults do
|
3
|
+
set :puma_monit_conf_dir, -> { "/etc/monit/conf.d/#{puma_monit_service_name}.conf" }
|
4
|
+
set :puma_monit_use_sudo, true
|
5
|
+
set :puma_monit_bin, '/usr/bin/monit'
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
namespace :puma do
|
10
|
+
namespace :monit do
|
11
|
+
desc 'Config Puma monit-service'
|
12
|
+
task :config do
|
13
|
+
on roles(fetch(:puma_role)) do |role|
|
14
|
+
@role = role
|
15
|
+
template_puma 'puma_monit.conf', "#{fetch(:tmp_dir)}/monit.conf", @role
|
16
|
+
sudo_if_needed "mv #{fetch(:tmp_dir)}/monit.conf #{fetch(:puma_monit_conf_dir)}"
|
17
|
+
sudo_if_needed "#{fetch(:puma_monit_bin)} reload"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Monitor Puma monit-service'
|
22
|
+
task :monitor do
|
23
|
+
on roles(fetch(:puma_role)) do
|
24
|
+
sudo_if_needed "#{fetch(:puma_monit_bin)} monitor #{puma_monit_service_name}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'Unmonitor Puma monit-service'
|
29
|
+
task :unmonitor do
|
30
|
+
on roles(fetch(:puma_role)) do
|
31
|
+
sudo_if_needed "#{fetch(:puma_monit_bin)} unmonitor #{puma_monit_service_name}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'Start Puma monit-service'
|
36
|
+
task :start do
|
37
|
+
on roles(fetch(:puma_role)) do
|
38
|
+
sudo_if_needed "#{fetch(:puma_monit_bin)} start #{puma_monit_service_name}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
desc 'Stop Puma monit-service'
|
43
|
+
task :stop do
|
44
|
+
on roles(fetch(:puma_role)) do
|
45
|
+
sudo_if_needed "#{fetch(:puma_monit_bin)} stop #{puma_monit_service_name}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
desc 'Restart Puma monit-service'
|
50
|
+
task :restart do
|
51
|
+
on roles(fetch(:puma_role)) do
|
52
|
+
sudo_if_needed "#{fetch(:puma_monit_bin)} restart #{puma_monit_service_name}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
before 'deploy:updating', 'puma:monit:unmonitor'
|
57
|
+
after 'deploy:published', 'puma:monit:monitor'
|
58
|
+
|
59
|
+
def puma_monit_service_name
|
60
|
+
fetch(:puma_monit_service_name, "puma_#{fetch(:application)}_#{fetch(:stage)}")
|
61
|
+
end
|
62
|
+
|
63
|
+
def sudo_if_needed(command)
|
64
|
+
if fetch(:puma_monit_use_sudo)
|
65
|
+
sudo command
|
66
|
+
else
|
67
|
+
execute command
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|