foreman 0.47.0 → 0.50.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 (64) hide show
  1. data/bin/taskman +8 -0
  2. data/data/example/Procfile +4 -3
  3. data/data/example/spawnee +14 -0
  4. data/data/example/spawner +7 -0
  5. data/data/export/bluepill/master.pill.erb +10 -10
  6. data/data/export/launchd/launchd.plist.erb +3 -3
  7. data/data/export/runit/log/run.erb +7 -0
  8. data/data/export/runit/run.erb +2 -2
  9. data/data/export/supervisord/app.conf.erb +12 -12
  10. data/data/export/upstart/master.conf.erb +2 -2
  11. data/data/export/upstart/process.conf.erb +3 -3
  12. data/lib/foreman/cli.rb +57 -22
  13. data/lib/foreman/engine/cli.rb +105 -0
  14. data/lib/foreman/engine.rb +233 -148
  15. data/lib/foreman/env.rb +27 -0
  16. data/lib/foreman/export/base.rb +107 -27
  17. data/lib/foreman/export/bluepill.rb +3 -17
  18. data/lib/foreman/export/inittab.rb +8 -11
  19. data/lib/foreman/export/launchd.rb +4 -16
  20. data/lib/foreman/export/runit.rb +14 -39
  21. data/lib/foreman/export/supervisord.rb +3 -13
  22. data/lib/foreman/export/upstart.rb +9 -27
  23. data/lib/foreman/export.rb +1 -2
  24. data/lib/foreman/process.rb +73 -67
  25. data/lib/foreman/procfile.rb +59 -25
  26. data/lib/foreman/version.rb +1 -1
  27. data/lib/foreman.rb +4 -0
  28. data/man/foreman.1 +5 -1
  29. data/spec/foreman/cli_spec.rb +46 -150
  30. data/spec/foreman/engine_spec.rb +46 -80
  31. data/spec/foreman/export/base_spec.rb +4 -7
  32. data/spec/foreman/export/bluepill_spec.rb +7 -6
  33. data/spec/foreman/export/inittab_spec.rb +7 -7
  34. data/spec/foreman/export/launchd_spec.rb +4 -7
  35. data/spec/foreman/export/runit_spec.rb +12 -17
  36. data/spec/foreman/export/supervisord_spec.rb +7 -56
  37. data/spec/foreman/export/upstart_spec.rb +18 -23
  38. data/spec/foreman/process_spec.rb +27 -124
  39. data/spec/foreman/procfile_spec.rb +26 -16
  40. data/spec/resources/Procfile +4 -0
  41. data/spec/resources/bin/echo +2 -0
  42. data/spec/resources/bin/env +2 -0
  43. data/spec/resources/bin/test +2 -0
  44. data/spec/resources/export/bluepill/app-concurrency.pill +4 -4
  45. data/spec/resources/export/bluepill/app.pill +4 -4
  46. data/spec/resources/export/supervisord/app-alpha-1.conf +24 -0
  47. data/spec/resources/export/supervisord/app-alpha-2.conf +4 -4
  48. data/spec/spec_helper.rb +57 -6
  49. metadata +28 -19
  50. data/data/export/runit/log_run.erb +0 -7
  51. data/lib/foreman/color.rb +0 -40
  52. data/lib/foreman/procfile_entry.rb +0 -26
  53. data/lib/foreman/utils.rb +0 -18
  54. data/spec/foreman/color_spec.rb +0 -31
  55. data/spec/foreman/procfile_entry_spec.rb +0 -13
  56. data/spec/resources/export/supervisord/app-env-with-comma.conf +0 -24
  57. data/spec/resources/export/supervisord/app-env.conf +0 -21
  58. data/spec/resources/export/supervisord/app.conf +0 -24
  59. /data/spec/resources/export/runit/{app-alpha-1-log-run → app-alpha-1/log/run} +0 -0
  60. /data/spec/resources/export/runit/{app-alpha-1-run → app-alpha-1/run} +0 -0
  61. /data/spec/resources/export/runit/{app-alpha-2-log-run → app-alpha-2/log/run} +0 -0
  62. /data/spec/resources/export/runit/{app-alpha-2-run → app-alpha-2/run} +0 -0
  63. /data/spec/resources/export/runit/{app-bravo-1-log-run → app-bravo-1/log/run} +0 -0
  64. /data/spec/resources/export/runit/{app-bravo-1-run → app-bravo-1/run} +0 -0
