ypadlyak-foreman 0.81.0 → 0.85.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/data/export/daemon/process.conf.erb +1 -1
  4. data/data/export/launchd/launchd.plist.erb +1 -1
  5. data/data/export/supervisord/app.conf.erb +0 -1
  6. data/data/export/systemd/process.service.erb +7 -4
  7. data/data/export/systemd/process_master.target.erb +0 -1
  8. data/data/export/upstart/process.conf.erb +2 -1
  9. data/lib/foreman/cli.rb +3 -1
  10. data/lib/foreman/engine.rb +11 -3
  11. data/lib/foreman/engine/cli.rb +2 -1
  12. data/lib/foreman/export/base.rb +16 -1
  13. data/lib/foreman/export/systemd.rb +17 -9
  14. data/lib/foreman/export/upstart.rb +7 -4
  15. data/lib/foreman/procfile.rb +1 -1
  16. data/lib/foreman/version.rb +1 -1
  17. data/man/foreman.1 +2 -2
  18. data/spec/foreman/cli_spec.rb +1 -1
  19. data/spec/foreman/engine_spec.rb +8 -8
  20. data/spec/foreman/export/base_spec.rb +1 -1
  21. data/spec/foreman/export/bluepill_spec.rb +2 -2
  22. data/spec/foreman/export/daemon_spec.rb +11 -11
  23. data/spec/foreman/export/inittab_spec.rb +2 -2
  24. data/spec/foreman/export/launchd_spec.rb +1 -1
  25. data/spec/foreman/export/runit_spec.rb +2 -2
  26. data/spec/foreman/export/supervisord_spec.rb +2 -2
  27. data/spec/foreman/export/systemd_spec.rb +43 -21
  28. data/spec/foreman/export/upstart_spec.rb +12 -12
  29. data/spec/foreman/export_spec.rb +1 -1
  30. data/spec/foreman/process_spec.rb +2 -2
  31. data/spec/foreman/procfile_spec.rb +7 -0
  32. data/spec/foreman_spec.rb +1 -1
  33. data/spec/resources/export/supervisord/app-alpha-1.conf +0 -4
  34. data/spec/resources/export/supervisord/app-alpha-2.conf +0 -2
  35. data/spec/resources/export/systemd/app-alpha.target +2 -0
  36. data/spec/resources/export/systemd/{concurrency/app-alpha-1.service → app-alpha@.service} +4 -3
  37. data/spec/resources/export/systemd/app-bravo.target +2 -0
  38. data/spec/resources/export/systemd/{standard/app-bravo-1.service → app-bravo@.service} +4 -3
  39. data/spec/resources/export/systemd/{standard/app.target → app.target} +0 -0
  40. data/spec/spec_helper.rb +12 -5
  41. metadata +6 -10
  42. data/spec/resources/export/systemd/concurrency/app-alpha-2.service +0 -14
  43. data/spec/resources/export/systemd/concurrency/app-alpha.target +0 -3
  44. data/spec/resources/export/systemd/concurrency/app.target +0 -5
  45. data/spec/resources/export/systemd/standard/app-alpha-1.service +0 -14
  46. data/spec/resources/export/systemd/standard/app-alpha.target +0 -3
  47. data/spec/resources/export/systemd/standard/app-bravo.target +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7295387a8caf93d8d5b19abcd78aa75b81c3cb757496cfce0031e71935e43ded
4
- data.tar.gz: 8b092d5f7ac71bd544c61cd6a8592d2de9d7642c00c3d896e98d546ea8e77cca
3
+ metadata.gz: 145db4820cded60b98a0289d128818f2c9817447bef82bfc9aff99f5226c93eb
4
+ data.tar.gz: 9be7423b6e0eacf6d360f3b6d4f963cee48201fdc303cbaf45e329f486e0b6d4
5
5
  SHA512:
6
- metadata.gz: b910868bd8e1fa49314246654d5dbb4b747398f8aba4b90626958da6eab0291b7025d9528e4b4799d751d40a4a4f077e449d9cf122941b5cd0e2e47f2a13f724
7
- data.tar.gz: 1ba0b3cfa6c651d84d183d01d0a5b9ae1489920c48f91c7948c099430eb964684d00cb9dee5648ce57f786435cafaf0e8597fbe59a3bb8c41b506532a072b547
6
+ metadata.gz: 3f41f1bbfe899665826b451c919d097082ac6127fe027f2b82a4ffc02af04627a3e066cf964cafc278b8bff94b5a0c45932f2245ea949c547f62e0689e796d87
7
+ data.tar.gz: 55881e86f94ae5ece01f23a72720ed4e21857dd009a31181c10e7bb5dda57ccb0692d641115000228074c669958b0d600d65cec55feaffbdd119ef5bc0a1916b
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Foreman
2
2
 
