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