@@ -1,8 +1,7 @@
1
1
  require "foreman"
2
- require "foreman/color"
2
+ require "foreman/env"
3
3
  require "foreman/process"
4
4
  require "foreman/procfile"
5
- require "foreman/utils"
6
5
  require "tempfile"
7
6
  require "timeout"
8
7
  require "fileutils"
@@ -10,219 +9,305 @@ require "thread"
10
9
 
11
10
  class Foreman::Engine
12
11
 
13
- attr_reader :environment
14
- attr_reader :procfile
15
- attr_reader :directory
12
+ attr_reader :env
16
13
  attr_reader :options
17
-
18
- COLORS = %w( cyan yellow green magenta red blue intense_cyan intense_yellow
19
- intense_green intense_magenta intense_red, intense_blue )
20
-
21
- Foreman::Color.enable($stdout)
22
-
23
- def initialize(procfile, options={})
24
- @procfile = Foreman::Procfile.new(procfile) if File.exists?(procfile)
25
- @directory = options[:app_root] || File.expand_path(File.dirname(procfile))
14
+ attr_reader :processes
15
+
16
+ # Create an +Engine+ for running processes
17
+ #
18
+ # @param [Hash] options
19
+ #
20
+ # @option options [String] :formation (all=1) The process formation to use
21
+ # @option options [Fixnum] :port (5000) The base port to assign to processes
22
+ # @option options [String] :root (Dir.pwd) The root directory from which to run processes
23
+ #
24
+ def initialize(options={})
26
25
  @options = options.dup
27
- @output_mutex = Mutex.new
28
26
 
29
- @options[:env] ||= default_env
30
- @environment = read_environment_files(@options[:env])
27
+ @options[:formation] ||= (options[:concurrency] || "all=1")
28
+
29
+ @env = {}
30
+ @mutex = Mutex.new
31
+ @names = {}
32
+ @processes = []
33
+ @running = {}
34
+ @readers = {}
31
35
  end
32
36
 
37
+ # Start the processes registered to this +Engine+
38
+ #
33
39
  def start
34
- proctitle "ruby: foreman master"
35
- termtitle "#{File.basename(@directory)} - foreman"
36
-
37
40
  trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
38
41
  trap("INT") { puts "SIGINT received"; terminate_gracefully }
39
- trap("HUP") { puts "SIGHUP received"; terminate_gracefully }
42
+ trap("HUP") { puts "SIGHUP received"; terminate_gracefully } if ::Signal.list.keys.include? 'HUP'
40
43
 
41
- assign_colors
44
+ startup
42
45
  spawn_processes
43
46
  watch_for_output
44
- watch_for_termination
47
+ sleep 0.1
48
+ watch_for_termination { terminate_gracefully }
49
+ shutdown
45
50
  end
46
51
 
47
- def port_for(process, num, base_port=nil)
48
- base_port ||= 5000
49
- offset = procfile.process_names.index(process.name) * 100
50
- base_port.to_i + offset + num - 1
52
+ # Register a process to be run by this +Engine+
53
+ #
54
+ # @param [String] name A name for this process
55
+ # @param [String] command The command to run
56
+ # @param [Hash] options
57
+ #
58
+ # @option options [Hash] :env A custom environment for this process
59
+ #
60
+ def register(name, command, options={})
61
+ options[:env] ||= env
62
+ options[:cwd] ||= File.dirname(command.split(" ").first)
63
+ process = Foreman::Process.new(command, options)
64
+ @names[process] = name
65
+ @processes << process
51
66
  end
52
67
 
