fiveman 0.1.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.
- checksums.yaml +7 -0
- data/README.md +11 -0
- data/bin/fiveman +7 -0
- data/bin/fiveman-runner +41 -0
- data/data/example/Procfile +4 -0
- data/data/example/Procfile.without_colon +2 -0
- data/data/example/error +7 -0
- data/data/example/log/neverdie.log +4 -0
- data/data/example/spawnee +14 -0
- data/data/example/spawner +7 -0
- data/data/example/ticker +14 -0
- data/data/example/utf8 +11 -0
- data/data/export/bluepill/master.pill.erb +28 -0
- data/data/export/daemon/master.conf.erb +14 -0
- data/data/export/daemon/process.conf.erb +8 -0
- data/data/export/daemon/process_master.conf.erb +2 -0
- data/data/export/launchd/launchd.plist.erb +33 -0
- data/data/export/runit/log/run.erb +7 -0
- data/data/export/runit/run.erb +4 -0
- data/data/export/supervisord/app.conf.erb +31 -0
- data/data/export/systemd/master.target.erb +5 -0
- data/data/export/systemd/process.service.erb +21 -0
- data/data/export/upstart/master.conf.erb +2 -0
- data/data/export/upstart/process.conf.erb +15 -0
- data/data/export/upstart/process_master.conf.erb +2 -0
- data/lib/fiveman/cli.rb +162 -0
- data/lib/fiveman/distribution.rb +9 -0
- data/lib/fiveman/engine/cli.rb +101 -0
- data/lib/fiveman/engine.rb +494 -0
- data/lib/fiveman/env.rb +29 -0
- data/lib/fiveman/export/base.rb +171 -0
- data/lib/fiveman/export/bluepill.rb +12 -0
- data/lib/fiveman/export/daemon.rb +28 -0
- data/lib/fiveman/export/inittab.rb +42 -0
- data/lib/fiveman/export/launchd.rb +22 -0
- data/lib/fiveman/export/runit.rb +34 -0
- data/lib/fiveman/export/supervisord.rb +16 -0
- data/lib/fiveman/export/systemd.rb +34 -0
- data/lib/fiveman/export/upstart.rb +46 -0
- data/lib/fiveman/export.rb +36 -0
- data/lib/fiveman/helpers.rb +45 -0
- data/lib/fiveman/process.rb +80 -0
- data/lib/fiveman/procfile.rb +94 -0
- data/lib/fiveman/vendor/thor/lib/thor/actions/create_file.rb +103 -0
- data/lib/fiveman/vendor/thor/lib/thor/actions/create_link.rb +59 -0
- data/lib/fiveman/vendor/thor/lib/thor/actions/directory.rb +118 -0
- data/lib/fiveman/vendor/thor/lib/thor/actions/empty_directory.rb +135 -0
- data/lib/fiveman/vendor/thor/lib/thor/actions/file_manipulation.rb +327 -0
- data/lib/fiveman/vendor/thor/lib/thor/actions/inject_into_file.rb +103 -0
- data/lib/fiveman/vendor/thor/lib/thor/actions.rb +318 -0
- data/lib/fiveman/vendor/thor/lib/thor/base.rb +656 -0
- data/lib/fiveman/vendor/thor/lib/thor/command.rb +133 -0
- data/lib/fiveman/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +85 -0
- data/lib/fiveman/vendor/thor/lib/thor/core_ext/io_binary_read.rb +12 -0
- data/lib/fiveman/vendor/thor/lib/thor/core_ext/ordered_hash.rb +129 -0
- data/lib/fiveman/vendor/thor/lib/thor/error.rb +32 -0
- data/lib/fiveman/vendor/thor/lib/thor/group.rb +281 -0
- data/lib/fiveman/vendor/thor/lib/thor/invocation.rb +177 -0
- data/lib/fiveman/vendor/thor/lib/thor/line_editor/basic.rb +35 -0
- data/lib/fiveman/vendor/thor/lib/thor/line_editor/readline.rb +88 -0
- data/lib/fiveman/vendor/thor/lib/thor/line_editor.rb +17 -0
- data/lib/fiveman/vendor/thor/lib/thor/parser/argument.rb +70 -0
- data/lib/fiveman/vendor/thor/lib/thor/parser/arguments.rb +175 -0
- data/lib/fiveman/vendor/thor/lib/thor/parser/option.rb +146 -0
- data/lib/fiveman/vendor/thor/lib/thor/parser/options.rb +220 -0
- data/lib/fiveman/vendor/thor/lib/thor/parser.rb +4 -0
- data/lib/fiveman/vendor/thor/lib/thor/rake_compat.rb +71 -0
- data/lib/fiveman/vendor/thor/lib/thor/runner.rb +322 -0
- data/lib/fiveman/vendor/thor/lib/thor/shell/basic.rb +436 -0
- data/lib/fiveman/vendor/thor/lib/thor/shell/color.rb +149 -0
- data/lib/fiveman/vendor/thor/lib/thor/shell/html.rb +126 -0
- data/lib/fiveman/vendor/thor/lib/thor/shell.rb +81 -0
- data/lib/fiveman/vendor/thor/lib/thor/util.rb +268 -0
- data/lib/fiveman/vendor/thor/lib/thor/version.rb +3 -0
- data/lib/fiveman/vendor/thor/lib/thor.rb +492 -0
- data/lib/fiveman/version.rb +5 -0
- data/lib/fiveman.rb +17 -0
- data/man/fiveman.1 +284 -0
- data/spec/fiveman/cli_spec.rb +111 -0
- data/spec/fiveman/engine_spec.rb +114 -0
- data/spec/fiveman/export/base_spec.rb +19 -0
- data/spec/fiveman/export/bluepill_spec.rb +37 -0
- data/spec/fiveman/export/daemon_spec.rb +97 -0
- data/spec/fiveman/export/inittab_spec.rb +40 -0
- data/spec/fiveman/export/launchd_spec.rb +31 -0
- data/spec/fiveman/export/runit_spec.rb +36 -0
- data/spec/fiveman/export/supervisord_spec.rb +38 -0
- data/spec/fiveman/export/systemd_spec.rb +155 -0
- data/spec/fiveman/export/upstart_spec.rb +118 -0
- data/spec/fiveman/export_spec.rb +24 -0
- data/spec/fiveman/helpers_spec.rb +26 -0
- data/spec/fiveman/process_spec.rb +71 -0
- data/spec/fiveman/procfile_spec.rb +57 -0
- data/spec/fiveman_spec.rb +16 -0
- data/spec/helper_spec.rb +19 -0
- data/spec/resources/Procfile +5 -0
- data/spec/resources/Procfile.bad +2 -0
- data/spec/resources/bin/echo +2 -0
- data/spec/resources/bin/env +2 -0
- data/spec/resources/bin/test +2 -0
- data/spec/resources/bin/utf8 +2 -0
- data/spec/resources/export/bluepill/app-concurrency.pill +49 -0
- data/spec/resources/export/bluepill/app.pill +81 -0
- data/spec/resources/export/daemon/app-alpha-1.conf +7 -0
- data/spec/resources/export/daemon/app-alpha-2.conf +7 -0
- data/spec/resources/export/daemon/app-alpha.conf +2 -0
- data/spec/resources/export/daemon/app-bravo-1.conf +7 -0
- data/spec/resources/export/daemon/app-bravo.conf +2 -0
- data/spec/resources/export/daemon/app.conf +14 -0
- data/spec/resources/export/inittab/inittab.concurrency +4 -0
- data/spec/resources/export/inittab/inittab.default +6 -0
- data/spec/resources/export/launchd/launchd-a.default +29 -0
- data/spec/resources/export/launchd/launchd-b.default +29 -0
- data/spec/resources/export/launchd/launchd-c.default +30 -0
- data/spec/resources/export/runit/app-alpha-1/log/run +7 -0
- data/spec/resources/export/runit/app-alpha-1/run +4 -0
- data/spec/resources/export/runit/app-alpha-2/log/run +7 -0
- data/spec/resources/export/runit/app-alpha-2/run +4 -0
- data/spec/resources/export/runit/app-bravo-1/log/run +7 -0
- data/spec/resources/export/runit/app-bravo-1/run +4 -0
- data/spec/resources/export/supervisord/app-alpha-1.conf +42 -0
- data/spec/resources/export/supervisord/app-alpha-2.conf +22 -0
- data/spec/resources/export/systemd/app-alpha.1.service +18 -0
- data/spec/resources/export/systemd/app-alpha.2.service +18 -0
- data/spec/resources/export/systemd/app-alpha.target +2 -0
- data/spec/resources/export/systemd/app-bravo.1.service +18 -0
- data/spec/resources/export/systemd/app-bravo.target +2 -0
- data/spec/resources/export/systemd/app.target +5 -0
- data/spec/resources/export/upstart/app-alpha-1.conf +11 -0
- data/spec/resources/export/upstart/app-alpha-2.conf +11 -0
- data/spec/resources/export/upstart/app-alpha.conf +2 -0
- data/spec/resources/export/upstart/app-bravo-1.conf +11 -0
- data/spec/resources/export/upstart/app-bravo.conf +2 -0
- data/spec/resources/export/upstart/app.conf +2 -0
- data/spec/spec_helper.rb +177 -0
- metadata +177 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "erb"
|
|
2
|
+
require "fiveman/export"
|
|
3
|
+
|
|
4
|
+
class Fiveman::Export::Daemon < Fiveman::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,42 @@
|
|
|
1
|
+
require "fiveman/export"
|
|
2
|
+
|
|
3
|
+
class Fiveman::Export::Inittab < Fiveman::Export::Base
|
|
4
|
+
|
|
5
|
+
def export
|
|
6
|
+
error("Must specify a location") unless location
|
|
7
|
+
|
|
8
|
+
inittab = []
|
|
9
|
+
inittab << "# ----- fiveman #{app} processes -----"
|
|
10
|
+
|
|
11
|
+
index = 1
|
|
12
|
+
engine.each_process do |name, process|
|
|
13
|
+
1.upto(engine.formation[name]) do |num|
|
|
14
|
+
id = app.slice(0, 2).upcase + sprintf("%02d", index)
|
|
15
|
+
port = engine.port_for(process, num)
|
|
16
|
+
|
|
17
|
+
commands = []
|
|
18
|
+
commands << "cd #{engine.root}"
|
|
19
|
+
commands << "export PORT=#{port}"
|
|
20
|
+
engine.env.each_pair do |var, env|
|
|
21
|
+
commands << "export #{var.upcase}=#{shell_quote(env)}"
|
|
22
|
+
end
|
|
23
|
+
commands << "#{process.command} >> #{log}/#{name}-#{num}.log 2>&1"
|
|
24
|
+
|
|
25
|
+
inittab << "#{id}:4:respawn:/bin/su - #{user} -c '#{commands.join(";")}'"
|
|
26
|
+
index += 1
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
inittab << "# ----- end fiveman #{app} processes -----"
|
|
31
|
+
|
|
32
|
+
inittab = inittab.join("\n") + "\n"
|
|
33
|
+
|
|
34
|
+
if location == "-"
|
|
35
|
+
puts inittab
|
|
36
|
+
else
|
|
37
|
+
say "writing: #{location}"
|
|
38
|
+
File.open(location, "w") { |file| file.puts inittab }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require "erb"
|
|
2
|
+
require "fiveman/export"
|
|
3
|
+
|
|
4
|
+
class Fiveman::Export::Launchd < Fiveman::Export::Base
|
|
5
|
+
|
|
6
|
+
def export
|
|
7
|
+
super
|
|
8
|
+
engine.each_process do |name, process|
|
|
9
|
+
1.upto(engine.formation[name]) do |num|
|
|
10
|
+
port = engine.port_for(process, num)
|
|
11
|
+
command_args = process.command.split(/\s+/).map{|arg|
|
|
12
|
+
case arg
|
|
13
|
+
when "$PORT" then port
|
|
14
|
+
else arg
|
|
15
|
+
end
|
|
16
|
+
}
|
|
17
|
+
write_template "launchd/launchd.plist.erb", "#{app}-#{name}-#{num}.plist", binding
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require "erb"
|
|
2
|
+
require "fiveman/export"
|
|
3
|
+
|
|
4
|
+
class Fiveman::Export::Runit < Fiveman::Export::Base
|
|
5
|
+
|
|
6
|
+
ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
|
|
7
|
+
|
|
8
|
+
def export
|
|
9
|
+
super
|
|
10
|
+
|
|
11
|
+
engine.each_process do |name, process|
|
|
12
|
+
1.upto(engine.formation[name]) do |num|
|
|
13
|
+
process_directory = "#{app}-#{name}-#{num}"
|
|
14
|
+
|
|
15
|
+
create_directory process_directory
|
|
16
|
+
create_directory "#{process_directory}/env"
|
|
17
|
+
create_directory "#{process_directory}/log"
|
|
18
|
+
|
|
19
|
+
write_template "runit/run.erb", "#{process_directory}/run", binding
|
|
20
|
+
chmod 0755, "#{process_directory}/run"
|
|
21
|
+
|
|
22
|
+
port = engine.port_for(process, num)
|
|
23
|
+
engine.env.merge("PORT" => port.to_s).each do |key, value|
|
|
24
|
+
write_file "#{process_directory}/env/#{key}", value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
write_template "runit/log/run.erb", "#{process_directory}/log/run", binding
|
|
28
|
+
chmod 0755, "#{process_directory}/log/run"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require "erb"
|
|
2
|
+
require "fiveman/export"
|
|
3
|
+
|
|
4
|
+
class Fiveman::Export::Supervisord < Fiveman::Export::Base
|
|
5
|
+
|
|
6
|
+
def export
|
|
7
|
+
super
|
|
8
|
+
|
|
9
|
+
Dir["#{location}/#{app}.conf"].each do |file|
|
|
10
|
+
clean file
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
write_template "supervisord/app.conf.erb", "#{app}.conf", binding
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require "erb"
|
|
2
|
+
require "fiveman/export"
|
|
3
|
+
|
|
4
|
+
class Fiveman::Export::Systemd < Fiveman::Export::Base
|
|
5
|
+
|
|
6
|
+
def export
|
|
7
|
+
super
|
|
8
|
+
|
|
9
|
+
Dir["#{location}/#{app}*.target"]
|
|
10
|
+
.concat(Dir["#{location}/#{app}*.service"])
|
|
11
|
+
.concat(Dir["#{location}/#{app}*.target.wants/#{app}*.service"])
|
|
12
|
+
.each do |file|
|
|
13
|
+
clean file
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Dir["#{location}/#{app}*.target.wants"].each do |file|
|
|
17
|
+
clean_dir file
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
service_names = []
|
|
21
|
+
|
|
22
|
+
engine.each_process do |name, process|
|
|
23
|
+
1.upto(engine.formation[name]) do |num|
|
|
24
|
+
port = engine.port_for(process, num)
|
|
25
|
+
process_name = "#{name}.#{num}"
|
|
26
|
+
service_filename = "#{app}-#{process_name}.service"
|
|
27
|
+
write_template "systemd/process.service.erb", service_filename, binding
|
|
28
|
+
service_names << service_filename
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
write_template "systemd/master.target.erb", "#{app}.target", binding
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require "erb"
|
|
2
|
+
require "fiveman/export"
|
|
3
|
+
|
|
4
|
+
class Fiveman::Export::Upstart < Fiveman::Export::Base
|
|
5
|
+
|
|
6
|
+
def export
|
|
7
|
+
super
|
|
8
|
+
|
|
9
|
+
master_file = "#{app}.conf"
|
|
10
|
+
|
|
11
|
+
clean File.join(location, master_file)
|
|
12
|
+
write_template master_template, master_file, binding
|
|
13
|
+
|
|
14
|
+
engine.each_process do |name, process|
|
|
15
|
+
process_master_file = "#{app}-#{name}.conf"
|
|
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) }
|
|
22
|
+
|
|
23
|
+
next if engine.formation[name] < 1
|
|
24
|
+
write_template process_master_template, process_master_file, binding
|
|
25
|
+
|
|
26
|
+
1.upto(engine.formation[name]) do |num|
|
|
27
|
+
port = engine.port_for(process, num)
|
|
28
|
+
write_template process_template, process_file % num, binding
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def master_template
|
|
36
|
+
"upstart/master.conf.erb"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def process_master_template
|
|
40
|
+
"upstart/process_master.conf.erb"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def process_template
|
|
44
|
+
"upstart/process.conf.erb"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require "fiveman"
|
|
2
|
+
require "fiveman/helpers"
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Fiveman::Export
|
|
6
|
+
extend Fiveman::Helpers
|
|
7
|
+
|
|
8
|
+
class Exception < ::Exception; end
|
|
9
|
+
|
|
10
|
+
def self.formatter(format)
|
|
11
|
+
begin
|
|
12
|
+
require "fiveman/export/#{ format.tr('-', '_') }"
|
|
13
|
+
classy_format = classify(format)
|
|
14
|
+
formatter = constantize("Fiveman::Export::#{ classy_format }")
|
|
15
|
+
rescue NameError => ex
|
|
16
|
+
error "Unknown export format: #{format} (no class Fiveman::Export::#{ classy_format })."
|
|
17
|
+
rescue LoadError => ex
|
|
18
|
+
error "Unknown export format: #{format} (unable to load file 'fiveman/export/#{ format.tr('-', '_') }')."
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.error(message)
|
|
23
|
+
raise Fiveman::Export::Exception.new(message)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
require "fiveman/export/base"
|
|
29
|
+
require "fiveman/export/inittab"
|
|
30
|
+
require "fiveman/export/upstart"
|
|
31
|
+
require "fiveman/export/daemon"
|
|
32
|
+
require "fiveman/export/bluepill"
|
|
33
|
+
require "fiveman/export/runit"
|
|
34
|
+
require "fiveman/export/supervisord"
|
|
35
|
+
require "fiveman/export/launchd"
|
|
36
|
+
require "fiveman/export/systemd"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Fiveman::Helpers
|
|
2
|
+
# Copied whole sale from, https://github.com/defunkt/resque/
|
|
3
|
+
|
|
4
|
+
# Given a word with dashes, returns a camel cased version of it.
|
|
5
|
+
#
|
|
6
|
+
# classify('job-name') # => 'JobName'
|
|
7
|
+
def classify(dashed_word)
|
|
8
|
+
dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
|
|
9
|
+
end # Tries to find a constant with the name specified in the argument string:
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# constantize("Module") # => Module
|
|
13
|
+
# constantize("Test::Unit") # => Test::Unit
|
|
14
|
+
#
|
|
15
|
+
# The name is assumed to be the one of a top-level constant, no matter
|
|
16
|
+
# whether it starts with "::" or not. No lexical context is taken into
|
|
17
|
+
# account:
|
|
18
|
+
#
|
|
19
|
+
# C = 'outside'
|
|
20
|
+
# module M
|
|
21
|
+
# C = 'inside'
|
|
22
|
+
# C # => 'inside'
|
|
23
|
+
# constantize("C") # => 'outside', same as ::C
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# NameError is raised when the constant is unknown.
|
|
27
|
+
def constantize(camel_cased_word)
|
|
28
|
+
camel_cased_word = camel_cased_word.to_s
|
|
29
|
+
|
|
30
|
+
names = camel_cased_word.split('::')
|
|
31
|
+
names.shift if names.empty? || names.first.empty?
|
|
32
|
+
|
|
33
|
+
constant = Object
|
|
34
|
+
names.each do |name|
|
|
35
|
+
args = Module.method(:const_get).arity != 1 ? [false] : []
|
|
36
|
+
|
|
37
|
+
if constant.const_defined?(name, *args)
|
|
38
|
+
constant = constant.const_get(name)
|
|
39
|
+
else
|
|
40
|
+
constant = constant.const_missing(name)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
constant
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
require "fiveman"
|
|
2
|
+
require "shellwords"
|
|
3
|
+
|
|
4
|
+
class Fiveman::Process
|
|
5
|
+
|
|
6
|
+
attr_reader :command
|
|
7
|
+
attr_reader :env
|
|
8
|
+
|
|
9
|
+
# Create a Process
|
|
10
|
+
#
|
|
11
|
+
# @param [String] command The command to run
|
|
12
|
+
# @param [Hash] options
|
|
13
|
+
#
|
|
14
|
+
# @option options [String] :cwd (./) Change to this working directory before executing the process
|
|
15
|
+
# @option options [Hash] :env ({}) Environment variables to set for this process
|
|
16
|
+
#
|
|
17
|
+
def initialize(command, options={})
|
|
18
|
+
@command = command
|
|
19
|
+
@options = options.dup
|
|
20
|
+
|
|
21
|
+
@options[:env] ||= {}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Get environment-expanded command for a +Process+
|
|
25
|
+
#
|
|
26
|
+
# @param [Hash] custom_env ({}) Environment variables to merge with defaults
|
|
27
|
+
#
|
|
28
|
+
# @return [String] The expanded command
|
|
29
|
+
#
|
|
30
|
+
def expanded_command(custom_env={})
|
|
31
|
+
env = @options[:env].merge(custom_env)
|
|
32
|
+
expanded_command = command.dup
|
|
33
|
+
env.each do |key, val|
|
|
34
|
+
expanded_command.gsub!("$#{key}", val)
|
|
35
|
+
end
|
|
36
|
+
expanded_command
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Run a +Process+
|
|
40
|
+
#
|
|
41
|
+
# @param [Hash] options
|
|
42
|
+
#
|
|
43
|
+
# @option options :env ({}) Environment variables to set for this execution
|
|
44
|
+
# @option options :output ($stdout) The output stream
|
|
45
|
+
#
|
|
46
|
+
# @returns [Fixnum] pid The +pid+ of the process
|
|
47
|
+
#
|
|
48
|
+
def run(options={})
|
|
49
|
+
env = @options[:env].merge(options[:env] || {})
|
|
50
|
+
output = options[:output] || $stdout
|
|
51
|
+
runner = "#{Fiveman.runner}".shellescape
|
|
52
|
+
|
|
53
|
+
Dir.chdir(cwd) do
|
|
54
|
+
Process.spawn env, expanded_command(env), :out => output, :err => output
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Exec a +Process+
|
|
59
|
+
#
|
|
60
|
+
# @param [Hash] options
|
|
61
|
+
#
|
|
62
|
+
# @option options :env ({}) Environment variables to set for this execution
|
|
63
|
+
#
|
|
64
|
+
# @return Does not return
|
|
65
|
+
def exec(options={})
|
|
66
|
+
env = @options[:env].merge(options[:env] || {})
|
|
67
|
+
env.each { |k, v| ENV[k] = v }
|
|
68
|
+
Dir.chdir(cwd)
|
|
69
|
+
Kernel.exec expanded_command(env)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns the working directory for this +Process+
|
|
73
|
+
#
|
|
74
|
+
# @returns [String]
|
|
75
|
+
#
|
|
76
|
+
def cwd
|
|
77
|
+
File.expand_path(@options[:cwd] || ".")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require "fiveman"
|
|
2
|
+
|
|
3
|
+
# Reads and writes Procfiles
|
|
4
|
+
#
|
|
5
|
+
# A valid Procfile entry is captured by this regex:
|
|
6
|
+
#
|
|
7
|
+
# /^([A-Za-z0-9_]+):\s*(.+)$/
|
|
8
|
+
#
|
|
9
|
+
# All other lines are ignored.
|
|
10
|
+
#
|
|
11
|
+
class Fiveman::Procfile
|
|
12
|
+
|
|
13
|
+
# Initialize a Procfile
|
|
14
|
+
#
|
|
15
|
+
# @param [String] filename (nil) An optional filename to read from
|
|
16
|
+
#
|
|
17
|
+
def initialize(filename=nil)
|
|
18
|
+
@entries = []
|
|
19
|
+
load(filename) if filename
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Yield each +Procfile+ entry in order
|
|
23
|
+
#
|
|
24
|
+
def entries
|
|
25
|
+
@entries.each do |(name, command)|
|
|
26
|
+
yield name, command
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Retrieve a +Procfile+ command by name
|
|
31
|
+
#
|
|
32
|
+
# @param [String] name The name of the Procfile entry to retrieve
|
|
33
|
+
#
|
|
34
|
+
def [](name)
|
|
35
|
+
if entry = @entries.detect { |n,c| name == n }
|
|
36
|
+
entry.last
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Create a +Procfile+ entry
|
|
41
|
+
#
|
|
42
|
+
# @param [String] name The name of the +Procfile+ entry to create
|
|
43
|
+
# @param [String] command The command of the +Procfile+ entry to create
|
|
44
|
+
#
|
|
45
|
+
def []=(name, command)
|
|
46
|
+
delete name
|
|
47
|
+
@entries << [name, command]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Remove a +Procfile+ entry
|
|
51
|
+
#
|
|
52
|
+
# @param [String] name The name of the +Procfile+ entry to remove
|
|
53
|
+
#
|
|
54
|
+
def delete(name)
|
|
55
|
+
@entries.reject! { |n,c| name == n }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Load a Procfile from a file
|
|
59
|
+
#
|
|
60
|
+
# @param [String] filename The filename of the +Procfile+ to load
|
|
61
|
+
#
|
|
62
|
+
def load(filename)
|
|
63
|
+
@entries.replace parse(filename)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Save a Procfile to a file
|
|
67
|
+
#
|
|
68
|
+
# @param [String] filename Save the +Procfile+ to this file
|
|
69
|
+
#
|
|
70
|
+
def save(filename)
|
|
71
|
+
File.open(filename, 'w') do |file|
|
|
72
|
+
file.puts self.to_s
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Get the +Procfile+ as a +String+
|
|
77
|
+
#
|
|
78
|
+
def to_s
|
|
79
|
+
@entries.map do |name, command|
|
|
80
|
+
[ name, command ].join(": ")
|
|
81
|
+
end.join("\n")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def parse(filename)
|
|
87
|
+
File.read(filename).gsub("\r\n","\n").split("\n").map do |line|
|
|
88
|
+
if line =~ /^([A-Za-z0-9_-]+):\s*(.+)$/
|
|
89
|
+
[$1, $2]
|
|
90
|
+
end
|
|
91
|
+
end.compact
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require "fiveman/vendor/thor/lib/thor/actions/empty_directory"
|
|
2
|
+
|
|
3
|
+
class Fiveman::Thor
|
|
4
|
+
module Actions
|
|
5
|
+
# Create a new file relative to the destination root with the given data,
|
|
6
|
+
# which is the return value of a block or a data string.
|
|
7
|
+
#
|
|
8
|
+
# ==== Parameters
|
|
9
|
+
# destination<String>:: the relative path to the destination root.
|
|
10
|
+
# data<String|NilClass>:: the data to append to the file.
|
|
11
|
+
# config<Hash>:: give :verbose => false to not log the status.
|
|
12
|
+
#
|
|
13
|
+
# ==== Examples
|
|
14
|
+
#
|
|
15
|
+
# create_file "lib/fun_party.rb" do
|
|
16
|
+
# hostname = ask("What is the virtual hostname I should use?")
|
|
17
|
+
# "vhost.name = #{hostname}"
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# create_file "config/apache.conf", "your apache config"
|
|
21
|
+
#
|
|
22
|
+
def create_file(destination, *args, &block)
|
|
23
|
+
config = args.last.is_a?(Hash) ? args.pop : {}
|
|
24
|
+
data = args.first
|
|
25
|
+
action CreateFile.new(self, destination, block || data.to_s, config)
|
|
26
|
+
end
|
|
27
|
+
alias_method :add_file, :create_file
|
|
28
|
+
|
|
29
|
+
# CreateFile is a subset of Template, which instead of rendering a file with
|
|
30
|
+
# ERB, it gets the content from the user.
|
|
31
|
+
#
|
|
32
|
+
class CreateFile < EmptyDirectory #:nodoc:
|
|
33
|
+
attr_reader :data
|
|
34
|
+
|
|
35
|
+
def initialize(base, destination, data, config = {})
|
|
36
|
+
@data = data
|
|
37
|
+
super(base, destination, config)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Checks if the content of the file at the destination is identical to the rendered result.
|
|
41
|
+
#
|
|
42
|
+
# ==== Returns
|
|
43
|
+
# Boolean:: true if it is identical, false otherwise.
|
|
44
|
+
#
|
|
45
|
+
def identical?
|
|
46
|
+
exists? && File.binread(destination) == render
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Holds the content to be added to the file.
|
|
50
|
+
#
|
|
51
|
+
def render
|
|
52
|
+
@render ||= if data.is_a?(Proc)
|
|
53
|
+
data.call
|
|
54
|
+
else
|
|
55
|
+
data
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def invoke!
|
|
60
|
+
invoke_with_conflict_check do
|
|
61
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
|
62
|
+
File.open(destination, "wb") { |f| f.write render }
|
|
63
|
+
end
|
|
64
|
+
given_destination
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
protected
|
|
68
|
+
|
|
69
|
+
# Now on conflict we check if the file is identical or not.
|
|
70
|
+
#
|
|
71
|
+
def on_conflict_behavior(&block)
|
|
72
|
+
if identical?
|
|
73
|
+
say_status :identical, :blue
|
|
74
|
+
else
|
|
75
|
+
options = base.options.merge(config)
|
|
76
|
+
force_or_skip_or_conflict(options[:force], options[:skip], &block)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# If force is true, run the action, otherwise check if it's not being
|
|
81
|
+
# skipped. If both are false, show the file_collision menu, if the menu
|
|
82
|
+
# returns true, force it, otherwise skip.
|
|
83
|
+
#
|
|
84
|
+
def force_or_skip_or_conflict(force, skip, &block)
|
|
85
|
+
if force
|
|
86
|
+
say_status :force, :yellow
|
|
87
|
+
yield unless pretend?
|
|
88
|
+
elsif skip
|
|
89
|
+
say_status :skip, :yellow
|
|
90
|
+
else
|
|
91
|
+
say_status :conflict, :red
|
|
92
|
+
force_or_skip_or_conflict(force_on_collision?, true, &block)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Shows the file collision menu to the user and gets the result.
|
|
97
|
+
#
|
|
98
|
+
def force_on_collision?
|
|
99
|
+
base.shell.file_collision(destination) { render }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require "fiveman/vendor/thor/lib/thor/actions/create_file"
|
|
2
|
+
|
|
3
|
+
class Fiveman::Thor
|
|
4
|
+
module Actions
|
|
5
|
+
# Create a new file relative to the destination root from the given source.
|
|
6
|
+
#
|
|
7
|
+
# ==== Parameters
|
|
8
|
+
# destination<String>:: the relative path to the destination root.
|
|
9
|
+
# source<String|NilClass>:: the relative path to the source root.
|
|
10
|
+
# config<Hash>:: give :verbose => false to not log the status.
|
|
11
|
+
# :: give :symbolic => false for hard link.
|
|
12
|
+
#
|
|
13
|
+
# ==== Examples
|
|
14
|
+
#
|
|
15
|
+
# create_link "config/apache.conf", "/etc/apache.conf"
|
|
16
|
+
#
|
|
17
|
+
def create_link(destination, *args)
|
|
18
|
+
config = args.last.is_a?(Hash) ? args.pop : {}
|
|
19
|
+
source = args.first
|
|
20
|
+
action CreateLink.new(self, destination, source, config)
|
|
21
|
+
end
|
|
22
|
+
alias_method :add_link, :create_link
|
|
23
|
+
|
|
24
|
+
# CreateLink is a subset of CreateFile, which instead of taking a block of
|
|
25
|
+
# data, just takes a source string from the user.
|
|
26
|
+
#
|
|
27
|
+
class CreateLink < CreateFile #:nodoc:
|
|
28
|
+
attr_reader :data
|
|
29
|
+
|
|
30
|
+
# Checks if the content of the file at the destination is identical to the rendered result.
|
|
31
|
+
#
|
|
32
|
+
# ==== Returns
|
|
33
|
+
# Boolean:: true if it is identical, false otherwise.
|
|
34
|
+
#
|
|
35
|
+
def identical?
|
|
36
|
+
exists? && File.identical?(render, destination)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def invoke!
|
|
40
|
+
invoke_with_conflict_check do
|
|
41
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
|
42
|
+
# Create a symlink by default
|
|
43
|
+
config[:symbolic] = true if config[:symbolic].nil?
|
|
44
|
+
File.unlink(destination) if exists?
|
|
45
|
+
if config[:symbolic]
|
|
46
|
+
File.symlink(render, destination)
|
|
47
|
+
else
|
|
48
|
+
File.link(render, destination)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
given_destination
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def exists?
|
|
55
|
+
super || File.symlink?(destination)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|