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
@@ -0,0 +1,37 @@
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 'chef/platform/provider_priority_map'
18
+
19
+ require 'poise_service/service_providers/dummy'
20
+ require 'poise_service/service_providers/systemd'
21
+ require 'poise_service/service_providers/sysvinit'
22
+ require 'poise_service/service_providers/upstart'
23
+
24
+
25
+ module PoiseService
26
+ # Inversion providers for the poise_service resource.
27
+ #
28
+ # @since 1.0.0
29
+ module ServiceProviders
30
+ # Set up priority maps
31
+ Chef::Platform::ProviderPriorityMap.instance.priority(:poise_service, [
32
+ PoiseService::ServiceProviders::Systemd,
33
+ PoiseService::ServiceProviders::Upstart,
34
+ PoiseService::ServiceProviders::Sysvinit,
35
+ ])
36
+ end
37
+ end
@@ -0,0 +1,193 @@
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 'chef/provider'
18
+ require 'poise'
19
+
20
+
21
+ module PoiseService
22
+ module ServiceProviders
23
+ class Base < Chef::Provider
24
+ include Poise(inversion: :poise_service)
25
+
26
+ # Extend the default lookup behavior to check for service_name too.
27
+ #
28
+ # @api private
29
+ def self.resolve_inversion_provider(node, resource)
30
+ attrs = resolve_inversion_attribute(node)
31
+ (attrs[resource.service_name] && attrs[resource.service_name]['provider']) || super
32
+ end
33
+
34
+ # Extend the default options to check for service_name too.
35
+ #
36
+ # @api private
37
+ def self.inversion_options(node, resource)
38
+ super.tap do |opts|
39
+ attrs = resolve_inversion_attribute(node)
40
+ opts.update(attrs[resource.service_name]) if attrs[resource.service_name]
41
+ run_state = Mash.new(node.run_state.fetch('poise_inversion', {}).fetch(inversion_resource, {}))[resource.service_name] || {}
42
+ opts.update(run_state['*']) if run_state['*']
43
+ opts.update(run_state[provides]) if run_state[provides]
44
+ end
45
+ end
46
+
47
+ # Cache the service hints to improve performance. This is called from the
48
+ # provides_auto? on most service providers and hits the filesystem a lot.
49
+ #
50
+ # @return [Array<Symbol>]
51
+ def self.service_resource_hints
52
+ @@service_resource_hints ||= Chef::Platform::ServiceHelpers.service_resource_providers
53
+ end
54
+
55
+ def action_enable
56
+ include_recipe(*Array(recipes)) if recipes
57
+ notifying_block do
58
+ create_service
59
+ end
60
+ enable_service
61
+ action_start
62
+ end
63
+
64
+ def action_disable
65
+ action_stop
66
+ disable_service
67
+ notifying_block do
68
+ destroy_service
69
+ end
70
+ end
71
+
72
+ def action_start
73
+ notify_if_service do
74
+ service_resource.run_action(:start)
75
+ end
76
+ end
77
+
78
+ def action_stop
79
+ notify_if_service do
80
+ service_resource.run_action(:stop)
81
+ end
82
+ end
83
+
84
+ def action_restart
85
+ return if options['never_restart']
86
+ notify_if_service do
87
+ service_resource.run_action(:restart)
88
+ end
89
+ end
90
+
91
+ def action_reload
92
+ return if options['never_reload']
93
+ notify_if_service do
94
+ service_resource.run_action(:reload)
95
+ end
96
+ end
97
+
98
+ def pid
99
+ raise NotImplementedError
100
+ end
101
+
102
+ private
103
+
104
+ # Recipes to include for this provider to work. Subclasses can override.
105
+ #
106
+ # @return [String, Array]
107
+ def recipes
108
+ end
109
+
110
+ # Subclass hook to create the required files et al for the service.
111
+ def create_service
112
+ raise NotImplementedError
113
+ end
114
+
115
+ # Subclass hook to remove the required files et al for the service.
116
+ def destroy_service
117
+ raise NotImplementedError
118
+ end
119
+
120
+ def enable_service
121
+ notify_if_service do
122
+ service_resource.run_action(:enable)
123
+ end
124
+ end
125
+
126
+ def disable_service
127
+ notify_if_service do
128
+ service_resource.run_action(:disable)
129
+ end
130
+ end
131
+
132
+ def notify_if_service(&block)
133
+ service_resource.updated_by_last_action(false)
134
+ block.call
135
+ new_resource.updated_by_last_action(true) if service_resource.updated_by_last_action?
136
+ end
137
+
138
+ # Subclass hook to create the resource used to delegate start, stop, and
139
+ # restart actions.
140
+ def service_resource
141
+ @service_resource ||= Chef::Resource::Service.new(new_resource.service_name, run_context).tap do |r|
142
+ r.enclosing_provider = self
143
+ r.source_line = new_resource.source_line
144
+ r.supports(status: true, restart: true, reload: true)
145
+ end
146
+ end
147
+
148
+ def service_template(path, default_source, &block)
149
+ # Sigh scoping.
150
+ template path do
151
+ owner 'root'
152
+ group 'root'
153
+ mode '644'
154
+ if options['template']
155
+ # If we have a template override, allow specifying a cookbook via
156
+ # "cookbook:template".
157
+ parts = options['template'].split(/:/, 2)
158
+ if parts.length == 2
159
+ source parts[1]
160
+ cookbook parts[0]
161
+ else
162
+ source parts.first
163
+ cookbook new_resource.cookbook_name.to_s
164
+ end
165
+ else
166
+ source default_source
167
+ cookbook 'poise-service'
168
+ end
169
+ variables(
170
+ command: options['command'] || new_resource.command,
171
+ directory: options['directory'] || new_resource.directory,
172
+ environment: options['environment'] || new_resource.environment,
173
+ name: new_resource.service_name,
174
+ new_resource: new_resource,
175
+ options: options,
176
+ reload_signal: options['reload_signal'] || new_resource.reload_signal,
177
+ stop_signal: options['stop_signal'] || new_resource.stop_signal,
178
+ user: options['user'] || new_resource.user,
179
+ )
180
+ # Don't trigger a restart if the template doesn't already exist, this
181
+ # prevents restarting on the run that first creates the service.
182
+ restart_on_update = options.fetch('restart_on_update', new_resource.restart_on_update)
183
+ if restart_on_update && ::File.exists?(path)
184
+ mode = restart_on_update.to_s == 'immediately' ? :immediately : :delayed
185
+ notifies :restart, new_resource, mode
186
+ end
187
+ instance_exec(&block) if block
188
+ end
189
+ end
190
+
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,100 @@
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/service_providers/base'
18
+
19
+
20
+ module PoiseService
21
+ module ServiceProviders
22
+ class Dummy < Base
23
+ provides(:dummy)
24
+
25
+ def action_start
26
+ return if pid
27
+ if Process.fork
28
+ # Parent, wait for the final child to write the pid file.
29
+ until pid
30
+ sleep(1)
31
+ end
32
+ else
33
+ # :nocov:
34
+ # First child, daemonize and go to town.
35
+ Process.daemon(true)
36
+ # Daemonized, set up process environment.
37
+ Dir.chdir(new_resource.directory)
38
+ new_resource.environment.each do |key, val|
39
+ ENV[key.to_s] = val.to_s
40
+ end
41
+ Process.uid = new_resource.user
42
+ IO.write(pid_file, Process.pid)
43
+ Kernel.exec(new_resource.command)
44
+ # :nocov:
45
+ end
46
+ end
47
+
48
+ def action_stop
49
+ return unless pid
50
+ Process.kill(new_resource.stop_signal, pid)
51
+ ::File.unlink(pid_file)
52
+ end
53
+
54
+ def action_restart
55
+ action_stop
56
+ action_start
57
+ end
58
+
59
+ def action_reload
60
+ Process.kill(new_resource.reload_signal, pid)
61
+ end
62
+
63
+ def pid
64
+ return nil unless ::File.exists?(pid_file)
65
+ pid = IO.read(pid_file).to_i
66
+ begin
67
+ # Check if the PID is running.
68
+ Process.kill(0, pid)
69
+ pid
70
+ rescue Errno::ESRCH
71
+ nil
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def service_resource
78
+ # Intentionally not implemented.
79
+ raise NotImplementedError
80
+ end
81
+
82
+ def enable_service
83
+ end
84
+
85
+ def create_service
86
+ end
87
+
88
+ def disable_service
89
+ end
90
+
91
+ def destroy_service
92
+ end
93
+
94
+ def pid_file
95
+ "/var/run/#{new_resource.service_name}.pid"
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,63 @@
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 'chef/mixin/shell_out'
18
+
19
+ require 'poise_service/service_providers/base'
20
+
21
+
22
+ module PoiseService
23
+ module ServiceProviders
24
+ class Systemd < Base
25
+ include Chef::Mixin::ShellOut
26
+ provides(:systemd)
27
+
28
+ def self.provides_auto?(node, resource)
29
+ # Don't allow systemd under docker, it won't work in most cases.
30
+ return false if node['virtualization'] && %w{docker lxc}.include?(node['virtualization']['system'])
31
+ service_resource_hints.include?(:systemd)
32
+ end
33
+
34
+ def pid
35
+ cmd = shell_out(%w{systemctl status} + [new_resource.service_name])
36
+ if !cmd.error? && cmd.stdout.include?('Active: active (running)') && md = cmd.stdout.match(/Main PID: (\d+)/)
37
+ md[1].to_i
38
+ else
39
+ nil
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def service_resource
46
+ super.tap do |r|
47
+ r.provider(Chef::Provider::Service::Systemd)
48
+ end
49
+ end
50
+
51
+ def create_service
52
+ service_template("/etc/systemd/system/#{new_resource.service_name}.service", 'systemd.service.erb')
53
+ end
54
+
55
+ def destroy_service
56
+ file "/etc/systemd/system/#{new_resource.service_name}.service" do
57
+ action :delete
58
+ end
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,86 @@
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/service_providers/base'
18
+
19
+
20
+ module PoiseService
21
+ module ServiceProviders
22
+ class Sysvinit < Base
23
+ provides(:sysvinit)
24
+
25
+ def self.provides_auto?(node, resource)
26
+ [:debian, :redhat, :invokercd].any? {|name| service_resource_hints.include?(name) }
27
+ end
28
+
29
+ def pid
30
+ IO.read(pid_file).to_i if ::File.exists?(pid_file)
31
+ end
32
+
33
+ private
34
+
35
+ def service_resource
36
+ super.tap do |r|
37
+ r.provider(case node['platform_family']
38
+ when 'debian'
39
+ Chef::Provider::Service::Init::Debian
40
+ when 'rhel'
41
+ Chef::Provider::Service::Init::Redhat
42
+ else
43
+ # This will explode later in the template, but better than nothing for later.
44
+ Chef::Provider::Service::Init
45
+ end)
46
+ end
47
+ end
48
+
49
+ def create_service
50
+ # Split the command into the binary and its arguments. This is for
51
+ # start-stop-daemon since it treats those differently.
52
+ parts = new_resource.command.split(/ /, 2)
53
+ daemon = ENV['PATH'].split(/:/)
54
+ .map {|path| ::File.absolute_path(parts[0], path) }
55
+ .find {|path| ::File.exist?(path) } || parts[0]
56
+ # Sigh scoping.
57
+ pid_file_ = pid_file
58
+ # Render the service template
59
+ service_template("/etc/init.d/#{new_resource.service_name}", 'sysvinit.sh.erb') do
60
+ mode '755'
61
+ variables.update(
62
+ daemon: daemon,
63
+ daemon_options: parts[1].to_s,
64
+ pid_file: pid_file_,
65
+ pid_file_external: !!options['pid_file'],
66
+ platform_family: node['platform_family'],
67
+ )
68
+ end
69
+ end
70
+
71
+ def destroy_service
72
+ file "/etc/init.d/#{new_resource.service_name}" do
73
+ action :delete
74
+ end
75
+
76
+ file pid_file do
77
+ action :delete
78
+ end
79
+ end
80
+
81
+ def pid_file
82
+ options['pid_file'] || "/var/run/#{new_resource.service_name}.pid"
83
+ end
84
+ end
85
+ end
86
+ end