53
- def apply_environment!
54
- environment.each { |k,v| ENV[k] = v }
68
+ # Clear the processes registered to this +Engine+
69
+ #
70
+ def clear
71
+ @names = {}
72
+ @processes = []
55
73
  end
56
74
 
57
- def self.read_environment(filename)
58
- return {} unless File.exists?(filename)
75
+ # Register processes by reading a Procfile
76
+ #
77
+ # @param [String] filename A Procfile from which to read processes to register
78
+ #
79
+ def load_procfile(filename)
80
+ options[:root] ||= File.dirname(filename)
81
+ Foreman::Procfile.new(filename).entries do |name, command|
82
+ register name, command, :cwd => options[:root]
83
+ end
84
+ self
85
+ end
86
+
87
+ # Load a .env file into the +env+ for this +Engine+
88
+ #
89
+ # @param [String] filename A .env file to load into the environment
90
+ #
91
+ def load_env(filename)
92
+ Foreman::Env.new(filename).entries do |name, value|
93
+ @env[name] = value
94
+ end
95
+ end
59
96
 
60
- File.read(filename).split("\n").inject({}) do |hash, line|
61
- if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
62
- key, val = [$1, $2]
63
- case val
64
- when /\A'(.*)'\z/ then hash[key] = $1
65
- when /\A"(.*)"\z/ then hash[key] = $1.gsub(/\\(.)/, '\1')
66
- else hash[key] = val
97
+ # Send a signal to all processesstarted by this +Engine+
98
+ #
99
+ # @param [String] signal The signal to send to each process
100
+ #
101
+ def killall(signal="SIGTERM")
102
+ if Foreman.windows?
103
+ @running.each do |pid, (process, index)|
104
+ system "sending #{signal} to #{name_for(pid)} at pid #{pid}"
105
+ begin
106
+ Process.kill(signal, pid)
107
+ rescue Errno::ESRCH, Errno::EPERM
67
108
  end
68
109
  end
69
- hash
110
+ else
111
+ begin
112
+ Process.kill "-#{signal}", Process.pid
113
+ rescue Errno::ESRCH, Errno::EPERM
114
+ end
70
115
  end
71
116
  end
72
117
 
73
- private ######################################################################
74
-
75
- def spawn_processes
76
- concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
118
+ # Get the process formation
119
+ #
120
+ # @returns [Fixnum] The formation count for the specified process
121
+ #
122
+ def formation
123
+ @formation ||= parse_formation(options[:formation])
124
+ end
77
125
 
78
- procfile.entries.each do |entry|
79
- reader, writer = (IO.method(:pipe).arity == 0 ? IO.pipe : IO.pipe("BINARY"))
80
- entry.spawn(concurrency[entry.name], writer, @directory, @environment, port_for(entry, 1, base_port)).each do |process|
81
- running_processes[process.pid] = process
82
- readers[process] = reader
83
- end
84
- end
126
+ # List the available process names
127
+ #
128
+ # @returns [Array] A list of process names
129
+ #
130
+ def process_names
131
+ @processes.map { |p| @names[p] }
85
132
  end
86
133
 
87
- def base_port
88
- options[:port] || environment["PORT"] || ENV["PORT"] || 5000
134
+ # Get the +Process+ for a specifid name
135
+ #
136
+ # @param [String] name The process name
137
+ #
138
+ # @returns [Foreman::Process] The +Process+ for the specified name
139
+ #
140
+ def process(name)
141
+ @names.invert[name]
89
142
  end
90
143
 
91
- def kill_all(signal="SIGTERM")
92
- running_processes.each do |pid, process|
93
- info "sending #{signal} to pid #{pid}"
94
- process.kill signal
144
+ # Yield each +Process+ in order
145
+ #
146
+ def each_process
147
+ process_names.each do |name|
148
+ yield name, process(name)
95
149
  end
96
150
  end
97
151
 
