poise-application-python 4.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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.kitchen.travis.yml +9 -0
  4. data/.kitchen.yml +10 -0
  5. data/.travis.yml +20 -0
  6. data/.yardopts +3 -0
  7. data/Berksfile +35 -0
  8. data/CHANGELOG.md +71 -0
  9. data/Gemfile +37 -0
  10. data/LICENSE +201 -0
  11. data/README.md +334 -0
  12. data/Rakefile +17 -0
  13. data/SUPPORTERS.md +81 -0
  14. data/chef/templates/celeryconfig.py.erb +5 -0
  15. data/chef/templates/settings.py.erb +13 -0
  16. data/lib/poise_application_python.rb +23 -0
  17. data/lib/poise_application_python/app_mixin.rb +67 -0
  18. data/lib/poise_application_python/cheftie.rb +17 -0
  19. data/lib/poise_application_python/error.rb +25 -0
  20. data/lib/poise_application_python/resources.rb +26 -0
  21. data/lib/poise_application_python/resources/celery_beat.rb +43 -0
  22. data/lib/poise_application_python/resources/celery_config.rb +109 -0
  23. data/lib/poise_application_python/resources/celery_worker.rb +77 -0
  24. data/lib/poise_application_python/resources/django.rb +355 -0
  25. data/lib/poise_application_python/resources/gunicorn.rb +127 -0
  26. data/lib/poise_application_python/resources/pip_requirements.rb +47 -0
  27. data/lib/poise_application_python/resources/python.rb +57 -0
  28. data/lib/poise_application_python/resources/python_execute.rb +89 -0
  29. data/lib/poise_application_python/resources/python_package.rb +62 -0
  30. data/lib/poise_application_python/resources/virtualenv.rb +75 -0
  31. data/lib/poise_application_python/service_mixin.rb +57 -0
  32. data/lib/poise_application_python/version.rb +19 -0
  33. data/poise-application-python.gemspec +45 -0
  34. data/test/cookbooks/application_python_test/attributes/default.rb +17 -0
  35. data/test/cookbooks/application_python_test/metadata.rb +20 -0
  36. data/test/cookbooks/application_python_test/recipes/default.rb +83 -0
  37. data/test/cookbooks/application_python_test/recipes/django.rb +32 -0
  38. data/test/cookbooks/application_python_test/recipes/flask.rb +25 -0
  39. data/test/gemfiles/chef-12.gemfile +19 -0
  40. data/test/gemfiles/master.gemfile +27 -0
  41. data/test/integration/default/serverspec/default_spec.rb +81 -0
  42. data/test/integration/default/serverspec/django_spec.rb +56 -0
  43. data/test/integration/default/serverspec/flask_spec.rb +39 -0
  44. data/test/spec/app_mixin_spec.rb +69 -0
  45. data/test/spec/resources/celery_config_spec.rb +58 -0
  46. data/test/spec/resources/django_spec.rb +303 -0
  47. data/test/spec/resources/gunicorn_spec.rb +96 -0
  48. data/test/spec/resources/python_execute_spec.rb +46 -0
  49. data/test/spec/resources/python_spec.rb +44 -0
  50. data/test/spec/resources/virtualenv_spec.rb +44 -0
  51. data/test/spec/spec_helper.rb +19 -0
  52. metadata +216 -0
