procsd 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c4a1dba4f8d57cec70cebb0cffd95d3f6e51ab256f534790bac042ec11078e2
4
- data.tar.gz: 40ca2c7432f1ab690c61b073d305245a2b74e957627cd286907ee0e5c1ca3c61
3
+ metadata.gz: a029c6f4d0953cc9b21f3508a14e9057a4386fd53c786167737a9097c4175f13
4
+ data.tar.gz: 2f3fa1f897cb3bbf65aa6e00c1b8fbc0c3f1bde13d5dc559514633404ef691ce
5
5
  SHA512:
6
- metadata.gz: ecbf43025ab6387543cec6775655575af81cb12f590828f4f00fbf5261444d3e06427c252f095fd943493963822f703f87532fe4c8ff7afb6b8c2c8c139cf8e8
7
- data.tar.gz: 01c8d0561473679da4dcae1203fccf4f8168b72c9e298719459eba1ca4774e934b2c32db3e3536ed6a1d4a40361d264019042f5e73a7f93656da9b5cbb5f6c23
6
+ metadata.gz: 6f5a266a604bdbc1cbd9519b510a1eabf801d753d8497729a7ed64c2acedcfd6d1873f16da89896fedb5e49467f7c987c09e53ddef6d2d1ff947cbd07501173e
7
+ data.tar.gz: 8724b65f9e834e5654690148a0c21444608add28c60df412dd76d47ad18f2c2edbef20ae8029de39b2431a2c6585531656e7b4a629d0427f71e717c4bd314d8b
data/CHANGELOG.md CHANGED
@@ -1,4 +1,30 @@
1
1
  # CHANGELOG
2
+ ## 0.3.0
3
+ * **Breaking change:** `.procsd.yml` renamed to `procsd.yml` (without dot)
4
+ * **Breaking change:** `environment` option in the procsd.yml now has hash format, not array:
5
+
6
+ Was:
7
+ ```
8
+ environment:
9
+ - PORT=2501
10
+ - RAILS_ENV=production
11
+ - RAILS_LOG_TO_STDOUT=true
12
+ ```
13
+
14
+ Now:
15
+ ```
16
+ environment:
17
+ PORT: 2501
18
+ RAILS_ENV: production
19
+ RAILS_LOG_TO_STDOUT: true
20
+ ```
21
+
22
+ * Add `--or-restart` option for `create` command
23
+ * Options `--user`, `--path` and `--dir` for `create` command are not required anymore (but still can be provided)
24
+ * Add new `config` command (currently it can print only content for sudoers file: `$ procsd config sudoers`)
25
+ * Add `--add-to-sudoers` option for `create` command to automatically add `/bin/systemctl start/stop/restart app_name.target` commands to sudoers `/etc/sudoers.d/app_name` file (passwordless sudo)
26
+
27
+
2
28
  ## 0.2.0
3
29
  * Allow to use erb inside .procsd.yml
4
30
  * Add dotenv support
data/README.md CHANGED
@@ -8,6 +8,8 @@ Can we have something similar on the cheap Ubuntu VPS from DigitalOcean? Yes we
8
8
 
9
9
  ## Getting started
10
10
 
11
+ **Note:** latest version of Procsd is `0.3.0`. Since version `0.2.0` there are some breaking changes. Check the [CHANGELOG.md](CHANGELOG.md). Run `$ gem update procsd` or `$ bundle update procsd` (if you have already installed procsd) to update to the latest version.
12
+
11
13
  > Install `procsd` first: `$ gem install procsd`. Required Ruby version is `>= 2.3.0`.
12
14
 
13
15
  Let's say you have following application's Procfile:
@@ -16,17 +18,18 @@ Let's say you have following application's Procfile:
16
18
  web: bundle exec rails server -p $PORT
17
19
  worker: bundle exec sidekiq -e $RAILS_ENV
18
20
  ```
19
- and you want to have one instance of web process && two instances of worker process. Create inside application directory `.procsd.yml` config file:
21
+ and you want to have one instance of web process && two instances of worker process. Create inside application directory `procsd.yml` config file:
20
22
 
21
23
  ```yaml