98
- def terminate_gracefully
99
- return if @terminating
100
- @terminating = true
101
- info "sending SIGTERM to all processes"
102
- kill_all "SIGTERM"
103
- Timeout.timeout(5) do
104
- while running_processes.length > 0
105
- pid, status = Process.wait2
106
- process = running_processes.delete(pid)
107
- info "process terminated", process.name
108
- end
109
- end
110
- rescue Timeout::Error
111
- info "sending SIGKILL to all processes"
112
- kill_all "SIGKILL"
113
- end
114
-
115
- def poll_readers
116
- rs, ws = IO.select(readers.values, [], [], 1)
117
- (rs || []).each do |r|
118
- data = r.gets
119
- next unless data
120
- data.force_encoding("BINARY") if data.respond_to?(:force_encoding)
121
- ps, message = data.split(",", 2)
122
- color = colors[ps.split(".").first]
123
- info message, ps, color
124
- end
152
+ # Get the root directory for this +Engine+
153
+ #
154
+ # @returns [String] The root directory
155
+ #
156
+ def root
157
+ File.expand_path(options[:root] || Dir.pwd)
125
158
  end
126
159
 
127
- def watch_for_output
128
- Thread.new do
129
- require "win32console" if Foreman.windows?
130
- begin
131
- loop do
132
- poll_readers
133
- end
134
- rescue Exception => ex
135
- puts ex.message
136
- puts ex.backtrace
137
- end
160
+ # Get the port for a given process and offset
161
+ #
162
+ # @param [Foreman::Process] process A +Process+ associated with this engine
163
+ # @param [Fixnum] instance The instance of the process
164
+ #
165
+ # @returns [Fixnum] port The port to use for this instance of this process
166
+ #
167
+ def port_for(process, instance, base=nil)
168
+ if base
169
+ base + (@processes.index(process.process) * 100) + (instance - 1)
170
+ else
171
+ base_port + (@processes.index(process) * 100) + (instance - 1)
138
172
  end
139
173
  end
140
174
 
141
- def watch_for_termination
142
- pid, status = Process.wait2
143
- process = running_processes.delete(pid)
144
- info "process terminated", process.name
145
- terminate_gracefully
146
- rescue Errno::ECHILD
175
+ # Get the base port for this foreman instance
176
+ #
177
+ # @returns [Fixnum] port The base port
178
+ #
179
+ def base_port
180
+ (options[:port] || env["PORT"] || ENV["PORT"] || 5000).to_i
147
181
  end
148
182
 
149
- def info(message, name="system", color=:white)
150
- output = ""
151
- output += $stdout.color(color)
152
- output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
153
- output += $stdout.color(:reset)
154
- output += message.chomp
155
- puts output
183
+ # deprecated
184
+ def environment
185
+ env
156
186
  end
157
187
 
158
- def print(message=nil)
159
- @output_mutex.synchronize do
160
- $stdout.print message
161
- end
162
- end
188
+ private
163
189
 
164
- def puts(message=nil)
165
- @output_mutex.synchronize do
166
- $stdout.puts message
167
- end
190
+ ### Engine API ######################################################
191
+
192
+ def startup
193
+ raise TypeError, "must use a subclass of Foreman::Engine"
168
194
  end
169
195
 
170
- def longest_process_name
171
- @longest_process_name ||= begin
172
- longest = procfile.process_names.map { |name| name.length }.sort.last
173
- longest = 6 if longest < 6 # system
174
- longest
175
- end
196
+ def output(name, data)
197
+ raise TypeError, "must use a subclass of Foreman::Engine"
176
198
  end
177
199
 
178
- def pad_process_name(name="system")
179
- name.to_s.ljust(longest_process_name + 3) # add 3 for process number padding
200
+ def shutdown
201
+ raise TypeError, "must use a subclass of Foreman::Engine"
180
202
  end
181
203
 
182
- def proctitle(title)
183
- $0 = title
204
+ ## Helpers ##########################################################
205
+
206
+ def create_pipe
207
+ IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
184
208
  end
185
209
 
