php_fpm_docker 0.0.1

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.
@@ -0,0 +1,5 @@
1
+ # coding: utf-8
2
+ # VERSION SPEC
3
+ module PhpFpmDocker
4
+ VERSION = '0.0.1'
5
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'php_fpm_docker/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'php_fpm_docker'
8
+ spec.version = PhpFpmDocker::VERSION
9
+ spec.authors = ['Christian Simon']
10
+ spec.email = ['simon@swine.de']
11
+ spec.description = 'Use docker containers for PHP from FPM config'
12
+ spec.summary = 'Use docker containers for PHP from FPM config'
13
+ spec.homepage = 'https://github.com/simonswine/php_fpm_docker'
14
+ spec.license = 'GPLv3'
15
+
16
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(/^(test|spec|features)\//)
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'docker-api'
22
+ spec.add_dependency 'inifile'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.3'
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'rubocop'
27
+ spec.add_development_dependency 'rspec'
28
+ spec.add_development_dependency 'coveralls'
29
+
30
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,19 @@
1
+ module Helper
2
+ def inst_set(var, value)
3
+ if value.nil?
4
+ value=nil
5
+ else
6
+ value=Marshal.load( Marshal.dump(value))
7
+ end
8
+ a_i.instance_variable_set(var, value)
9
+ end
10
+
11
+ def inst_get(var)
12
+ a_i.instance_variable_get(var)
13
+ end
14
+
15
+ def method(*args)
16
+ func = self.class.description[1..-1].to_sym
17
+ a_i.instance_exec(func) {|f| send(f,*args)}
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ require 'coveralls'
2
+ require 'helper'
3
+
4
+ Coveralls.wear!
5
+
6
+ # Set load path for this module
7
+ dir = File.expand_path(File.join(File.dirname(__FILE__),'..'))
8
+ $LOAD_PATH.unshift File.join(dir, 'lib')
9
+
10
+ RSpec.configure do |config|
11
+ config.include Helper
12
+
13
+ config.expect_with :rspec do |expectations|
14
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
15
+ end
16
+
17
+ config.mock_with :rspec do |mocks|
18
+ mocks.verify_partial_doubles = true
19
+ end
20
+
21
+ config.order = :random
22
+ end
@@ -0,0 +1,197 @@
1
+ require 'spec_helper'
2
+ require 'php_fpm_docker/application'
3
+
4
+ describe PhpFpmDocker::Application do
5
+ before(:example) {
6
+ @dbl_logger_instance = double
7
+ @dbl_logger = class_double('Logger').as_stubbed_const()
8
+ allow(@dbl_logger).to receive(:new).and_return(@dbl_logger_instance)
9
+
10
+ @dbl_fileutils = class_double('FileUtils').as_stubbed_const()
11
+ allow(@dbl_fileutils).to receive(:mkdir_p)
12
+ }
13
+
14
+ let (:a_i){
15
+ described_class.new
16
+ }
17
+ let (:a_c){
18
+ described_class
19
+ }
20
+ let (:pid_default) {
21
+ 1234
22
+ }
23
+
24
+ describe '#initialize' do
25
+ it "not raise error" do
26
+ expect{a_i}.not_to raise_error
27
+ end
28
+ end
29
+ describe '#help' do
30
+ let (:method) {
31
+ a_i.instance_eval{ help }
32
+ }
33
+ it "return help" do
34
+ expect(a_i).to receive(:allowed_methods).and_return([:valid])
35
+ expect{method}.to output(/valid/).to_stderr
36
+ end
37
+ end
38
+ describe '#run' do
39
+ let (:method) {
40
+ a_i.instance_eval{ run }
41
+ }
42
+ before (:example) do
43
+ a_i.instance_variable_set(:@php_name,'php_name')
44
+ @argv = []
45
+ stub_const('ARGV', @argv)
46
+ end
47
+ it "correct arguments" do
48
+ expect(a_i).to receive(:parse_arguments).with(@argv).and_return(:valid)
49
+ expect(a_i).to receive(:send).with(:valid).and_return(123)
50
+ expect(a_i).to receive(:exit).with(123)
51
+ expect{method}.not_to raise_error
52
+ end
53
+ it "incorrect arguments" do
54
+ allow(@dbl_logger_instance).to receive(:warn).with('php_name')
55
+ expect(a_i).to receive(:parse_arguments).with(@argv).and_raise(RuntimeError, 'wrong')
56
+ expect(a_i).to receive(:help)
57
+ expect(a_i).to receive(:exit).with(3)
58
+ expect{method}.not_to raise_error
59
+ end
60
+ end
61
+ describe '#parse_arguments' do
62
+ let (:method) {
63
+ a_i.instance_exec(@args) {|x| parse_arguments(x)}
64
+ }
65
+ let (:valid_args) {
66
+ a_i.instance_variable_set(:@php_name,'php_name')
67
+ expect(a_i).to receive(:allowed_methods).and_return([:valid])
68
+ }
69
+ it 'fails without arguments' do
70
+ @args = []
71
+ expect{method}.to raise_error(/no argument/)
72
+ end
73
+ it "args=['install']" do
74
+ @args = ['install']
75
+ expect(method).to eq(:install)
76
+ end
77
+ it "args=['name','valid']" do
78
+ valid_args
79
+ @args = ['name','valid']
80
+ expect(@dbl_logger_instance).to receive(:info)
81
+ expect(method).to eq(:valid)
82
+ expect(a_i.instance_variable_get(:@php_name)).to eq('name')
83
+ end
84
+ it "args=['name','invalid']" do
85
+ valid_args
86
+ @args = ['name','invalid']
87
+ expect{method}.to raise_error(/unknown method/)
88
+ end
89
+ it "args=['name']" do
90
+ @args = ['name']
91
+ expect{method}.to raise_error(/wrong argument count/)
92
+ end
93
+ end
94
+ describe '#allowed_methods' do
95
+ let (:method) {
96
+ a_i.instance_eval{ allowed_methods }
97
+ }
98
+ [:start, :stop, :reload, :restart, :status].each do |cmd|
99
+ it "have command #{cmd}" do
100
+ expect(method).to include(cmd)
101
+ end
102
+ end
103
+ [:run, :install].each do |cmd|
104
+ it "have no command #{cmd}" do
105
+ expect(method).not_to include(cmd)
106
+ end
107
+ end
108
+ end
109
+ describe '#pid' do
110
+ let (:method) {
111
+ a_i.instance_eval{ pid }
112
+ }
113
+ let (:file) {
114
+ Tempfile.new('foo')
115
+ }
116
+ it 'return pid from file if exists and int' do
117
+ file.write(pid_default.to_s)
118
+ file.flush
119
+ allow(a_i).to receive(:pid_file).and_return(file.path)
120
+ expect(method).to eq(pid_default)
121
+ end
122
+ it 'return nil without pid file' do
123
+ allow(a_i).to receive(:pid_file).and_return('/tmp/not/existing')
124
+ expect(method).to eq(nil)
125
+ end
126
+ it 'return nil pid from file if exists and no int' do
127
+ file.write("fuckyeah!")
128
+ file.flush
129
+ allow(a_i).to receive(:pid_file).and_return(file.path)
130
+ expect(method).to eq(nil)
131
+ end
132
+ end
133
+ describe '#pid=' do
134
+ before(:example) do
135
+ @file=Tempfile.new('foo')
136
+ @file.write(1234)
137
+ @file.flush
138
+ end
139
+ context 'argument pid is 456' do
140
+ let (:method) {
141
+ a_i.instance_exec(nil) {|x| self.pid=456 }
142
+ }
143
+ after(:example) do
144
+ allow(a_i).to receive(:pid_file).and_return(@file.path)
145
+ method
146
+ expect(open(@file.path).read.strip.to_i).to eq(456)
147
+ end
148
+ it 'pid file exists before' do
149
+ end
150
+ it 'pid file not existing' do
151
+ File.unlink @file.path
152
+ end
153
+ end
154
+ context 'argument pid is nil' do
155
+ let (:method) {
156
+ a_i.instance_exec(nil) {|x| self.pid=x }
157
+ }
158
+ it 'pid file will removed if exists' do
159
+ allow(a_i).to receive(:pid_file).and_return(@file.path)
160
+ method
161
+ expect(File.exist?(@file.path)).to eq(false)
162
+ end
163
+ it 'pid file not existing' do
164
+ path = @file.path
165
+ @file.delete
166
+ expect(@dbl_logger_instance).to receive(:debug).with(/No pid file found/)
167
+ allow(a_i).to receive(:pid_file).and_return(path)
168
+ method
169
+ end
170
+ end
171
+ end
172
+ describe '#running?' do
173
+ let (:method) {
174
+ a_i.instance_eval{ running? }
175
+ }
176
+ it 'return false if no pid' do
177
+ expect(a_i).to receive(:pid).and_return(nil)
178
+ expect(method).to be(false)
179
+ end
180
+ it 'return true if pid exists' do
181
+ pid = 1234
182
+ allow(a_i).to receive(:pid).and_return(pid)
183
+ # mock process
184
+ dbl_process = class_double('Process').as_stubbed_const()
185
+ allow(dbl_process).to receive(:getpgid).with(pid)
186
+ expect(method).to be(true)
187
+ end
188
+ it 'return false for non existing pid' do
189
+ pid = 1234
190
+ allow(a_i).to receive(:pid).and_return(pid)
191
+ # mock process
192
+ dbl_process = class_double('Process').as_stubbed_const()
193
+ allow(dbl_process).to receive(:getpgid).and_raise(Errno::ESRCH)
194
+ expect(method).to be(false)
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,339 @@
1
+ require 'spec_helper'
2
+ require 'php_fpm_docker/launcher'
3
+
4
+ describe PhpFpmDocker::Launcher do
5
+ before(:example) {
6
+ # Logger
7
+ @dbl_logger_instance = double
8
+ [:debug,:error,:warn,:info,:fatal].each do |loglevel|
9
+ allow(@dbl_logger_instance).to receive(loglevel)
10
+ end
11
+ @dbl_logger = class_double('Logger').as_stubbed_const()
12
+ allow(@dbl_logger).to receive(:new).and_return(@dbl_logger_instance)
13
+
14
+ @dbl_pool = class_double('PhpFpmDocker::Pool').as_stubbed_const()
15
+
16
+ # Fileutils
17
+ @dbl_fileutils = class_double('FileUtils').as_stubbed_const()
18
+ allow(@dbl_fileutils).to receive(:mkdir_p)
19
+ }
20
+ let (:a_i){
21
+ allow_any_instance_of(described_class).to receive(:test)
22
+ described_class.new(@name ||= 'launcher1')
23
+ }
24
+ let (:a_c){
25
+ described_class
26
+ }
27
+ describe '#initialize' do
28
+ let (:method){
29
+ expect_any_instance_of(described_class).to receive(:test)
30
+ described_class.new(@name ||= 'launcher1')
31
+ }
32
+ it 'should not raise error' do
33
+ expect{method}.not_to raise_error
34
+ end
35
+ end
36
+ describe '#test' do
37
+ before(:example) {
38
+ expect_any_instance_of(described_class).to receive(:test).and_call_original
39
+ @downstream_methods = [:test_docker_image,:test_directories, :parse_config]
40
+ }
41
+ let (:method){
42
+ described_class.new(@name ||= 'launcher1')
43
+ }
44
+ it 'should call down stream functions' do
45
+ @downstream_methods.each do |m|
46
+ expect_any_instance_of(described_class).to receive(m)
47
+ end
48
+ method
49
+ end
50
+ it 'should exit on runtime errors' do
51
+ @downstream_methods.each do |m|
52
+ allow_any_instance_of(described_class).to receive(m).and_raise(RuntimeError,"error")
53
+ end
54
+ expect_any_instance_of(described_class).to receive(:exit).with(1)
55
+ method
56
+ end
57
+ end
58
+ describe '#run' do
59
+ before (:example) {
60
+ @pid = 12345
61
+ allow(a_i).to receive(:start_pools)
62
+ allow(a_i).to receive(:fork).and_return(@pid)
63
+ }
64
+ it 'should start pools' do
65
+ # start pools
66
+ expect(a_i).to receive(:start_pools)
67
+ method
68
+ end
69
+ it 'should fork and detach daemon' do
70
+ expect(a_i).to receive(:fork).once do |&block|
71
+ expect(a_i).to receive(:fork_run).once
72
+ block.call
73
+ end.and_return(@pid)
74
+ expect(Process).to receive(:detach).with(@pid)
75
+ method
76
+ end
77
+ it 'shoud return pid' do
78
+ expect(method).to eq(@pid)
79
+ end
80
+ end
81
+ describe '#fork_run' do
82
+ before (:example) {
83
+ allow(Signal).to receive(:trap)
84
+ allow(Kernel).to receive(:loop)
85
+ }
86
+ it 'should handle signal USR1' do
87
+ expect(Signal).to receive(:trap).with('USR1').once do |&block|
88
+ expect(a_i).to receive(:reload_pools)
89
+ block.call
90
+ end
91
+ method
92
+ end
93
+ it 'should handle signal TERM' do
94
+ expect(Signal).to receive(:trap).with('TERM').once do |&block|
95
+ expect(a_i).to receive(:stop_pools)
96
+ expect(a_i).to receive(:exit).with(0)
97
+ block.call
98
+ end
99
+ method
100
+ end
101
+ it 'should loop and check pools' do
102
+ expect(Kernel).to receive(:loop) do |&block|
103
+ expect(a_i).to receive(:sleep).with(1)
104
+ expect(a_i).to receive(:check_pools)
105
+ block.call
106
+ end
107
+ method
108
+ end
109
+ end
110
+ describe '#check_pools'
111
+ describe '#start_pools' do
112
+ before (:example) {
113
+ allow(a_i).to receive(:reload_pools)
114
+ }
115
+ it 'should reset @pools' do
116
+ a_i.instance_variable_set(:@pools, {:lala => :test})
117
+ method
118
+ expect(a_i.instance_variable_get(:@pools)).to eq({})
119
+ end
120
+ it 'should trigger reload pools' do
121
+ expect(a_i).to receive(:reload_pools)
122
+ method
123
+ end
124
+ end
125
+ describe '#stop_pools' do
126
+ before (:example) {
127
+ allow(a_i).to receive(:reload_pools)
128
+ }
129
+ it 'should trigger reload pools' do
130
+ expect(a_i).to receive(:reload_pools)
131
+ method
132
+ end
133
+ end
134
+ describe '#create_missing_pool_objects' do
135
+ let (:set) {
136
+ inst_set(:@pools,@pools)
137
+ inst_set(:@pools_old,@pools_old)
138
+ }
139
+ let (:compare) {
140
+ expect(inst_get(:@pools)).to match(@pools)
141
+ expect(inst_get(:@pools_old)).to match(@pools_old)
142
+ }
143
+ let (:method1) {
144
+ set
145
+ method
146
+ compare
147
+ }
148
+ it 'should not fail with nil values' do
149
+ @pools = nil
150
+ @pools_old = nil
151
+ method1
152
+ end
153
+ it 'should not fail with empty hashes' do
154
+ @pools = {}
155
+ @pools_old = {}
156
+ method1
157
+ end
158
+ it 'should copy objects correctly' do
159
+ @pools = { :hash1 => {}, :hash3 => {}}
160
+ @pools_old = {
161
+ :hash1 => {:object => :object1},
162
+ :hash2 => {:object => :object2},
163
+ :hash3 => {:object => :object3}
164
+ }
165
+ set
166
+ method
167
+ @pools = {
168
+ :hash1 => {:object => :object1},
169
+ :hash3 => {:object => :object3}
170
+ }
171
+ compare
172
+ end
173
+ end
174
+ describe '#move_existing_pool_objects' do
175
+ let (:set) {
176
+ inst_set(:@pools,@pools)
177
+ }
178
+ let (:compare) {
179
+ expect(a_i.instance_variable_get(:@pools)).to match(@result)
180
+ }
181
+ let (:set_method_compare) {
182
+ set
183
+ method
184
+ compare
185
+ }
186
+ before(:example) {
187
+ allow(@dbl_pool).to receive(:new).and_return(:object)
188
+ }
189
+ it 'should not fail with nil value' do
190
+ @pools = nil
191
+ @result = nil
192
+ set_method_compare
193
+ end
194
+ it 'should not fail with empty hash' do
195
+ @pools = {}
196
+ @result = {}
197
+ set_method_compare
198
+ end
199
+ it 'should create missing objects' do
200
+ @pools = {
201
+ :hash1 => {:object => :object1},
202
+ :hash2 => {:name => :name2, :config => :config2},
203
+ :hash3 => {:object => :object3}
204
+ }
205
+ expect(@dbl_pool).to receive(:new) do |args|
206
+ expect(args).to have_key(:launcher)
207
+ expect(args).to include(@pools[:hash2])
208
+ end.and_return(:object2)
209
+ set
210
+ method
211
+ @result = @pools
212
+ @result[:hash2][:object] = :object2
213
+ compare
214
+ end
215
+ it 'should not recreate existing objects' do
216
+ @result = @pools = {
217
+ :hash1 => {:object => :object1},
218
+ :hash2 => {:object => :object2},
219
+ }
220
+ expect(@dbl_pool).not_to receive(:new)
221
+ set_method_compare
222
+ end
223
+ end
224
+ describe '#reload_pools' do
225
+ before(:example) {
226
+ [
227
+ :move_existing_pool_objects,
228
+ :pools_action,
229
+ :create_missing_pool_objects,
230
+ ].each {|m| allow(a_i).to receive(m)}
231
+ allow(a_i).to receive(:pools_from_config).and_return({})
232
+ inst_set(:@pools, {})
233
+ inst_set(:@pools_old, {})
234
+ }
235
+ let(:check_set_op){
236
+ expect(a_i).to receive(:pools_action) do |p,p_h,action|
237
+ next if action != @method
238
+
239
+ source = {
240
+ :stop => :@pools_old,
241
+ :start => :@pools,
242
+ }
243
+ expect(p).to be(inst_get(source[@method]))
244
+ expect(p_h).to eq(@result)
245
+
246
+ end.at_least(:once)
247
+ inst_set(:@pools, {
248
+ :hash1 => {:object => :object1},
249
+ :hash2 => {:object => :object2},
250
+ })
251
+ method({
252
+ :hash1 => {:object => :object1},
253
+ :hash3 => {:object => :object3},
254
+ :hash4 => {:object => :object4},
255
+ })
256
+ }
257
+ it 'should read pool from config with arg=nil' do
258
+ test = {:me => :myself}
259
+ expect(a_i).to receive(:pools_from_config).once.and_return(test)
260
+ method
261
+ expect(inst_get(:@pools)).to eq(test)
262
+ end
263
+ it 'should read pool from args' do
264
+ test = {:me => :myself}
265
+ method(test)
266
+ expect(inst_get(:@pools)).to eq(test)
267
+ end
268
+ it 'should call :create_missing_pool_objects' do
269
+ expect(a_i).to receive(:create_missing_pool_objects).once
270
+ method
271
+ end
272
+ it 'should call :move_existing_pool_objects' do
273
+ expect(a_i).to receive(:move_existing_pool_objects).once
274
+ method
275
+ end
276
+ it 'should move @pools to @pools_old' do
277
+ pools_old = inst_get(:@pools)
278
+ method
279
+ expect(inst_get(:@pools_old)).to be(pools_old)
280
+ end
281
+ it 'should stop unneeded pools' do
282
+ @method = :stop
283
+ @result = [:hash2]
284
+ check_set_op
285
+ end
286
+ it 'should start the new pools' do
287
+ @method = :start
288
+ @result = [:hash3, :hash4]
289
+ check_set_op
290
+ end
291
+ end
292
+ describe '#check_pools_n' do
293
+ it 'forwards to pools_action' do
294
+ hash = {
295
+ :hash1 => { :name => :map1},
296
+ :hash2 => { :name => :map2},
297
+ }
298
+ inst_set :@pools, hash
299
+ args = [hash, hash.keys, :check]
300
+ expect(a_i).to receive(:pools_action) do |*my_args|
301
+ expect(my_args).to eq(args)
302
+ end.once
303
+ method
304
+ end
305
+ end
306
+ describe '#pools_action' do
307
+ let!(:templates) {
308
+ @objects = [ double('object1'), double('object2') ]
309
+ @hash = {
310
+ :hash1 => { :name => :map1, :object => @objects[0]},
311
+ :hash2 => { :name => :map2, :object => @objects[1]},
312
+ }
313
+ }
314
+ let(:method_with_args){
315
+ @objects.each do |obj|
316
+ expect(obj).to receive(@method)
317
+ end
318
+ method(@hash, @hash.keys, @method)
319
+ }
320
+ it 'all pools run without error' do
321
+ @method = :start
322
+ expect(@dbl_logger_instance).not_to receive(:warn)
323
+ templates
324
+ method_with_args
325
+ end
326
+ it 'all pools error' do
327
+ @method = :start
328
+ error = 'servus der error'
329
+ expect(@dbl_logger_instance).to receive(:warn) do |pool,&block|
330
+ expect(block.call).to match(/#{error}/)
331
+ end.twice
332
+ templates
333
+ @objects.each do |obj|
334
+ expect(obj).to receive(@method).and_raise(RuntimeError, error)
335
+ end
336
+ method(@hash, @hash.keys, @method)
337
+ end
338
+ end
339
+ end