pleaserun 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +13 -0
  3. data/Gemfile.lock +73 -0
  4. data/Guardfile +17 -0
  5. data/Makefile +50 -0
  6. data/README.md +98 -0
  7. data/bin/pleaserun +7 -0
  8. data/examples/runit.rb +16 -0
  9. data/lib/pleaserun/cli.rb +241 -0
  10. data/lib/pleaserun/configurable.rb +143 -0
  11. data/lib/pleaserun/detector.rb +58 -0
  12. data/lib/pleaserun/mustache_methods.rb +41 -0
  13. data/lib/pleaserun/namespace.rb +3 -0
  14. data/lib/pleaserun/platform/base.rb +144 -0
  15. data/lib/pleaserun/platform/launchd.rb +27 -0
  16. data/lib/pleaserun/platform/runit.rb +18 -0
  17. data/lib/pleaserun/platform/systemd.rb +24 -0
  18. data/lib/pleaserun/platform/sysv.rb +12 -0
  19. data/lib/pleaserun/platform/upstart.rb +11 -0
  20. data/pleaserun.gemspec +27 -0
  21. data/spec/pleaserun/configurable_spec.rb +215 -0
  22. data/spec/pleaserun/mustache_methods_spec.rb +46 -0
  23. data/spec/pleaserun/platform/base_spec.rb +27 -0
  24. data/spec/pleaserun/platform/launchd_spec.rb +93 -0
  25. data/spec/pleaserun/platform/systemd_spec.rb +119 -0
  26. data/spec/pleaserun/platform/sysv_spec.rb +133 -0
  27. data/spec/pleaserun/platform/upstart_spec.rb +117 -0
  28. data/spec/testenv.rb +69 -0
  29. data/templates/launchd/10.9/program.plist +47 -0
  30. data/templates/runit/log +4 -0
  31. data/templates/runit/run +17 -0
  32. data/templates/systemd/default/prestart.sh +2 -0
  33. data/templates/systemd/default/program.service +17 -0
  34. data/templates/sysv/lsb-3.1/default +0 -0
  35. data/templates/sysv/lsb-3.1/init.d +141 -0
  36. data/templates/upstart/1.5/init.conf +41 -0
  37. data/templates/upstart/1.5/init.d.sh +4 -0
  38. data/test.rb +33 -0
  39. data/test/helpers.rb +20 -0
  40. data/test/test.rb +60 -0
  41. data/test/vagrant/Vagrantfile +40 -0
  42. data/test/vagrant/fedora-18/Vagrantfile +28 -0
  43. data/test/vagrant/fedora-18/provision.sh +10 -0
  44. metadata +187 -0
