php_fpm_docker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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