3
3
  [![Build Status](https://travis-ci.org/ddollar/foreman.svg?branch=master)](https://travis-ci.org/ddollar/foreman)
4
- [![Code Climate](https://codeclimate.com/github/ddollar/foreman.png)](https://codeclimate.com/github/ddollar/foreman)
4
+ [![Code Climate](https://codeclimate.com/github/ddollar/foreman.svg)](https://codeclimate.com/github/ddollar/foreman)
5
5
  [![Inline docs](http://inch-ci.org/github/ddollar/foreman.svg?branch=master)](http://inch-ci.org/github/ddollar/foreman)
6
6
 
7
7
  Manage Procfile-based applications
@@ -37,6 +37,7 @@ See [.travis.yml](.travis.yml) for a list of Ruby versions against which Foreman
37
37
  * [shoreman](https://github.com/chrismytton/shoreman) - shell
38
38
  * [crank](https://github.com/arktisklada/crank) - Crystal
39
39
  * [houseman](https://github.com/fujimura/houseman) - Haskell
40
+ * [spm](https://github.com/bytegust/spm) - Go
40
41
 
41
42
  ## Authors
42
43
 
@@ -3,6 +3,6 @@ stop on stopping <%= app %>-<%= name.gsub('_', '-') %>
3
3
  respawn
4
4
 
5
5
  env PORT=<%= port %><% engine.env.each_pair do |var, env| %>
6
- env <%= var.upcase %>=<%= env %><% end %>
6
+ env <%= var %>=<%= env %><% end %>
7
7
 
8
8
  exec start-stop-daemon --start --chuid <%= user %> --chdir <%= engine.root %> --make-pidfile --pidfile <%= run %>/<%= app %>-<%= name %>-<%= num %>.pid --exec <%= executable %><%= arguments %> >> <%= log %>/<%= app %>-<%= name %>-<%= num %>.log 2>&1
@@ -7,7 +7,7 @@
7
7
  <key>EnvironmentVariables</key>
8
8
  <dict>
9
9
  <%- engine.env.merge("PORT" => port).each_pair do |var,env| -%>
10
- <key><%= var.upcase %></key>
10
+ <key><%= var %></key>
11
11
  <string><%= env %></string>
12
12
  <%- end -%>
13
13
  </dict>
@@ -17,7 +17,6 @@ engine.each_process do |name, process|
17
17
  command=<%= process.command %>
18
18
  autostart=true
19
19
  autorestart=true
20
- stopsignal=QUIT
21
20
  stdout_logfile=<%= log %>/<%= name %>-<%= num %>.log
22
21
  stderr_logfile=<%= log %>/<%= name %>-<%= num %>.error.log
23
22
  user=<%= user %>
@@ -4,12 +4,15 @@ PartOf=<%= app %>-<%= name %>.target
4
4
  [Service]
5
5
  User=<%= user %>
6
6
  WorkingDirectory=<%= engine.root %>
7
- Environment=PORT=<%= port %><% engine.env.each_pair do |var,env| %>
8
- Environment=<%= var.upcase %>=<%= env %><% end %>
9
- ExecStart=/bin/bash -lc '<%= process.command %>'
7
+ Environment=PORT=%i
8
+ <% engine.env.each_pair do |var,env| -%>
9
+ Environment="<%= var %>=<%= env %>"
10
+ <% end -%>
11
+ ExecStart=/bin/bash -lc 'exec <%= process.command %>'
10
12
  Restart=always
11
13
  StandardInput=null
12
14
  StandardOutput=syslog
13
15
  StandardError=syslog
14
16
  SyslogIdentifier=%n
15
- KillMode=process
17
+ KillMode=mixed
18
+ TimeoutStopSec=<%= engine.options[:timeout] %>
@@ -1,3 +1,2 @@
1
1
  [Unit]
2
2
  PartOf=<%= app %>.target
3
- Wants=<%= process_names.join(' ') %>
@@ -8,7 +8,8 @@ respawn
8
8
 
9
9
  env PORT=<%= port %>
10
10
  <% engine.env.each do |name,value| -%>
11
- env <%= name.upcase %>='<%= value.gsub(/'/, "'\"'\"'") %>'
11
+ <% next if name.upcase == "PORT" -%>
12
+ env <%= name %>='<%= value.gsub(/'/, "'\"'\"'") %>'
12
13
  <% end -%>
13
14
 
14
15
  setuid <%= user %>
data/lib/foreman/cli.rb CHANGED
@@ -24,6 +24,7 @@ class Foreman::CLI < Thor
24
24
  method_option :formation, :type => :string, :aliases => "-m", :banner => '"alpha=5,bar=3"', :desc => 'Specify what processes will run and how many. Default: "all=1"'
25
25
  method_option :port, :type => :numeric, :aliases => "-p"
26
26
  method_option :timeout, :type => :numeric, :aliases => "-t", :desc => "Specify the amount of time (in seconds) processes have to shutdown gracefully before receiving a SIGKILL, defaults to 5."
27
+ method_option :timestamp, :type => :boolean, :default => true, :desc => "Include timestamp in output"
27
28
 
28
29
  class << self
29
30
  # Hackery. Take the run method away from Thor so that we can redefine it.
@@ -50,7 +51,8 @@ class Foreman::CLI < Thor
50
51
  method_option :port, :type => :numeric, :aliases => "-p"
51
52
  method_option :user, :type => :string, :aliases => "-u"
52
53
  method_option :template, :type => :string, :aliases => "-t"
53
- method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"', :desc => 'Specify what processes will run and how many. Default: "all=1"'
54
+ method_option :formation, :type => :string, :aliases => "-m", :banner => '"alpha=5,bar=3"', :desc => 'Specify what processes will run and how many. Default: "all=1"'
55
+ method_option :timeout, :type => :numeric, :aliases => "-t", :desc => "Specify the amount of time (in seconds) processes have to shutdown gracefully before receiving a SIGKILL, defaults to 5."
54
56
 
55
57
  def export(format, location=nil)
56
58
  check_procfile!
@@ -10,7 +10,7 @@ class Foreman::Engine
10
10
 
11
11
  # The signals that the engine cares about.
12
12
  #
13
- HANDLED_SIGNALS = [ :TERM, :INT, :HUP ]
13
+ HANDLED_SIGNALS = [ :TERM, :INT, :HUP, :USR1, :USR2 ]
14
14
 
15
15
  attr_reader :env
16
16
  attr_reader :options
@@ -27,7 +27,7 @@ class Foreman::Engine
27
27
  def initialize(options={})
28
28
  @options = options.dup
29
29
 
30
- @options[:formation] ||= (options[:concurrency] || "all=1")
30
+ @options[:formation] ||= "all=1"
31
31
  @options[:timeout] ||= 5
32
32
 
33
33
  @env = {}
@@ -104,6 +104,8 @@ class Foreman::Engine
104
104
  handle_interrupt
105
105
  when :HUP
106
106
  handle_hangup
107
+ when *HANDLED_SIGNALS
108
+ handle_signal_forward(sig)
107
109
  else
108
110
  system "unhandled signal #{sig}"
109
111
  end
@@ -130,6 +132,11 @@ class Foreman::Engine
130
132
  @shutdown = true
131
133
  end
132
134
 
135
+ def handle_signal_forward(signal)
136
+ system "#{signal} received, forwarding it to children"
137
+ kill_children signal
138
+ end
139
+
133
140
  # Register a process to be run by this +Engine+
134
141
  #
135
142
  # @param [String] name A name for this process
@@ -190,7 +197,8 @@ class Foreman::Engine
190
197
  end
191
198
  else
192
199
  begin
193
- Process.kill signal, *@running.keys unless @running.empty?
200
+ pids = @running.keys.compact
201
+ Process.kill signal, *pids unless pids.empty?
194
202
  rescue Errno::ESRCH, Errno::EPERM
195
203
  end
196
204
  end
@@ -57,7 +57,8 @@ class Foreman::Engine::CLI < Foreman::Engine
57
57
  data.to_s.lines.map(&:chomp).each do |message|
58
58
  output = ""
59
59
  output += $stdout.color(@colors[name.split(".").first].to_sym)
60
- output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
60
+ output += "#{Time.now.strftime("%H:%M:%S")} " if options[:timestamp]
61
+ output += "#{pad_process_name(name)} | "
61
62
  output += $stdout.color(:reset)
62
63
  output += message
63
64
  $stdout.puts output
@@ -100,6 +100,12 @@ private ######################################################################
100
100
  FileUtils.rm(filename)
101
101
  end
102
102
 
103
+ def clean_dir(dirname)
104
+ return unless File.exists?(dirname)
105
+ say "cleaning up directory: #{dirname}"
106
+ FileUtils.rm_r(dirname)
107
+ end
108
+
103
109
  def shell_quote(value)
104
110
  Shellwords.escape(value)
105
111
  end
@@ -129,7 +135,11 @@ private ######################################################################
129
135
  end
130
136
 
131
137
  def write_template(name, target, binding)
132
- compiled = ERB.new(export_template(name), nil, '-').result(binding)
138
+ compiled = if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+
139
+ ERB.new(export_template(name), trim_mode: '-').result(binding)
140
+ else
141
+ ERB.new(export_template(name), nil, '-').result(binding)
142
+ end
133
143
  write_file target, compiled
134
144
  end
135
145
 
@@ -143,6 +153,11 @@ private ######################################################################
143
153
  FileUtils.mkdir_p(File.join(location, dir))
144
154
  end
145
155
 
156
+ def create_symlink(link, target)
157
+ say "symlinking: #{link} -> #{target}"
158
+ FileUtils.symlink(target, File.join(location, link))
159
+ end
160
+
146
161
  def write_file(filename, contents)
147
162
  say "writing: #{filename}"
148
163
 
@@ -6,21 +6,29 @@ class Foreman::Export::Systemd < Foreman::Export::Base
6
6
  def export
7
7
  super
8
8
 
9
- Dir["#{location}/#{app}*.target"].concat(Dir["#{location}/#{app}*.service"]).each do |file|
9
+ Dir["#{location}/#{app}*.target"]
10
+ .concat(Dir["#{location}/#{app}*.service"])
11
+ .concat(Dir["#{location}/#{app}*.target.wants/#{app}*.service"])
12
+ .each do |file|
10
13
  clean file
11
14
  end
12
15
 
16
+ Dir["#{location}/#{app}*.target.wants"].each do |file|
17
+ clean_dir file
18
+ end
19
+
13
20
  process_master_names = []
14
21
 
15
22
  engine.each_process do |name, process|
16
- next if engine.formation[name] < 1
17
-
18
- process_names = []
19
-
20
- 1.upto(engine.formation[name]) do |num|
21
- port = engine.port_for(process, num)
22
- write_template "systemd/process.service.erb", "#{app}-#{name}-#{num}.service", binding
23
- process_names << "#{app}-#{name}-#{num}.service"
23
+ service_fn = "#{app}-#{name}@.service"
24
+ write_template "systemd/process.service.erb", service_fn, binding
25
+
26
+ create_directory("#{app}-#{name}.target.wants")
27
+ 1.upto(engine.formation[name])
28
+ .collect { |num| engine.port_for(process, num) }
29
+ .collect { |port| "#{app}-#{name}@#{port}.service" }
30
+ .each do |process_name|
31
+ create_symlink("#{app}-#{name}.target.wants/#{process_name}", "../#{service_fn}") rescue Errno::EEXIST # This is needed because rr-mocks do not call the origial cleanup
24
32
  end
25
33
 
26
34
  write_template "systemd/process_master.target.erb", "#{app}-#{name}.target", binding
@@ -13,16 +13,19 @@ class Foreman::Export::Upstart < Foreman::Export::Base
13
13
 
14
14
  engine.each_process do |name, process|
15
15
  process_master_file = "#{app}-#{name}.conf"
16
- clean File.join(location, process_master_file)
16
+ process_file = "#{app}-#{name}-%s.conf"
17
+
18
+ Dir[
19
+ File.join(location, process_master_file),
20
+ File.join(location, process_file % "*")
21
+ ].each { |f| clean(f) }
17
22
 
18
23
  next if engine.formation[name] < 1
19
24
  write_template process_master_template, process_master_file, binding
20
25
 
21
26
  1.upto(engine.formation[name]) do |num|
22
27
  port = engine.port_for(process, num)
23
- process_file = "#{app}-#{name}-#{num}.conf"
24
- clean File.join(location, process_file)
25
- write_template process_template, process_file, binding
28
+ write_template process_template, process_file % num, binding
26
29
  end
27
30
  end
28
31
  end
@@ -21,7 +21,7 @@ class Foreman::Procfile
21
21
 
22
22
  # Yield each +Procfile+ entry in order
23
23
  #
24
- def entries(&blk)
24
+ def entries
25
25
  @entries.each do |(name, command)|
26
26
  yield name, command
27
27
  end
@@ -1,5 +1,5 @@
1
1
  module Foreman
2
2
 
3
- VERSION = "0.81.0"
3
+ VERSION = "0.85.0"
4
4
 
5
5
  end
data/man/foreman.1 CHANGED
@@ -1,7 +1,7 @@
1
1
  .\" generated with Ronn/v0.7.3
2
2
  .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
3
  .
4
- .TH "FOREMAN" "1" "April 2016" "Foreman 0.81.0" "Foreman Manual"
4
+ .TH "FOREMAN" "1" "January 2017" "Foreman 0.84.0" "Foreman Manual"
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBforeman\fR \- manage Procfile\-based applications
@@ -57,7 +57,7 @@ Specify the amount of time (in seconds) processes have to shutdown gracefully be
57
57
  \fBforeman export\fR is used to export your application to another process management format\.
58
58
  .
59
59
  .P
60
- An location to export can be passed as an argument\. This argument may be either required or optional depending on the export format\.
60
+ A location to export can be passed as an argument\. This argument may be either required or optional depending on the export format\.
61
61
  .
62
62
  .P
63
63
  The following options control how the application is run:
@@ -21,7 +21,7 @@ describe "Foreman::CLI", :fakefs do
21
21
  describe "when a Procfile doesnt exist", :fakefs do
22
22
  it "displays an error" do
23
23
  mock_error(subject, "Procfile does not exist.") do
24
- dont_allow.instance_of(Foreman::Engine).start
24
+ expect_any_instance_of(Foreman::Engine).to_not receive(:start)
25
25
  subject.start
26
26
  end
27
27
  end
@@ -35,19 +35,19 @@ describe "Foreman::Engine", :fakefs do
35
35
 
36
36
  describe "start" do
37
37
  it "forks the processes" do
38
- mock(subject.process("alpha")).run(anything)
39
- mock(subject.process("bravo")).run(anything)
40
- mock(subject).watch_for_output
41
- mock(subject).wait_for_shutdown_or_child_termination
38
+ expect(subject.process("alpha")).to receive(:run)
39
+ expect(subject.process("bravo")).to receive(:run)
40
+ expect(subject).to receive(:watch_for_output)
41
+ expect(subject).to receive(:wait_for_shutdown_or_child_termination)
42
42
  subject.start
43
43
  end
44
44
 
45
45
  it "handles concurrency" do
46
46
  subject.options[:formation] = "alpha=2"
47
- mock(subject.process("alpha")).run(anything).twice
48
- mock(subject.process("bravo")).run(anything).never
49
- mock(subject).watch_for_output
50
- mock(subject).wait_for_shutdown_or_child_termination
47
+ expect(subject.process("alpha")).to receive(:run).twice
48
+ expect(subject.process("bravo")).to_not receive(:run)
49
+ expect(subject).to receive(:watch_for_output)
50
+ expect(subject).to receive(:wait_for_shutdown_or_child_termination)
51
51
  subject.start
52
52
  end
53
53
  end
@@ -9,7 +9,7 @@ describe "Foreman::Export::Base", :fakefs do
9
9
  let(:subject) { Foreman::Export::Base.new(location, engine) }
10
10
 
11
11
  it "has a say method for displaying info" do
12
- mock(subject).puts("[foreman export] foo")
12
+ expect(subject).to receive(:puts).with("[foreman export] foo")
13
13
  subject.send(:say, "foo")
14
14
  end
15
15
 
@@ -11,7 +11,7 @@ describe Foreman::Export::Bluepill, :fakefs do
11
11
  let(:bluepill) { Foreman::Export::Bluepill.new("/tmp/init", engine, options) }
12
12
 
13
13
  before(:each) { load_export_templates_into_fakefs("bluepill") }
14
- before(:each) { stub(bluepill).say }
14
+ before(:each) { allow(bluepill).to receive(:say) }
15
15
 
16
16
  it "exports to the filesystem" do
17
17
  bluepill.export
@@ -19,7 +19,7 @@ describe Foreman::Export::Bluepill, :fakefs do
19
19
  end
20
20
 
21
21
  it "cleans up if exporting into an existing dir" do
22
- mock(FileUtils).rm("/tmp/init/app.pill")
22
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app.pill")
23
23
 
24
24
  bluepill.export
25
25
  bluepill.export
@@ -11,7 +11,7 @@ describe Foreman::Export::Daemon, :fakefs do
11
11
  let(:daemon) { Foreman::Export::Daemon.new("/tmp/init", engine, options) }
12
12
 
13
13
  before(:each) { load_export_templates_into_fakefs("daemon") }
14
- before(:each) { stub(daemon).say }
14
+ before(:each) { allow(daemon).to receive(:say) }
15
15
 
16
16
  it "exports to the filesystem" do
17
17
  daemon.export
@@ -24,15 +24,15 @@ describe Foreman::Export::Daemon, :fakefs do
24
24
  end
25
25
 
26
26
  it "cleans up if exporting into an existing dir" do
27
- mock(FileUtils).rm("/tmp/init/app.conf")
28
- mock(FileUtils).rm("/tmp/init/app-alpha.conf")
29
- mock(FileUtils).rm("/tmp/init/app-alpha-1.conf")
30
- mock(FileUtils).rm("/tmp/init/app-bravo.conf")
31
- mock(FileUtils).rm("/tmp/init/app-bravo-1.conf")
32
- mock(FileUtils).rm("/tmp/init/app-foo-bar.conf")
33
- mock(FileUtils).rm("/tmp/init/app-foo-bar-1.conf")
34
- mock(FileUtils).rm("/tmp/init/app-foo_bar.conf")
35
- mock(FileUtils).rm("/tmp/init/app-foo_bar-1.conf")
27
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app.conf")
28
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha.conf")
29
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha-1.conf")
30
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo.conf")
31
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo-1.conf")
32
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar.conf")
33
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar-1.conf")
34
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar.conf")
35
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar-1.conf")
36
36
 
37
37
  daemon.export
38
38
  daemon.export
@@ -44,7 +44,7 @@ describe Foreman::Export::Daemon, :fakefs do
44
44
  ["app2", "app2-alpha", "app2-alpha-1"].each do |name|
45
45
  path = "/tmp/init/#{name}.conf"
46
46
  FileUtils.touch(path)
47
- dont_allow(FileUtils).rm(path)
47
+ expect(FileUtils).to_not receive(:rm).with(path)
48
48
  end
49
49
 
50
50
  daemon.export
@@ -12,7 +12,7 @@ describe Foreman::Export::Inittab, :fakefs do
12
12
  let(:inittab) { Foreman::Export::Inittab.new(location, engine, options) }
13
13
 
14
14
  before(:each) { load_export_templates_into_fakefs("inittab") }
15
- before(:each) { stub(inittab).say }
15
+ before(:each) { allow(inittab).to receive(:say) }
16
16
 
17
17
  it "exports to the filesystem" do
18
18
  inittab.export
@@ -23,7 +23,7 @@ describe Foreman::Export::Inittab, :fakefs do
23
23
  let(:location) { "-" }
24
24
 
25
25
  it "exports to stdout" do
26
- mock(inittab).puts example_export_file("inittab/inittab.default")
26
+ expect(inittab).to receive(:puts).with(example_export_file("inittab/inittab.default"))
27
27
  inittab.export
28
28
  end
29
29
  end
@@ -10,7 +10,7 @@ describe Foreman::Export::Launchd, :fakefs do
10
10
  let(:launchd) { Foreman::Export::Launchd.new("/tmp/init", engine, options) }
11
11
 
12
12
  before(:each) { load_export_templates_into_fakefs("launchd") }
13
- before(:each) { stub(launchd).say }
13
+ before(:each) { allow(launchd).to receive(:say) }
14
14
 
15
15
  it "exports to the filesystem" do
16
16
  launchd.export
@@ -10,8 +10,8 @@ describe Foreman::Export::Runit, :fakefs do
10
10
  let(:runit) { Foreman::Export::Runit.new('/tmp/init', engine, options) }
11
11
 
12
12
  before(:each) { load_export_templates_into_fakefs("runit") }
13
- before(:each) { stub(runit).say }
14
- before(:each) { stub(FakeFS::FileUtils).chmod }
13
+ before(:each) { allow(runit).to receive(:say) }
14
+ before(:each) { allow(FakeFS::FileUtils).to receive(:chmod) }
15
15
 
16
16
  it "exports to the filesystem" do
17
17
  engine.env["BAR"] = "baz"
@@ -11,7 +11,7 @@ describe Foreman::Export::Supervisord, :fakefs do
11
11
  let(:supervisord) { Foreman::Export::Supervisord.new("/tmp/init", engine, options) }
12
12
 
13
13
  before(:each) { load_export_templates_into_fakefs("supervisord") }
14
- before(:each) { stub(supervisord).say }
14
+ before(:each) { allow(supervisord).to receive(:say) }
15
15
 
16
16
  it "exports to the filesystem" do
17
17
  write_env(".env", "FOO"=>"bar", "URL"=>"http://example.com/api?foo=bar&baz=1")
@@ -21,7 +21,7 @@ describe Foreman::Export::Supervisord, :fakefs do
21
21
  end
22
22
 
23
23
  it "cleans up if exporting into an existing dir" do
24
- mock(FileUtils).rm("/tmp/init/app.conf")
24
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app.conf")
25
25
  supervisord.export
26
26
  supervisord.export
27
27
  end
@@ -11,28 +11,44 @@ describe Foreman::Export::Systemd, :fakefs do
11
11
  let(:systemd) { Foreman::Export::Systemd.new("/tmp/init", engine, options) }
12
12
 
13
13
  before(:each) { load_export_templates_into_fakefs("systemd") }
14
- before(:each) { stub(systemd).say }
14
+ before(:each) { allow(systemd).to receive(:say) }
15
15
 
16
16
  it "exports to the filesystem" do
17
17
  systemd.export
18
18
 
19
- expect(File.read("/tmp/init/app.target")).to eq(example_export_file("systemd/standard/app.target"))
20
- expect(File.read("/tmp/init/app-alpha.target")).to eq(example_export_file("systemd/standard/app-alpha.target"))
21
- expect(File.read("/tmp/init/app-alpha-1.service")).to eq(example_export_file("systemd/standard/app-alpha-1.service"))
22
- expect(File.read("/tmp/init/app-bravo.target")).to eq(example_export_file("systemd/standard/app-bravo.target"))
23
- expect(File.read("/tmp/init/app-bravo-1.service")).to eq(example_export_file("systemd/standard/app-bravo-1.service"))
19
+ expect(File.read("/tmp/init/app.target")).to eq(example_export_file("systemd/app.target"))
20
+ expect(File.read("/tmp/init/app-alpha.target")).to eq(example_export_file("systemd/app-alpha.target"))
21
+ expect(File.read("/tmp/init/app-alpha@.service")).to eq(example_export_file("systemd/app-alpha@.service"))
22
+ expect(File.read("/tmp/init/app-bravo.target")).to eq(example_export_file("systemd/app-bravo.target"))
23
+ expect(File.read("/tmp/init/app-bravo@.service")).to eq(example_export_file("systemd/app-bravo@.service"))
24
+
25
+ expect(File.directory?("/tmp/init/app-alpha.target.wants")).to be_truthy
26
+ expect(File.symlink?("/tmp/init/app-alpha.target.wants/app-alpha@5000.service")).to be_truthy
24
27
  end
25
28
 
26
29
  it "cleans up if exporting into an existing dir" do
27
- mock(FileUtils).rm("/tmp/init/app.target")
28
- mock(FileUtils).rm("/tmp/init/app-alpha.target")
29
- mock(FileUtils).rm("/tmp/init/app-alpha-1.service")
30
- mock(FileUtils).rm("/tmp/init/app-bravo.target")
31
- mock(FileUtils).rm("/tmp/init/app-bravo-1.service")
32
- mock(FileUtils).rm("/tmp/init/app-foo-bar.target")
33
- mock(FileUtils).rm("/tmp/init/app-foo-bar-1.service")
34
- mock(FileUtils).rm("/tmp/init/app-foo_bar.target")
35
- mock(FileUtils).rm("/tmp/init/app-foo_bar-1.service")
30
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app.target")
31
+
32
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha@.service")
33
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha.target")
34
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha.target.wants/app-alpha@5000.service")
35
+ expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-alpha.target.wants")
36
+
37
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo.target")
38
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo@.service")
39
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo.target.wants/app-bravo@5100.service")
40
+ expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-bravo.target.wants")
41
+
42
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar.target")
43
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar@.service")
44
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar.target.wants/app-foo_bar@5200.service")
45
+ expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-foo_bar.target.wants")
46
+
47
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar.target")
48
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar@.service")
49
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar.target.wants/app-foo-bar@5300.service")
50
+ expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-foo-bar.target.wants")
51
+
36
52
 
37
53
  systemd.export
38
54
  systemd.export
@@ -41,7 +57,13 @@ describe Foreman::Export::Systemd, :fakefs do
41
57
  it "includes environment variables" do
42
58
  engine.env['KEY'] = 'some "value"'
43
59
  systemd.export
44
- expect(File.read("/tmp/init/app-alpha-1.service")).to match(/KEY=some "value"$/)
60
+ expect(File.read("/tmp/init/app-alpha@.service")).to match(/KEY=some "value"/)
61
+ end
62
+
63
+ it "includes ExecStart line" do
64
+ engine.env['KEY'] = 'some "value"'
65
+ systemd.export
66
+ expect(File.read("/tmp/init/app-alpha@.service")).to match(/^ExecStart=/)
45
67
  end
46
68
 
47
69
  context "with a formation" do
@@ -50,11 +72,11 @@ describe Foreman::Export::Systemd, :fakefs do
50
72
  it "exports to the filesystem with concurrency" do
51
73
  systemd.export
52
74
 
53
- expect(File.read("/tmp/init/app.target")).to eq(example_export_file("systemd/concurrency/app.target"))
54
- expect(File.read("/tmp/init/app-alpha.target")).to eq(example_export_file("systemd/concurrency/app-alpha.target"))
55
- expect(File.read("/tmp/init/app-alpha-1.service")).to eq(example_export_file("systemd/concurrency/app-alpha-1.service"))
56
- expect(File.read("/tmp/init/app-alpha-2.service")).to eq(example_export_file("systemd/concurrency/app-alpha-2.service"))
57
- expect(File.exists?("/tmp/init/app-bravo-1.service")).to eq(false)
75
+ expect(File.read("/tmp/init/app.target")).to eq(example_export_file("systemd/app.target"))
76
+ expect(File.read("/tmp/init/app-alpha.target")).to eq(example_export_file("systemd/app-alpha.target"))
77
+ expect(File.read("/tmp/init/app-alpha@.service")).to eq(example_export_file("systemd/app-alpha@.service"))
78
+ expect(File.read("/tmp/init/app-bravo.target")).to eq(example_export_file("systemd/app-bravo.target"))
79
+ expect(File.read("/tmp/init/app-bravo@.service")).to eq(example_export_file("systemd/app-bravo@.service"))
58
80
  end
59
81
  end
60
82
 
@@ -11,7 +11,7 @@ describe Foreman::Export::Upstart, :fakefs do
11
11
  let(:upstart) { Foreman::Export::Upstart.new("/tmp/init", engine, options) }
12
12
 
13
13
  before(:each) { load_export_templates_into_fakefs("upstart") }
14
- before(:each) { stub(upstart).say }
14
+ before(:each) { allow(upstart).to receive(:say) }
15
15
 
16
16
  it "exports to the filesystem" do
17
17
  upstart.export
@@ -24,15 +24,15 @@ describe Foreman::Export::Upstart, :fakefs do
24
24
  end
25
25
 
26
26
  it "cleans up if exporting into an existing dir" do
27
- mock(FileUtils).rm("/tmp/init/app.conf")
28
- mock(FileUtils).rm("/tmp/init/app-alpha.conf")
29
- mock(FileUtils).rm("/tmp/init/app-alpha-1.conf")
30
- mock(FileUtils).rm("/tmp/init/app-bravo.conf")
31
- mock(FileUtils).rm("/tmp/init/app-bravo-1.conf")
32
- mock(FileUtils).rm("/tmp/init/app-foo-bar.conf")
33
- mock(FileUtils).rm("/tmp/init/app-foo-bar-1.conf")
34
- mock(FileUtils).rm("/tmp/init/app-foo_bar.conf")
35
- mock(FileUtils).rm("/tmp/init/app-foo_bar-1.conf")
27
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app.conf")
28
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha.conf")
29
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha-1.conf")
30
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo.conf")
31
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo-1.conf")
32
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar.conf")
33
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar-1.conf")
34
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar.conf")
35
+ expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar-1.conf")
36
36
 
37
37
  upstart.export
38
38
  upstart.export
@@ -44,7 +44,7 @@ describe Foreman::Export::Upstart, :fakefs do
44
44
  ["app2", "app2-alpha", "app2-alpha-1"].each do |name|
45
45
  path = "/tmp/init/#{name}.conf"
46
46
  FileUtils.touch(path)
47
- dont_allow(FileUtils).rm(path)
47
+ expect(FileUtils).to_not receive(:rm).with(path)
48
48
  end
49
49
 
50
50
  upstart.export
@@ -56,7 +56,7 @@ describe Foreman::Export::Upstart, :fakefs do
56
56
  ["app-worker", "app-worker-worker", "app-worker-worker-1"].each do |name|
57
57
  path = "/tmp/init/#{name}.conf"
58
58
  FileUtils.touch(path)
59
- dont_allow(FileUtils).rm(path)
59
+ expect(FileUtils).to_not receive(:rm).with(path)
60
60
  end
61
61
 
62
62
  upstart.export
@@ -6,7 +6,7 @@ describe "Foreman::Export" do
6
6
 
7
7
  describe "with a formatter that doesn't declare the appropriate class" do
8
8
  it "prints an error" do
9
- mock(subject).require("foreman/export/invalidformatter")
9
+ expect(subject).to receive(:require).with("foreman/export/invalidformatter")
10
10
  mock_export_error("Unknown export format: invalidformatter (no class Foreman::Export::Invalidformatter).") do
11
11
  subject.formatter("invalidformatter")
12
12
  end
@@ -55,13 +55,13 @@ describe Foreman::Process do
55
55
  end
56
56
 
57
57
  it "can execute" do
58
- mock(Kernel).exec "bin/command"
58
+ expect(Kernel).to receive(:exec).with("bin/command")
59
59
  process = Foreman::Process.new("bin/command")
60
60
  process.exec
61
61
  end
62
62
 
63
63
  it "can execute with env" do
64
- mock(Kernel).exec "bin/command bar"
64
+ expect(Kernel).to receive(:exec).with("bin/command bar")
65
65
  process = Foreman::Process.new("bin/command $FOO")
66
66
  process.exec(:env => { "FOO" => "bar" })
67
67
  end
@@ -22,6 +22,13 @@ describe Foreman::Procfile, :fakefs do
22
22
  expect(procfile["foo_bar"]).to eq("./foo_bar")
23
23
  end
24
24
 
25
+ it 'only creates Procfile entries for lines matching regex' do
26
+ write_procfile
27
+ procfile = Foreman::Procfile.new("Procfile")
28
+ keys = procfile.instance_variable_get(:@entries).map(&:first)
29
+ expect(keys).to match_array(%w[alpha bravo foo-bar foo_bar])
30
+ end
31
+
25
32
  it "returns nil when attempting to retrieve an non-existing entry" do
26
33
  write_procfile
27
34
  procfile = Foreman::Procfile.new("Procfile")
data/spec/foreman_spec.rb CHANGED
@@ -5,7 +5,7 @@ describe Foreman do
5
5
 
6
6
  describe "VERSION" do
7
7
  subject { Foreman::VERSION }
8
- it { should be_a String }
8
+ it { is_expected.to be_a String }
9
9
  end
10
10
 
11
11
  describe "runner" do
@@ -2,7 +2,6 @@
2
2
  command=./alpha
3
3
  autostart=true
4
4
  autorestart=true
5
- stopsignal=QUIT
6
5
  stdout_logfile=/var/log/app/alpha-1.log
7
6
  stderr_logfile=/var/log/app/alpha-1.error.log
8
7
  user=app
@@ -13,7 +12,6 @@ environment=FOO="bar",URL="http://example.com/api?foo=bar&baz=1",PORT="5000"
13
12
  command=./bravo
14
13
  autostart=true
15
14
  autorestart=true
16
- stopsignal=QUIT
17
15
  stdout_logfile=/var/log/app/bravo-1.log
18
16
  stderr_logfile=/var/log/app/bravo-1.error.log
19
17
  user=app
@@ -24,7 +22,6 @@ environment=FOO="bar",URL="http://example.com/api?foo=bar&baz=1",PORT="5100"
24
22
  command=./foo_bar
25
23
  autostart=true
26
24
  autorestart=true
27
- stopsignal=QUIT
28
25
  stdout_logfile=/var/log/app/foo_bar-1.log
29
26
  stderr_logfile=/var/log/app/foo_bar-1.error.log
30
27
  user=app
@@ -35,7 +32,6 @@ environment=FOO="bar",URL="http://example.com/api?foo=bar&baz=1",PORT="5200"
35
32
  command=./foo-bar
36
33
  autostart=true
37
34
  autorestart=true
38
- stopsignal=QUIT
39
35
  stdout_logfile=/var/log/app/foo-bar-1.log
40
36
  stderr_logfile=/var/log/app/foo-bar-1.error.log
41
37
  user=app
@@ -2,7 +2,6 @@
2
2
  command=./alpha
3
3
  autostart=true
4
4
  autorestart=true
5
- stopsignal=QUIT
6
5
  stdout_logfile=/var/log/app/alpha-1.log
7
6
  stderr_logfile=/var/log/app/alpha-1.error.log
8
7
  user=app
@@ -13,7 +12,6 @@ environment=PORT="5000"
13
12
  command=./alpha
14
13
  autostart=true
15
14
  autorestart=true
16
- stopsignal=QUIT
17
15
  stdout_logfile=/var/log/app/alpha-2.log
18
16
  stderr_logfile=/var/log/app/alpha-2.error.log
19
17
  user=app
@@ -0,0 +1,2 @@
1
+ [Unit]
2
+ PartOf=app.target
@@ -4,11 +4,12 @@ PartOf=app-alpha.target
4
4
  [Service]
5
5
  User=app
6
6
  WorkingDirectory=/tmp/app
7
- Environment=PORT=5000
8
- ExecStart=/bin/bash -lc './alpha'
7
+ Environment=PORT=%i
8
+ ExecStart=/bin/bash -lc 'exec ./alpha'
9
9
  Restart=always
10
10
  StandardInput=null
11
11
  StandardOutput=syslog
12
12
  StandardError=syslog
13
13
  SyslogIdentifier=%n
14
- KillMode=process
14
+ KillMode=mixed
15
+ TimeoutStopSec=5
@@ -0,0 +1,2 @@
1
+ [Unit]
2
+ PartOf=app.target
@@ -4,11 +4,12 @@ PartOf=app-bravo.target
4
4
  [Service]
5
5
  User=app
6
6
  WorkingDirectory=/tmp/app
7
- Environment=PORT=5100
8
- ExecStart=/bin/bash -lc './bravo'
7
+ Environment=PORT=%i
8
+ ExecStart=/bin/bash -lc 'exec ./bravo'
9
9
  Restart=always
10
10
  StandardInput=null
11
11
  StandardOutput=syslog
12
12
  StandardError=syslog
13
13
  SyslogIdentifier=%n
14
- KillMode=process
14
+ KillMode=mixed
15
+ TimeoutStopSec=5
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,12 @@
1
- require "codeclimate-test-reporter"
2
- CodeClimate::TestReporter.start
1
+ # it throws following message:
2
+ # W, [2018-02-06T18:16:06.360071 #72025] WARN -- : This usage of the Code Climate Test Reporter is now deprecated. Since version
3
+ # 1.0, we now require you to run `SimpleCov` in your test/spec helper, and then
4
+ # run the provided `codeclimate-test-reporter` binary separately to report your
5
+ # results to Code Climate.
6
+ #
7
+ # More information here: https://github.com/codeclimate/ruby-test-reporter/blob/master/README.md
8
+ # require "codeclimate-test-reporter"
9
+ # CodeClimate::TestReporter.start
3
10
 
4
11
  require "simplecov"
5
12
  SimpleCov.start do
@@ -8,6 +15,7 @@ end
8
15
 
9
16
  require "rspec"
10
17
  require "timecop"
18
+ require "pp"
11
19
  require "fakefs/safe"
12
20
  require "fakefs/spec_helpers"
13
21
 
@@ -19,7 +27,7 @@ end
19
27
 
20
28
  def mock_error(subject, message)
21
29
  mock_exit do
22
- mock(subject).puts("ERROR: #{message}")
30
+ expect(subject).to receive(:puts).with("ERROR: #{message}")
23
31
  yield
24
32
  end
25
33
  end
@@ -89,6 +97,7 @@ def write_procfile(procfile="Procfile", alpha_env="")
89
97
  file.puts "bravo:\t./bravo"
90
98
  file.puts "foo_bar:\t./foo_bar"
91
99
  file.puts "foo-bar:\t./foo-bar"
100
+ file.puts "# baz:\t./baz"
92
101
  end
93
102
  File.expand_path(procfile)
94
103
  end
@@ -166,11 +175,9 @@ ensure
166
175
  end
167
176
 
168
177
  RSpec.configure do |config|
169
- config.treat_symbols_as_metadata_keys_with_true_values = true
170
178
  config.color = true
171
179
  config.order = 'rand'
172
180
  config.include FakeFS::SpecHelpers, :fakefs
173
- config.mock_with :rr
174
181
  config.before(:each) do
175
182
  FileUtils.mkdir_p('/tmp')
176
183
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ypadlyak-foreman
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.81.0
4
+ version: 0.85.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Dollar
@@ -121,15 +121,11 @@ files:
121
121
  - spec/resources/export/runit/app-bravo-1/run
122
122
  - spec/resources/export/supervisord/app-alpha-1.conf
123
123
  - spec/resources/export/supervisord/app-alpha-2.conf
124
- - spec/resources/export/systemd/concurrency/app-alpha-1.service
125
- - spec/resources/export/systemd/concurrency/app-alpha-2.service
126
- - spec/resources/export/systemd/concurrency/app-alpha.target
127
- - spec/resources/export/systemd/concurrency/app.target
128
- - spec/resources/export/systemd/standard/app-alpha-1.service
129
- - spec/resources/export/systemd/standard/app-alpha.target
130
- - spec/resources/export/systemd/standard/app-bravo-1.service
131
- - spec/resources/export/systemd/standard/app-bravo.target
132
- - spec/resources/export/systemd/standard/app.target
124
+ - spec/resources/export/systemd/app-alpha.target
125
+ - spec/resources/export/systemd/app-alpha@.service
126
+ - spec/resources/export/systemd/app-bravo.target
127
+ - spec/resources/export/systemd/app-bravo@.service
128
+ - spec/resources/export/systemd/app.target
133
129
  - spec/resources/export/upstart/app-alpha-1.conf
134
130
  - spec/resources/export/upstart/app-alpha-2.conf
135
131
  - spec/resources/export/upstart/app-alpha.conf
@@ -1,14 +0,0 @@
1
- [Unit]
2
- PartOf=app-alpha.target
3
-
4
- [Service]
5
- User=app
6
- WorkingDirectory=/tmp/app
7
- Environment=PORT=5001
8
- ExecStart=/bin/bash -lc './alpha'
9
- Restart=always
10
- StandardInput=null
11
- StandardOutput=syslog
12
- StandardError=syslog
13
- SyslogIdentifier=%n
14
- KillMode=process
@@ -1,3 +0,0 @@
1
- [Unit]
2
- PartOf=app.target
3
- Wants=app-alpha-1.service app-alpha-2.service
@@ -1,5 +0,0 @@
1
- [Unit]
2
- Wants=app-alpha.target
3
-
4
- [Install]
5
- WantedBy=multi-user.target
@@ -1,14 +0,0 @@
1
- [Unit]
2
- PartOf=app-alpha.target
3
-
4
- [Service]
5
- User=app
6
- WorkingDirectory=/tmp/app
7
- Environment=PORT=5000
8
- ExecStart=/bin/bash -lc './alpha'
9
- Restart=always
10
- StandardInput=null
11
- StandardOutput=syslog
12
- StandardError=syslog
13
- SyslogIdentifier=%n
14
- KillMode=process
@@ -1,3 +0,0 @@
1
- [Unit]
2
- PartOf=app.target
3
- Wants=app-alpha-1.service
@@ -1,3 +0,0 @@
1
- [Unit]
2
- PartOf=app.target
3
- Wants=app-bravo-1.service