procsd 0.2.0 → 0.3.0

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.
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