foreman 0.63.0 → 0.64.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 (37) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1 -0
  3. data/data/export/daemon/master.conf.erb +14 -0
  4. data/data/export/daemon/process.conf.erb +8 -0
  5. data/data/export/daemon/process_master.conf.erb +2 -0
  6. data/data/export/systemd/master.target.erb +1 -0
  7. data/data/export/systemd/process.service.erb +18 -0
  8. data/data/export/systemd/process_master.target.erb +5 -0
  9. data/lib/foreman/cli.rb +10 -0
  10. data/lib/foreman/engine.rb +7 -2
  11. data/lib/foreman/export.rb +2 -0
  12. data/lib/foreman/export/base.rb +6 -0
  13. data/lib/foreman/export/daemon.rb +28 -0
  14. data/lib/foreman/export/systemd.rb +25 -0
  15. data/lib/foreman/process.rb +17 -13
  16. data/lib/foreman/version.rb +1 -1
  17. data/man/foreman.1 +23 -2
  18. data/spec/foreman/cli_spec.rb +15 -0
  19. data/spec/foreman/export/daemon_spec.rb +97 -0
  20. data/spec/foreman/export/systemd_spec.rb +91 -0
  21. data/spec/foreman/process_spec.rb +1 -1
  22. data/spec/resources/Procfile +1 -0
  23. data/spec/resources/export/daemon/app-alpha-1.conf +7 -0
  24. data/spec/resources/export/daemon/app-alpha-2.conf +7 -0
  25. data/spec/resources/export/daemon/app-alpha.conf +2 -0
  26. data/spec/resources/export/daemon/app-bravo-1.conf +7 -0
  27. data/spec/resources/export/daemon/app-bravo.conf +2 -0
  28. data/spec/resources/export/daemon/app.conf +14 -0
  29. data/spec/resources/export/systemd/app-alpha-1.service +17 -0
  30. data/spec/resources/export/systemd/app-alpha-2.service +17 -0
  31. data/spec/resources/export/systemd/app-alpha.target +5 -0
  32. data/spec/resources/export/systemd/app-bravo-1.service +17 -0
  33. data/spec/resources/export/systemd/app-bravo.target +5 -0
  34. data/spec/resources/export/systemd/app.target +1 -0
  35. data/spec/spec_helper.rb +27 -5
  36. metadata +44 -25
  37. data/lib/foreman/capistrano.rb +0 -54
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bbb82e88b1e4cc986e7af63cc6d631fa04a3835f
4
+ data.tar.gz: 628e407c7dc4c6b1c45feae01f0721226015fc77
5
+ SHA512:
6
+ metadata.gz: 97b7f37dd66b6d5959c68b02f088e1d13f9ca5e0521020e44134d720ad985498eddb3e6694c1718c84263bbfd50e6ec2a6807ad19ae6c2f615a1c278f1b9f925
7
+ data.tar.gz: a5233b13f6516fd91fbdac8919d97b5c9538e84147a937bc6c9b071f0eed55a09f8fe3016c66d67d1036528a104b0eff04ed4d82fcba32bd800466911a1df013
data/README.md CHANGED
@@ -32,6 +32,7 @@ Manage Procfile-based applications
32
32
  * [shoreman](https://github.com/hecticjeff/shoreman) - shell
33
33
  * [honcho](https://github.com/nickstenning/honcho) - python
34
34
  * [norman](https://github.com/josh/norman) - node.js
35
+ * [forego](https://github.com/ddollar/forego) - Go
35
36
 
36
37
  ## Authors
37
38
 
@@ -0,0 +1,14 @@
1
+ pre-start script
2
+
3
+ bash << "EOF"
4
+ mkdir -p <%= log %>
5
+ chown -R <%= user %> <%= log %>
6
+ mkdir -p <%= run %>
7
+ chown -R <%= user %> <%= run %>
8
+ EOF
9
+
10
+ end script
11
+
12
+ start on runlevel [2345]
13
+
14
+ stop on runlevel [016]
@@ -0,0 +1,8 @@
1
+ start on starting <%= app %>-<%= name.gsub('_', '-') %>
2
+ stop on stopping <%= app %>-<%= name.gsub('_', '-') %>
3
+ respawn
4
+
5
+ env PORT=<%= port %><% engine.env.each_pair do |var, env| %>
6
+ env <%= var.upcase %>=<%= env %><% end %>
7
+
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
@@ -0,0 +1,2 @@
1
+ start on starting <%= app %>
2
+ stop on stopping <%= app %>
@@ -0,0 +1 @@
1
+ [Unit]
@@ -0,0 +1,18 @@
1
+ [Unit]
2
+ StopWhenUnneeded=true
3
+
4
+ [Service]
5
+ User=<%= user %>
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 %>'
10
+ Restart=always
11
+ StandardInput=null
12
+ StandardOutput=syslog
13
+ StandardError=syslog
14
+ SyslogIdentifier=%n
15
+ KillMode=process
16
+
17
+ [Install]
18
+ WantedBy=<%= app %>-<%= name %>.target
@@ -0,0 +1,5 @@
1
+ [Unit]
2
+ StopWhenUnneeded=true
3
+
4
+ [Install]
5
+ WantedBy=<%= app %>.target
@@ -34,6 +34,7 @@ class Foreman::CLI < Thor
34
34
  end
35
35
 
36
36
  def start(process=nil)
37
+ require_posix_spawn_for_ruby_18!
37
38
  check_procfile!
38
39
  load_environment!
39
40
  engine.load_procfile(procfile)
@@ -45,6 +46,7 @@ class Foreman::CLI < Thor
45
46
 
46
47
  method_option :app, :type => :string, :aliases => "-a"
47
48
  method_option :log, :type => :string, :aliases => "-l"
49
+ method_option :run, :type => :string, :aliases => "-r", :desc => "Specify the pid file directory, defaults to /var/run/<application>"
48
50
  method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
49
51
  method_option :port, :type => :numeric, :aliases => "-p"
50
52
  method_option :user, :type => :string, :aliases => "-u"
@@ -137,6 +139,14 @@ private ######################################################################
137
139
  end
138
140
  end
139
141
 
142
+ def require_posix_spawn_for_ruby_18!
143
+ begin
144
+ Kernel.require 'posix/spawn' # Use Kernel explicitly so we can mock the require call in the spec
145
+ rescue LoadError
146
+ error "foreman requires gem `posix-spawn` on Ruby #{RUBY_VERSION}. Please `gem install posix-spawn`."
147
+ end if Foreman.ruby_18?
148
+ end
149
+
140
150
  def procfile
141
151
  case
142
152
  when options[:procfile] then options[:procfile]
@@ -302,6 +302,10 @@ private
302
302
 
303
303
  def name_for(pid)
304
304
  process, index = @running[pid]
305
+ name_for_index(process, index)
306
+ end
307
+
308
+ def name_for_index(process, index)
305
309
  [ @names[process], index.to_s ].compact.join(".")
306
310
  end
307
311
 
@@ -350,7 +354,8 @@ private
350
354
  reader, writer = create_pipe
351
355
  begin
352
356
  pid = process.run(:output => writer, :env => {
353
- "PORT" => port_for(process, n).to_s
357
+ "PORT" => port_for(process, n).to_s,
358
+ "PS" => name_for_index(process, n)
354
359
  })
355
360
  writer.puts "started with pid #{pid}"
356
361
  rescue Errno::ENOENT
@@ -422,7 +427,7 @@ private
422
427
  end
423
428
  rescue Timeout::Error
424
429
  system "sending SIGKILL to all processes"
425
- killall "SIGKILL"
430
+ kill_children "SIGKILL"
426
431
  end
427
432
 
428
433
  end
@@ -28,7 +28,9 @@ end
28
28
  require "foreman/export/base"
29
29
  require "foreman/export/inittab"
30
30
  require "foreman/export/upstart"
31
+ require "foreman/export/daemon"
31
32
  require "foreman/export/bluepill"
32
33
  require "foreman/export/runit"
33
34
  require "foreman/export/supervisord"
34
35
  require "foreman/export/launchd"
36
+ require "foreman/export/systemd"
@@ -47,7 +47,9 @@ class Foreman::Export::Base
47
47
  error("Must specify a location") unless location
48
48
  FileUtils.mkdir_p(location) rescue error("Could not create: #{location}")
49
49
  FileUtils.mkdir_p(log) rescue error("Could not create: #{log}")
50
+ FileUtils.mkdir_p(run) rescue error("Could not create: #{run}")
50
51
  FileUtils.chown(user, nil, log) rescue error("Could not chown #{log} to #{user}")
52
+ FileUtils.chown(user, nil, run) rescue error("Could not chown #{run} to #{user}")
51
53
  end
52
54
 
53
55
  def app
@@ -58,6 +60,10 @@ class Foreman::Export::Base
58
60
  options[:log] || "/var/log/#{app}"
59
61
  end
60
62
 
63
+ def run
64
+ options[:run] || "/var/run/#{app}"
65
+ end
66
+
61
67
  def user
62
68
  options[:user] || app
63
69
  end
@@ -0,0 +1,28 @@
1
+ require "erb"
2
+ require "foreman/export"
3
+
4
+ class Foreman::Export::Daemon < Foreman::Export::Base
5
+
6
+ def export
7
+ super
8
+
9
+ (Dir["#{location}/#{app}-*.conf"] << "#{location}/#{app}.conf").each do |file|
10
+ clean file
11
+ end
12
+
13
+ write_template "daemon/master.conf.erb", "#{app}.conf", binding
14
+
15
+ engine.each_process do |name, process|
16
+ next if engine.formation[name] < 1
17
+ write_template "daemon/process_master.conf.erb", "#{app}-#{name}.conf", binding
18
+
19
+ 1.upto(engine.formation[name]) do |num|
20
+ port = engine.port_for(process, num)
21
+ arguments = process.command.split(" ")
22
+ executable = arguments.slice!(0)
23
+ arguments = arguments.size > 0 ? " -- #{arguments.join(' ')}" : ""
24
+ write_template "daemon/process.conf.erb", "#{app}-#{name}-#{num}.conf", binding
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ require "erb"
2
+ require "foreman/export"
3
+
4
+ class Foreman::Export::Systemd < Foreman::Export::Base
5
+
6
+ def export
7
+ super
8
+
9
+ Dir["#{location}/#{app}*.target"].concat(Dir["#{location}/#{app}*.service"]).each do |file|
10
+ clean file
11
+ end
12
+
13
+ write_template "systemd/master.target.erb", "#{app}.target", binding
14
+
15
+ engine.each_process do |name, process|
16
+ next if engine.formation[name] < 1
17
+ write_template "systemd/process_master.target.erb", "#{app}-#{name}.target", binding
18
+
19
+ 1.upto(engine.formation[name]) do |num|
20
+ port = engine.port_for(process, num)
21
+ write_template "systemd/process.service.erb", "#{app}-#{name}-#{num}.service", binding
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,4 +1,5 @@
1
1
  require "foreman"
2
+ require "shellwords"
2
3
 
3
4
  class Foreman::Process
4
5
 
@@ -47,25 +48,18 @@ class Foreman::Process
47
48
  def run(options={})
48
49
  env = @options[:env].merge(options[:env] || {})
49
50
  output = options[:output] || $stdout
50
-
51
+ runner = "#{Foreman.runner}".shellescape
52
+
51
53
  if Foreman.windows?
52
54
  Dir.chdir(cwd) do
53
55
  Process.spawn env, expanded_command(env), :out => output, :err => output
54
56
  end
55
- elsif Foreman.jruby_18?
57
+ elsif Foreman.jruby_18? || Foreman.ruby_18?
56
58
  require "posix/spawn"
57
- wrapped_command = "#{Foreman.runner} -d '#{cwd}' -p -- #{command}"
58
- POSIX::Spawn.spawn env, wrapped_command, :out => output, :err => output
59
- elsif Foreman.ruby_18?
60
- fork do
61
- $stdout.reopen output
62
- $stderr.reopen output
63
- env.each { |k,v| ENV[k] = v }
64
- wrapped_command = "#{Foreman.runner} -d '#{cwd}' -p -- #{command}"
65
- Kernel.exec wrapped_command
66
- end
59
+ wrapped_command = "#{runner} -d '#{cwd.shellescape}' -p -- #{expanded_command(env)}"
60
+ POSIX::Spawn.spawn(*spawn_args(env, wrapped_command.shellsplit, {:out => output, :err => output}))
67
61
  else
68
- wrapped_command = "#{Foreman.runner} -d '#{cwd}' -p -- #{command}"
62
+ wrapped_command = "#{runner} -d '#{cwd.shellescape}' -p -- #{command}"
69
63
  Process.spawn env, wrapped_command, :out => output, :err => output
70
64
  end
71
65
  end
@@ -122,4 +116,14 @@ class Foreman::Process
122
116
  File.expand_path(@options[:cwd] || ".")
123
117
  end
124
118
 
119
+ private
120
+
121
+ def spawn_args(env, argv, options)
122
+ args = []
123
+ args << env
124
+ args += argv
125
+ args << options
126
+ args
127
+ end
128
+
125
129
  end
@@ -1,5 +1,5 @@
1
1
  module Foreman
2
2
 
3
- VERSION = "0.63.0"
3
+ VERSION = "0.64.0"
4
4
 
5
5
  end
@@ -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" "January 2013" "Foreman 0.61.0" "Foreman Manual"
4
+ .TH "FOREMAN" "1" "April 2014" "Foreman 0.64.0" "Foreman Manual"
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBforeman\fR \- manage Procfile\-based applications
@@ -107,9 +107,18 @@ bluepill
107
107
  inittab
108
108
  .
109
109
  .IP "\(bu" 4
110
+ launchd
111
+ .
112
+ .IP "\(bu" 4
110
113
  runit
111
114
  .
112
115
  .IP "\(bu" 4
116
+ supervisord
117
+ .
118
+ .IP "\(bu" 4
119
+ systemd
120
+ .
121
+ .IP "\(bu" 4
113
122
  upstart
114
123
  .
115
124
  .IP "" 0
@@ -130,6 +139,18 @@ EX02:4:respawn:/bin/su \- example \-c \'PORT=5100 bundle exec rake jobs:work >>
130
139
  .
131
140
  .IP "" 0
132
141
  .
142
+ .SH "SYSTEMD EXPORT"
143
+ Will create a series of systemd scripts in the location you specify\. Scripts will be structured to make the following commands valid:
144
+ .
145
+ .P
146
+ \fBsystemctl start appname\.target\fR
147
+ .
148
+ .P
149
+ \fBsystemctl stop appname\-processname\.target\fR
150
+ .
151
+ .P
152
+ \fBsystemctl restart appname\-processname\-3\.service\fR
153
+ .
133
154
  .SH "UPSTART EXPORT"
134
155
  Will create a series of upstart scripts in the location you specify\. Scripts will be structured to make the following commands valid:
135
156
  .
@@ -157,7 +178,7 @@ job: bundle exec rake jobs:work
157
178
  .IP "" 0
158
179
  .
159
180
  .P
160
- A process name may contain letters, numbers amd the underscore character\. You can validate your Procfile format using the \fBcheck\fR command:
181
+ A process name may contain letters, numbers and the underscore character\. You can validate your Procfile format using the \fBcheck\fR command:
161
182
  .
162
183
  .IP "" 4
163
184
  .
@@ -44,6 +44,13 @@ describe "Foreman::CLI", :fakefs do
44
44
  output.should =~ /test.1 \| testing/
45
45
  end
46
46
  end
47
+
48
+ it "sets PS variable with the process name" do
49
+ without_fakefs do
50
+ output = foreman("start -f #{resource_path("Procfile")}")
51
+ output.should =~ /ps.1 \| PS env var is ps.1/
52
+ end
53
+ end
47
54
  end
48
55
  end
49
56
 
@@ -93,4 +100,12 @@ describe "Foreman::CLI", :fakefs do
93
100
  end
94
101
  end
95
102
 
103
+ describe "when posix-spawn is not present on ruby 1.8" do
104
+ it "should fail with an error" do
105
+ mock(Kernel).require('posix/spawn') { raise LoadError }
106
+ output = foreman("start -f #{resource_path("Procfile")}")
107
+ output.should == "ERROR: foreman requires gem `posix-spawn` on Ruby #{RUBY_VERSION}. Please `gem install posix-spawn`.\n"
108
+ end
109
+ end if running_ruby_18?
110
+
96
111
  end
@@ -0,0 +1,97 @@
1
+ require "spec_helper"
2
+ require "foreman/engine"
3
+ require "foreman/export/daemon"
4
+ require "tmpdir"
5
+
6
+ describe Foreman::Export::Daemon, :fakefs do
7
+ let(:procfile) { write_procfile("/tmp/app/Procfile") }
8
+ let(:formation) { nil }
9
+ let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
10
+ let(:options) { Hash.new }
11
+ let(:daemon) { Foreman::Export::Daemon.new("/tmp/init", engine, options) }
12
+
13
+ before(:each) { load_export_templates_into_fakefs("daemon") }
14
+ before(:each) { stub(daemon).say }
15
+
16
+ it "exports to the filesystem" do
17
+ daemon.export
18
+
19
+ File.read("/tmp/init/app.conf").should == example_export_file("daemon/app.conf")
20
+ File.read("/tmp/init/app-alpha.conf").should == example_export_file("daemon/app-alpha.conf")
21
+ File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("daemon/app-alpha-1.conf")
22
+ File.read("/tmp/init/app-bravo.conf").should == example_export_file("daemon/app-bravo.conf")
23
+ File.read("/tmp/init/app-bravo-1.conf").should == example_export_file("daemon/app-bravo-1.conf")
24
+ end
25
+
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")
36
+
37
+ daemon.export
38
+ daemon.export
39
+ end
40
+
41
+ it "does not delete exported files for similarly named applications" do
42
+ FileUtils.mkdir_p "/tmp/init"
43
+
44
+ ["app2", "app2-alpha", "app2-alpha-1"].each do |name|
45
+ path = "/tmp/init/#{name}.conf"
46
+ FileUtils.touch(path)
47
+ dont_allow(FileUtils).rm(path)
48
+ end
49
+
50
+ daemon.export
51
+ end
52
+
53
+ context "with a formation" do
54
+ let(:formation) { "alpha=2" }
55
+
56
+ it "exports to the filesystem with concurrency" do
57
+ daemon.export
58
+
59
+ File.read("/tmp/init/app.conf").should == example_export_file("daemon/app.conf")
60
+ File.read("/tmp/init/app-alpha.conf").should == example_export_file("daemon/app-alpha.conf")
61
+ File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("daemon/app-alpha-1.conf")
62
+ File.read("/tmp/init/app-alpha-2.conf").should == example_export_file("daemon/app-alpha-2.conf")
63
+ File.exists?("/tmp/init/app-bravo-1.conf").should == false
64
+ end
65
+ end
66
+
67
+ context "with alternate templates" do
68
+ let(:template) { "/tmp/alternate" }
69
+ let(:options) { { :app => "app", :template => template } }
70
+
71
+ before do
72
+ FileUtils.mkdir_p template
73
+ File.open("#{template}/master.conf.erb", "w") { |f| f.puts "alternate_template" }
74
+ end
75
+
76
+ it "can export with alternate template files" do
77
+ daemon.export
78
+ File.read("/tmp/init/app.conf").should == "alternate_template\n"
79
+ end
80
+ end
81
+
82
+ context "with alternate templates from home dir" do
83
+
84
+ before do
85
+ FileUtils.mkdir_p File.expand_path("~/.foreman/templates/daemon")
86
+ File.open(File.expand_path("~/.foreman/templates/daemon/master.conf.erb"), "w") do |file|
87
+ file.puts "default_alternate_template"
88
+ end
89
+ end
90
+
91
+ it "can export with alternate template files" do
92
+ daemon.export
93
+ File.read("/tmp/init/app.conf").should == "default_alternate_template\n"
94
+ end
95
+ end
96
+
97
+ end
@@ -0,0 +1,91 @@
1
+ require "spec_helper"
2
+ require "foreman/engine"
3
+ require "foreman/export/systemd"
4
+ require "tmpdir"
5
+
6
+ describe Foreman::Export::Systemd, :fakefs do
7
+ let(:procfile) { write_procfile("/tmp/app/Procfile") }
8
+ let(:formation) { nil }
9
+ let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
10
+ let(:options) { Hash.new }
11
+ let(:systemd) { Foreman::Export::Systemd.new("/tmp/init", engine, options) }
12
+
13
+ before(:each) { load_export_templates_into_fakefs("systemd") }
14
+ before(:each) { stub(systemd).say }
15
+
16
+ it "exports to the filesystem" do
17
+ systemd.export
18
+
19
+ File.read("/tmp/init/app.target").should == example_export_file("systemd/app.target")
20
+ File.read("/tmp/init/app-alpha.target").should == example_export_file("systemd/app-alpha.target")
21
+ File.read("/tmp/init/app-alpha-1.service").should == example_export_file("systemd/app-alpha-1.service")
22
+ File.read("/tmp/init/app-bravo.target").should == example_export_file("systemd/app-bravo.target")
23
+ File.read("/tmp/init/app-bravo-1.service").should == example_export_file("systemd/app-bravo-1.service")
24
+ end
25
+
26
+ 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")
36
+
37
+ systemd.export
38
+ systemd.export
39
+ end
40
+
41
+ it "includes environment variables" do
42
+ engine.env['KEY'] = 'some "value"'
43
+ systemd.export
44
+ File.read("/tmp/init/app-alpha-1.service").should =~ /KEY=some "value"$/
45
+ end
46
+
47
+ context "with a formation" do
48
+ let(:formation) { "alpha=2" }
49
+
50
+ it "exports to the filesystem with concurrency" do
51
+ systemd.export
52
+
53
+ File.read("/tmp/init/app.target").should == example_export_file("systemd/app.target")
54
+ File.read("/tmp/init/app-alpha.target").should == example_export_file("systemd/app-alpha.target")
55
+ File.read("/tmp/init/app-alpha-1.service").should == example_export_file("systemd/app-alpha-1.service")
56
+ File.read("/tmp/init/app-alpha-2.service").should == example_export_file("systemd/app-alpha-2.service")
57
+ File.exists?("/tmp/init/app-bravo-1.service").should == false
58
+ end
59
+ end
60
+
61
+ context "with alternate templates" do
62
+ let(:template) { "/tmp/alternate" }
63
+ let(:options) { { :app => "app", :template => template } }
64
+
65
+ before do
66
+ FileUtils.mkdir_p template
67
+ File.open("#{template}/master.target.erb", "w") { |f| f.puts "alternate_template" }
68
+ end
69
+
70
+ it "can export with alternate template files" do
71
+ systemd.export
72
+ File.read("/tmp/init/app.target").should == "alternate_template\n"
73
+ end
74
+ end
75
+
76
+ context "with alternate templates from home dir" do
77
+
78
+ before do
79
+ FileUtils.mkdir_p File.expand_path("~/.foreman/templates/systemd")
80
+ File.open(File.expand_path("~/.foreman/templates/systemd/master.target.erb"), "w") do |file|
81
+ file.puts "default_alternate_template"
82
+ end
83
+ end
84
+
85
+ it "can export with alternate template files" do
86
+ systemd.export
87
+ File.read("/tmp/init/app.target").should == "default_alternate_template\n"
88
+ end
89
+ end
90
+
91
+ end
@@ -41,7 +41,7 @@ describe Foreman::Process do
41
41
 
42
42
  it "should output utf8 properly" do
43
43
  process = Foreman::Process.new(resource_path("bin/utf8"))
44
- run(process).should == "\xFF\x03\n".force_encoding('binary')
44
+ run(process).should == (Foreman.ruby_18? ? "\xFF\x03\n" : "\xFF\x03\n".force_encoding('binary'))
45
45
  end
46
46
  end
47
47
 
@@ -2,3 +2,4 @@ echo: bin/echo echoing
2
2
  env: bin/env FOO
3
3
  test: bin/test
4
4
  utf8: bin/utf8
5
+ ps: bin/echo PS env var is $PS
@@ -0,0 +1,7 @@
1
+ start on starting app-alpha
2
+ stop on stopping app-alpha
3
+ respawn
4
+
5
+ env PORT=5000
6
+
7
+ exec start-stop-daemon --start --chuid app --chdir /tmp/app --make-pidfile --pidfile /var/run/app/app-alpha-1.pid --exec ./alpha >> /var/log/app/app-alpha-1.log 2>&1
@@ -0,0 +1,7 @@
1
+ start on starting app-alpha
2
+ stop on stopping app-alpha
3
+ respawn
4
+
5
+ env PORT=5001
6
+
7
+ exec start-stop-daemon --start --chuid app --chdir /tmp/app --make-pidfile --pidfile /var/run/app/app-alpha-2.pid --exec ./alpha >> /var/log/app/app-alpha-2.log 2>&1
@@ -0,0 +1,2 @@
1
+ start on starting app
2
+ stop on stopping app
@@ -0,0 +1,7 @@
1
+ start on starting app-bravo
2
+ stop on stopping app-bravo
3
+ respawn
4
+
5
+ env PORT=5100
6
+
7
+ exec start-stop-daemon --start --chuid app --chdir /tmp/app --make-pidfile --pidfile /var/run/app/app-bravo-1.pid --exec ./bravo >> /var/log/app/app-bravo-1.log 2>&1
@@ -0,0 +1,2 @@
1
+ start on starting app
2
+ stop on stopping app
@@ -0,0 +1,14 @@
1
+ pre-start script
2
+
3
+ bash << "EOF"
4
+ mkdir -p /var/log/app
5
+ chown -R app /var/log/app
6
+ mkdir -p /var/run/app
7
+ chown -R app /var/run/app
8
+ EOF
9
+
10
+ end script
11
+
12
+ start on runlevel [2345]
13
+
14
+ stop on runlevel [016]
@@ -0,0 +1,17 @@
1
+ [Unit]
2
+ StopWhenUnneeded=true
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
15
+
16
+ [Install]
17
+ WantedBy=app-alpha.target
@@ -0,0 +1,17 @@
1
+ [Unit]
2
+ StopWhenUnneeded=true
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
15
+
16
+ [Install]
17
+ WantedBy=app-alpha.target
@@ -0,0 +1,5 @@
1
+ [Unit]
2
+ StopWhenUnneeded=true
3
+
4
+ [Install]
5
+ WantedBy=app.target
@@ -0,0 +1,17 @@
1
+ [Unit]
2
+ StopWhenUnneeded=true
3
+
4
+ [Service]
5
+ User=app
6
+ WorkingDirectory=/tmp/app
7
+ Environment=PORT=5100
8
+ ExecStart=/bin/bash -lc './bravo'
9
+ Restart=always
10
+ StandardInput=null
11
+ StandardOutput=syslog
12
+ StandardError=syslog
13
+ SyslogIdentifier=%n
14
+ KillMode=process
15
+
16
+ [Install]
17
+ WantedBy=app-bravo.target
@@ -0,0 +1,5 @@
1
+ [Unit]
2
+ StopWhenUnneeded=true
3
+
4
+ [Install]
5
+ WantedBy=app.target
@@ -10,6 +10,15 @@ require "fakefs/spec_helpers"
10
10
 
11
11
  $:.unshift File.expand_path("../../lib", __FILE__)
12
12
 
13
+ begin
14
+ def running_ruby_18?
15
+ defined?(RUBY_VERSION) and RUBY_VERSION =~ /^1\.8\.\d+/
16
+ end
17
+ require 'posix/spawn' if running_ruby_18?
18
+ rescue LoadError
19
+ STDERR.puts "WARNING: foreman requires gem `posix-spawn` on Ruby #{RUBY_VERSION}. Please `gem install posix-spawn`."
20
+ end
21
+
13
22
  def mock_export_error(message)
14
23
  lambda { yield }.should raise_error(Foreman::Export::Exception, message)
15
24
  end
@@ -21,6 +30,10 @@ def mock_error(subject, message)
21
30
  end
22
31
  end
23
32
 
33
+ def make_pipe
34
+ IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
35
+ end
36
+
24
37
  def foreman(args)
25
38
  capture_stdout do
26
39
  begin
@@ -31,14 +44,18 @@ def foreman(args)
31
44
  end
32
45
 
33
46
  def forked_foreman(args)
34
- rd, wr = IO.pipe("BINARY")
35
- Process.spawn("bundle exec bin/foreman #{args}", :out => wr, :err => wr)
47
+ rd, wr = make_pipe
48
+ if running_ruby_18?
49
+ POSIX::Spawn.spawn({}, "bundle exec bin/foreman #{args}", :out => wr, :err => wr)
50
+ else
51
+ Process.spawn("bundle exec bin/foreman #{args}", :out => wr, :err => wr)
52
+ end
36
53
  wr.close
37
54
  rd.read
38
55
  end
39
56
 
40
57
  def fork_and_capture(&blk)
41
- rd, wr = IO.pipe("BINARY")
58
+ rd, wr = make_pipe
42
59
  pid = fork do
43
60
  rd.close
44
61
  wr.sync = true
@@ -57,7 +74,11 @@ def fork_and_capture(&blk)
57
74
  end
58
75
 
59
76
  def fork_and_get_exitstatus(args)
60
- pid = Process.spawn("bundle exec bin/foreman #{args}", :out => "/dev/null", :err => "/dev/null")
77
+ pid = if running_ruby_18?
78
+ POSIX::Spawn.spawn({}, "bundle exec bin/foreman #{args}", :out => "/dev/null", :err => "/dev/null")
79
+ else
80
+ Process.spawn("bundle exec bin/foreman #{args}", :out => "/dev/null", :err => "/dev/null")
81
+ end
61
82
  Process.wait(pid)
62
83
  $?.exitstatus
63
84
  end
@@ -141,7 +162,7 @@ end
141
162
 
142
163
  def capture_stdout
143
164
  old_stdout = $stdout.dup
144
- rd, wr = IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
165
+ rd, wr = make_pipe
145
166
  $stdout = wr
146
167
  yield
147
168
  wr.close
@@ -156,4 +177,5 @@ RSpec.configure do |config|
156
177
  config.order = 'rand'
157
178
  config.include FakeFS::SpecHelpers, :fakefs
158
179
  config.mock_with :rr
180
+ config.backtrace_clean_patterns = []
159
181
  end
metadata CHANGED
@@ -1,38 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.63.0
5
- prerelease:
4
+ version: 0.64.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - David Dollar
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-04-15 00:00:00.000000000 Z
11
+ date: 2014-04-22 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: thor
16
- requirement: &70110396465840 !ruby/object:Gem::Requirement
17
- none: false
15
+ requirement: !ruby/object:Gem::Requirement
18
16
  requirements:
19
- - - ! '>='
17
+ - - '>='
20
18
  - !ruby/object:Gem::Version
21
19
  version: 0.13.6
22
20
  type: :runtime
23
21
  prerelease: false
24
- version_requirements: *70110396465840
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.13.6
25
27
  - !ruby/object:Gem::Dependency
26
28
  name: dotenv
27
- requirement: &70110396464720 !ruby/object:Gem::Requirement
28
- none: false
29
+ requirement: !ruby/object:Gem::Requirement
29
30
  requirements:
30
- - - ! '>='
31
+ - - ~>
31
32
  - !ruby/object:Gem::Version
32
- version: '0.7'
33
+ version: 0.7.0
33
34
  type: :runtime
34
35
  prerelease: false
35
- version_requirements: *70110396464720
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 0.7.0
36
41
  description: Process manager for applications with multiple components
37
42
  email: ddollar@gmail.com
38
43
  executables:
@@ -52,24 +57,31 @@ files:
52
57
  - data/example/ticker
53
58
  - data/example/utf8
54
59
  - data/export/bluepill/master.pill.erb
60
+ - data/export/daemon/master.conf.erb
61
+ - data/export/daemon/process.conf.erb
62
+ - data/export/daemon/process_master.conf.erb
55
63
  - data/export/launchd/launchd.plist.erb
56
64
  - data/export/runit/log/run.erb
57
65
  - data/export/runit/run.erb
58
66
  - data/export/supervisord/app.conf.erb
67
+ - data/export/systemd/master.target.erb
68
+ - data/export/systemd/process.service.erb
69
+ - data/export/systemd/process_master.target.erb
59
70
  - data/export/upstart/master.conf.erb
60
71
  - data/export/upstart/process.conf.erb
61
72
  - data/export/upstart/process_master.conf.erb
62
- - lib/foreman/capistrano.rb
63
73
  - lib/foreman/cli.rb
64
74
  - lib/foreman/distribution.rb
65
75
  - lib/foreman/engine/cli.rb
66
76
  - lib/foreman/engine.rb
67
77
  - lib/foreman/export/base.rb
68
78
  - lib/foreman/export/bluepill.rb
79
+ - lib/foreman/export/daemon.rb
69
80
  - lib/foreman/export/inittab.rb
70
81
  - lib/foreman/export/launchd.rb
71
82
  - lib/foreman/export/runit.rb
72
83
  - lib/foreman/export/supervisord.rb
84
+ - lib/foreman/export/systemd.rb
73
85
  - lib/foreman/export/upstart.rb
74
86
  - lib/foreman/export.rb
75
87
  - lib/foreman/helpers.rb
@@ -82,10 +94,12 @@ files:
82
94
  - spec/foreman/engine_spec.rb
83
95
  - spec/foreman/export/base_spec.rb
84
96
  - spec/foreman/export/bluepill_spec.rb
97
+ - spec/foreman/export/daemon_spec.rb
85
98
  - spec/foreman/export/inittab_spec.rb
86
99
  - spec/foreman/export/launchd_spec.rb
87
100
  - spec/foreman/export/runit_spec.rb
88
101
  - spec/foreman/export/supervisord_spec.rb
102
+ - spec/foreman/export/systemd_spec.rb
89
103
  - spec/foreman/export/upstart_spec.rb
90
104
  - spec/foreman/export_spec.rb
91
105
  - spec/foreman/helpers_spec.rb
@@ -99,6 +113,12 @@ files:
99
113
  - spec/resources/bin/utf8
100
114
  - spec/resources/export/bluepill/app-concurrency.pill
101
115
  - spec/resources/export/bluepill/app.pill
116
+ - spec/resources/export/daemon/app-alpha-1.conf
117
+ - spec/resources/export/daemon/app-alpha-2.conf
118
+ - spec/resources/export/daemon/app-alpha.conf
119
+ - spec/resources/export/daemon/app-bravo-1.conf
120
+ - spec/resources/export/daemon/app-bravo.conf
121
+ - spec/resources/export/daemon/app.conf
102
122
  - spec/resources/export/inittab/inittab.concurrency
103
123
  - spec/resources/export/inittab/inittab.default
104
124
  - spec/resources/export/launchd/launchd-a.default
@@ -112,6 +132,12 @@ files:
112
132
  - spec/resources/export/runit/app-bravo-1/run
113
133
  - spec/resources/export/supervisord/app-alpha-1.conf
114
134
  - spec/resources/export/supervisord/app-alpha-2.conf
135
+ - spec/resources/export/systemd/app-alpha-1.service
136
+ - spec/resources/export/systemd/app-alpha-2.service
137
+ - spec/resources/export/systemd/app-alpha.target
138
+ - spec/resources/export/systemd/app-bravo-1.service
139
+ - spec/resources/export/systemd/app-bravo.target
140
+ - spec/resources/export/systemd/app.target
115
141
  - spec/resources/export/upstart/app-alpha-1.conf
116
142
  - spec/resources/export/upstart/app-alpha-2.conf
117
143
  - spec/resources/export/upstart/app-alpha.conf
@@ -124,33 +150,26 @@ files:
124
150
  homepage: http://github.com/ddollar/foreman
125
151
  licenses:
126
152
  - MIT
153
+ metadata: {}
127
154
  post_install_message:
128
155
  rdoc_options: []
129
156
  require_paths:
130
157
  - lib
131
158
  required_ruby_version: !ruby/object:Gem::Requirement
132
- none: false
133
159
  requirements:
134
- - - ! '>='
160
+ - - '>='
135
161
  - !ruby/object:Gem::Version
136
162
  version: '0'
137
- segments:
138
- - 0
139
- hash: -2345182653779911180
140
163
  required_rubygems_version: !ruby/object:Gem::Requirement
141
- none: false
142
164
  requirements:
143
- - - ! '>='
165
+ - - '>='
144
166
  - !ruby/object:Gem::Version
145
167
  version: '0'
146
- segments:
147
- - 0
148
- hash: -2345182653779911180
149
168
  requirements: []
150
169
  rubyforge_project:
151
- rubygems_version: 1.8.11
170
+ rubygems_version: 2.0.0
152
171
  signing_key:
153
- specification_version: 3
172
+ specification_version: 4
154
173
  summary: Process manager for applications with multiple components
155
174
  test_files: []
156
175
  has_rdoc:
@@ -1,54 +0,0 @@
1
- if defined?(Capistrano)
2
- Capistrano::Configuration.instance(:must_exist).load do
3
-
4
- namespace :foreman do
5
- desc <<-DESC
6
- Export the Procfile to upstart. Will use sudo if available.
7
-
8
- You can override any of these defaults by setting the variables shown below.
9
-
10
- set :foreman_format, "upstart"
11
- set :foreman_location, "/etc/init"
12
- set :foreman_procfile, "Procfile"
13
- set :foreman_app, application
14
- set :foreman_user, user
15
- set :foreman_log, 'shared_path/log'
16
- set :foreman_concurrency, false
17
- DESC
18
- task :export, :roles => :app do
19
- bundle_cmd = fetch(:bundle_cmd, "bundle")
20
- foreman_format = fetch(:foreman_format, "upstart")
21
- foreman_location = fetch(:foreman_location, "/etc/init")
22
- foreman_procfile = fetch(:foreman_procfile, "Procfile")
23
- foreman_app = fetch(:foreman_app, application)
24
- foreman_user = fetch(:foreman_user, user)
25
- foreman_log = fetch(:foreman_log, "#{shared_path}/log")
26
- foreman_concurrency = fetch(:foreman_concurrency, false)
27
-
28
- args = ["#{foreman_format} #{foreman_location}"]
29
- args << "-f #{foreman_procfile}"
30
- args << "-a #{foreman_app}"
31
- args << "-u #{foreman_user}"
32
- args << "-l #{foreman_log}"
33
- args << "-c #{foreman_concurrency}" if foreman_concurrency
34
- run "cd #{release_path} && #{sudo} #{bundle_cmd} exec foreman export #{args.join(' ')}"
35
- end
36
-
37
- desc "Start the application services"
38
- task :start, :roles => :app do
39
- run "#{sudo} start #{application}"
40
- end
41
-
42
- desc "Stop the application services"
43
- task :stop, :roles => :app do
44
- run "#{sudo} stop #{application}"
45
- end
46
-
47
- desc "Restart the application services"
48
- task :restart, :roles => :app do
49
- run "#{sudo} start #{application} || #{sudo} restart #{application}"
50
- end
51
-
52
- end
53
- end
54
- end