poise-service 1.0.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 (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