22
24
  app: sample_app
23
25
  formation: web=1,worker=2
24
26
  environment:
25
- - PORT=2500
26
- - RAILS_ENV=production
27
- - RAILS_LOG_TO_STDOUT=true
27
+ PORT: 2501
28
+ RAILS_ENV: production
29
+ RAILS_LOG_TO_STDOUT: true
28
30
  ```
29
- > The only required option in `.procsd.yml` is `app` (application's name). Also you can provide custom Systemd directory path (`systemd_dir` option, default is _/etc/systemd/system_)
31
+
32
+ > The only required option in `procsd.yml` is `app` (application name). Also you can provide custom Systemd directory path (`systemd_dir` option, default is _/etc/systemd/system_)
30
33
 
31
34
  Configuration is done.
32
35
 
@@ -36,9 +39,13 @@ Configuration is done.
36
39
  > Note: `create` command needs to provide a few arguments: _--user_ (name of the current user), _--dir_ (application's working directory) and `--path` (user's $PATH). Usually it's fine to provide them like on example below:
37
40
 
38
41
  ```
39
- deploy@server:~/sample_app$ procsd create -u $USER -d $PWD -p $PATH
42
+ deploy@server:~/sample_app$ procsd create
40
43
 
44
+ Value of the --user option: deploy
45
+ Value of the --dir option: /home/deploy/sample_app
46
+ Value of the --path option: /home/deploy/.rbenv/shims:/home/deploy/.rbenv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
41
47
  Systemd directory: /etc/systemd/system
48
+
42
49
  create sample_app-web.1.service
43
50
  create sample_app-worker.1.service
44
51
  create sample_app-worker.2.service
@@ -47,8 +54,19 @@ Created symlink /etc/systemd/system/multi-user.target.wants/sample_app.target
47
54
  Enabled app target sample_app.target
48
55
  Reloaded configuraion (daemon-reload)
49
56
  App services were created and enabled. Run `start` to start them
57
+
58
+ Note: add following line to the sudoers file (`$ sudo visudo`) if you don't want to type password each time for start/stop/restart commands:
59
+ deploy ALL=NOPASSWD: /bin/systemctl start sample_app.target, /bin/systemctl stop sample_app.target, /bin/systemctl restart sample_app.target
50
60
  ```
51
61
 
62
+ You can provide additional options for `create` command:
63
+ * `--user` - name of the user, default is current _$USER_ env variable
64
+ * `--dir` - application's working directory, default is current _$PWD_ env variable
65
+ * `--path` - $PATH to include to the each service. Default is current _$PATH_ env variable
66
+ * `--add-to-sudoers` - if option present, procsd will create sudoers rule `/etc/sudoers.d/app_name` allowing to start/stop/restart app services without password prompt.
67
+ * `--or-restart` - if option present and servides already created, procsd will skip creation and instead call `restart` command
68
+
69
+
52
70
  ### Start application
53
71
  > Other control commands: `stop` and `restart`
54
72
 
@@ -120,7 +138,7 @@ deploy@server:~/sample_app$ procsd status
120
138
  2018-11-04T01:54:17+0400 sample_app-worker.2[8827]: 2018-11-03T21:54:17.716Z 8827 TID-gniahzm1r INFO: Starting processing, hit Ctrl-C to stop
121
139
  ```
122
140
 
123
- Also you can see status in a short format:
141
+ Also you can see status in short format:
124
142
 
