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,98 @@
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 'spec_helper'
18
+
19
+ describe PoiseService::ServiceProviders::Systemd do
20
+ service_provider('systemd')
21
+ step_into(:poise_service)
22
+ recipe do
23
+ poise_service 'test' do
24
+ command 'myapp --serve'
25
+ end
26
+ end
27
+
28
+ it { is_expected.to render_file('/etc/systemd/system/test.service').with_content(<<-EOH) }
29
+ [Unit]
30
+ Description=test
31
+
32
+ [Service]
33
+ Environment=
34
+ ExecStart=myapp --serve
35
+ ExecReload=/bin/kill -HUP $MAINPID
36
+ KillSignal=TERM
37
+ User=root
38
+ WorkingDirectory=/
39
+
40
+ [Install]
41
+ WantedBy=multi-user.target
42
+ EOH
43
+
44
+ context 'with action :disable' do
45
+ recipe do
46
+ poise_service 'test' do
47
+ action :disable
48
+ end
49
+ end
50
+
51
+ it { is_expected.to delete_file('/etc/systemd/system/test.service') }
52
+ end # /context with action :disable
53
+
54
+ describe '#pid' do
55
+ context 'service is running' do
56
+ before do
57
+ fake_cmd = double('shellout', error?: false, live_stream: true, run_command: nil, stdout: <<-EOH)
58
+ test.service - test
59
+ Loaded: loaded (/etc/systemd/system/test.service; enabled)
60
+ Active: active (running) since Tue 2015-03-24 08:16:12 UTC; 15h ago
61
+ Main PID: 10029 (myapp)
62
+ CGroup: /system.slice/test.service
63
+ └─10029 /opt/chef/embedded/bin/ruby /usr/bin/myapp --serve
64
+
65
+ Mar 24 08:16:12 hostname systemd[1]: Started test.
66
+ EOH
67
+ expect(Mixlib::ShellOut).to receive(:new).with(%w{systemctl status test}, kind_of(Hash)).and_return(fake_cmd)
68
+ end
69
+ subject { described_class.new(double(service_name: 'test'), nil) }
70
+ its(:pid) { is_expected.to eq 10029 }
71
+ end # context service is running
72
+
73
+ context 'service is stopped' do
74
+ before do
75
+ fake_cmd = double('shellout', error?: false, live_stream: true, run_command: nil, stdout: <<-EOH)
76
+ test.service - test
77
+ Loaded: loaded (/etc/systemd/system/test.service; enabled)
78
+ Active: inactive (dead) since Tue 2015-03-24 23:23:56 UTC; 4s ago
79
+ Main PID: 10029 (code=killed, signal=TERM)
80
+
81
+ Mar 24 23:23:56 hostname systemd[1]: Stopping test...
82
+ EOH
83
+ expect(Mixlib::ShellOut).to receive(:new).with(%w{systemctl status test}, kind_of(Hash)).and_return(fake_cmd)
84
+ end
85
+ subject { described_class.new(double(service_name: 'test'), nil) }
86
+ its(:pid) { is_expected.to be_nil }
87
+ end # context service is stopped
88
+
89
+ context 'systemctl errors' do
90
+ before do
91
+ fake_cmd = double('shellout', error?: true, live_stream: true, run_command: nil)
92
+ expect(Mixlib::ShellOut).to receive(:new).with(%w{systemctl status test}, kind_of(Hash)).and_return(fake_cmd)
93
+ end
94
+ subject { described_class.new(double(service_name: 'test'), nil) }
95
+ its(:pid) { is_expected.to be_nil }
96
+ end # context systemctl errors
97
+ end # /describe #pid
98
+ end
@@ -0,0 +1,96 @@
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 'spec_helper'
18
+
19
+ describe PoiseService::ServiceProviders::Sysvinit do
20
+ service_provider('sysvinit')
21
+ step_into(:poise_service)
22
+ recipe do
23
+ poise_service 'test' do
24
+ command 'myapp --serve'
25
+ end
26
+ end
27
+
28
+ context 'on Ubuntu' do
29
+ let(:chefspec_options) { { platform: 'ubuntu', version: '14.04'} }
30
+
31
+ it { is_expected.to render_file('/etc/init.d/test').with_content(<<-'EOH') }
32
+ start-stop-daemon --start --quiet --background \
33
+ --pidfile "/var/run/test.pid" --make-pidfile \
34
+ --chuid "root" --chdir "/" \
35
+ --exec "myapp" -- --serve
36
+ EOH
37
+
38
+ context 'with an external PID file' do
39
+ before do
40
+ override_attributes['poise-service']['test'] ||= {}
41
+ override_attributes['poise-service']['test']['pid_file'] = '/tmp/pid'
42
+ end
43
+
44
+ it { is_expected.to render_file('/etc/init.d/test').with_content(<<-'EOH') }
45
+ start-stop-daemon --start --quiet --background \
46
+ --pidfile "/tmp/pid" \
47
+ --chuid "root" --chdir "/" \
48
+ --exec "myapp" -- --serve
49
+ EOH
50
+ end # /context with an external PID file
51
+ end # /context on Ubuntu
52
+
53
+ context 'on CentOS' do
54
+ let(:chefspec_options) { { platform: 'centos', version: '7.0'} }
55
+
56
+ it { is_expected.to render_file('/etc/init.d/test').with_content(<<-EOH) }
57
+ ( cd "/" && daemon --user "root" --pidfile "/var/run/test.pid" "myapp --serve" >/dev/null 2>&1 ) &
58
+ sleep 1 # Give it some time to start before checking for a pid
59
+ _pid "myapp" > "/var/run/test.pid" || return 3
60
+ EOH
61
+
62
+ context 'with an external PID file' do
63
+ before do
64
+ override_attributes['poise-service']['test'] ||= {}
65
+ override_attributes['poise-service']['test']['pid_file'] = '/tmp/pid'
66
+ end
67
+
68
+ it { is_expected.to render_file('/etc/init.d/test').with_content(<<-EOH) }
69
+ ( cd "/" && daemon --user "root" --pidfile "/tmp/pid" "myapp --serve" >/dev/null 2>&1 ) &
70
+ EOH
71
+ end # /context with an external PID file
72
+ end # /context on CentOS
73
+
74
+ context 'with action :disable' do
75
+ recipe do
76
+ poise_service 'test' do
77
+ action :disable
78
+ end
79
+ end
80
+
81
+ it { is_expected.to delete_file('/etc/init.d/test') }
82
+ it { is_expected.to delete_file('/var/run/test.pid') }
83
+ end # /context with action :disable
84
+
85
+ describe '#pid' do
86
+ subject { described_class.new(nil, nil) }
87
+ before do
88
+ allow(File).to receive(:exists?).and_call_original
89
+ allow(File).to receive(:exists?).with('/pid').and_return(true)
90
+ allow(IO).to receive(:read).and_call_original
91
+ allow(IO).to receive(:read).with('/pid').and_return('100')
92
+ expect(subject).to receive(:pid_file).and_return('/pid').at_least(:once)
93
+ end
94
+ its(:pid) { is_expected.to eq 100 }
95
+ end # /describe #pid
96
+ end
@@ -0,0 +1,300 @@
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 'spec_helper'
18
+
19
+ describe PoiseService::ServiceProviders::Upstart do
20
+ describe '#action_enable' do
21
+ service_provider('upstart')
22
+ step_into(:poise_service)
23
+ let(:upstart_version) { '0' }
24
+ before do
25
+ allow_any_instance_of(described_class).to receive(:upstart_version).and_return(upstart_version)
26
+ end
27
+ recipe do
28
+ poise_service 'test' do
29
+ command 'myapp --serve'
30
+ end
31
+ end
32
+
33
+ context 'with upstart 0.6.5' do
34
+ let(:upstart_version) { '0.6.5' }
35
+ it { is_expected.to render_file('/etc/init/test.conf').with_content(<<-EOH) }
36
+ # test generated by poise-service for poise_service[test]
37
+
38
+ description "test"
39
+
40
+ start on runlevel [2345]
41
+ stop on runlevel [!2345]
42
+
43
+ respawn
44
+ respawn limit 10 5
45
+ umask 022
46
+ chdir /
47
+
48
+ exec /opt/chef/embedded/bin/ruby -e 'Process.uid = "root"; ENV["HOME"] = Dir.home("root") rescue nil; exec(*["myapp", "--serve"])'
49
+ EOH
50
+
51
+ context 'with a stop signal' do
52
+ recipe do
53
+ poise_service 'test' do
54
+ command 'myapp --serve'
55
+ stop_signal 'KILL'
56
+ end
57
+ end
58
+
59
+ it { is_expected.to render_file('/etc/init/test.conf').with_content(<<-EOH) }
60
+ # test generated by poise-service for poise_service[test]
61
+
62
+ description "test"
63
+
64
+ start on runlevel [2345]
65
+ stop on runlevel [!2345]
66
+
67
+ respawn
68
+ respawn limit 10 5
69
+ umask 022
70
+ chdir /
71
+
72
+ exec /opt/chef/embedded/bin/ruby -e 'Process.uid = "root"; ENV["HOME"] = Dir.home("root") rescue nil; exec(*["myapp", "--serve"])'
73
+ pre-stop script
74
+ PID=`initctl status test | sed 's/^.*process \\([0-9]*\\)$/\\1/'`
75
+ if [ -n "$PID" ]; then
76
+ kill -KILL "$PID"
77
+ fi
78
+ end script
79
+ EOH
80
+ end # /context with a stop signal
81
+ end # /context with upstart 0.6.5
82
+
83
+ context 'with upstart 1.5' do
84
+ let(:upstart_version) { '1.5' }
85
+ it { is_expected.to render_file('/etc/init/test.conf').with_content(<<-EOH) }
86
+ # test generated by poise-service for poise_service[test]
87
+
88
+ description "test"
89
+
90
+ start on runlevel [2345]
91
+ stop on runlevel [!2345]
92
+
93
+ respawn
94
+ respawn limit 10 5
95
+ umask 022
96
+ chdir /
97
+ setuid root
98
+ kill signal TERM
99
+
100
+ exec myapp --serve
101
+ EOH
102
+
103
+ context 'with a reload signal' do
104
+ recipe do
105
+ poise_service 'test' do
106
+ command 'myapp --serve'
107
+ reload_signal 'USR1'
108
+ end
109
+ end
110
+
111
+ it { expect { subject }.to raise_error PoiseService::Error }
112
+ end # /context with a reload signal
113
+ end # /context with upstart 1.5
114
+
115
+ context 'with upstart 1.12.1' do
116
+ let(:upstart_version) { '1.12.1' }
117
+ it { is_expected.to render_file('/etc/init/test.conf').with_content(<<-EOH) }
118
+ # test generated by poise-service for poise_service[test]
119
+
120
+ description "test"
121
+
122
+ start on runlevel [2345]
123
+ stop on runlevel [!2345]
124
+
125
+ respawn
126
+ respawn limit 10 5
127
+ umask 022
128
+ chdir /
129
+ setuid root
130
+ kill signal TERM
131
+ reload signal HUP
132
+
133
+ exec myapp --serve
134
+ EOH
135
+ end # /context with upstart 1.12.1
136
+ end # /describe #action_enable
137
+
138
+ describe '#action_disable' do
139
+ service_provider('upstart')
140
+ step_into(:poise_service)
141
+ recipe do
142
+ poise_service 'test' do
143
+ action :disable
144
+ end
145
+ end
146
+
147
+ it { is_expected.to delete_file('/etc/init/test.conf') }
148
+ end # /describe #action_disable
149
+
150
+ describe '#action_reload' do
151
+ service_provider('upstart')
152
+ step_into(:poise_service)
153
+ let(:upstart_version) { '0' }
154
+ before do
155
+ allow_any_instance_of(described_class).to receive(:upstart_version).and_return(upstart_version)
156
+ end
157
+
158
+ context 'with upstart 1.5' do
159
+ let(:upstart_version) { '1.5' }
160
+
161
+ context 'with a reload signal' do
162
+ recipe do
163
+ poise_service 'test' do
164
+ action :reload
165
+ command 'myapp --serve'
166
+ reload_signal 'USR1'
167
+ end
168
+ end
169
+
170
+ it { expect { subject }.to raise_error PoiseService::Error }
171
+ end # /context with a reload signal
172
+
173
+ context 'with a reload signal and reload_shim:true' do
174
+ recipe do
175
+ poise_service 'test' do
176
+ action :reload
177
+ command 'myapp --serve'
178
+ reload_signal 'USR1'
179
+ options :upstart, reload_shim: true
180
+ end
181
+ end
182
+
183
+ it do
184
+ expect_any_instance_of(described_class).to receive(:pid).and_return(123)
185
+ expect(Process).to receive(:kill).with('USR1', 123)
186
+ run_chef
187
+ end
188
+ end # /context with a reload signal and reload_shim:true
189
+ end # /context with upstart 1.5
190
+
191
+ context 'with upstart 1.12.1' do
192
+ let(:upstart_version) { '1.12.1' }
193
+
194
+ context 'with a reload signal' do
195
+ recipe do
196
+ poise_service 'test' do
197
+ action :reload
198
+ command 'myapp --serve'
199
+ reload_signal 'USR1'
200
+ end
201
+ end
202
+
203
+ it do
204
+ fake_service = double('service_resource', updated_by_last_action: nil, updated_by_last_action?: false)
205
+ expect(fake_service).to receive(:run_action).with(:reload)
206
+ expect_any_instance_of(described_class).to receive(:service_resource).at_least(:once).and_return(fake_service)
207
+ run_chef
208
+ end
209
+ end # /context with a reload signal
210
+ end # /context with upstart 1.12.1
211
+ end # /describe #action_reload
212
+
213
+ describe '#upstart_version' do
214
+ subject { described_class.new(nil, nil).send(:upstart_version) }
215
+ context 'with version 1.12.1' do
216
+ before do
217
+ fake_cmd = double('shellout', error?: false, live_stream: true, run_command: true, stdout: <<-EOH)
218
+ init (upstart 1.12.1)
219
+ Copyright (C) 2006-2014 Canonical Ltd., 2011 Scott James Remnant
220
+
221
+ This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
222
+ EOH
223
+ expect(Mixlib::ShellOut).to receive(:new).with(%w{initctl --version}, kind_of(Hash)).and_return(fake_cmd)
224
+ end
225
+ it { is_expected.to eq '1.12.1' }
226
+ end # /context with version 1.12.1
227
+
228
+ context 'with an error' do
229
+ before do
230
+ fake_cmd = double('shellout', error?: true, live_stream: true, run_command: true)
231
+ expect(Mixlib::ShellOut).to receive(:new).with(%w{initctl --version}, kind_of(Hash)).and_return(fake_cmd)
232
+ end
233
+ it { is_expected.to eq '0' }
234
+ end # /context with an error
235
+ end # /describe #upstart_version
236
+
237
+ describe '#upstart_features' do
238
+ subject do
239
+ provider = described_class.new(nil, nil)
240
+ expect(provider).to receive(:upstart_version).and_return(upstart_version)
241
+ provider.send(:upstart_features)
242
+ end
243
+
244
+ context 'with upstart 0' do
245
+ let(:upstart_version) { '0' }
246
+ it { is_expected.to eq({kill_signal: false, reload_signal: false, setuid: false}) }
247
+ end # /context with upstart 0
248
+
249
+ # RHEL 6
250
+ context 'with upstart 0.6.5' do
251
+ let(:upstart_version) { '0.6.5' }
252
+ it { is_expected.to eq({kill_signal: false, reload_signal: false, setuid: false}) }
253
+ end # /context with upstart 0.6.5
254
+
255
+ # Ubuntu 12.04
256
+ context 'with upstart 1.5' do
257
+ let(:upstart_version) { '1.5' }
258
+ it { is_expected.to eq({kill_signal: true, reload_signal: false, setuid: true}) }
259
+ end # /context with upstart 1.5
260
+
261
+ # Ubuntu 14.04
262
+ context 'with upstart 1.12.1' do
263
+ let(:upstart_version) { '1.12.1' }
264
+ it { is_expected.to eq({kill_signal: true, reload_signal: true, setuid: true}) }
265
+ end # /context with upstart 1.12.1
266
+ end # /describe #upstart_features
267
+
268
+ describe '#pid' do
269
+ context 'service is running' do
270
+ before do
271
+ fake_cmd = double('shellout', error?: false, live_stream: true, run_command: nil, stdout: <<-EOH)
272
+ test start/running, process 2132
273
+ EOH
274
+ expect(Mixlib::ShellOut).to receive(:new).with(%w{initctl status test}, kind_of(Hash)).and_return(fake_cmd)
275
+ end
276
+ subject { described_class.new(double(service_name: 'test'), nil) }
277
+ its(:pid) { is_expected.to eq 2132 }
278
+ end # context service is running
279
+
280
+ context 'service is stopped' do
281
+ before do
282
+ fake_cmd = double('shellout', error?: false, live_stream: true, run_command: nil, stdout: <<-EOH)
283
+ test stop/waiting
284
+ EOH
285
+ expect(Mixlib::ShellOut).to receive(:new).with(%w{initctl status test}, kind_of(Hash)).and_return(fake_cmd)
286
+ end
287
+ subject { described_class.new(double(service_name: 'test'), nil) }
288
+ its(:pid) { is_expected.to be_nil }
289
+ end # context service is stopped
290
+
291
+ context 'initctl errors' do
292
+ before do
293
+ fake_cmd = double('shellout', error?: true, live_stream: true, run_command: nil)
294
+ expect(Mixlib::ShellOut).to receive(:new).with(%w{initctl status test}, kind_of(Hash)).and_return(fake_cmd)
295
+ end
296
+ subject { described_class.new(double(service_name: 'test'), nil) }
297
+ its(:pid) { is_expected.to be_nil }
298
+ end # context initctl errors
299
+ end # /describe #pid
300
+ end