foreman 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,77 @@
1
+ = Foreman
2
+
3
+ === Procfile
4
+
5
+ alpha ./bin/alpha
6
+ bravo ./bin/bravo some args
7
+ charlie ./bin/charlie -n 5
8
+
9
+ == Development mode
10
+
11
+ === Running
12
+
13
+ $ foreman start
14
+ [foreman] [Tue May 18 01:27:08 UTC 2010] [alpha] started with pid 4393
15
+ [foreman] [Tue May 18 01:27:08 UTC 2010] [bravo] started with pid 4394
16
+ [foreman] [Tue May 18 01:27:08 UTC 2010] [charlie] started with pid 4395
17
+
18
+ === Standardized Logging
19
+
20
+ log/alpha.log
21
+ log/bravo.log
22
+ log/charlie.log
23
+
24
+ == Upstart
25
+
26
+ === Export to upstart scripts
27
+
28
+ $ foreman export sampleapp
29
+
30
+ $ initctl list | grep sampleapp
31
+ sampleapp start/running
32
+ sampleapp-alpha (1) start/running, process 4204
33
+ sampleapp-bravo (1) start/running, process 4589
34
+ sampleapp-charlie (1) start/running, process 4597
35
+
36
+ === Change process concurrency levels
37
+
38
+ $ foreman scale sampleapp alpha 4
39
+ sampleapp-alpha (2) start/running, process 4164
40
+ sampleapp-alpha (3) start/running, process 4166
41
+ sampleapp-alpha (4) start/running, process 4168
42
+
43
+ $ initctl list | grep sampleapp
44
+ sampleapp start/running
45
+ sampleapp-alpha (4) start/running, process 4168
46
+ sampleapp-alpha (3) start/running, process 4166
47
+ sampleapp-alpha (2) start/running, process 4164
48
+ sampleapp-alpha (1) start/running, process 4204
49
+ sampleapp-bravo (1) start/running, process 4589
50
+ sampleapp-charlie (1) start/running, process 4597
51
+
52
+ $ foreman scale sampleapp alpha 1
53
+ sampleapp-alpha stop/waiting
54
+ sampleapp-alpha stop/waiting
55
+ sampleapp-alpha stop/waiting
56
+
57
+ === Good Upstart citizen
58
+
59
+ All Upstart commands work as expected
60
+
61
+ $ start sampleapp
62
+ $ stop sampleapp
63
+ $ restart sampleapp
64
+
65
+ === Standardized Logging
66
+
67
+ /var/log/sampleapp/alpha.log
68
+ /var/log/sampleapp/bravo.log
69
+ /var/log/sampleapp/charlie.log
70
+
71
+ == License
72
+
73
+ MIT
74
+
75
+ == Copyright
76
+
77
+ (c) 2010 David Dollar
@@ -0,0 +1,60 @@
1
+ require "rubygems"
2
+ require "rake"
3
+ require "rspec/core/rake_task"
4
+
5
+ $:.unshift File.expand_path("../lib", __FILE__)
6
+ require "foreman"
7
+
8
+ task :default => :spec
9
+
10
+ desc "Run all specs"
11
+ Rspec::Core::RakeTask.new(:spec) do |t|
12
+ t.pattern = 'spec/**/*_spec.rb'
13
+ end
14
+
15
+ desc "Generate RCov code coverage report"
16
+ task :rcov => "rcov:build" do
17
+ %x{ open coverage/index.html }
18
+ end
19
+
20
+ Rspec::Core::RakeTask.new("rcov:build") do |t|
21
+ t.pattern = 'spec/**/*_spec.rb'
22
+ t.rcov = true
23
+ t.rcov_opts = [ "--exclude", Gem.default_dir , "--exclude", "spec" ]
24
+ end
25
+
26
+ ######################################################
27
+
28
+ begin
29
+ require 'jeweler'
30
+ Jeweler::Tasks.new do |s|
31
+ s.name = "foreman"
32
+ s.version = Foreman::VERSION
33
+
34
+ s.summary = "Process manager for applications with multiple components"
35
+ s.description = s.summary
36
+ s.author = "David Dollar"
37
+ s.email = "ddollar@gmail.com"
38
+ s.homepage = "http://github.com/ddollar/foreman"
39
+
40
+ s.platform = Gem::Platform::RUBY
41
+ s.has_rdoc = false
42
+
43
+ s.files = %w(Rakefile README.md) + Dir["{bin,lib,spec}/**/*"]
44
+ s.require_path = "lib"
45
+
46
+ # #s.bindir = "bin"
47
+ # s.executables = Dir["bin/*"]
48
+ s.default_executable = "foreman"
49
+
50
+ s.add_development_dependency 'fakefs', '~> 0.2.1'
51
+ s.add_development_dependency 'rake', '~> 0.8.7'
52
+ s.add_development_dependency 'rcov', '~> 0.9.8'
53
+ s.add_development_dependency 'rr', '~> 0.10.11'
54
+ s.add_development_dependency 'rspec', '~> 2.0.0'
55
+
56
+ s.add_dependency 'thor', '~> 0.13.6'
57
+ end
58
+ rescue LoadError
59
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
60
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path("../../lib", __FILE__)
4
+
5
+ require "foreman/cli"
6
+
7
+ Foreman::CLI.start
@@ -0,0 +1,8 @@
1
+ module Foreman
2
+
3
+ VERSION = "0.0.1"
4
+
5
+ class AppDoesNotExist < Exception; end
6
+
7
+ end
8
+
@@ -0,0 +1,50 @@
1
+ require "foreman"
2
+ require "foreman/configuration"
3
+ require "foreman/engine"
4
+ require "foreman/export"
5
+ require "thor"
6
+
7
+ class Foreman::CLI < Thor
8
+
9
+ desc "start [PROCFILE]", "Run the app described in PROCFILE"
10
+
11
+ def start(procfile="Procfile")
12
+ error "#{procfile} does not exist." unless procfile_exists?(procfile)
13
+ Foreman::Engine.new(procfile).start
14
+ end
15
+
16
+ desc "export APP [PROCFILE] [FORMAT]", "Export the app described in PROCFILE as APP to another FORMAT"
17
+
18
+ def export(app, procfile="Procfile", format="upstart")
19
+ error "#{procfile} does not exist." unless procfile_exists?(procfile)
20
+
21
+ formatter = case format
22
+ when "upstart" then Foreman::Export::Upstart
23
+ else error "Unknown export format: #{format}."
24
+ end
25
+
26
+ formatter.new(Foreman::Engine.new(procfile)).export(app)
27
+ end
28
+
29
+ desc "scale APP PROCESS AMOUNT", "Change the concurrency of a given process type"
30
+
31
+ def scale(app, process, amount)
32
+ config = Foreman::Configuration.new(app)
33
+ error "No such process: #{process}." unless config.processes[process]
34
+ config.scale(process, amount)
35
+ rescue Foreman::AppDoesNotExist
36
+ error "No such app: #{app}."
37
+ end
38
+
39
+ private ######################################################################
40
+
41
+ def error(message)
42
+ puts "ERROR: #{message}"
43
+ exit 1
44
+ end
45
+
46
+ def procfile_exists?(procfile)
47
+ File.exist?(procfile)
48
+ end
49
+
50
+ end
@@ -0,0 +1,59 @@
1
+ require "foreman"
2
+
3
+ class Foreman::Configuration
4
+
5
+ attr_reader :app
6
+ attr_reader :processes
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ @processes = {}
11
+ read_initial_config
12
+ end
13
+
14
+ def scale(process, amount)
15
+ old_amount = processes[process].to_i
16
+ processes[process] = amount.to_i
17
+ amount = amount.to_i
18
+
19
+ if (old_amount < amount)
20
+ ((old_amount + 1) .. amount).each { |num| run "start #{app}-#{process} NUM=#{num}" }
21
+ elsif (amount < old_amount)
22
+ ((amount + 1) .. old_amount).each { |num| run "stop #{app}-#{process} NUM=#{num}" }
23
+ end
24
+
25
+ write
26
+ end
27
+
28
+ def write
29
+ write_file "/etc/foreman/#{app}.conf", <<-UPSTART_CONFIG
30
+ #{app}_processes="#{processes.keys.join(' ')}"
31
+ #{processes.keys.map { |k| "#{app}_#{k}=\"#{processes[k]}\"" }.join("\n")}
32
+ UPSTART_CONFIG
33
+ end
34
+
35
+ private ######################################################################
36
+
37
+ def read_initial_config
38
+ config = File.read("/etc/foreman/#{app}.conf").split("\n").inject({}) do |accum, line|
39
+ key, value = line.match(/^(.+?)\s*=\s*"(.+?)"\s*$/).captures
40
+ #accum.update(parts(1) => parts(2))
41
+ accum.update(key => value)
42
+ end
43
+ config["#{app}_processes"].split(" ").each do |process|
44
+ processes[process] = config["#{app}_#{process}"].to_i
45
+ end
46
+ rescue Errno::ENOENT
47
+ end
48
+
49
+ def run(command)
50
+ system command
51
+ end
52
+
53
+ def write_file(filename, contents)
54
+ File.open(filename, "w") do |file|
55
+ file.puts contents
56
+ end
57
+ end
58
+
59
+ end
@@ -0,0 +1,91 @@
1
+ require "foreman"
2
+ require "foreman/process"
3
+
4
+ class Foreman::Engine
5
+
6
+ attr_reader :procfile
7
+ attr_reader :directory
8
+
9
+ def initialize(procfile)
10
+ @procfile = read_procfile(procfile)
11
+ @directory = File.expand_path(File.dirname(procfile))
12
+ end
13
+
14
+ def processes
15
+ @processes ||= begin
16
+ procfile.split("\n").inject({}) do |hash, line|
17
+ next if line.strip == ""
18
+ process = Foreman::Process.new(*line.split(" ", 2))
19
+ hash.update(process.name => process)
20
+ end
21
+ end
22
+ end
23
+
24
+ def start
25
+ proctitle "ruby: foreman master"
26
+
27
+ processes.each do |name, process|
28
+ fork process
29
+ end
30
+
31
+ trap("TERM") { kill_and_exit("TERM") }
32
+ trap("INT") { kill_and_exit("INT") }
33
+
34
+ while true
35
+ pid, status = Process.wait2
36
+ process = running_processes.delete(pid)
37
+ info "exited with code #{status}", process
38
+ fork process
39
+ end
40
+ end
41
+
42
+ private ######################################################################
43
+
44
+ def fork(process)
45
+ pid = Process.fork do
46
+ proctitle "ruby: foreman #{process.name}"
47
+
48
+ Dir.chdir directory do
49
+ FileUtils.mkdir_p "log"
50
+ system "#{process.command} >>log/#{process.name}.log 2>&1"
51
+ exit $?.exitstatus || 255
52
+ end
53
+ end
54
+
55
+ info "started with pid #{pid}", process
56
+ running_processes[pid] = process
57
+ end
58
+
59
+ def kill_and_exit(signal="TERM")
60
+ info "termination requested"
61
+ running_processes.each do |pid, process|
62
+ info "killing pid #{pid}", process
63
+ Process.kill(signal, pid)
64
+ end
65
+ exit 0
66
+ end
67
+
68
+ def info(message, process=nil)
69
+ puts "[foreman] [#{Time.now.utc}] [#{process ? process.name : "system"}] #{message}"
70
+ end
71
+
72
+ def print_info
73
+ info "currently running processes:"
74
+ running_processes.each do |pid, process|
75
+ info "pid #{pid}", process
76
+ end
77
+ end
78
+
79
+ def read_procfile(procfile)
80
+ File.read(procfile)
81
+ end
82
+
83
+ def running_processes
84
+ @running_processes ||= {}
85
+ end
86
+
87
+ def proctitle(title)
88
+ $0 = title
89
+ end
90
+
91
+ end
@@ -0,0 +1,6 @@
1
+ require "foreman"
2
+
3
+ module Foreman::Export
4
+ end
5
+
6
+ require "foreman/export/upstart"
@@ -0,0 +1,66 @@
1
+ require "foreman/configuration"
2
+ require "foreman/export"
3
+
4
+ class Foreman::Export::Upstart
5
+
6
+ attr_reader :engine
7
+
8
+ def initialize(engine)
9
+ @engine = engine
10
+ end
11
+
12
+ def export(app)
13
+ FileUtils.mkdir_p "/etc/foreman"
14
+ FileUtils.mkdir_p "/etc/init"
15
+
16
+ config = Foreman::Configuration.new(app)
17
+
18
+ engine.processes.each do |name, process|
19
+ config.scale(name, 1)
20
+ end
21
+ config.write
22
+
23
+ write_file "/etc/init/#{app}.conf", <<-UPSTART_MASTER
24
+ pre-start script
25
+
26
+ bash << "EOF"
27
+ mkdir -p /var/log/#{app}
28
+
29
+ if [ -f /etc/foreman/#{app}.conf ]; then
30
+ source /etc/foreman/#{app}.conf
31
+ fi
32
+
33
+ for process in $( echo "$#{app}_processes" ); do
34
+ process_count_config="#{app}_$process"
35
+ process_count=${!process_count_config}
36
+
37
+ for ((i=1; i<=${process_count:=1}; i+=1)); do
38
+ start #{app}-$process NUM=$i
39
+ done
40
+ done
41
+ EOF
42
+
43
+ end script
44
+ UPSTART_MASTER
45
+
46
+ engine.processes.values.each do |process|
47
+ write_file "/etc/init/#{app}-#{process.name}.conf", <<-UPSTART_CHILD
48
+ instance $NUM
49
+ stop on stopping #{app}
50
+ respawn
51
+
52
+ chdir #{engine.directory}
53
+ exec #{process.command} >>/var/log/#{app}/#{process.name}.log 2>&1
54
+ UPSTART_CHILD
55
+ end
56
+ end
57
+
58
+ private ######################################################################
59
+
60
+ def write_file(filename, contents)
61
+ File.open(filename, "w") do |file|
62
+ file.puts contents
63
+ end
64
+ end
65
+
66
+ end
@@ -0,0 +1,13 @@
1
+ require "foreman"
2
+
3
+ class Foreman::Process
4
+
5
+ attr_reader :name
6
+ attr_reader :command
7
+
8
+ def initialize(name, command)
9
+ @name = name
10
+ @command = command
11
+ end
12
+
13
+ end
@@ -0,0 +1,88 @@
1
+ require "spec_helper"
2
+ require "foreman/cli"
3
+
4
+ describe "Foreman::CLI" do
5
+ subject { Foreman::CLI.new }
6
+
7
+ describe "start" do
8
+ #let(:engine) { stub_engine }
9
+
10
+ describe "with a non-existent Procifile" do
11
+ it "prints an error" do
12
+ mock_error(subject, "Procfile does not exist.") do
13
+ dont_allow.instance_of(Foreman::Engine).start
14
+ subject.start
15
+ end
16
+ end
17
+ end
18
+
19
+ describe "with a Procfile" do
20
+ before(:each) { write_procfile }
21
+
22
+ it "runs successfully" do
23
+ dont_allow(subject).error
24
+ mock.instance_of(Foreman::Engine).start
25
+ subject.start
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "export" do
31
+ describe "with a non-existent Procifile" do
32
+ it "prints an error" do
33
+ mock_error(subject, "Procfile does not exist.") do
34
+ dont_allow.instance_of(Foreman::Engine).export
35
+ subject.export("testapp")
36
+ end
37
+ end
38
+ end
39
+
40
+ describe "with a Procfile" do
41
+ before(:each) { write_procfile }
42
+
43
+ describe "with an invalid formatter" do
44
+ it "prints an error" do
45
+ mock_error(subject, "Unknown export format: invalidformatter.") do
46
+ subject.export("testapp", "Procfile", "invalidformatter")
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "with a valid config" do
52
+ before(:each) { write_foreman_config("testapp") }
53
+
54
+ it "runs successfully" do
55
+ dont_allow(subject).error
56
+ subject.export("testapp")
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "scale" do
63
+ describe "without an existing configuration" do
64
+ it "displays an error" do
65
+ mock_error(subject, "No such app: testapp.") do
66
+ subject.scale("testapp", "alpha", "2")
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "with an existing configuration" do
72
+ before(:each) { write_foreman_config("testapp") }
73
+
74
+ it "scales a process that exists" do
75
+ mock.instance_of(Foreman::Configuration).scale("alpha", "2")
76
+ subject.scale("testapp", "alpha", "2")
77
+ end
78
+
79
+ it "errors if a process that does not exist is specified" do
80
+ mock_error(subject, "No such process: invalidprocess.") do
81
+ dont_allow.instance_of(Foreman::Configuration).scale
82
+ subject.scale("testapp", "invalidprocess", "2")
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ end
@@ -0,0 +1,2 @@
1
+ require "spec_helper"
2
+ require "foreman/configuration"
@@ -0,0 +1,2 @@
1
+ require "spec_helper"
2
+ require "foreman/engine"
@@ -0,0 +1,2 @@
1
+ require "spec_helper"
2
+ require "foreman/export/upstart"
@@ -0,0 +1,2 @@
1
+ require "spec_helper"
2
+ require "foreman/export"
@@ -0,0 +1,2 @@
1
+ require "spec_helper"
2
+ require "foreman/process"
@@ -0,0 +1,11 @@
1
+ require "spec_helper"
2
+ require "foreman"
3
+
4
+ describe Foreman do
5
+
6
+ describe "VERSION" do
7
+ subject { Foreman::VERSION }
8
+ it { should be_a String }
9
+ end
10
+
11
+ end
@@ -0,0 +1,36 @@
1
+ require "fakefs/spec_helpers"
2
+ require "rspec"
3
+
4
+ $:.unshift "lib"
5
+
6
+ def mock_error(subject, message)
7
+ mock_exit do
8
+ mock(subject).puts("ERROR: #{message}")
9
+ yield
10
+ end
11
+ end
12
+
13
+ def mock_exit(&block)
14
+ block.should raise_error(SystemExit)
15
+ end
16
+
17
+ def write_foreman_config(app)
18
+ File.open("/etc/foreman/#{app}.conf", "w") do |file|
19
+ file.puts %{#{app}_processes="alpha bravo"}
20
+ file.puts %{#{app}_alpha="1"}
21
+ file.puts %{#{app}_bravo="2"}
22
+ end
23
+ end
24
+
25
+ def write_procfile(procfile="Procfile")
26
+ File.open(procfile, "w") do |file|
27
+ file.puts "alpha ./alpha"
28
+ file.puts "bravo ./bravo"
29
+ end
30
+ end
31
+
32
+ Rspec.configure do |config|
33
+ config.color_enabled = true
34
+ config.include FakeFS::SpecHelpers
35
+ config.mock_with :rr
36
+ end
metadata ADDED
@@ -0,0 +1,186 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: foreman
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - David Dollar
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-05-20 00:00:00 -04:00
19
+ default_executable: foreman
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: fakefs
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 21
30
+ segments:
31
+ - 0
32
+ - 2
33
+ - 1
34
+ version: 0.2.1
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rake
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 49
46
+ segments:
47
+ - 0
48
+ - 8
49
+ - 7
50
+ version: 0.8.7
51
+ type: :development
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: rcov
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 43
62
+ segments:
63
+ - 0
64
+ - 9
65
+ - 8
66
+ version: 0.9.8
67
+ type: :development
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: rr
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ hash: 33
78
+ segments:
79
+ - 0
80
+ - 10
81
+ - 11
82
+ version: 0.10.11
83
+ type: :development
84
+ version_requirements: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ name: rspec
87
+ prerelease: false
88
+ requirement: &id005 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ hash: 15
94
+ segments:
95
+ - 2
96
+ - 0
97
+ - 0
98
+ version: 2.0.0
99
+ type: :development
100
+ version_requirements: *id005
101
+ - !ruby/object:Gem::Dependency
102
+ name: thor
103
+ prerelease: false
104
+ requirement: &id006 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ hash: 39
110
+ segments:
111
+ - 0
112
+ - 13
113
+ - 6
114
+ version: 0.13.6
115
+ type: :runtime
116
+ version_requirements: *id006
117
+ description: Process manager for applications with multiple components
118
+ email: ddollar@gmail.com
119
+ executables:
120
+ - foreman
121
+ extensions: []
122
+
123
+ extra_rdoc_files:
124
+ - README.rdoc
125
+ files:
126
+ - Rakefile
127
+ - bin/foreman
128
+ - lib/foreman.rb
129
+ - lib/foreman/cli.rb
130
+ - lib/foreman/configuration.rb
131
+ - lib/foreman/engine.rb
132
+ - lib/foreman/export.rb
133
+ - lib/foreman/export/upstart.rb
134
+ - lib/foreman/process.rb
135
+ - spec/foreman/cli_spec.rb
136
+ - spec/foreman/configuration_spec.rb
137
+ - spec/foreman/engine_spec.rb
138
+ - spec/foreman/export/upstart_spec.rb
139
+ - spec/foreman/export_spec.rb
140
+ - spec/foreman/process_spec.rb
141
+ - spec/foreman_spec.rb
142
+ - spec/spec_helper.rb
143
+ - README.rdoc
144
+ has_rdoc: false
145
+ homepage: http://github.com/ddollar/foreman
146
+ licenses: []
147
+
148
+ post_install_message:
149
+ rdoc_options:
150
+ - --charset=UTF-8
151
+ require_paths:
152
+ - lib
153
+ required_ruby_version: !ruby/object:Gem::Requirement
154
+ none: false
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ hash: 3
159
+ segments:
160
+ - 0
161
+ version: "0"
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ none: false
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ hash: 3
168
+ segments:
169
+ - 0
170
+ version: "0"
171
+ requirements: []
172
+
173
+ rubyforge_project:
174
+ rubygems_version: 1.3.7
175
+ signing_key:
176
+ specification_version: 3
177
+ summary: Process manager for applications with multiple components
178
+ test_files:
179
+ - spec/foreman/cli_spec.rb
180
+ - spec/foreman/configuration_spec.rb
181
+ - spec/foreman/engine_spec.rb
182
+ - spec/foreman/export/upstart_spec.rb
183
+ - spec/foreman/export_spec.rb
184
+ - spec/foreman/process_spec.rb
185
+ - spec/foreman_spec.rb
186
+ - spec/spec_helper.rb