125
143
  ```
126
144
  deploy@server:~/sample_app$ procsd status --short
@@ -175,18 +193,19 @@ Systemd provides [a lot of possibilities](https://www.digitalocean.com/community
175
193
  $ procsd --help
176
194
 
177
195
  Commands:
178
- procsd --version, -v # Print the version
179
- procsd create d, --dir=$PWD p, --path=$PATH u, --user=$USER # Create and enable app services
180
- procsd destroy # Stop, disable and remove app services
181
- procsd disable # Disable app target
182
- procsd enable # Enable app target
183
- procsd help [COMMAND] # Describe available commands or one specific command
184
- procsd list # List all app services
185
- procsd logs # Show app services logs
186
- procsd restart # Restart app services
187
- procsd start # Start app services
188
- procsd status # Show app services status
189
- procsd stop # Stop app services
196
+ procsd --version, -v # Print the version
197
+ procsd config # Show configuration. Available types: sudoers
198
+ procsd create # Create and enable app services
199
+ procsd destroy # Stop, disable and remove app services
200
+ procsd disable # Disable app target
201
+ procsd enable # Enable app target
202
+ procsd help [COMMAND] # Describe available commands or one specific command
203
+ procsd list # List all app services
204
+ procsd logs # Show app services logs
205
+ procsd restart # Restart app services
206
+ procsd start # Start app services
207
+ procsd status # Show app services status
208
+ procsd stop # Stop app services
190
209
  ```
191
210
 
192
211
 