186
- def termtitle(title)
187
- printf("\033]0;#{title}\007") unless Foreman.windows?
210
+ def name_for(pid)
211
+ process, index = @running[pid]
212
+ [ @names[process], index.to_s ].compact.join(".")
188
213
  end
189
214
 
190
- def running_processes
191
- @running_processes ||= {}
215
+ def parse_formation(formation)
216
+ pairs = formation.to_s.gsub(/\s/, "").split(",")
217
+
218
+ pairs.inject(Hash.new(0)) do |ax, pair|
219
+ process, amount = pair.split("=")
220
+ process == "all" ? ax.default = amount.to_i : ax[process] = amount.to_i
221
+ ax
222
+ end
192
223
  end
193
224
 
194
- def readers
195
- @readers ||= {}
225
+ def output_with_mutex(name, message)
226
+ @mutex.synchronize do
227
+ output name, message
228
+ end
196
229
  end
197
230
 
198
- def colors
199
- @colors ||= {}
231
+ def system(message)
232
+ output_with_mutex "system", message
200
233
  end
201
234
 
202
- def assign_colors
203
- procfile.entries.each_with_index do |entry, idx|
204
- colors[entry.name] = COLORS[idx % COLORS.length]
235
+ def termination_message_for(status)
236
+ if status.exited?
237
+ "exited with code #{status.exitstatus}"
238
+ elsif status.signaled?
239
+ "terminated by SIG#{Signal.list.invert[status.termsig]}"
240
+ else
241
+ "died a mysterious death"
205
242
  end
206
243
  end
207
244
 
208
- def process_by_reader(reader)
209
- readers.invert[reader]
245
+ def flush_reader(reader)
246
+ until reader.eof?
247
+ data = reader.gets
248
+ output_with_mutex name_for(@readers.key(reader)), data
249
+ end
210
250
  end
211
251
 
212
- def read_environment_files(filenames)
213
- environment = {}
252
+ ## Engine ###########################################################
214
253
 
215
- (filenames || "").split(",").map(&:strip).each do |filename|
216
- error "No such file: #{filename}" unless File.exists?(filename)
217
- environment.merge!(Foreman::Engine.read_environment(filename))
254
+ def spawn_processes
255
+ @processes.each do |process|
256
+ 1.upto(formation[@names[process]]) do |n|
257
+ reader, writer = create_pipe
258
+ begin
259
+ pid = process.run(:output => writer, :env => { "PORT" => port_for(process, n).to_s })
260
+ writer.puts "started with pid #{pid}"
261
+ rescue Errno::ENOENT
262
+ writer.puts "unknown command: #{process.command}"
263
+ end
264
+ @running[pid] = [process, n]
265
+ @readers[pid] = reader
266
+ end
218
267
  end
268
+ end
219
269
 
220
- environment
270
+ def watch_for_output
271
+ Thread.new do
272
+ begin
273
+ loop do
274
+ (IO.select(@readers.values).first || []).each do |reader|
275
+ data = reader.gets
276
+ output_with_mutex name_for(@readers.invert[reader]), data
277
+ end
278
+ end
279
+ rescue Exception => ex
280
+ puts ex.message
281
+ puts ex.backtrace
282
+ end
283
+ end
284
+ end
285
+
286
+ def watch_for_termination
287
+ pid, status = Process.wait2
288
+ output_with_mutex name_for(pid), termination_message_for(status)
289
+ @running.delete(pid)
290
+ yield if block_given?
291
+ pid
292
+ rescue Errno::ECHILD
221
293
  end
222
294
 
223
- def default_env
224
- env = File.join(directory, ".env")
225
- File.exists?(env) ? env : ""
295
+ def terminate_gracefully
296
+ return if @terminating
297
+ @terminating = true
298
+ if Foreman.windows?
299
+ system "sending SIGKILL to all processes"
300
+ killall "SIGKILL"
301
+ else
302
+ system "sending SIGTERM to all processes"
303
+ killall "SIGTERM"
304
+ end
305
+ Timeout.timeout(5) do
306
+ watch_for_termination while @running.length > 0
307
+ end
308
+ rescue Timeout::Error
309
+ system "sending SIGKILL to all processes"
310
+ killall "SIGKILL"
226
311
  end
