poise-service 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.kitchen.travis.yml +4 -0
  4. data/.kitchen.yml +9 -0
  5. data/.travis.yml +23 -0
  6. data/.yardopts +6 -0
  7. data/Berksfile +27 -0
  8. data/Gemfile +33 -0
  9. data/LICENSE +201 -0
  10. data/README.md +381 -0
  11. data/Rakefile +17 -0
  12. data/chef/attributes/default.rb +19 -0
  13. data/chef/templates/systemd.service.erb +13 -0
  14. data/chef/templates/sysvinit.sh.erb +177 -0
  15. data/chef/templates/upstart.conf.erb +37 -0
  16. data/lib/poise_service.rb +25 -0
  17. data/lib/poise_service/cheftie.rb +18 -0
  18. data/lib/poise_service/error.rb +20 -0
  19. data/lib/poise_service/resources.rb +28 -0
  20. data/lib/poise_service/resources/poise_service.rb +161 -0
  21. data/lib/poise_service/resources/poise_service_test.rb +200 -0
  22. data/lib/poise_service/resources/poise_service_user.rb +136 -0
  23. data/lib/poise_service/service_mixin.rb +192 -0
  24. data/lib/poise_service/service_providers.rb +37 -0
  25. data/lib/poise_service/service_providers/base.rb +193 -0
  26. data/lib/poise_service/service_providers/dummy.rb +100 -0
  27. data/lib/poise_service/service_providers/systemd.rb +63 -0
  28. data/lib/poise_service/service_providers/sysvinit.rb +86 -0
  29. data/lib/poise_service/service_providers/upstart.rb +117 -0
  30. data/lib/poise_service/utils.rb +45 -0
  31. data/lib/poise_service/version.rb +19 -0
  32. data/poise-service.gemspec +41 -0
  33. data/test/cookbooks/poise-service_test/metadata.rb +18 -0
  34. data/test/cookbooks/poise-service_test/providers/mixin.rb +43 -0
  35. data/test/cookbooks/poise-service_test/recipes/default.rb +47 -0
  36. data/test/cookbooks/poise-service_test/recipes/mixin.rb +34 -0
  37. data/test/cookbooks/poise-service_test/resources/mixin.rb +26 -0
  38. data/test/gemfiles/chef-12.gemfile +19 -0
  39. data/test/gemfiles/master.gemfile +22 -0
  40. data/test/integration/default/serverspec/Gemfile +19 -0
  41. data/test/integration/default/serverspec/default_spec.rb +45 -0
  42. data/test/integration/default/serverspec/mixin_spec.rb +37 -0
  43. data/test/spec/resources/poise_service_spec.rb +235 -0
  44. data/test/spec/resources/poise_service_user_spec.rb +119 -0
  45. data/test/spec/service_mixin_spec.rb +164 -0
  46. data/test/spec/service_providers/base_spec.rb +231 -0
  47. data/test/spec/service_providers/dummy_spec.rb +91 -0
  48. data/test/spec/service_providers/systemd_spec.rb +98 -0
  49. data/test/spec/service_providers/sysvinit_spec.rb +96 -0
  50. data/test/spec/service_providers/upstart_spec.rb +300 -0
  51. data/test/spec/spec_helper.rb +50 -0
  52. data/test/spec/utils_spec.rb +44 -0
  53. metadata +172 -0
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'poise_boiler/rakefile'
@@ -0,0 +1,19 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ default['poise-service']['provider'] = 'auto'
18
+
19
+ default['poise-service']['options'] = {}
@@ -0,0 +1,13 @@
1
+ [Unit]
2
+ Description=<%= @name %>
3
+
4
+ [Service]
5
+ Environment=<%= @environment.map {|key, val| %Q{"#{key}=#{val}"} }.join(' ') %>
6
+ ExecStart=<%= @command %>
7
+ ExecReload=/bin/kill -<%= @reload_signal %> $MAINPID
8
+ KillSignal=<%= @stop_signal %>
9
+ User=<%= @user %>
10
+ WorkingDirectory=<%= @directory %>
11
+
12
+ [Install]
13
+ WantedBy=multi-user.target
@@ -0,0 +1,177 @@
1
+ #!/bin/sh
2
+ # Init script for <%= @name %> generated by poise-service
3
+ #
4
+ ### BEGIN INIT INFO
5
+ # Provides: <%= @name %>
6
+ # Required-Start: $remote_fs $syslog
7
+ # Required-Stop: $remote_fs $syslog
8
+ # Default-Start: 2 3 4 5
9
+ # Default-Stop: 0 1 6
10
+ # Short-Description: Init script for <%= @name %>
11
+ # Description: Init script for <%= @name %>
12
+ ### END INIT INFO
13
+
14
+ <%- @environment.each do |key, val| -%>
15
+ export <%= key %>="<%= val %>"
16
+ <%- end -%>
17
+
18
+ <%- if @platform_family == 'debian' -%>
19
+ . /lib/lsb/init-functions
20
+
21
+ _start() {
22
+ start-stop-daemon --start --quiet --background \
23
+ --pidfile "<%= @pid_file %>"<% unless @pid_file_external %> --make-pidfile<% end %> \
24
+ --chuid "<%= @user %>" --chdir "<%= @directory %>" \
25
+ --exec "<%= @daemon %>" -- <%= @daemon_options %>
26
+ }
27
+
28
+ _stop() {
29
+ start-stop-daemon --stop --quiet --pidfile "<%= @pid_file %>" --user "<%= @user %>" --signal "<%= @stop_signal %>"
30
+ }
31
+
32
+ _status() {
33
+ status_of_proc -p "<%= @pid_file %>" "<%= @daemon %>" "<%= @name %>"
34
+ }
35
+
36
+ _reload() {
37
+ start-stop-daemon --stop --quiet --pidfile "<%= @pid_file %>" --user "<%= @user %>" --signal "<%= @reload_signal %>"
38
+ }
39
+
40
+ <%- elsif @platform_family == 'rhel' -%>
41
+ . /etc/rc.d/init.d/functions
42
+
43
+ _pid () {
44
+ pidof -s -o $$ -o $PPID -o %PPID -x "$1" || pidof -s -o $$ -o $PPID -o %PPID -x "${1##*/}"
45
+ }
46
+
47
+ _start() {
48
+ ( cd "<%= @directory %>" && daemon --user "<%= @user %>" --pidfile "<%= @pid_file %>" "<%= @daemon %> <%= @daemon_options %>" >/dev/null 2>&1 ) &
49
+ <%- unless @pid_file_external -%>
50
+ sleep 1 # Give it some time to start before checking for a pid
51
+ _pid "<%= @daemon %>" > "<%= @pid_file %>" || return 3
52
+ <%- end -%>
53
+ return 0 # ¯\_(ツ)_/¯
54
+ }
55
+
56
+ _stop() {
57
+ if [ -r "<%= @pid_file %>" ]; then
58
+ kill -<%= @stop_signal%> "$(cat "<%= @pid_file %>")"
59
+ else
60
+ killproc "<%= @daemon %>" -<%= @stop_signal%> >/dev/null
61
+ fi
62
+ }
63
+
64
+ _status() {
65
+ status -p "<%= @pid_file %>" "<%= @daemon %>"
66
+ }
67
+
68
+ _reload() {
69
+ if [ -r "<%= @pid_file %>" ]; then
70
+ kill -<%= @reload_signal%> "$(cat "<%= @pid_file %>")"
71
+ else
72
+ return 1
73
+ fi
74
+ }
75
+
76
+ <%- # Some functions to match LSB because RHEL doesn't (╯°□°)╯︵ ┻━┻ -%>
77
+
78
+ log_daemon_msg() {
79
+ echo -n "$1"
80
+ }
81
+
82
+ log_progress_msg() {
83
+ echo -n "$1"
84
+ }
85
+
86
+ log_warning_msg() {
87
+ echo -n "$1"
88
+ }
89
+
90
+ log_failure_msg() {
91
+ echo -n "$1"
92
+ }
93
+
94
+ log_end_msg() {
95
+ if [ "$1" = 0 ]; then
96
+ success
97
+ else
98
+ failure
99
+ fi
100
+ }
101
+ <%- else -%>
102
+ <%- raise "Platform family #{@platform_family} is not supported" -%>
103
+ <%- end -%>
104
+
105
+ set -e
106
+
107
+ start() {
108
+ if _start
109
+ then
110
+ rc=0
111
+ sleep 1
112
+ if ! kill -0 "$(cat "<%= @pid_file %>")" >/dev/null 2>&1; then
113
+ log_failure_msg "<%= @name %> failed to start"
114
+ rc=1
115
+ fi
116
+ else
117
+ rc=1
118
+ fi
119
+ if [ "$rc" -eq 0 ]; then
120
+ log_end_msg 0
121
+ else
122
+ log_end_msg 1
123
+ rm -f "<%= @pid_file %>"
124
+ fi
125
+ }
126
+
127
+ export PATH="${PATH:+$PATH:}/usr/sbin:/sbin"
128
+
129
+ case "$1" in
130
+ start)
131
+ log_daemon_msg "Starting <%= @name %>"
132
+ if [ -s "<%= @pid_file %>" ] && kill -0 "$(cat "<%= @pid_file %>")" >/dev/null 2>&1; then
133
+ log_progress_msg "apparently already running"
134
+ log_end_msg 0
135
+ exit 0
136
+ fi
137
+ start
138
+ ;;
139
+
140
+ stop)
141
+ log_daemon_msg "Stopping <%= @name %>"
142
+ _stop
143
+ log_end_msg "$?"
144
+ rm -f "<%= @pid_file %>"
145
+ ;;
146
+
147
+ reload|force-reload)
148
+ log_daemon_msg "Reloading <%= @name %>"
149
+ _reload
150
+ log_end_msg "$?"
151
+ ;;
152
+
153
+ restart)
154
+ set +e
155
+ log_daemon_msg "Restarting <%= @name %>"
156
+ if [ -s "<%= @pid_file %>" ] && kill -0 "$(cat "<%= @pid_file %>")" >/dev/null 2>&1; then
157
+ _stop || true
158
+ sleep 1
159
+ else
160
+ log_warning_msg "<%= @name %> not running, attempting to start."
161
+ rm -f "<%= @pid_file %>"
162
+ fi
163
+ start
164
+ ;;
165
+
166
+ status)
167
+ set +e
168
+ _status
169
+ exit $?
170
+ ;;
171
+
172
+ *)
173
+ echo "Usage: /etc/init.d/<%= @name %> {start|stop|reload|force-reload|restart|status}"
174
+ exit 1
175
+ esac
176
+
177
+ exit 0
@@ -0,0 +1,37 @@
1
+ # <%= @name %> generated by poise-service for <%= @new_resource.to_s %>
2
+
3
+ description "<%= @name %>"
4
+
5
+ start on runlevel [2345]
6
+ stop on runlevel [!2345]
7
+
8
+ respawn
9
+ respawn limit 10 5
10
+ umask 022
11
+ chdir <%= @directory %>
12
+ <%- @environment.each do |key, val| -%>
13
+ env <%= key %>="<%= val %>"
14
+ <%- end -%>
15
+ <%- if @upstart_features[:setuid] -%>
16
+ setuid <%= @user %>
17
+ <%- end -%>
18
+ <%- if @upstart_features[:kill_signal] -%>
19
+ kill signal <%= @stop_signal %>
20
+ <%- end -%>
21
+ <%- if @upstart_features[:reload_signal] -%>
22
+ reload signal <%= @reload_signal %>
23
+ <%- end -%>
24
+
25
+ <%- if @upstart_features[:setuid] -%>
26
+ exec <%= @command %>
27
+ <%- else -%>
28
+ exec /opt/chef/embedded/bin/ruby -e 'Process.uid = "<%= @user %>"; ENV["HOME"] = Dir.home("<%= @user %>") rescue nil; exec(*<%= Shellwords.split(@command).inspect %>)'
29
+ <%- end -%>
30
+ <%- if !@upstart_features[:kill_signal] && @stop_signal != 'TERM' -%>
31
+ pre-stop script
32
+ PID=`initctl status <%= @name %> | sed 's/^.*process \([0-9]*\)$/\1/'`
33
+ if [ -n "$PID" ]; then
34
+ kill -<%= @stop_signal %> "$PID"
35
+ fi
36
+ end script
37
+ <%- end -%>
@@ -0,0 +1,25 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+
18
+ module PoiseService
19
+ autoload :Error, 'poise_service/error'
20
+ autoload :Resources, 'poise_service/resources'
21
+ autoload :ServiceMixin, 'poise_service/service_mixin'
22
+ autoload :ServiceProviders, 'poise_service/service_providers'
23
+ autoload :Utils, 'poise_service/utils'
24
+ autoload :VERSION, 'poise_service/version'
25
+ end
@@ -0,0 +1,18 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'poise_service/resources'
18
+ require 'poise_service/service_providers'
@@ -0,0 +1,20 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ module PoiseService
18
+ class Error < ::Exception
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'poise_service/resources/poise_service'
18
+ require 'poise_service/resources/poise_service_test'
19
+ require 'poise_service/resources/poise_service_user'
20
+
21
+
22
+ module PoiseService
23
+ # Chef resources and providers for poise-service.
24
+ #
25
+ # @since 1.0.0
26
+ module Resources
27
+ end
28
+ end
@@ -0,0 +1,161 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'etc'
18
+
19
+ require 'chef/resource'
20
+ require 'poise'
21
+
22
+ require 'poise_service/error'
23
+
24
+
25
+ module PoiseService
26
+ module Resources
27
+ # (see PoiseService::Resource)
28
+ module PoiseService
29
+ # `poise_service` resource. Provides a unified service interface with a
30
+ # dependency injection framework.
31
+ #
32
+ # @since 1.0.0
33
+ # @provides poise_service
34
+ # @action enable
35
+ # @action disable
36
+ # @action start
37
+ # @action stop
38
+ # @action restart
39
+ # @action reload
40
+ # @example
41
+ # poise_service 'myapp' do
42
+ # command 'myapp --serve'
43
+ # user 'myuser'
44
+ # directory '/home/myapp'
45
+ # end
46
+ class Resource < Chef::Resource
47
+ include Poise(inversion: true)
48
+ provides(:poise_service)
49
+ actions(:enable, :disable, :start, :stop, :restart, :reload)
50
+
51
+ # @!attribute service_name
52
+ # Name of the service to the underlying init system. Defaults to the name
53
+ # of the resource.
54
+ # @return [String]
55
+ attribute(:service_name, kind_of: String, name_attribute: true)
56
+ # @!attribute command
57
+ # Command to run inside the service. This command must remain in the
58
+ # foreground and not daemoinize itself.
59
+ # @return [String]
60
+ attribute(:command, kind_of: String, required: true)
61
+ # @!attribute user
62
+ # User to run the service as. See {UserResource} for an easy way to
63
+ # create service users. Defaults to root.
64
+ # @return [String]
65
+ attribute(:user, kind_of: String, default: 'root')
66
+ # @!attribute directory
67
+ # Working directory for the service. Defaults to the home directory of
68
+ # the configured user or / if not found.
69
+ # @return [String]
70
+ attribute(:directory, kind_of: String, default: lazy { default_directory })
71
+ # @!attribute environment
72
+ # Environment variables for the service.
73
+ # @return [Hash]
74
+ attribute(:environment, kind_of: Hash, default: {})
75
+ # @!attribute stop_signal
76
+ # Signal to use to stop the service. Some systems will fall back to
77
+ # KILL if this signal fails to stop the process. Defaults to TERM.
78
+ # @return [String, Symbol, Integer]
79
+ attribute(:stop_signal, kind_of: [String, Symbol, Integer], default: 'TERM')
80
+ # @!attribute reload_signal
81
+ # Signal to use to reload the service. Defaults to HUP.
82
+ # @return [String, Symbol, Integer]
83
+ attribute(:reload_signal, kind_of: [String, Symbol, Integer], default: 'HUP')
84
+ # @!attribute restart_on_update
85
+ # If true, the service will be restarted if the service definition or
86
+ # configuration changes. If 'immediately', the notification will happen
87
+ # in immediate mode.
88
+ # @return [Boolean, String]
89
+ attribute(:restart_on_update, equal_to: [true, false, 'immediately', :immediately], default: true)
90
+
91
+ # Resource DSL callback.
92
+ #
93
+ # @api private
94
+ def after_created
95
+ # Set signals to clean values.
96
+ stop_signal(clean_signal(stop_signal))
97
+ reload_signal(clean_signal(reload_signal))
98
+ end
99
+
100
+ # Return the PID of the main process for this service or nil if the service
101
+ # isn't running or the PID cannot be found.
102
+ #
103
+ # @return [Integer, nil]
104
+ # @example
105
+ # execute "kill -WINCH #{resources('poise_test[myapp]').pid}"
106
+ def pid
107
+ # :pid isn't a real action, but this should still work.
108
+ provider_for_action(:pid).pid
109
+ end
110
+
111
+ private
112
+
113
+ # Try to find the home diretory for the configured user. This will fail if
114
+ # nsswitch.conf was changed during this run such as with LDAP. Defaults to
115
+ # the system root directory.
116
+ #
117
+ # @see #directory
118
+ # @return [String]
119
+ def default_directory
120
+ # For root we always want the system root path.
121
+ unless user == 'root'
122
+ # Force a reload in case any users were created earlier in the run.
123
+ Etc.endpwent
124
+ home = begin
125
+ Dir.home(user)
126
+ rescue ArgumentError
127
+ nil
128
+ end
129
+ end
130
+ # Better than nothing
131
+ home || case node['platform_family']
132
+ when 'windows'
133
+ ENV.fetch('SystemRoot', 'C:\\')
134
+ else
135
+ '/'
136
+ end
137
+ end
138
+
139
+ # Clean up a signal string/integer. Ints are mapped to the signal name,
140
+ # and strings are reformatted to upper case and without the SIG.
141
+ #
142
+ # @see #stop_signal
143
+ # @param signal [String, Symbol, Integer] Signal value to clean.
144
+ # @return [String]
145
+ def clean_signal(signal)
146
+ if signal.is_a?(Integer)
147
+ raise Error.new("Unknown signal #{signal}") unless (0..31).include?(signal)
148
+ Signal.signame(signal)
149
+ else
150
+ short_sig = signal.to_s.upcase
151
+ short_sig = short_sig[3..-1] if short_sig.start_with?('SIG')
152
+ raise Error.new("Unknown signal #{signal}") unless Signal.list.include?(short_sig)
153
+ short_sig
154
+ end
155
+ end
156
+
157
+ # Provides can be found under service_providers/.
158
+ end
159
+ end
160
+ end
161
+ end