@@ -209,30 +228,31 @@ Procsd following one rule: simplicity. For export it uses static service files (
209
228
  ```
210
229
  deploy@server:~/sample_app$ VERBOSE=true procsd logs -n 3
211
230
 
212
- > Executing command: journalctl --no-pager --all --no-hostname --output short-iso -n 3 --unit sample_app-web.1.service --unit sample_app-worker.1.service --unit sample_app-worker.2.service
231
+ > Executing command: `journalctl --no-pager --no-hostname --all --output short-iso -n 3 --unit sample_app-*`
213
232
 
214
233
  -- Logs begin at Sun 2018-10-21 00:38:42 +04, end at Sun 2018-11-04 19:17:01 +04. --
215
234
  2018-11-04T19:11:59+0400 sample_app-worker.2[29907]: 2018-11-04T15:11:59.597Z 29907 TID-gne5aeyuz INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org
216
235
  2018-11-04T19:11:59+0400 sample_app-worker.2[29907]: 2018-11-04T15:11:59.597Z 29907 TID-gne5aeyuz INFO: Booting Sidekiq 5.2.2 with redis options {:id=>"Sidekiq-server-PID-29907", :url=>nil}
217
236
  2018-11-04T19:11:59+0400 sample_app-worker.2[29907]: 2018-11-04T15:11:59.601Z 29907 TID-gne5aeyuz INFO: Starting processing, hit Ctrl-C to stop
218
237
  ```
219
- * You can use extended format of a Procfile to provide additional restart/stop commands for a process:
220
- > Keep in mind that syntax below is not supported by Foreman or Heroku
238
+ * You can use extended format of processes commands inside `procsd.yml` to provide additional restart/stop commands for processes:
221
239
 
222
240
  > All possible options: `start`, `restart` and `stop`
241
+ > If procsd.yml has `processes:` option defined, then content of Procfile will be ignored
223
242
 
224
243
  ```yml
225
- web:
226
- start: bundle exec rails server -p $PORT
227
- restart: bundle exec pumactl phased-restart
228
- worker: bundle exec sidekiq -e production
244
+ processes:
245
+ web:
246
+ start: bundle exec rails server -p $PORT
247
+ restart: bundle exec pumactl phased-restart
248
+ worker: bundle exec sidekiq -e production
249
+ app: sample_app
229
250
  ```
230
251
 
231
252
  Why? For example default Ruby on Rails application server [Puma](http://puma.io/) supports [Phased or Rolling restart](https://github.com/puma/puma/blob/master/docs/restart.md#normal-vs-hot-vs-phased-restart) feature. If you provide separate `restart`command for a process, then this command will be called (`$ procsd restart`) by Systemd instead of just killing and starting process again.
232
253
 
233
254
 
234
255
  ## ToDo
235
- * Add Capistrano integration examples
236
256
  * Optional possibility to generate Ngnix config (with out-of-box SSL using [Certbot](https://certbot.eff.org/lets-encrypt/ubuntubionic-nginx)) for an application to use Ngnix as a proxy and serve static files
237
257
  * Add integration with [Inspeqtor](https://github.com/mperham/inspeqtor) to monitor application services and get alert notifications if something happened
238
258
 
data/lib/procsd.rb CHANGED
@@ -4,4 +4,5 @@ require 'procsd/version'
4
4
 
5
5
  module Procsd
6
6
  DEFAULT_SYSTEMD_DIR = "/etc/systemd/system".freeze
7
+ SUDOERS_DIR = "/etc/sudoers.d".freeze
7
8
  end
data/lib/procsd/cli.rb CHANGED
@@ -5,27 +5,73 @@ require_relative 'generator'
5
5
  module Procsd
6
6
  class CLI < Thor
7
7
  class ConfigurationError < StandardError; end
8
+ class ArgumentError < StandardError; end
8
9
  map %w[--version -v] => :__print_version
9
10
 
10
11
  desc "create", "Create and enable app services"
11
- option :user, aliases: :u, type: :string, required: true, banner: "$USER"
12
- option :dir, aliases: :d, type: :string, required: true, banner: "$PWD"
13
- option :path, aliases: :p, type: :string, required: true, banner: "$PATH"
12
+ option :user, aliases: :u, type: :string, banner: "$USER"
13
+ option :dir, aliases: :d, type: :string, banner: "$PWD"
14
+ option :path, aliases: :p, type: :string, banner: "$PATH"
15
+ option :'or-restart', type: :boolean, banner: "Create and start app services if not created yet, otherwise restart"
16
+ option :'add-to-sudoers', type: :boolean, banner: "Create sudoers rule at /etc/sudoers.d/app_name to allow manage app target without password prompt"
14
17
  def create
15
18
  preload!
16
19
 
17
20
  if !target_exist?
21
+ opts = {
22
+ user: options["user"] || ENV["USER"],
23
+ dir: options["dir"] || ENV["PWD"],
24
+ path: options["path"] || fetch_path_env
25
+ }
26
+
27
+ opts.each do |key, value|
28
+ if value.nil? || value.empty?
29
+ say("Can't fetch value for --#{key}, please provide it as an argument", :red) and return
30
+ else
31
+ say "Value of the --#{key} option: #{value}"
32
+ end
33
+ end
34
+
18
35
  gen = Generator.new
19
- gen.export!(services, procsd: @procsd, options: options)
36
+ gen.export!(services, config: @config, options: options.merge(opts))
20
37
 
21
38
  enable
22
39
  if execute %w(sudo systemctl daemon-reload)
23
40
  say("Reloaded configuraion (daemon-reload)", :green)
24
41
  end
25
42
 
26
- say("App services were created and enabled. Run `start` to start them", :green)
43
+ if options["or-restart"]
44
+ start
45
+ say("App services were created, enabled and started", :green)
46
+ else
47
+ say("App services were created and enabled. Run `start` to start them", :green)
48
+ end
49
+
50
+ sudoers_rule_content = generate_sudoers_rule(opts[:user])
51
+ if options["add-to-sudoers"]
52
+ sudoers_file_temp_path = "/tmp/#{app_name}"
53
+ sudoers_file_dest_path = "#{SUDOERS_DIR}/#{app_name}"
54
+ if Dir.exist?(SUDOERS_DIR)
55
+ File.open(sudoers_file_temp_path, "w") { |f| f.puts sudoers_rule_content }
56
+ execute %W(sudo chown root:root #{sudoers_file_temp_path})
57
+ execute %W(sudo chmod 0440 #{sudoers_file_temp_path})
58
+ if execute %W(sudo mv #{sudoers_file_temp_path} #{sudoers_file_dest_path})
59
+ say("Sudoers file #{sudoers_file_dest_path} was created", :green)
60
+ end
61
+ else
62
+ say "Directory #{SUDOERS_DIR} does not exist, sudoers file wan't created"
63
+ end
64
+ else
65
+ say "Note: add following line to the sudoers file (`$ sudo visudo`) if you don't " \
66
+ "want to type password each time for start/stop/restart commands:"
67
+ puts sudoers_rule_content
68
+ end
27
69
  else
28
- say("App target `#{target_name}` already exists", :red)
70
+ if options["or-restart"]
71
+ restart
72
+ else
73
+ say("App target `#{target_name}` already exists", :red)
74
+ end
29
75
  end
30
76
  end
31
77
 
@@ -39,17 +85,20 @@ module Procsd
39
85
 
40
86
  services.keys.push(target_name).each do |filename|
41
87
  path = File.join(systemd_dir, filename)
42
- if File.exist? path
43
- execute %W(sudo rm #{path})
44
- say "Deleted #{path}"
45
- end
88
+ execute %W(sudo rm #{path}) and say "Deleted #{path}" if File.exist? path
46
89
  end
47
90
 
48
91
  if execute %w(sudo systemctl daemon-reload)
49
92
  say("Reloaded configuraion (daemon-reload)", :green)
50
93
  end
51
-
52
94
  say("App services were stopped, disabled and removed", :green)
95
+
96
+ sudoers_file_path = "#{SUDOERS_DIR}/#{app_name}"
97
+ if File.exist?(sudoers_file_path)
98
+ if yes?("Remove sudoers rule #{sudoers_file_path} ? (yes/no)")
99
+ say("Sudoers file removed", :green) if execute %W(sudo rm #{sudoers_file_path})
100
+ end
101
+ end
53
102
  else
54
103
  say_target_not_exists
55
104
  end
@@ -60,12 +109,9 @@ module Procsd
60
109
  preload!
61
110
  say_target_not_exists and return unless target_exist?
62
111
 
63
- if target_enabled?
64
- say "App target #{target_name} already enabled"
65
- else
66
- if execute %W(sudo systemctl enable #{target_name})
67
- say("Enabled app target #{target_name}", :green)
68
- end
112
+ say "Note: app target #{target_name} already enabled" if target_enabled?
113
+ if execute %W(sudo systemctl enable #{target_name})
114
+ say("Enabled app target #{target_name}", :green)
69
115
  end
70
116
  end
71
117
 
@@ -74,12 +120,9 @@ module Procsd
74
120
  preload!
75
121
  say_target_not_exists and return unless target_exist?
76
122
 
77
- if !target_enabled?
78
- say "App target #{target_name} already disabled"
79
- else
80
- if execute %W(sudo systemctl disable #{target_name})
81
- say("Disabled app target #{target_name}", :green)
82
- end
123
+ say "Note: app target #{target_name} already disabled" if !target_enabled?
124
+ if execute %W(sudo systemctl disable #{target_name})
125
+ say("Disabled app target #{target_name}", :green)
83
126
  end
84
127
  end
85
128
 
@@ -88,12 +131,9 @@ module Procsd
88
131
  preload!
89
132
  say_target_not_exists and return unless target_exist?
90
133
 
91
- if target_active?
92
- say "Already started/active (#{target_name})"
93
- else
94
- if execute %W(sudo systemctl start #{target_name})
95
- say("Started app services (#{target_name})", :green)
96
- end
134
+ say "Note: app target #{target_name} already started/active" if target_active?
135
+ if execute %W(sudo systemctl start #{target_name})
136
+ say("Started app services (#{target_name})", :green)
97
137
  end
98
138
  end
99
139
 
@@ -102,12 +142,9 @@ module Procsd
102
142
  preload!
103
143
  say_target_not_exists and return unless target_exist?
104
144
 
105
- if !target_active?
106
- say "Already stopped/inactive (#{target_name})"
107
- else
108
- if execute %W(sudo systemctl stop #{target_name})
109
- say("Stopped app services (#{target_name})", :green)
110
- end
145
+ say "Note: app target #{target_name} already stopped/inactive" if !target_active?
146
+ if execute %W(sudo systemctl stop #{target_name})
147
+ say("Stopped app services (#{target_name})", :green)
111
148
  end
112
149
  end
113
150
 
@@ -118,10 +155,10 @@ module Procsd
118
155
 
119
156
  # If one of the child services of a target has `ExecReload` and `ReloadPropagatedFrom`
120
157
  # options defined, then use `reload-or-restart` to call all services (not the main target)
121
- # because https://github.com/systemd/systemd/issues/10638
158
+ # because of systemd bug https://github.com/systemd/systemd/issues/10638
122
159
  success =
123
- if services.any? { |_, command| command["restart"] }
124
- execute %w(sudo systemctl reload-or-restart) + services.keys
160
+ if has_reload?
161
+ execute %W(sudo systemctl reload-or-restart #{app_name}-* --all)
125
162
  else
126
163
  execute %W(sudo systemctl restart #{target_name})
127
164
  end
@@ -139,19 +176,12 @@ module Procsd
139
176
  say_target_not_exists and return unless target_exist?
140
177
 
141
178
  if options["short"]
142
- command = %w(sudo systemctl list-units --no-pager --no-legend --all)
179
+ command = %w(systemctl list-units --no-pager --no-legend --all)
143
180
  else
144
- command = %w(sudo systemctl status --no-pager --output short-iso)
145
- end
146
-
147
- if options["target"]
148
- command << target_name
149
- else
150
- filtered = filter_services(service_name)
151
- say("Can't find any services matching given name: #{service_name}", :red) and return if filtered.empty?
152
- command += filtered
181
+ command = %w(systemctl status --no-pager --output short-iso --all)
153
182
  end
154
183
 
184
+ command << (options["target"] ? target_name : "#{app_name}-#{service_name}*")
155
185
  execute command
156
186
  end
157
187
 
@@ -164,17 +194,14 @@ module Procsd
164
194
  def logs(service_name = nil)
165
195
  preload!
166
196
 
167
- command = %w(sudo journalctl --no-pager --all --no-hostname --output short-iso)
197
+ command = %w(journalctl --no-pager --no-hostname --all --output short-iso)
168
198
  command.push("-n", options.fetch("num", "100"))
169
199
  command.push("-f") if options["tail"]
170
200
  command.push("--system") if options["system"]
171
201
  command.push("--priority", options["priority"]) if options["priority"]
172
202
  command.push("--grep", "'" + options["grep"] + "'") if options["grep"]
173
203
 
174
- filtered = filter_services(service_name)
175
- say("Can't find any services matching given name: #{service_name}", :red) and return if filtered.empty?
176
-
177
- filtered.each { |service| command.push("--unit", service) }
204
+ command.push("--unit", "#{app_name}-#{service_name}*")
178
205
  execute command
179
206
  end
180
207
 
@@ -183,7 +210,7 @@ module Procsd
183
210
  preload!
184
211
  say_target_not_exists and return unless target_exist?
185
212
 
186
- execute %W(sudo systemctl list-dependencies #{target_name})
213
+ execute %W(systemctl list-dependencies #{target_name})
187
214
  end
188
215
 
189
216
  desc "--version, -v", "Print the version"
@@ -191,9 +218,42 @@ module Procsd
191
218
  puts VERSION
192
219
  end
193
220
 
221
+ desc "config", "Show configuration. Available types: sudoers"
222
+ def config(name)
223
+ preload!
224
+
225
+ case name
226
+ when "sudoers"
227
+ say generate_sudoers_rule(ENV["USER"])
228
+ else
229
+ raise ArgumentError, "Wring type of argument: #{name}"
230
+ end
231
+ end
232
+
194
233
  private
195
234
 
235
+ def generate_sudoers_rule(user)
236
+ commands = []
237
+ systemctl_path = `which systemctl`.strip
238
+
239
+ %w(start stop restart).each { |cmd| commands << "#{systemctl_path} #{cmd} #{target_name}" }
240
+ commands << "#{systemctl_path} reload-or-restart #{app_name}-\\* --all" if has_reload?
241
+
242
+ "#{user} ALL=NOPASSWD: #{commands.join(', ')}"
243
+ end
244
+
245
+ def has_reload?
246
+ services.any? { |_, opts| opts["restart"] }
247
+ end
248
+
249
+ def fetch_path_env
250
+ # get value of the $PATH env variable including ~/.bashrc as well (-i flag)
251
+ `/bin/bash -ilc 'echo $PATH'`.strip
252
+ end
253
+
196
254
  def execute(command)
255
+ trap("INT") { puts "\nInterrupted" ; exit 130 }
256
+
197
257
  say("> Executing command: `#{command.join(' ')}`", :yellow) if ENV["VERBOSE"] == "true"
198
258
  system *command
199
259
  end
@@ -202,20 +262,12 @@ module Procsd
202
262
  say("App target #{target_name} is not exists", :red)
203
263
  end
204
264
 
205
- def filter_services(service_name)
206
- if service_name
207
- services.keys.select { |s| s.include?("#{app_name}-#{service_name}") }
208
- else
209
- services.keys
210
- end
211
- end
212
-
213
265
  def target_exist?
214
266
  File.exist?(File.join systemd_dir, target_name)
215
267
  end
216
268
 
217
269
  def systemd_dir
218
- @procsd["systemd_dir"]
270
+ @config[:systemd_dir]
219
271
  end
220
272
 
221
273
  def target_enabled?
@@ -231,15 +283,15 @@ module Procsd
231
283
  end
232
284
 
233
285
  def app_name
234
- @procsd["app"]
286
+ @config[:app]
235
287
  end
236
288
 
237
289
  def services
238
290
  all = {}
239
- @procfile.each do |process_name, process_command|
240
- processes_count = @procsd["formation"][process_name] || 1
241
- processes_count.times do |i|
242
- all["#{app_name}-#{process_name}.#{i + 1}.service"] = process_command
291
+ @config[:processes].each do |process_name, opts|
292
+ opts["count"].times do |i|
293
+ commands = { "start" => opts["start"], "stop" => opts["stop"], "restart" => opts["restart"] }
294
+ all["#{app_name}-#{process_name}.#{i + 1}.service"] = commands
243
295
  end
244
296
  end
245
297
 
@@ -247,32 +299,55 @@ module Procsd
247
299
  end
248
300
 
249
301
  def preload!
250
- raise ConfigurationError, "Procfile file doesn't exists" unless File.exist? "Procfile"
251
- raise ConfigurationError, ".procsd.yml config file doesn't exists" unless File.exist? ".procsd.yml"
302
+ @config = {}
252
303
 
253
- @procfile = YAML.load_file("Procfile")
254
- @procsd = YAML.load(ERB.new(File.read ".procsd.yml").result)
255
- raise ConfigurationError, "Missing app name in the .procsd.yml file" unless @procsd["app"]
304
+ unless system("which", "systemctl", [:out, :err]=>"/dev/null")
305
+ raise ConfigurationError, "Your OS doesn't has systemctl executable available"
306
+ end
307
+ raise ConfigurationError, "Config file procsd.yml doesn't exists" unless File.exist? "procsd.yml"
308
+ begin
309
+ procsd = YAML.load(ERB.new(File.read "procsd.yml").result)
310
+ rescue => e
311
+ raise ConfigurationError, "Can't read procsd.yml: #{e.inspect}"
312
+ end
256
313
 
257
- @procfile.each do |process_name, process_command|
258
- if process_command.kind_of?(Hash)
259
- unless process_command["start"]
260
- raise ConfigurationError, "Missing start command for #{process_name} process in the Procfile"
261
- end
262
- else
263
- @procfile[process_name] = { "start" => process_command }
314
+ raise ConfigurationError, "Missing app name in the procsd.yml file" unless procsd["app"]
315
+ @config[:app] = procsd["app"]
316
+
317
+ # If procsd.yml doesn't contains processes defined, try to read Procfile
318
+ unless procsd["processes"]
319
+ msg = "Procfile doesn't exists. Define processes in procsd.yml or create Procfile"
320
+ raise ConfigurationError, msg unless File.exist? "Procfile"
321
+ begin
322
+ procfile = YAML.load_file("Procfile")
323
+ rescue => e
324
+ raise ConfigurationError, "Can't read Procfile: #{e.inspect}"
264
325
  end
265
326
  end
266
327
 
267
- if formation = @procsd["formation"]
268
- @procsd["formation"] = formation.split(",").map { |f| f.split("=") }.to_h
269
- @procsd["formation"].each { |k, v| @procsd["formation"][k] = v.to_i }
328
+ if procsd["formation"]
329
+ formation = procsd["formation"].split(",").map { |f| f.split("=") }.to_h
330
+ formation.each { |k, v| formation[k] = v.to_i }
270
331
  else
271
- @procsd["formation"] = {}
332
+ formation = {}
333
+ end
334
+
335
+ processes = procsd["processes"] || procfile
336
+ processes.each do |process_name, opts|
337
+ if opts.kind_of?(Hash)
338
+ raise ConfigurationError, "Missing start command for `#{process_name}` process" unless opts["start"]
339
+ else
340
+ processes[process_name] = { "start" => opts }
341
+ end
342
+
343
+ unless processes[process_name]["count"]
344
+ processes[process_name]["count"] = formation[process_name] || 1
345
+ end
272
346
  end
273
347
 
274
- @procsd["environment"] ||= []
275
- @procsd["systemd_dir"] ||= DEFAULT_SYSTEMD_DIR
348
+ @config[:processes] = processes
349
+ @config[:environment] = procsd["environment"] || {}
350
+ @config[:systemd_dir] = procsd["systemd_dir"] || DEFAULT_SYSTEMD_DIR
276
351
  end
277
352
  end
278
353
  end
@@ -6,12 +6,12 @@ module Procsd
6
6
  File.dirname(__FILE__)
7
7
  end
8
8
 
9
- def export!(services, procsd:, options:)
9
+ def export!(services, config:, options:)
10
10
  self.destination_root = "/tmp"
11
- @procsd = procsd
12
- say "Systemd directory: #{@procsd["systemd_dir"]}"
11
+ @config = config
12
+ say "Systemd directory: #{@config[:systemd_dir]}"
13
13
 
14
- app_name = @procsd["app"]
14
+ app_name = @config[:app]
15
15
  target_name = "#{app_name}.target"
16
16
 
17
17
  services.each do |service_name, service_command|
@@ -19,8 +19,9 @@ module Procsd
19
19
  "target_name" => target_name,
20
20
  "id" => service_name.sub(".service", ""),
21
21
  "command" => service_command,
22
- "environment" => @procsd["environment"]
22
+ "environment" => @config[:environment]
23
23
  )
24
+
24
25
  generate(service_name, service_config, type: :service)
25
26
  end
26
27
 
@@ -33,12 +34,14 @@ module Procsd
33
34
 
34
35
  private
35
36
 
36
- def generate(filename, confing, type:)
37
- template("templates/#{type}.erb", filename, confing)
37
+ def generate(filename, conf, type:)
38
+ template("templates/#{type}.erb", filename, conf)
38
39
 
39
40
  source_path = File.join(destination_root, filename)
40
- dest_path = File.join(@procsd["systemd_dir"], filename)
41
+ dest_path = File.join(@config[:systemd_dir], filename)
41
42
  system "sudo", "mv", source_path, dest_path
43
+ ensure
44
+ File.delete(source_path) if File.exist? source_path
42
45
  end
43
46
  end
44
47
  end
@@ -26,6 +26,6 @@ StandardInput=null
26
26
  SyslogIdentifier=<%= config["id"] %>
27
27
 
28
28
  Environment="PATH=<%= config["path"] %>"
29
- <% config["environment"].each do |env| -%>
30
- Environment="<%= env %>"
29
+ <% config["environment"].each do |key, value| -%>
30
+ Environment="<%= key %>=<%= value %>"
31
31
  <% end -%>
@@ -1,5 +1,4 @@
1
1
  [Unit]
2
- Description=<%= config["app"] %> target
3
2
  Wants=<%= config["services"].join(" ") %>
4
3
 
5
4
  [Install]
@@ -1,3 +1,3 @@
1
1
  module Procsd
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: procsd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Victor Afanasev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-11-05 00:00:00.000000000 Z
11
+ date: 2018-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor