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
@@ -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