227
312
 
228
313
  end
@@ -0,0 +1,27 @@
1
+ require "foreman"
2
+
3
+ class Foreman::Env
4
+
5
+ attr_reader :entries
6
+
7
+ def initialize(filename)
8
+ @entries = File.read(filename).split("\n").inject({}) do |ax, line|
9
+ if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
10
+ key = $1
11
+ case val = $2
12
+ when /\A'(.*)'\z/ then ax[key] = $1
13
+ when /\A"(.*)"\z/ then ax[key] = $1.gsub(/\\(.)/, '\1')
14
+ else ax[key] = val
15
+ end
16
+ end
17
+ ax
18
+ end
19
+ end
20
+
21
+ def entries
22
+ @entries.each do |key, value|
23
+ yield key, value
24
+ end
25
+ end
26
+
27
+ end
@@ -1,27 +1,81 @@
1
1
  require "foreman/export"
2
- require "foreman/utils"
2
+ require "ostruct"
3
+ require "pathname"
4
+ require "shellwords"
3
5
 
4
6
  class Foreman::Export::Base
5
7
 
6
- attr_reader :location, :engine, :app, :log, :port, :user, :template, :concurrency
8
+ attr_reader :location
9
+ attr_reader :engine
10
+ attr_reader :options
11
+ attr_reader :formation
12
+
13
+ # deprecated
14
+ attr_reader :port
7
15
 
8
16
  def initialize(location, engine, options={})
9
- @location = location
10
- @engine = engine
11
- @app = options[:app]
12
- @log = options[:log]
13
- @port = options[:port]
14
- @user = options[:user]
15
- @template = options[:template]
16
- @concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
17
+ @location = location
18
+ @engine = engine
19
+ @options = options.dup
20
+ @formation = engine.formation
21
+
22
+ # deprecated
23
+ def port
24
+ Foreman::Export::Base.warn_deprecation!
25
+ engine.base_port
26
+ end
27
+
28
+ # deprecated
29
+ def template
30
+ Foreman::Export::Base.warn_deprecation!
31
+ options[:template]
32
+ end
33
+
34
+ # deprecated
35
+ def @engine.procfile
36
+ Foreman::Export::Base.warn_deprecation!
37
+ @processes.map do |process|
38
+ OpenStruct.new(
39
+ :name => @names[process],
40
+ :process => process
41
+ )
42
+ end
43
+ end
17
44
  end
18
45
 
19
46
  def export
20
- raise "export method must be overridden"
47
+ error("Must specify a location") unless location
48
+ FileUtils.mkdir_p(location) rescue error("Could not create: #{location}")
49
+ FileUtils.mkdir_p(log) rescue error("Could not create: #{log}")
50
+ FileUtils.chown(user, nil, log) rescue error("Could not chown #{log} to #{user}")
51
+ end
52
+
53
+ def app
54
+ options[:app] || "app"
55
+ end
56
+
57
+ def log
58
+ options[:log] || "/var/log/#{app}"
59
+ end
60
+
61
+ def user
62
+ options[:user] || app
21
63
  end
22
64
 
23
65
  private ######################################################################
24
66
 
67
+ def self.warn_deprecation!
68
+ @@deprecation_warned ||= false
69
+ return if @@deprecation_warned
70
+ puts "WARNING: Using deprecated exporter interface. Please update your exporter"
71
+ puts "the interface shown in the upstart exporter:"
72
+ puts
73
+ puts "https://github.com/ddollar/foreman/blob/master/lib/foreman/export/upstart.rb"
74
+ puts "https://github.com/ddollar/foreman/blob/master/data/export/upstart/process.conf.erb"
75
+ puts
76
+ @@deprecation_warned = true
77
+ end
78
+
25
79
  def error(message)
26
80
  raise Foreman::Export::Exception.new(message)
27
81
  end