@@ -0,0 +1,133 @@
1
+ require "testenv"
2
+ require "pleaserun/platform/sysv"
3
+
4
+ describe PleaseRun::Platform::SYSV do
5
+ let(:start) { "/etc/init.d/#{subject.name} start" }
6
+ let(:stop) { "/etc/init.d/#{subject.name} stop" }
7
+ let(:status) { "/etc/init.d/#{subject.name} status" }
8
+ let(:restart) { "/etc/init.d/#{subject.name} restart" }
9
+
10
+ it "inherits correctly" do
11
+ insist { PleaseRun::Platform::SYSV.ancestors }.include?(PleaseRun::Platform::Base)
12
+ end
13
+
14
+ context "#files" do
15
+ subject do
16
+ runner = PleaseRun::Platform::SYSV.new("ubuntu-12.04")
17
+ runner.name = "fancypants"
18
+ next runner
19
+ end
20
+
21
+ let(:files) { subject.files.collect { |path, content| path } }
22
+
23
+ it "emits a file in /etc/init.d/" do
24
+ insist { files }.include?("/etc/init.d/fancypants")
25
+ end
26
+ it "emits a file in /etc/default/" do
27
+ insist { files }.include?("/etc/default/fancypants")
28
+ end
29
+ end
30
+
31
+ context "deployment" do
32
+ partytime = superuser? && File.directory?("/etc/init.d")
33
+ it "cannot be attempted", :if => !partytime do
34
+ pending("we are not the superuser") unless superuser?
35
+ pending("no /etc/init.d/ directory found") unless File.directory?("/etc/init.d")
36
+ end
37
+
38
+ context "as the super user", :if => partytime do
39
+ subject { PleaseRun::Platform::SYSV.new("ubuntu-12.04") }
40
+
41
+ before do
42
+ subject.name = "example"
43
+ subject.user = "root"
44
+ subject.program = "/bin/ping"
45
+ subject.args = [ "127.0.0.1" ]
46
+
47
+ activate(subject)
48
+ end
49
+
50
+ after do
51
+ system_quiet("/etc/init.d/#{subject.name} stop")
52
+ subject.files.each do |path, content|
53
+ File.unlink(path) if File.exist?(path)
54
+ end
55
+
56
+ # TODO(sissel): Remove the logs, too.
57
+ #log = "/var/log/example.log"
58
+ #File.unlink(log) if File.exist?(log)
59
+ end
60
+
61
+ it "should install" do
62
+ insist { File }.exist?("/etc/init.d/#{subject.name}")
63
+ end
64
+
65
+ it "should start" do
66
+ # Status should fail before starting
67
+ status_stopped
68
+ starts
69
+ end
70
+
71
+ it "should stop" do
72
+ status_stopped
73
+ starts
74
+ stops
75
+ end
76
+
77
+ context "with failing prestart" do
78
+ before do
79
+ subject.prestart = "false"
80
+ activate(subject)
81
+ end
82
+
83
+ it "should fail to start" do
84
+ insist { starts }.fails
85
+ end
86
+
87
+ context "with PRESTART=no" do
88
+ before { ENV["PRESTART"] = "no" }
89
+ after { ENV["PRESTART"] = nil }
90
+ it "should start" do
91
+ starts
92
+ end
93
+ end
94
+
95
+ it "should stop" do
96
+ # Force a start
97
+ ENV["PRESTART"] = "no"
98
+ starts
99
+ ENV["PRESTART"] = nil
100
+ stops
101
+ end
102
+
103
+ it "should fail to restart" do
104
+ ENV["PRESTART"] = "no"
105
+ starts
106
+ ENV["PRESTART"] = nil
107
+ insist { restarts }.fails
108
+ end
109
+ end
110
+
111
+ context "with a successful prestart" do
112
+ before do
113
+ subject.prestart = "echo hello world"
114
+ activate(subject)
115
+ end
116
+
117
+ it "should start" do
118
+ starts
119
+ end
120
+
121
+ it "should restart" do
122
+ # Force start
123
+ ENV["PRESTART"] = "no"
124
+ starts
125
+ ENV["PRESTART"] = nil
126
+
127
+ # Restart should succeed
128
+ restarts
129
+ end
130
+ end
131
+ end # as the super user
132
+ end # real tests
133
+ end
@@ -0,0 +1,117 @@
1
+ require "testenv"
2
+ require "pleaserun/platform/upstart"
3
+
4
+ describe PleaseRun::Platform::Upstart do
5
+ let(:start) { "initctl start #{subject.name}" }
6
+ let(:stop) { "initctl stop #{subject.name}" }
7
+ let(:status) { "initctl status #{subject.name} | egrep -v '#{subject.name} stop/'" }
8
+ let(:restart) { "initctl restart #{subject.name}" }
9
+
10
+ it "inherits correctly" do
11
+ insist { PleaseRun::Platform::Upstart.ancestors }.include?(PleaseRun::Platform::Base)
12
+ end
13
+
14
+ context "#files" do
15
+ subject do
16
+ runner = PleaseRun::Platform::Upstart.new("1.10")
17
+ runner.name = "fancypants"
18
+ next runner
19
+ end
20
+
21
+ let(:files) { subject.files.collect { |path, content| path } }
22
+
23
+ it "emits a file in /etc/init/" do
24
+ insist { files }.include?("/etc/init/fancypants.conf")
25
+ end
26
+
27
+ it "emits a file in /etc/init.d/" do
28
+ insist { files }.include?("/etc/init.d/fancypants")
29
+ end
30
+ end
31
+
32
+ context "#install_actions" do
33
+ subject do
34
+ runner = PleaseRun::Platform::Upstart.new("1.10")
35
+ runner.name = "fancypants"
36
+ next runner
37
+ end
38
+
39
+ it "has no install actions" do
40
+ insist { subject.install_actions }.empty?
41
+ end
42
+ end
43
+
44
+ context "deployment" do
45
+ partytime = (superuser? && platform?("linux") && program?("initctl") && File.directory?("/etc/init"))
46
+ it "cannot be attempted", :if => !partytime do
47
+ pending("we are not the superuser") unless superuser?
48
+ pending("platform is not linux") unless platform?("linux")
49
+ pending("no 'initctl' program found") unless program?("initctl")
50
+ pending("missing /etc/init/ directory") unless File.directory?("/etc/init")
51
+ end
52
+
53
+ context "as the super user", :if => partytime do
54
+ subject { PleaseRun::Platform::Upstart.new("1.10") }
55
+
56
+ before do
57
+ subject.name = "example"
58
+ subject.user = "root"
59
+ subject.program = "/bin/sh"
60
+ subject.args = [ "-c", "echo hello world; sleep 5" ]
61
+ activate(subject)
62
+ end
63
+
64
+ after do
65
+ system_quiet("initctl stop #{subject.name}")
66
+ subject.files.each do |path, content|
67
+ File.unlink(path) if File.exist?(path)
68
+ end
69
+
70
+ # Remove the logs, too.
71
+ log = "/var/log/upstart/example.log"
72
+ File.unlink(log) if File.exist?(log)
73
+ end
74
+
75
+ #it "should install" do
76
+ #system("initctl status #{subject.name}")
77
+ #status_stopped
78
+ #end
79
+
80
+ it "should start" do
81
+ starts
82
+
83
+ # Starting an already-started job will fail
84
+ insist { starts }.fails
85
+
86
+ status_running
87
+ end
88
+
89
+ it "should stop" do
90
+ starts
91
+ stops
92
+ end
93
+
94
+ context "with failing prestart" do
95
+ before do
96
+ subject.prestart = "false"
97
+ activate(subject)
98
+ end
99
+
100
+ it "should fail to start" do
101
+ insist { starts }.fails
102
+ end
103
+ end
104
+
105
+ context "with successful prestart" do
106
+ before do
107
+ subject.prestart = "true"
108
+ activate(subject)
109
+ end
110
+
111
+ it "should start" do
112
+ starts
113
+ end
114
+ end
115
+ end # as the super user
116
+ end # real tests
117
+ end
data/spec/testenv.rb ADDED
@@ -0,0 +1,69 @@
1
+ require "pleaserun/namespace"
2
+ require "insist"
3
+
4
+ def superuser?
5
+ return Process::UID.eid == 0
6
+ end
7
+
8
+ def platform?(name)
9
+ return RbConfig::CONFIG["host_os"] =~ /^#{name}/
10
+ end
11
+
12
+ def system_quiet(command)
13
+ system("#{command} > /dev/null 2>&1")
14
+ end
15
+
16
+ def sh(command)
17
+ system_quiet(command)
18
+ return $?
19
+ end
20
+
21
+ def program?(name)
22
+ ENV["PATH"].split(":").each do |path|
23
+ file = File.join(path, name)
24
+ return true if File.executable?(file)
25
+ end
26
+ return false
27
+ end
28
+
29
+ def activate(pleaserun)
30
+ pleaserun.files.each do |path, content, mode=nil|
31
+ File.write(path, content)
32
+ File.chmod(mode, path) if mode
33
+ end
34
+ pleaserun.install_actions.each do |command|
35
+ system(command)
36
+ raise "Command failed: #{command}" unless $?.success?
37
+ end
38
+ end
39
+
40
+ module Helpers
41
+ def starts
42
+ system_quiet(start); insist { $? }.success?
43
+ status_running
44
+ end
45
+
46
+ def stops
47
+ system_quiet(stop); insist { $? }.success?
48
+ status_stopped
49
+ end
50
+
51
+ def status_running
52
+ system_quiet(status); insist { $? }.success?
53
+ end
54
+
55
+ def status_stopped
56
+ system_quiet(status); reject { $? }.success?
57
+ end
58
+
59
+ def restarts
60
+ starts
61
+ system_quiet(restart)
62
+ insist { $? }.success?
63
+ status_running
64
+ end
65
+ end
66
+
67
+ RSpec.configure do |c|
68
+ c.include Helpers
69
+ end
@@ -0,0 +1,47 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Disabled</key><false/>
6
+ <key>Label</key><string>{{{ name }}}</string>
7
+ <key>Program</key><string>{{{ program }}}</string>
8
+ <key>ProgramArguments</key><array>
9
+ <string>{{{ program }}}</string>
10
+ {{{xml_args}}}
11
+ </array>
12
+ <key>KeepAlive</key> <true/>
13
+ <key>StandardOutPath</key> <string>/var/log/{{name}}.out</string>
14
+ <key>StandardErrorPath</key> <string>/var/log/{{name}}.err</string>
15
+ <!-- Things to implement eventually.
16
+ <key>UserName</key><string>{{{ user }}}</string>
17
+ <key>GroupName</key><string>{{{ group }}}</string>
18
+ <key>Umask</key><integer>{{{ umask }}}</integer>
19
+ <key>RootDirectory</key><string>{{{ chroot }}}</string>
20
+ <key>WorkingDirectory</key><string>{{{ chdir }}}</string>
21
+ <key>Nice</key><integer>0</integer>
22
+ <key>SoftResourceLimits</key><dict>
23
+ <key>Core</key><integer></integer>
24
+ <key>CPU</key><integer></integer>
25
+ <key>Data</key><integer></integer>
26
+ <key>FileSize</key><integer></integer>
27
+ <key>MemoryLock</key><integer></integer>
28
+ <key>NumberOfFiles</key><integer></integer>
29
+ <key>NumberOfProcesses</key><integer></integer>
30
+ <key>ResidentSetSize</key><integer></integer>
31
+ <key>Stack</key><integer></integer>
32
+ </dict>
33
+ <key>HardResourceLimits</key><dict>
34
+ <key>Core</key><integer></integer>
35
+ <key>CPU</key><integer></integer>
36
+ <key>Data</key><integer></integer>
37
+ <key>FileSize</key><integer></integer>
38
+ <key>MemoryLock</key><integer></integer>
39
+ <key>NumberOfFiles</key><integer></integer>
40
+ <key>NumberOfProcesses</key><integer></integer>
41
+ <key>ResidentSetSize</key><integer></integer>
42
+ <key>Stack</key><integer></integer>
43
+ </dict>
44
+ -->
45
+ </dict>
46
+ </plist>
47
+
@@ -0,0 +1,4 @@
1
+ #! /bin/sh
2
+
3
+ exec svlogd -tt /var/log/{{ name }}
4
+
@@ -0,0 +1,17 @@
1
+ #! /bin/sh
2
+
3
+ # Send stderr to stdout so svlogd catches all output.
4
+ exec 2>&1
5
+
6
+ PROGRAM={{#quoted}}{{{PROGRAM}}}{{/quoted}}
7
+ # Set up the arguments (for $@)
8
+ set -- {{{shell_args}}}
9
+
10
+ {{#shell_continuation}}
11
+ exec chpst
12
+ -u {{#quoted}}{{{user}}}:{{{group}}}{{/quoted}}
13
+ {{#chroot}} -/ {{#quoted}}{{{chroot}}}{{/quoted}} {{/chroot}}
14
+ {{#nice}} -n {{#quoted}}{{{nice}}}{{/quoted}} {{/nice}}
15
+ "$PROGRAM" "$@"
16
+ {{/shell_continuation}}
17
+
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ {{{ prestart }}}
@@ -0,0 +1,17 @@
1
+ [Unit]
2
+ {{#description}}
3
+ Description={{{ description }}}
4
+ {{/description}}
5
+
6
+ [Service]
7
+ Type=simple
8
+ User={{{ user }}}
9
+ Group={{{ group }}}
10
+ ExecStart={{{program}}} {{{shell_args}}}
11
+ {{#prestart}}
12
+ ExecStartPre=/lib/systemd/system/{{{name}}}-prestart.sh
13
+ {{/prestart}}
14
+ Restart=always
15
+
16
+ [Install]
17
+ WantedBy=multi-user.target
File without changes
@@ -0,0 +1,141 @@
1
+ #!/bin/sh
2
+ # Init script for {{{ name }}}
3
+ # Maintained by {{{ author }}}
4
+ # Generated by pleaserun.
5
+ # Implemented based on LSB Core 3.1:
6
+ # * Sections: 20.2, 20.3
7
+ #
8
+ ### BEGIN INIT INFO
9
+ # Provides: {{{ name }}}
10
+ # Required-Start: $remote_fs $syslog
11
+ # Required-Stop: $remote_fs $syslog
12
+ # Default-Start: 2 3 4 5
13
+ # Default-Stop: 0 1 6
14
+ # Short-Description: {{{ one_line_description }}}
15
+ # Description: {{{ description }}}
16
+ ### END INIT INFO
17
+
18
+ PATH=/sbin:/usr/sbin:/bin:/usr/bin
19
+ export PATH
20
+
21
+ name={{#escaped}}{{#safe_filename}}{{{ name }}}{{/safe_filename}}{{/escaped}}
22
+ program={{#escaped}}{{{ program }}}{{/escaped}}
23
+ args={{{ escaped_args }}}
24
+ pidfile="/var/run/$name.pid"
25
+
26
+ [ -r /etc/default/$name ] && . /etc/default/$name
27
+ [ -r /etc/sysconfig/$name ] && . /etc/sysconfig/$name
28
+
29
+ start() {
30
+ {{! I don't use 'su' here to run as a different user because the process 'su'
31
+ stays as the parent, causing our pidfile to contain the pid of 'su' not the
32
+ program we intended to run. Luckily, the 'chroot' program on OSX, FreeBSD, and Linux
33
+ all support switching users and it invokes execve immediately after chrooting.
34
+ }}
35
+
36
+ {{#prestart}}
37
+ if [ "$PRESTART" != "no" ] ; then
38
+ # If prestart fails, abort start.
39
+ prestart || return $?
40
+ fi
41
+ {{/prestart}}
42
+
43
+ # Run the program!
44
+ chroot --userspec {{{user}}}:{{{group}}} {{{chroot}}} sh -c "
45
+ {{#chdir}}cd {{{chdir}}}{{/chdir}}
46
+ {{#nice}}nice {{{nice}}}{{/nice}}
47
+ exec \"$program\" $args
48
+ " > /var/log/$name.log 2> /var/log/$name.err &
49
+
50
+ # Generate the pidfile from here. If we instead made the forked process
51
+ # generate it there will be a race condition between the pidfile writing
52
+ # and a process possibly asking for status.
53
+ echo $! > $pidfile
54
+
55
+ echo "$name started."
56
+ return 0
57
+ }
58
+
59
+ stop() {
60
+ # Try a few times to kill TERM the program
61
+ if status ; then
62
+ pid=`cat "$pidfile"`
63
+ echo "Killing $name (pid $pid) with SIGTERM"
64
+ kill -TERM $pid
65
+ # Wait for it to exit.
66
+ for i in 1 2 3 4 5 ; do
67
+ echo "Waiting $name (pid $pid) to die..."
68
+ status || break
69
+ sleep 1
70
+ done
71
+ if status ; then
72
+ echo "$name stop failed; still running."
73
+ else
74
+ echo "$name stopped."
75
+ fi
76
+ fi
77
+ }
78
+
79
+ status() {
80
+ if [ -f "$pidfile" ] ; then
81
+ pid=`cat "$pidfile"`
82
+ if kill -0 $pid > /dev/null 2> /dev/null ; then
83
+ # process by this pid is running.
84
+ # It may not be our pid, but that's what you get with just pidfiles.
85
+ # TODO(sissel): Check if this process seems to be the same as the one we
86
+ # expect. It'd be nice to use flock here, but flock uses fork, not exec,
87
+ # so it makes it quite awkward to use in this case.
88
+ return 0
89
+ else
90
+ return 2 # program is dead but pid file exists
91
+ fi
92
+ else
93
+ return 3 # program is not running
94
+ fi
95
+ }
96
+
97
+ force_stop() {
98
+ if status ; then
99
+ stop
100
+ status && kill -KILL `cat "$pidfile"`
101
+ fi
102
+ }
103
+
104
+ {{#prestart}}
105
+ prestart() {
106
+ {{{ prestart }}}
107
+
108
+ status=$?
109
+
110
+ if [ $status -gt 0 ] ; then
111
+ echo "Prestart command failed with code $status. If you wish to skip the prestart command, set PRESTART=no in your environment."
112
+ fi
113
+ return $status
114
+ }
115
+ {{/prestart}}
116
+
117
+ case "$1" in
118
+ start) status || start ;;
119
+ stop) stop ;;
120
+ force-stop) force_stop ;;
121
+ status)
122
+ status
123
+ code=$?
124
+ if [ $code -eq 0 ] ; then
125
+ echo "$name is running"
126
+ else
127
+ echo "$name is not running"
128
+ fi
129
+ exit $code
130
+ ;;
131
+ restart)
132
+ {{#prestart}}prestart || exit $?{{/prestart}}
133
+ stop && start
134
+ ;;
135
+ *)
136
+ echo "Usage: $SCRIPTNAME {start|stop|force-stop|status|restart}" >&2
137
+ exit 3
138
+ ;;
139
+ esac
140
+
141
+ exit $?