ypadlyak-foreman 0.81.0 → 0.85.0

Sign up to get free protection for your applications and to get access to all the features.
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