@@ -29,8 +83,19 @@ private ######################################################################
29
83
  def say(message)
30
84
  puts "[foreman export] %s" % message
31
85
  end
86
+
87
+ def clean(filename)
88
+ return unless File.exists?(filename)
89
+ say "cleaning up: #{filename}"
90
+ FileUtils.rm(filename)
91
+ end
32
92
 
33
- def export_template(exporter, file, template_root)
93
+ def shell_quote(value)
94
+ '"' + Shellwords.escape(value) + '"'
95
+ end
96
+
97
+ # deprecated
98
+ def old_export_template(exporter, file, template_root)
34
99
  if template_root && File.exist?(file_path = File.join(template_root, file))
35
100
  File.read(file_path)
36
101
  elsif File.exist?(file_path = File.expand_path(File.join("~/.foreman/templates", file)))
@@ -40,27 +105,42 @@ private ######################################################################
40
105
  end
41
106
  end
42
107
 
108
+ def export_template(name, file=nil, template_root=nil)
109
+ if file && template_root
110
+ old_export_template name, file, template_root
111
+ else
112
+ name_without_first = name.split("/")[1..-1].join("/")
113
+ matchers = []
114
+ matchers << File.join(options[:template], name_without_first) if options[:template]
115
+ matchers << File.expand_path("~/.foreman/templates/#{name}")
116
+ matchers << File.expand_path("../../../../data/export/#{name}", __FILE__)
117
+ File.read(matchers.detect { |m| File.exists?(m) })
118
+ end
119
+ end
120
+
121
+ def write_template(name, target, binding)
122
+ compiled = ERB.new(export_template(name)).result(binding)
123
+ write_file target, compiled
124
+ end
125
+
126
+ def chmod(mode, file)
127
+ say "setting #{file} to mode #{mode}"
128
+ FileUtils.chmod mode, File.join(location, file)
129
+ end
130
+
131
+ def create_directory(dir)
132
+ say "creating: #{dir}"
133
+ FileUtils.mkdir_p(File.join(location, dir))
134
+ end
135
+
43
136
  def write_file(filename, contents)
44
137
  say "writing: #{filename}"
45
138
 
139
+ filename = File.join(location, filename) unless Pathname.new(filename).absolute?
140
+
46
141
  File.open(filename, "w") do |file|
47
142
  file.puts contents
48
143
  end
49
144
  end
50
145
 
51
- # Quote a string to be used on the command line. Backslashes are escapde to \\ and quotes
52
- # escaped to \"
53
- #
54
- # str - string to be quoted
55
- #
56
- # Examples
57
- #
58
- # shell_quote("FB|123\"\\1")
59
- # # => "\"FB|123\"\\"\\\\1\""
60
- #
61
- # Returns the the escaped string surrounded by quotes
62
- def shell_quote(str)
63
- "\"#{str.gsub(/\\/){ '\\\\' }.gsub(/["]/){ "\\\"" }}\""
64
- end
65
-
66
146
  end
@@ -4,23 +4,9 @@ require "foreman/export"
4
4
  class Foreman::Export::Bluepill < Foreman::Export::Base
5
5
 
6
6
  def export
7
- error("Must specify a location") unless location
8
-
9
- FileUtils.mkdir_p location
10
-
11
- app = self.app || File.basename(engine.directory)
12
- user = self.user || app
13
- log_root = self.log || "/var/log/#{app}"
14
- template_root = self.template
15
-
16
- Dir["#{location}/#{app}.pill"].each do |file|
17
- say "cleaning up: #{file}"
18
- FileUtils.rm(file)
19
- end
20
-
21
- master_template = export_template("bluepill", "master.pill.erb", template_root)
22
- master_config = ERB.new(master_template).result(binding)
23
- write_file "#{location}/#{app}.pill", master_config
7
+ super
8
+ clean "#{location}/#{app}.pill"
9
+ write_template "bluepill/master.pill.erb", "#{app}.pill", binding
24
10
  end
25
11
 
26
12
  end