@@ -0,0 +1,58 @@
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 PoiseApplicationPython::Resources::CeleryConfig do
20
+ step_into(:application_celery_config)
21
+ before do
22
+ allow(File).to receive(:directory?).and_call_original
23
+ allow(File).to receive(:directory?).with('/test').and_return(true)
24
+ end
25
+
26
+ context 'with defaults' do
27
+ recipe do
28
+ application_celery_config '/test'
29
+ end
30
+ it { is_expected.to deploy_application_celery_config('/test').with(path: '/test/celeryconfig.py') }
31
+ it { is_expected.to render_file('/test/celeryconfig.py').with_content(eq(<<-CELERYCONFIG)) }
32
+ # Generated by Chef for application_celery_config[/test]
33
+
34
+ CELERYCONFIG
35
+ end # /context with defaults
36
+
37
+ context 'with a specific path' do
38
+ recipe do
39
+ application_celery_config '/test/foo.py'
40
+ end
41
+ it { is_expected.to deploy_application_celery_config('/test/foo.py').with(path: '/test/foo.py') }
42
+ end # /context with a specific path
43
+
44
+ context 'with template options' do
45
+ recipe do
46
+ application_celery_config '/test' do
47
+ options do
48
+ broker_url 'amqp://'
49
+ end
50
+ end
51
+ end
52
+ it { is_expected.to render_file('/test/celeryconfig.py').with_content(eq(<<-CELERYCONFIG)) }
53
+ # Generated by Chef for application_celery_config[/test]
54
+
55
+ BROKER_URL = "amqp://"
56
+ CELERYCONFIG
57
+ end # /context with template options
58
+ end
@@ -0,0 +1,303 @@
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 PoiseApplicationPython::Resources::Django do
20
+ describe PoiseApplicationPython::Resources::Django::Resource do
21
+ describe '#local_settings' do
22
+ subject { chef_run.application_django('/test').local_settings_content }
23
+
24
+ context 'with defaults' do
25
+ recipe(subject: false) do
26
+ application_django '/test'
27
+ end
28
+ it { is_expected.to eq <<-SETTINGS }
29
+ # Generated by Chef for application_django[/test]
30
+
31
+ DEBUG = False
32
+
33
+ DATABASES = {"default":{}}
34
+ SETTINGS
35
+ end # /context with defaults
36
+
37
+ context 'with a URL' do
38
+ recipe(subject: false) do
39
+ application_django '/test' do
40
+ database 'postgres://myuser@dbhost/myapp'
41
+ end
42
+ end
43
+ it { is_expected.to eq <<-SETTINGS }
44
+ # Generated by Chef for application_django[/test]
45
+
46
+ DEBUG = False
47
+
48
+ DATABASES = {"default":{"URL":"postgres://myuser@dbhost/myapp","ENGINE":"django.db.backends.postgresql_psycopg2","NAME":"myapp","USER":"myuser","HOST":"dbhost"}}
49
+ SETTINGS
50
+ end # /context with a URL
51
+
52
+ context 'with an options block' do
53
+ recipe(subject: false) do
54
+ application_django '/test' do
55
+ database do
56
+ engine 'postgres'
57
+ name 'myapp'
58
+ user 'myuser'
59
+ host 'dbhost'
60
+ end
61
+ end
62
+ end
63
+ it { is_expected.to eq <<-SETTINGS }
64
+ # Generated by Chef for application_django[/test]
65
+
66
+ DEBUG = False
67
+
68
+ DATABASES = {"default":{"ENGINE":"django.db.backends.postgresql_psycopg2","NAME":"myapp","USER":"myuser","HOST":"dbhost"}}
69
+ SETTINGS
70
+ end # /context with an options block
71
+
72
+ context 'with debug mode' do
73
+ recipe(subject: false) do
74
+ application_django '/test' do
75
+ debug true
76
+ end
77
+ end
78
+ it { is_expected.to eq <<-SETTINGS }
79
+ # Generated by Chef for application_django[/test]
80
+
81
+ DEBUG = True
82
+
83
+ DATABASES = {"default":{}}
84
+ SETTINGS
85
+ end # /context with debug mode
86
+
87
+ context 'with a single allowed host' do
88
+ recipe(subject: false) do
89
+ application_django '/test' do
90
+ allowed_hosts 'example.com'
91
+ end
92
+ end
93
+ it { is_expected.to eq <<-SETTINGS }
94
+ # Generated by Chef for application_django[/test]
95
+
96
+ ALLOWED_HOSTS = ["example.com"]
97
+
98
+ DEBUG = False
99
+
100
+ DATABASES = {"default":{}}
101
+ SETTINGS
102
+ end # /context with a single allowed host
103
+
104
+ context 'with multiple allowed hosts' do
105
+ recipe(subject: false) do
106
+ application_django '/test' do
107
+ allowed_hosts %w{example.com www.example.com}
108
+ end
109
+ end
110
+ it { is_expected.to eq <<-SETTINGS }
111
+ # Generated by Chef for application_django[/test]
112
+
113
+ ALLOWED_HOSTS = ["example.com","www.example.com"]
114
+
115
+ DEBUG = False
116
+
117
+ DATABASES = {"default":{}}
118
+ SETTINGS
119
+ end # /context with multiple allowed hosts
120
+
121
+ context 'with a secret key' do
122
+ recipe(subject: false) do
123
+ application_django '/test' do
124
+ secret_key 'swordfish'
125
+ end
126
+ end
127
+ it { is_expected.to eq <<-SETTINGS }
128
+ # Generated by Chef for application_django[/test]
129
+
130
+ DEBUG = False
131
+
132
+ DATABASES = {"default":{}}
133
+
134
+ SECRET_KEY = "swordfish"
135
+ SETTINGS
136
+ end # /context with a secret key
137
+ end # /describe #local_settings
138
+
139
+ describe '#default_local_settings_path' do
140
+ subject { chef_run.application_django('/test').send(:default_local_settings_path) }
141
+
142
+ context 'with no settings.py' do
143
+ recipe(subject: false) do
144
+ application_django '/test' do
145
+ def settings_module
146
+ nil
147
+ end
148
+ end
149
+ end
150
+ it { is_expected.to be_nil }
151
+ end # /context with no settings.py
152
+
153
+ context 'with basic settings.py' do
154
+ recipe(subject: false) do
155
+ application_django '/test' do
156
+ settings_module 'myapp.settings'
157
+ end
158
+ end
159
+ it { is_expected.to eq '/test/myapp/local_settings.py' }
160
+ end # /context with basic settings.py
161
+ end # /describe #default_local_settings_path
162
+
163
+ describe '#default_manage_path' do
164
+ subject { chef_run.application_django('/test').send(:default_manage_path) }
165
+ recipe(subject: false) do
166
+ application_django '/test'
167
+ end
168
+ before do
169
+ allow(chef_run.application_django('/test')).to receive(:find_file).with('manage.py').and_return('/test/manage.py')
170
+ end
171
+
172
+ it { is_expected.to eq '/test/manage.py' }
173
+ end # /describe #default_manage_path
174
+
175
+ describe '#default_settings_module' do
176
+ let(:settings_path) { nil }
177
+ subject { chef_run.application_django('/test').send(:default_settings_module) }
178
+ recipe(subject: false) do
179
+ application_django '/test'
180
+ end
181
+ before do
182
+ allow(chef_run.application_django('/test')).to receive(:find_file).with('settings.py').and_return(settings_path)
183
+ end
184
+
185
+ context 'with no settings.py' do
186
+ it { is_expected.to be_nil }
187
+ end # /context with no settings.py
188
+
189
+ context 'with simple settings.py' do
190
+ let(:settings_path) { '/test/myapp/settings.py' }
191
+ it { is_expected.to eq 'myapp.settings' }
192
+ end # /context with simple settings.py
193
+ end # /describe #default_settings_module
194
+
195
+ describe '#default_wsgi_module' do
196
+ let(:wsgi_path) { nil }
197
+ subject { chef_run.application_django('/test').send(:default_wsgi_module) }
198
+ recipe(subject: false) do
199
+ application_django '/test'
200
+ end
201
+ before do
202
+ allow(chef_run.application_django('/test')).to receive(:find_file).with('wsgi.py').and_return(wsgi_path)
203
+ end
204
+
205
+ context 'with no wsgi.py' do
206
+ it { is_expected.to be_nil }
207
+ end # /context with no wsgi.py
208
+
209
+ context 'with simple wsgi.py' do
210
+ let(:wsgi_path) { '/test/wsgi.py' }
211
+ it { is_expected.to eq 'wsgi' }
212
+ end # /context with simple wsgi.py
213
+ end # /describe #default_wsgi_module
214
+
215
+ describe '#find_file' do
216
+ let(:files) { [] }
217
+ recipe(subject: false) do
218
+ application_django '/test'
219
+ end
220
+ subject { chef_run.application_django('/test').send(:find_file, 'myfile.py') }
221
+ before do
222
+ allow(Dir).to receive(:[]).and_call_original
223
+ allow(Dir).to receive(:[]).with('/test/**/myfile.py').and_return(files)
224
+ end
225
+
226
+ context 'with no matching files' do
227
+ it { is_expected.to be_nil }
228
+ end # /context with no matching files
229
+
230
+ context 'with one matching file' do
231
+ let(:files) { %w{/test/myfile.py} }
232
+ it { is_expected.to eq '/test/myfile.py' }
233
+ end # /context with one matching file
234
+
235
+ context 'with two matching files' do
236
+ let(:files) { %w{/test/myfile.py /test/sub/myfile.py} }
237
+ it { is_expected.to eq '/test/myfile.py' }
238
+ end # /context with two matching files
239
+
240
+ context 'with two matching files in a different order' do
241
+ let(:files) { %w{/test/sub/myfile.py /test/myfile.py} }
242
+ it { is_expected.to eq '/test/myfile.py' }
243
+ end # /context with two matching files in a different order
244
+
245
+ context 'with two matching files on the same level' do
246
+ let(:files) { %w{/test/b/myfile.py /test/a/myfile.py} }
247
+ it { is_expected.to eq '/test/a/myfile.py' }
248
+ end # /context with two matching files on the same level
249
+ end # /describe #find_file
250
+ end # /describe PoiseApplicationPython::Resources::Django::Resource
251
+
252
+ describe PoiseApplicationPython::Resources::Django::Provider do
253
+ step_into(:application_django)
254
+ context 'with default settings' do
255
+ recipe do
256
+ application_django '/test' do
257
+ # Hardwire all paths so it doesn't have to search.
258
+ manage_path 'manage.py'
259
+ settings_module 'myapp.settings'
260
+ wsgi_module 'wsgi'
261
+ end
262
+ end
263
+
264
+ it { is_expected.to run_python_execute('manage.py collectstatic --noinput') }
265
+ it { is_expected.to_not run_python_execute('manage.py syncdb --noinput') }
266
+ it { is_expected.to_not run_python_execute('manage.py migrate --noinput') }
267
+ it { is_expected.to render_file('/test/myapp/local_settings.py') }
268
+ end # /context with default settings
269
+
270
+ context 'with syncdb' do
271
+ recipe do
272
+ application_django '/test' do
273
+ # Hardwire all paths so it doesn't have to search.
274
+ manage_path 'manage.py'
275
+ settings_module 'myapp.settings'
276
+ syncdb true
277
+ wsgi_module 'wsgi'
278
+ end
279
+ end
280
+
281
+ it { is_expected.to run_python_execute('manage.py collectstatic --noinput') }
282
+ it { is_expected.to run_python_execute('manage.py syncdb --noinput') }
283
+ it { is_expected.to_not run_python_execute('manage.py migrate --noinput') }
284
+ end # /context with syncdb
285
+
286
+ context 'with migrate' do
287
+ recipe do
288
+ application_django '/test' do
289
+ # Hardwire all paths so it doesn't have to search.
290
+ manage_path 'manage.py'
291
+ migrate true
292
+ settings_module 'myapp.settings'
293
+ wsgi_module 'wsgi'
294
+ end
295
+ end
296
+
297
+ it { is_expected.to run_python_execute('manage.py collectstatic --noinput') }
298
+ it { is_expected.to_not run_python_execute('manage.py syncdb --noinput') }
299
+ it { is_expected.to run_python_execute('manage.py migrate --noinput') }
300
+ it { is_expected.to render_file('/test/myapp/local_settings.py') }
301
+ end # /context with migrate
302
+ end # /describe PoiseApplicationPython::Resources::Django::Provider
303
+ 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 PoiseApplicationPython::Resources::Gunicorn do
20
+ describe PoiseApplicationPython::Resources::Gunicorn::Resource do
21
+ describe '#default_app_module' do
22
+ let(:app_state) { {} }
23
+ let(:files) { [] }
24
+ let(:test_resource) { described_class.new(nil, nil) }
25
+ before do
26
+ allow(test_resource).to receive(:app_state).and_return(app_state)
27
+ allow(Dir).to receive(:exist?).and_return(!files.empty?)
28
+ allow(Dir).to receive(:entries).and_return(files)
29
+ end
30
+ subject { test_resource.send(:default_app_module) }
31
+
32
+ context 'with an app_state key' do
33
+ let(:app_state) { {python_wsgi_module: 'django'} }
34
+ it { is_expected.to eq 'django' }
35
+ end # /context with an app_state key
36
+
37
+ context 'with a wsgi.py' do
38
+ let(:files) { %w{wsgi.py} }
39
+ it { is_expected.to eq 'wsgi' }
40
+ end # /context with a wsgi.py
41
+
42
+ context 'with an app.py and main.py' do
43
+ let(:files) { %w{app.py main.py} }
44
+ it { is_expected.to eq 'main' }
45
+ end # /context with an app.py and main.py
46
+
47
+ context 'with a foo.txt and bar.py' do
48
+ let(:files) { %w{foo.txt bar.py} }
49
+ it { is_expected.to eq 'bar' }
50
+ end # /context with a foo.txt and bar.py
51
+
52
+ context 'with a foo.txt' do
53
+ let(:files) { %w{foo.txt } }
54
+ it { is_expected.to be_nil }
55
+ end # /context with a foo.txt
56
+ end # /describe #default_app_module
57
+ end # /describe PoiseApplicationPython::Resources::Gunicorn::Resource
58
+
59
+ describe PoiseApplicationPython::Resources::Gunicorn::Provider do
60
+ let(:new_resource) { double('new_resource') }
61
+ let(:test_provider) { described_class.new(new_resource, nil) }
62
+
63
+ describe '#gunicorn_command_options' do
64
+ let(:props) { {} }
65
+ let(:new_resource) { PoiseApplicationPython::Resources::Gunicorn::Resource.new('/test', nil) }
66
+ subject { test_provider.send(:gunicorn_command_options).join(' ') }
67
+ before do
68
+ props.each {|key, value| new_resource.send(key, value) }
69
+ end
70
+
71
+ context 'with defaults' do
72
+ it { is_expected.to eq '--bind 0.0.0.0:80' }
73
+ end # /context with defaults
74
+
75
+ context 'with a config file' do
76
+ let(:props) { {config: '/test/myconfig.py'} }
77
+ it { is_expected.to eq '--config /test/myconfig.py --bind 0.0.0.0:80' }
78
+ end # /context with a config file
79
+
80
+ context 'with a blank config file' do
81
+ let(:props) { {config: ''} }
82
+ it { is_expected.to eq '--bind 0.0.0.0:80' }
83
+ end # /context with a blank config file
84
+
85
+ context 'with two binds' do
86
+ let(:props) { {bind: %w{0.0.0.0:80 0.0.0.0:81}} }
87
+ it { is_expected.to eq '--bind 0.0.0.0:80 --bind 0.0.0.0:81' }
88
+ end # /context with two binds
89
+
90
+ context 'with a config file and preload' do
91
+ let(:props) { {config: '/test/myconfig.py', preload_app: true} }
92
+ it { is_expected.to eq '--config /test/myconfig.py --bind 0.0.0.0:80 --preload' }
93
+ end # /context with a config file and preload
94
+ end # /describe #gunicorn_command_options
95
+ end # /describe PoiseApplicationPython::Resources::Gunicorn::Provider
96
+ end