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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.kitchen.travis.yml +9 -0
- data/.kitchen.yml +10 -0
- data/.travis.yml +20 -0
- data/.yardopts +3 -0
- data/Berksfile +35 -0
- data/CHANGELOG.md +71 -0
- data/Gemfile +37 -0
- data/LICENSE +201 -0
- data/README.md +334 -0
- data/Rakefile +17 -0
- data/SUPPORTERS.md +81 -0
- data/chef/templates/celeryconfig.py.erb +5 -0
- data/chef/templates/settings.py.erb +13 -0
- data/lib/poise_application_python.rb +23 -0
- data/lib/poise_application_python/app_mixin.rb +67 -0
- data/lib/poise_application_python/cheftie.rb +17 -0
- data/lib/poise_application_python/error.rb +25 -0
- data/lib/poise_application_python/resources.rb +26 -0
- data/lib/poise_application_python/resources/celery_beat.rb +43 -0
- data/lib/poise_application_python/resources/celery_config.rb +109 -0
- data/lib/poise_application_python/resources/celery_worker.rb +77 -0
- data/lib/poise_application_python/resources/django.rb +355 -0
- data/lib/poise_application_python/resources/gunicorn.rb +127 -0
- data/lib/poise_application_python/resources/pip_requirements.rb +47 -0
- data/lib/poise_application_python/resources/python.rb +57 -0
- data/lib/poise_application_python/resources/python_execute.rb +89 -0
- data/lib/poise_application_python/resources/python_package.rb +62 -0
- data/lib/poise_application_python/resources/virtualenv.rb +75 -0
- data/lib/poise_application_python/service_mixin.rb +57 -0
- data/lib/poise_application_python/version.rb +19 -0
- data/poise-application-python.gemspec +45 -0
- data/test/cookbooks/application_python_test/attributes/default.rb +17 -0
- data/test/cookbooks/application_python_test/metadata.rb +20 -0
- data/test/cookbooks/application_python_test/recipes/default.rb +83 -0
- data/test/cookbooks/application_python_test/recipes/django.rb +32 -0
- data/test/cookbooks/application_python_test/recipes/flask.rb +25 -0
- data/test/gemfiles/chef-12.gemfile +19 -0
- data/test/gemfiles/master.gemfile +27 -0
- data/test/integration/default/serverspec/default_spec.rb +81 -0
- data/test/integration/default/serverspec/django_spec.rb +56 -0
- data/test/integration/default/serverspec/flask_spec.rb +39 -0
- data/test/spec/app_mixin_spec.rb +69 -0
- data/test/spec/resources/celery_config_spec.rb +58 -0
- data/test/spec/resources/django_spec.rb +303 -0
- data/test/spec/resources/gunicorn_spec.rb +96 -0
- data/test/spec/resources/python_execute_spec.rb +46 -0
- data/test/spec/resources/python_spec.rb +44 -0
- data/test/spec/resources/virtualenv_spec.rb +44 -0
- data/test/spec/spec_helper.rb +19 -0
- metadata +216 -0
@@ -0,0 +1,355 @@
|
|
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 'uri'
|
18
|
+
|
19
|
+
require 'chef/provider'
|
20
|
+
require 'chef/resource'
|
21
|
+
require 'poise'
|
22
|
+
require 'poise_application'
|
23
|
+
require 'poise_python'
|
24
|
+
|
25
|
+
require 'poise_application_python/app_mixin'
|
26
|
+
require 'poise_application_python/error'
|
27
|
+
|
28
|
+
|
29
|
+
module PoiseApplicationPython
|
30
|
+
module Resources
|
31
|
+
# (see Django::Resource)
|
32
|
+
# @since 4.0.0
|
33
|
+
module Django
|
34
|
+
# Aliases for Django database engine names. Based on https://github.com/kennethreitz/dj-database-url/blob/master/dj_database_url.py
|
35
|
+
# Copyright 2014, Kenneth Reitz.
|
36
|
+
ENGINE_ALIASES = {
|
37
|
+
'postgres' => 'django.db.backends.postgresql_psycopg2',
|
38
|
+
'postgresql' => 'django.db.backends.postgresql_psycopg2',
|
39
|
+
'pgsql' => 'django.db.backends.postgresql_psycopg2',
|
40
|
+
'postgis' => 'django.contrib.gis.db.backends.postgis',
|
41
|
+
'mysql2' => 'django.db.backends.mysql',
|
42
|
+
'mysqlgis' => 'django.contrib.gis.db.backends.mysql',
|
43
|
+
'spatialite' => 'django.contrib.gis.db.backends.spatialite',
|
44
|
+
'sqlite' => 'django.db.backends.sqlite3',
|
45
|
+
}
|
46
|
+
|
47
|
+
# An `application_django` resource to configure Django applications.
|
48
|
+
#
|
49
|
+
# @since 4.0.0
|
50
|
+
# @provides application_django
|
51
|
+
# @action deploy
|
52
|
+
# @example
|
53
|
+
# application '/srv/myapp' do
|
54
|
+
# git '...'
|
55
|
+
# pip_requirements
|
56
|
+
# django do
|
57
|
+
# database do
|
58
|
+
# host node['db_host']
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
# gunicorn do
|
62
|
+
# port 8080
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
class Resource < Chef::Resource
|
66
|
+
include PoiseApplicationPython::AppMixin
|
67
|
+
provides(:application_django)
|
68
|
+
actions(:deploy)
|
69
|
+
|
70
|
+
# @!attribute allowed_hosts
|
71
|
+
# Value for `ALLOWED_HOSTS` in the Django settings.
|
72
|
+
# @return [String, Array<String>]
|
73
|
+
attribute(:allowed_hosts, kind_of: [String, Array], default: lazy { [] })
|
74
|
+
# @!attribute collectstatic
|
75
|
+
# Set to false to disable running manage.py collectstatic during
|
76
|
+
# deployment.
|
77
|
+
# @todo This could auto-detect based on config vars in settings?
|
78
|
+
# @return [Boolean]
|
79
|
+
attribute(:collectstatic, equal_to: [true, false], default: true)
|
80
|
+
# @!attribute database
|
81
|
+
# Option collector attribute for Django database configuration.
|
82
|
+
# @return [Hash]
|
83
|
+
# @example Setting via block
|
84
|
+
# database do
|
85
|
+
# engine 'postgresql'
|
86
|
+
# database 'blog'
|
87
|
+
# end
|
88
|
+
# @example Setting via URL
|
89
|
+
# database 'postgresql://localhost/blog'
|
90
|
+
attribute(:database, option_collector: true, parser: :parse_database_url, forced_keys: %i{name})
|
91
|
+
# @!attribute debug
|
92
|
+
# Enable debug mode for Django.
|
93
|
+
# @note
|
94
|
+
# If you use this in production you will get everything you deserve.
|
95
|
+
# @return [Boolean]
|
96
|
+
attribute(:debug, equal_to: [true, false], default: false)
|
97
|
+
# @!attribute group
|
98
|
+
# Owner for the Django application, defaults to application group.
|
99
|
+
# @return [String]
|
100
|
+
attribute(:group, kind_of: String, default: lazy { parent && parent.group })
|
101
|
+
# @!attribute local_settings
|
102
|
+
# Template content attribute for the contents of local_settings.py.
|
103
|
+
# @todo Redo this doc to cover the actual attributes created.
|
104
|
+
# @return [Poise::Helpers::TemplateContent]
|
105
|
+
attribute(:local_settings, template: true, default_source: 'settings.py.erb', default_options: lazy { default_local_settings_options })
|
106
|
+
# @!attribute local_settings_path
|
107
|
+
# Path to write local settings to. If given as a relative path,
|
108
|
+
# will be expanded against {#path}. Set to false to disable writing
|
109
|
+
# local settings. Defaults to local_settings.py next to
|
110
|
+
# {#setting_module}.
|
111
|
+
# @return [String, nil false]
|
112
|
+
attribute(:local_settings_path, kind_of: [String, NilClass, FalseClass], default: lazy { default_local_settings_path })
|
113
|
+
# @!attribute migrate
|
114
|
+
# Run database migrations. This is a bad idea for real apps. Please
|
115
|
+
# do not use it.
|
116
|
+
# @return [Boolean]
|
117
|
+
attribute(:migrate, equal_to: [true, false], default: false)
|
118
|
+
# @!attribute manage_path
|
119
|
+
# Path to manage.py. Defaults to scanning for the nearest manage.py
|
120
|
+
# to {#path}.
|
121
|
+
# @return [String]
|
122
|
+
attribute(:manage_path, kind_of: String, default: lazy { default_manage_path })
|
123
|
+
# @!attribute owner
|
124
|
+
# Owner for the Django application, defaults to application owner.
|
125
|
+
# @return [String]
|
126
|
+
attribute(:owner, kind_of: String, default: lazy { parent && parent.owner })
|
127
|
+
# @!attribute secret_key
|
128
|
+
# Value for `SECRET_KEY` in the Django settings. If unset, no key is
|
129
|
+
# added to the local settings.
|
130
|
+
# @return [String, false]
|
131
|
+
attribute(:secret_key, kind_of: [String, FalseClass])
|
132
|
+
# @!attribute settings_module
|
133
|
+
# Django settings module in dotted notation. Set to false to disable
|
134
|
+
# anything related to settings. Defaults to scanning for the nearest
|
135
|
+
# settings.py to {#path}.
|
136
|
+
# @return [Boolean]
|
137
|
+
attribute(:settings_module, kind_of: [String, NilClass, FalseClass], default: lazy { default_settings_module })
|
138
|
+
# @!attribute syncdb
|
139
|
+
# Run database sync. This is a bad idea for real apps. Please do not
|
140
|
+
# use it.
|
141
|
+
# @return [Boolean]
|
142
|
+
attribute(:syncdb, equal_to: [true, false], default: false)
|
143
|
+
# @!attribute wsgi_module
|
144
|
+
# WSGI application module in dotted notation. Set to false to disable
|
145
|
+
# anything related to WSGI. Defaults to scanning for the nearest
|
146
|
+
# wsgi.py to {#path}.
|
147
|
+
# @return [Boolean]
|
148
|
+
attribute(:wsgi_module, kind_of: [String, NilClass, FalseClass], default: lazy { default_wsgi_module })
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
# Default value for {#local_settings_options}. Adds Django settings data
|
153
|
+
# from the resource to be rendered in the local settings template.
|
154
|
+
#
|
155
|
+
# @return [Hash]
|
156
|
+
def default_local_settings_options
|
157
|
+
{}.tap do |options|
|
158
|
+
options[:allowed_hosts] = Array(allowed_hosts)
|
159
|
+
options[:databases] = {}
|
160
|
+
options[:databases]['default'] = database.inject({}) do |memo, (key, value)|
|
161
|
+
key = key.to_s.upcase
|
162
|
+
# Deal with engine aliases here too, just in case.
|
163
|
+
value = resolve_engine(value) if key == 'ENGINE'
|
164
|
+
memo[key] = value
|
165
|
+
memo
|
166
|
+
end
|
167
|
+
options[:debug] = debug
|
168
|
+
options[:secret_key] = secret_key
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Default value for {#local_settings_path}, local_settings.py next to
|
173
|
+
# the configured {#settings_module}.
|
174
|
+
#
|
175
|
+
# @return [String, nil]
|
176
|
+
def default_local_settings_path
|
177
|
+
# If no settings module, no default local settings.
|
178
|
+
return unless settings_module
|
179
|
+
settings_path = PoisePython::Utils.module_to_path(settings_module, path)
|
180
|
+
::File.expand_path(::File.join('..', 'local_settings.py'), settings_path)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Default value for {#manage_path}, searches for manage.py in the
|
184
|
+
# application path.
|
185
|
+
#
|
186
|
+
# @return [String, nil]
|
187
|
+
def default_manage_path
|
188
|
+
find_file('manage.py')
|
189
|
+
end
|
190
|
+
|
191
|
+
# Default value for {#settings_module}, searches for settings.py in the
|
192
|
+
# application path.
|
193
|
+
#
|
194
|
+
# @return [String, nil]
|
195
|
+
def default_settings_module
|
196
|
+
settings_path = find_file('settings.py')
|
197
|
+
if settings_path
|
198
|
+
PoisePython::Utils.path_to_module(settings_path, path)
|
199
|
+
else
|
200
|
+
nil
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Default value for {#wsgi_module}, searchs for wsgi.py in the
|
205
|
+
# application path.
|
206
|
+
#
|
207
|
+
# @return [String, nil]
|
208
|
+
def default_wsgi_module
|
209
|
+
wsgi_path = find_file('wsgi.py')
|
210
|
+
if wsgi_path
|
211
|
+
PoisePython::Utils.path_to_module(wsgi_path, path)
|
212
|
+
else
|
213
|
+
nil
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Format a URL for DATABASES.
|
218
|
+
#
|
219
|
+
# @return [Hash]
|
220
|
+
def parse_database_url(url)
|
221
|
+
parsed = URI(url)
|
222
|
+
{}.tap do |db|
|
223
|
+
# Store this for use later in #set_state, and maybe future use by
|
224
|
+
# Django in some magic world where operability happens.
|
225
|
+
db[:URL] = url
|
226
|
+
db[:ENGINE] = resolve_engine(parsed.scheme)
|
227
|
+
# Strip the leading /.
|
228
|
+
path = parsed.path ? parsed.path[1..-1] : parsed.path
|
229
|
+
# If we are using SQLite, make it an absolute path.
|
230
|
+
path = ::File.expand_path(path, self.path) if db[:ENGINE].include?('sqlite')
|
231
|
+
db[:NAME] = path if path && !path.empty?
|
232
|
+
db[:USER] = parsed.user if parsed.user && !parsed.user.empty?
|
233
|
+
db[:PASSWORD] = parsed.password if parsed.password && !parsed.password.empty?
|
234
|
+
db[:HOST] = parsed.host if parsed.host && !parsed.host.empty?
|
235
|
+
db[:PORT] = parsed.port if parsed.port && !parsed.port.empty?
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Search for a file somewhere under the application path. Prefers files
|
240
|
+
# closer to the root, then sort alphabetically for stability.
|
241
|
+
#
|
242
|
+
# @param name [String] Filename to search for.
|
243
|
+
# @return [String, nil]
|
244
|
+
def find_file(name)
|
245
|
+
num_separators = lambda do |path|
|
246
|
+
if ::File::ALT_SEPARATOR && path.include?(::File::ALT_SEPARATOR)
|
247
|
+
# :nocov:
|
248
|
+
path.count(::File::ALT_SEPARATOR)
|
249
|
+
# :nocov:
|
250
|
+
else
|
251
|
+
path.count(::File::SEPARATOR)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
Dir[::File.join(path, '**', name)].min do |a, b|
|
255
|
+
cmp = num_separators.call(a) <=> num_separators.call(b)
|
256
|
+
if cmp == 0
|
257
|
+
cmp = a <=> b
|
258
|
+
end
|
259
|
+
cmp
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# Resolve Django database engine from shortname to dotted module.
|
264
|
+
#
|
265
|
+
# @param name [String, nil] Engine name.
|
266
|
+
# @return [String, nil]
|
267
|
+
def resolve_engine(name)
|
268
|
+
if name && !name.empty? && !name.include?('.')
|
269
|
+
ENGINE_ALIASES[name] || "django.db.backends.#{name}"
|
270
|
+
else
|
271
|
+
name
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|
276
|
+
|
277
|
+
# Provider for `application_django`.
|
278
|
+
#
|
279
|
+
# @since 4.0.0
|
280
|
+
# @see Resource
|
281
|
+
# @provides application_django
|
282
|
+
class Provider < Chef::Provider
|
283
|
+
include PoiseApplicationPython::AppMixin
|
284
|
+
provides(:application_django)
|
285
|
+
|
286
|
+
# `deploy` action for `application_django`. Ensure all configuration
|
287
|
+
# files are created and other deploy tasks resolved.
|
288
|
+
#
|
289
|
+
# @return [void]
|
290
|
+
def action_deploy
|
291
|
+
set_state
|
292
|
+
notifying_block do
|
293
|
+
write_config
|
294
|
+
run_syncdb
|
295
|
+
run_migrate
|
296
|
+
run_collectstatic
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
private
|
301
|
+
|
302
|
+
# Set app_state variables for future services et al.
|
303
|
+
def set_state
|
304
|
+
# Set environment variables for later services.
|
305
|
+
new_resource.app_state_environment[:DJANGO_SETTINGS_MODULE] = new_resource.settings_module if new_resource.settings_module
|
306
|
+
new_resource.app_state_environment[:DATABASE_URL] = new_resource.database[:URL] if new_resource.database[:URL]
|
307
|
+
# Set the app module.
|
308
|
+
new_resource.app_state[:python_wsgi_module] = new_resource.wsgi_module if new_resource.wsgi_module
|
309
|
+
end
|
310
|
+
|
311
|
+
# Create the database using the older syncdb command.
|
312
|
+
def run_syncdb
|
313
|
+
manage_py_execute('syncdb', '--noinput') if new_resource.syncdb
|
314
|
+
end
|
315
|
+
|
316
|
+
# Create the database using the newer migrate command. This should work
|
317
|
+
# for either South or the built-in migrations support.
|
318
|
+
def run_migrate
|
319
|
+
manage_py_execute('migrate', '--noinput') if new_resource.migrate
|
320
|
+
end
|
321
|
+
|
322
|
+
# Run the asset pipeline.
|
323
|
+
def run_collectstatic
|
324
|
+
manage_py_execute('collectstatic', '--noinput') if new_resource.collectstatic
|
325
|
+
end
|
326
|
+
|
327
|
+
# Create the local config settings.
|
328
|
+
def write_config
|
329
|
+
# Allow disabling the local settings.
|
330
|
+
return unless new_resource.local_settings_path
|
331
|
+
file new_resource.local_settings_path do
|
332
|
+
content new_resource.local_settings_content
|
333
|
+
mode '640'
|
334
|
+
owner new_resource.owner
|
335
|
+
group new_resource.group
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Run a manage.py command using `python_execute`.
|
340
|
+
def manage_py_execute(*cmd)
|
341
|
+
raise PoiseApplicationPython::Error.new("Unable to find a find a manage.py for #{new_resource}, please set manage_path") unless new_resource.manage_path
|
342
|
+
python_execute "manage.py #{cmd.join(' ')}" do
|
343
|
+
python_from_parent new_resource
|
344
|
+
command [::File.expand_path(new_resource.manage_path, new_resource.path)] + cmd
|
345
|
+
cwd new_resource.path
|
346
|
+
environment new_resource.app_state_environment
|
347
|
+
group new_resource.group
|
348
|
+
user new_resource.owner
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
@@ -0,0 +1,127 @@
|
|
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 'shellwords'
|
18
|
+
|
19
|
+
require 'chef/provider'
|
20
|
+
require 'chef/resource'
|
21
|
+
require 'poise'
|
22
|
+
|
23
|
+
require 'poise_application_python/service_mixin'
|
24
|
+
|
25
|
+
|
26
|
+
module PoiseApplicationPython
|
27
|
+
module Resources
|
28
|
+
# (see Gunicorn::Resource)
|
29
|
+
# @since 4.0.0
|
30
|
+
module Gunicorn
|
31
|
+
class Resource < Chef::Resource
|
32
|
+
include PoiseApplicationPython::ServiceMixin
|
33
|
+
provides(:application_gunicorn)
|
34
|
+
|
35
|
+
attribute(:app_module, kind_of: [String, NilClass], default: lazy { default_app_module })
|
36
|
+
attribute(:bind, kind_of: [String, Array], default: '0.0.0.0:80')
|
37
|
+
attribute(:config, kind_of: [String, NilClass])
|
38
|
+
attribute(:preload_app, equal_to: [true, false], default: false)
|
39
|
+
attribute(:version, kind_of: [String, TrueClass, FalseClass], default: true)
|
40
|
+
|
41
|
+
# Helper to set {#bind} with just a port number.
|
42
|
+
#
|
43
|
+
# @param val [String, Integer] Port number to use.
|
44
|
+
# @return [void]
|
45
|
+
def port(val)
|
46
|
+
bind("0.0.0.0:#{val}")
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Compute the default application module to pass to gunicorn. This
|
52
|
+
# checks the app state and then looks for commonly used filenames.
|
53
|
+
# Raises an exception if no default can be found.
|
54
|
+
#
|
55
|
+
# @return [String]
|
56
|
+
def default_app_module
|
57
|
+
# If set in app_state, use that.
|
58
|
+
return app_state[:python_wsgi_module] if app_state[:python_wsgi_module]
|
59
|
+
files = Dir.exist?(path) ? Dir.entries(path) : []
|
60
|
+
# Try to find a known filename.
|
61
|
+
candidate_file = %w{wsgi.py main.py app.py application.py}.find {|file| files.include?(file) }
|
62
|
+
# Try the first Python file. Do I really want this?
|
63
|
+
candidate_file ||= files.find {|file| file.end_with?('.py') }
|
64
|
+
if candidate_file
|
65
|
+
::File.basename(candidate_file, '.py')
|
66
|
+
else
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
class Provider < Chef::Provider
|
74
|
+
include PoiseApplicationPython::ServiceMixin
|
75
|
+
provides(:application_gunicorn)
|
76
|
+
|
77
|
+
def action_enable
|
78
|
+
notifying_block do
|
79
|
+
install_gunicorn
|
80
|
+
end
|
81
|
+
super
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def install_gunicorn
|
87
|
+
return unless new_resource.version
|
88
|
+
python_package 'gunicorn' do
|
89
|
+
python_from_parent new_resource
|
90
|
+
version new_resource.version if new_resource.version.is_a?(String)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def gunicorn_command_options
|
95
|
+
# Based on http://docs.gunicorn.org/en/latest/settings.html
|
96
|
+
[].tap do |cmd|
|
97
|
+
# What options are common enough to deal with here?
|
98
|
+
# %w{config backlog workers worker_class threads worker_connections timeout graceful_timeout keepalive}.each do |opt|
|
99
|
+
%w{config}.each do |opt|
|
100
|
+
val = new_resource.send(opt)
|
101
|
+
if val && !(val.respond_to?(:empty?) && val.empty?)
|
102
|
+
cmd_opt = opt.gsub(/_/, '-')
|
103
|
+
cmd << "--#{cmd_opt} #{Shellwords.escape(val)}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
# Can be given multiple times.
|
107
|
+
Array(new_resource.bind).each do |bind|
|
108
|
+
cmd << "--bind #{bind}" if bind
|
109
|
+
end
|
110
|
+
# --preload doesn't take an argument and the name doesn't match.
|
111
|
+
if new_resource.preload_app
|
112
|
+
cmd << '--preload'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# (see PoiseApplication::ServiceMixin#service_options)
|
118
|
+
def service_options(resource)
|
119
|
+
super
|
120
|
+
raise PoiseApplicationPython::Error.new("Unable to determine app module for #{new_resource}") unless new_resource.app_module
|
121
|
+
resource.command("#{new_resource.python} -m gunicorn.app.wsgiapp #{gunicorn_command_options.join(' ')} #{new_resource.app_module}")
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|