2020-03-09 10:11:07 +01:00
|
|
|
#!/usr/bin/python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
# (c) 2013, Scott Anderson <scottanderson42@gmail.com>
|
|
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__metaclass__ = type
|
|
|
|
|
|
|
|
|
|
|
|
DOCUMENTATION = '''
|
|
|
|
---
|
|
|
|
module: django_manage
|
|
|
|
short_description: Manages a Django application.
|
|
|
|
description:
|
|
|
|
- Manages a Django application using the I(manage.py) application frontend to I(django-admin). With the I(virtualenv) parameter, all
|
|
|
|
management commands will be executed by the given I(virtualenv) installation.
|
|
|
|
options:
|
|
|
|
command:
|
|
|
|
choices: [ 'cleanup', 'collectstatic', 'flush', 'loaddata', 'migrate', 'runfcgi', 'syncdb', 'test', 'validate', ]
|
|
|
|
description:
|
|
|
|
- The name of the Django management command to run. Built in commands are cleanup, collectstatic, flush, loaddata, migrate, runfcgi, syncdb,
|
|
|
|
test, and validate.
|
|
|
|
- Other commands can be entered, but will fail if they're unknown to Django. Other commands that may prompt for user input should be run
|
|
|
|
with the I(--noinput) flag.
|
|
|
|
required: true
|
|
|
|
app_path:
|
|
|
|
description:
|
|
|
|
- The path to the root of the Django application where B(manage.py) lives.
|
|
|
|
required: true
|
|
|
|
settings:
|
|
|
|
description:
|
|
|
|
- The Python path to the application's settings module, such as 'myapp.settings'.
|
|
|
|
required: false
|
|
|
|
pythonpath:
|
|
|
|
description:
|
|
|
|
- A directory to add to the Python path. Typically used to include the settings module if it is located external to the application directory.
|
|
|
|
required: false
|
|
|
|
virtualenv:
|
|
|
|
description:
|
|
|
|
- An optional path to a I(virtualenv) installation to use while running the manage application.
|
|
|
|
aliases: [virtualenv]
|
|
|
|
apps:
|
|
|
|
description:
|
|
|
|
- A list of space-delimited apps to target. Used by the 'test' command.
|
|
|
|
required: false
|
|
|
|
cache_table:
|
|
|
|
description:
|
|
|
|
- The name of the table used for database-backed caching. Used by the 'createcachetable' command.
|
|
|
|
required: false
|
|
|
|
clear:
|
|
|
|
description:
|
|
|
|
- Clear the existing files before trying to copy or link the original file.
|
|
|
|
- Used only with the 'collectstatic' command. The C(--noinput) argument will be added automatically.
|
|
|
|
required: false
|
|
|
|
default: no
|
|
|
|
type: bool
|
|
|
|
database:
|
|
|
|
description:
|
2020-10-17 14:45:21 +02:00
|
|
|
- The database to target. Used by the C(createcachetable), C(flush), C(loaddata), C(syncdb), and C(migrate) I(command).
|
2020-03-09 10:11:07 +01:00
|
|
|
required: false
|
|
|
|
failfast:
|
|
|
|
description:
|
|
|
|
- Fail the command immediately if a test fails. Used by the 'test' command.
|
|
|
|
required: false
|
|
|
|
default: "no"
|
|
|
|
type: bool
|
|
|
|
fixtures:
|
|
|
|
description:
|
|
|
|
- A space-delimited list of fixture file names to load in the database. B(Required) by the 'loaddata' command.
|
|
|
|
required: false
|
|
|
|
skip:
|
|
|
|
description:
|
|
|
|
- Will skip over out-of-order missing migrations, you can only use this parameter with I(migrate)
|
|
|
|
required: false
|
|
|
|
type: bool
|
|
|
|
merge:
|
|
|
|
description:
|
|
|
|
- Will run out-of-order or missing migrations as they are not rollback migrations, you can only use this parameter with 'migrate' command
|
|
|
|
required: false
|
|
|
|
type: bool
|
|
|
|
link:
|
|
|
|
description:
|
|
|
|
- Will create links to the files instead of copying them, you can only use this parameter with 'collectstatic' command
|
|
|
|
required: false
|
|
|
|
type: bool
|
|
|
|
notes:
|
|
|
|
- I(virtualenv) (U(http://www.virtualenv.org)) must be installed on the remote host if the virtualenv parameter is specified.
|
|
|
|
- This module will create a virtualenv if the virtualenv parameter is specified and a virtualenv does not already exist at the given location.
|
|
|
|
- This module assumes English error messages for the 'createcachetable' command to detect table existence, unfortunately.
|
|
|
|
- To be able to use the migrate command with django versions < 1.7, you must have south installed and added as an app in your settings.
|
|
|
|
- To be able to use the collectstatic command, you must have enabled staticfiles in your settings.
|
|
|
|
- As of ansible 2.x, your I(manage.py) application must be executable (rwxr-xr-x), and must have a valid I(shebang), i.e. "#!/usr/bin/env python",
|
|
|
|
for invoking the appropriate Python interpreter.
|
|
|
|
requirements: [ "virtualenv", "django" ]
|
|
|
|
author: "Scott Anderson (@tastychutney)"
|
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = """
|
2020-05-15 12:27:06 +02:00
|
|
|
- name: Run cleanup on the application installed in django_dir
|
2020-07-13 21:50:31 +02:00
|
|
|
community.general.django_manage:
|
2020-03-09 10:11:07 +01:00
|
|
|
command: cleanup
|
|
|
|
app_path: "{{ django_dir }}"
|
|
|
|
|
2020-05-15 12:27:06 +02:00
|
|
|
- name: Load the initial_data fixture into the application
|
2020-07-13 21:50:31 +02:00
|
|
|
community.general.django_manage:
|
2020-03-09 10:11:07 +01:00
|
|
|
command: loaddata
|
|
|
|
app_path: "{{ django_dir }}"
|
|
|
|
fixtures: "{{ initial_data }}"
|
|
|
|
|
2020-05-15 12:27:06 +02:00
|
|
|
- name: Run syncdb on the application
|
2020-07-13 21:50:31 +02:00
|
|
|
community.general.django_manage:
|
2020-03-09 10:11:07 +01:00
|
|
|
command: syncdb
|
|
|
|
app_path: "{{ django_dir }}"
|
|
|
|
settings: "{{ settings_app_name }}"
|
|
|
|
pythonpath: "{{ settings_dir }}"
|
|
|
|
virtualenv: "{{ virtualenv_dir }}"
|
|
|
|
|
2020-05-15 12:27:06 +02:00
|
|
|
- name: Run the SmokeTest test case from the main app. Useful for testing deploys
|
2020-07-13 21:50:31 +02:00
|
|
|
community.general.django_manage:
|
2020-03-09 10:11:07 +01:00
|
|
|
command: test
|
|
|
|
app_path: "{{ django_dir }}"
|
|
|
|
apps: main.SmokeTest
|
|
|
|
|
2020-05-15 12:27:06 +02:00
|
|
|
- name: Create an initial superuser
|
2020-07-13 21:50:31 +02:00
|
|
|
community.general.django_manage:
|
2020-03-09 10:11:07 +01:00
|
|
|
command: "createsuperuser --noinput --username=admin --email=admin@example.com"
|
|
|
|
app_path: "{{ django_dir }}"
|
|
|
|
"""
|
|
|
|
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
|
|
|
|
|
|
|
|
def _fail(module, cmd, out, err, **kwargs):
|
|
|
|
msg = ''
|
|
|
|
if out:
|
|
|
|
msg += "stdout: %s" % (out, )
|
|
|
|
if err:
|
|
|
|
msg += "\n:stderr: %s" % (err, )
|
|
|
|
module.fail_json(cmd=cmd, msg=msg, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_virtualenv(module):
|
|
|
|
|
|
|
|
venv_param = module.params['virtualenv']
|
|
|
|
if venv_param is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
vbin = os.path.join(venv_param, 'bin')
|
|
|
|
activate = os.path.join(vbin, 'activate')
|
|
|
|
|
|
|
|
if not os.path.exists(activate):
|
|
|
|
virtualenv = module.get_bin_path('virtualenv', True)
|
|
|
|
vcmd = '%s %s' % (virtualenv, venv_param)
|
|
|
|
vcmd = [virtualenv, venv_param]
|
|
|
|
rc, out_venv, err_venv = module.run_command(vcmd)
|
|
|
|
if rc != 0:
|
|
|
|
_fail(module, vcmd, out_venv, err_venv)
|
|
|
|
|
|
|
|
os.environ["PATH"] = "%s:%s" % (vbin, os.environ["PATH"])
|
|
|
|
os.environ["VIRTUAL_ENV"] = venv_param
|
|
|
|
|
|
|
|
|
2020-09-29 08:17:27 +02:00
|
|
|
def createcachetable_check_changed(output):
|
|
|
|
return "already exists" not in output
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
|
|
|
|
def flush_filter_output(line):
|
|
|
|
return "Installed" in line and "Installed 0 object" not in line
|
|
|
|
|
|
|
|
|
|
|
|
def loaddata_filter_output(line):
|
|
|
|
return "Installed" in line and "Installed 0 object" not in line
|
|
|
|
|
|
|
|
|
|
|
|
def syncdb_filter_output(line):
|
|
|
|
return ("Creating table " in line) or ("Installed" in line and "Installed 0 object" not in line)
|
|
|
|
|
|
|
|
|
|
|
|
def migrate_filter_output(line):
|
|
|
|
return ("Migrating forwards " in line) or ("Installed" in line and "Installed 0 object" not in line) or ("Applying" in line)
|
|
|
|
|
|
|
|
|
|
|
|
def collectstatic_filter_output(line):
|
|
|
|
return line and "0 static files" not in line
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
command_allowed_param_map = dict(
|
|
|
|
cleanup=(),
|
|
|
|
createcachetable=('cache_table', 'database', ),
|
|
|
|
flush=('database', ),
|
|
|
|
loaddata=('database', 'fixtures', ),
|
|
|
|
syncdb=('database', ),
|
|
|
|
test=('failfast', 'testrunner', 'liveserver', 'apps', ),
|
|
|
|
validate=(),
|
|
|
|
migrate=('apps', 'skip', 'merge', 'database',),
|
|
|
|
collectstatic=('clear', 'link', ),
|
|
|
|
)
|
|
|
|
|
|
|
|
command_required_param_map = dict(
|
|
|
|
loaddata=('fixtures', ),
|
|
|
|
)
|
|
|
|
|
|
|
|
# forces --noinput on every command that needs it
|
|
|
|
noinput_commands = (
|
|
|
|
'flush',
|
|
|
|
'syncdb',
|
|
|
|
'migrate',
|
|
|
|
'test',
|
|
|
|
'collectstatic',
|
|
|
|
)
|
|
|
|
|
|
|
|
# These params are allowed for certain commands only
|
|
|
|
specific_params = ('apps', 'clear', 'database', 'failfast', 'fixtures', 'liveserver', 'testrunner')
|
|
|
|
|
|
|
|
# These params are automatically added to the command if present
|
|
|
|
general_params = ('settings', 'pythonpath', 'database',)
|
|
|
|
specific_boolean_params = ('clear', 'failfast', 'skip', 'merge', 'link')
|
|
|
|
end_of_command_params = ('apps', 'cache_table', 'fixtures')
|
|
|
|
|
|
|
|
module = AnsibleModule(
|
|
|
|
argument_spec=dict(
|
|
|
|
command=dict(default=None, required=True),
|
|
|
|
app_path=dict(default=None, required=True, type='path'),
|
|
|
|
settings=dict(default=None, required=False),
|
|
|
|
pythonpath=dict(default=None, required=False, aliases=['python_path']),
|
|
|
|
virtualenv=dict(default=None, required=False, type='path', aliases=['virtual_env']),
|
|
|
|
|
|
|
|
apps=dict(default=None, required=False),
|
|
|
|
cache_table=dict(default=None, required=False),
|
|
|
|
clear=dict(default=None, required=False, type='bool'),
|
|
|
|
database=dict(default=None, required=False),
|
2020-06-22 15:56:35 +02:00
|
|
|
failfast=dict(default=False, required=False, type='bool', aliases=['fail_fast']),
|
2020-03-09 10:11:07 +01:00
|
|
|
fixtures=dict(default=None, required=False),
|
|
|
|
liveserver=dict(default=None, required=False, aliases=['live_server']),
|
|
|
|
testrunner=dict(default=None, required=False, aliases=['test_runner']),
|
|
|
|
skip=dict(default=None, required=False, type='bool'),
|
|
|
|
merge=dict(default=None, required=False, type='bool'),
|
|
|
|
link=dict(default=None, required=False, type='bool'),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
command = module.params['command']
|
|
|
|
app_path = module.params['app_path']
|
|
|
|
virtualenv = module.params['virtualenv']
|
|
|
|
|
|
|
|
for param in specific_params:
|
|
|
|
value = module.params[param]
|
|
|
|
if param in specific_boolean_params:
|
|
|
|
value = module.boolean(value)
|
|
|
|
if value and param not in command_allowed_param_map[command]:
|
|
|
|
module.fail_json(msg='%s param is incompatible with command=%s' % (param, command))
|
|
|
|
|
|
|
|
for param in command_required_param_map.get(command, ()):
|
|
|
|
if not module.params[param]:
|
|
|
|
module.fail_json(msg='%s param is required for command=%s' % (param, command))
|
|
|
|
|
|
|
|
_ensure_virtualenv(module)
|
|
|
|
|
|
|
|
cmd = "./manage.py %s" % (command, )
|
|
|
|
|
|
|
|
if command in noinput_commands:
|
|
|
|
cmd = '%s --noinput' % cmd
|
|
|
|
|
|
|
|
for param in general_params:
|
|
|
|
if module.params[param]:
|
|
|
|
cmd = '%s --%s=%s' % (cmd, param, module.params[param])
|
|
|
|
|
|
|
|
for param in specific_boolean_params:
|
|
|
|
if module.boolean(module.params[param]):
|
|
|
|
cmd = '%s --%s' % (cmd, param)
|
|
|
|
|
|
|
|
# these params always get tacked on the end of the command
|
|
|
|
for param in end_of_command_params:
|
|
|
|
if module.params[param]:
|
|
|
|
cmd = '%s %s' % (cmd, module.params[param])
|
|
|
|
|
|
|
|
rc, out, err = module.run_command(cmd, cwd=app_path)
|
|
|
|
if rc != 0:
|
|
|
|
if command == 'createcachetable' and 'table' in err and 'already exists' in err:
|
2020-09-29 08:17:27 +02:00
|
|
|
out = 'already exists.'
|
2020-03-09 10:11:07 +01:00
|
|
|
else:
|
|
|
|
if "Unknown command:" in err:
|
|
|
|
_fail(module, cmd, err, "Unknown django command: %s" % command)
|
|
|
|
_fail(module, cmd, out, err, path=os.environ["PATH"], syspath=sys.path)
|
|
|
|
|
|
|
|
changed = False
|
|
|
|
|
|
|
|
lines = out.split('\n')
|
|
|
|
filt = globals().get(command + "_filter_output", None)
|
|
|
|
if filt:
|
|
|
|
filtered_output = list(filter(filt, lines))
|
|
|
|
if len(filtered_output):
|
|
|
|
changed = True
|
2020-09-29 08:17:27 +02:00
|
|
|
check_changed = globals().get("{0}_check_changed".format(command), None)
|
|
|
|
if check_changed:
|
|
|
|
changed = check_changed(out)
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
module.exit_json(changed=changed, out=out, cmd=cmd, app_path=app_path, virtualenv=virtualenv,
|
|
|
|
settings=module.params['settings'], pythonpath=module.params['pythonpath'])
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|