django_manage 10.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#!/usr/bin/python
# -*- coding: utf-8 -*-

# (c) 2013, Scott Anderson <scottanderson42@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
#

DOCUMENTATION = '''
---
24
module: django_manage
25 26
short_description: Manages a Django application.
description:
27
     - 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.
28 29 30
version_added: "1.1"
options:
  command:
31
    choices: [ 'cleanup', 'flush', 'loaddata', 'runfcgi', 'syncdb', 'test', 'validate', 'migrate', 'collectstatic' ]
32
    description:
33
      - The name of the Django management command to run. Allowed commands are cleanup, createcachetable, flush, loaddata, syncdb, test, validate.
34 35 36
    required: true
  app_path:
    description:
37
      - The path to the root of the Django application where B(manage.py) lives.
38 39 40
    required: true
  settings:
    description:
41
      - The Python path to the application's settings module, such as 'myapp.settings'.
42 43 44
    required: false
  pythonpath:
    description:
45
      - A directory to add to the Python path. Typically used to include the settings module if it is located external to the application directory.
46 47 48
    required: false
  virtualenv:
    description:
49
      - An optional path to a I(virtualenv) installation to use while running the manage application.
50 51 52
    required: false
  apps:
    description:
53
      - A list of space-delimited apps to target. Used by the 'test' command.
54
    required: false
55
  cache_table:
56
    description:
57
      - The name of the table used for database-backed caching. Used by the 'createcachetable' command.
58
    required: false
59
  database:
60
    description:
61
      - The database to target. Used by the 'createcachetable', 'flush', 'loaddata', and 'syncdb' commands.
62 63 64
    required: false
  failfast:
    description:
65
      - Fail the command immediately if a test fails. Used by the 'test' command.
66
    required: false
67 68
    default: "no"
    choices: [ "yes", "no" ]
69 70
  fixtures:
    description:
71
      - A space-delimited list of fixture file names to load in the database. B(Required) by the 'loaddata' command.
72
    required: false
73 74 75 76 77 78 79 80 81 82 83 84
  skip:
    description:
     - Will skip over out-of-order missing migrations, you can only use this parameter with I(migrate)
    required: false
  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
  link:
    description:
     - Will create links to the files instead of copying them, you can only use this parameter with 'collectstatic' command
    required: false
85
notes:
86
   - I(virtualenv) (U(http://www.virtualenv.org)) must be installed on the remote host if the virtualenv parameter is specified.
Scott Anderson committed
87
   - This module will create a virtualenv if the virtualenv parameter is specified and a virtualenv does not already exist at the given location.
88
   - This module assumes English error messages for the 'createcachetable' command to detect table existence, unfortunately.
89 90
   - To be able to use the migrate command, 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
91
requirements: [ "virtualenv", "django" ]
92 93 94
author: Scott Anderson
'''

95
EXAMPLES = """
96 97
# Run cleanup on the application installed in 'django_dir'.
- django_manage: command=cleanup app_path={{ django_dir }}
98

99 100
# Load the initial_data fixture into the application
- django_manage: command=loaddata app_path={{ django_dir }} fixtures={{ initial_data }}
101 102

#Run syncdb on the application
103
- django_manage: >
104
      command=syncdb
105
      app_path={{ django_dir }}
106
      settings={{ settings_app_name }}
107 108
      pythonpath={{ settings_dir }}
      virtualenv={{ virtualenv_dir }}
109 110

#Run the SmokeTest test case from the main app. Useful for testing deploys.
111
- django_manage: command=test app_path=django_dir apps=main.SmokeTest
112 113 114
"""


115 116 117 118 119 120 121 122 123 124 125 126
import os

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):
127

128
    venv_param = module.params['virtualenv']
129 130
    if venv_param is None:
        return
131

132
    vbin = os.path.join(os.path.expanduser(venv_param), 'bin')
133 134 135
    activate = os.path.join(vbin, 'activate')

    if not os.path.exists(activate):
136
        virtualenv = module.get_bin_path('virtualenv', True)
137 138 139 140 141 142 143 144
        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"])

145 146 147
def createcachetable_filter_output(line):
    return "Already exists" not in line

148 149 150 151 152 153 154 155 156
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)

157 158 159
def migrate_filter_output(line):
    return ("Migrating forwards " in line) or ("Installed" in line and "Installed 0 object" not in line)

160 161 162
def main():
    command_allowed_param_map = dict(
        cleanup=(),
163
        createcachetable=('cache_table', 'database', ),
164 165 166 167 168
        flush=('database', ),
        loaddata=('database', 'fixtures', ),
        syncdb=('database', ),
        test=('failfast', 'testrunner', 'liveserver', 'apps', ),
        validate=(),
169
        migrate=('apps', 'skip', 'merge'),
170
        collectstatic=('link', ),
171 172 173 174
        )

    command_required_param_map = dict(
        loaddata=('fixtures', ),
175
        createcachetable=('cache_table', ),
176 177 178 179 180 181
        )

    # forces --noinput on every command that needs it
    noinput_commands = (
        'flush',
        'syncdb',
182
        'migrate',
183
        'test',
184
        'collectstatic',
185 186 187
        )

    # These params are allowed for certain commands only
188
    specific_params = ('apps', 'database', 'failfast', 'fixtures', 'liveserver', 'testrunner')
189 190

    # These params are automatically added to the command if present
191
    general_params = ('settings', 'pythonpath', 'database',)
192 193
    specific_boolean_params = ('failfast', 'skip', 'merge', 'link')
    end_of_command_params = ('apps', 'cache_table', 'fixtures')
194 195

    module = AnsibleModule(
196
        argument_spec=dict(
197
            command     = dict(default=None, required=True),
198 199 200 201 202 203 204 205 206 207 208 209
            app_path    = dict(default=None, required=True),
            settings    = dict(default=None, required=False),
            pythonpath  = dict(default=None, required=False, aliases=['python_path']),
            virtualenv  = dict(default=None, required=False, aliases=['virtual_env']),

            apps        = dict(default=None, required=False),
            cache_table = dict(default=None, required=False),
            database    = dict(default=None, required=False),
            failfast    = dict(default='no', required=False, choices=BOOLEANS, aliases=['fail_fast']),
            fixtures    = dict(default=None, required=False),
            liveserver  = dict(default=None, required=False, aliases=['live_server']),
            testrunner  = dict(default=None, required=False, aliases=['test_runner']),
210 211 212
            skip        = dict(default=None, required=False, choices=BOOLEANS),
            merge       = dict(default=None, required=False, choices=BOOLEANS),
            link        = dict(default=None, required=False, choices=BOOLEANS),
213 214 215 216 217 218 219 220 221 222 223 224 225 226
        ),
    )

    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))

227 228 229
    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))
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255

    venv = module.params['virtualenv']

    _ensure_virtualenv(module)

    os.chdir(app_path)
    cmd = "python 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)
    if rc != 0:
256 257 258
        if command == 'createcachetable' and 'table' in err and 'already exists' in err:
            out = 'Already exists.'
        else:
259 260
            if "Unknown command:" in err:
                _fail(module, cmd, err, "Unknown django command: %s" % command)
261
            _fail(module, cmd, out, err, path=os.environ["PATH"], syspath=sys.path)
262 263 264 265 266 267 268 269 270 271 272 273 274

    changed = False

    lines = out.split('\n')
    filt = globals().get(command + "_filter_output", None)
    if filt:
        filtered_output = filter(filt, out.split('\n'))
        if len(filtered_output):
            changed = filtered_output

    module.exit_json(changed=changed, out=out, cmd=cmd, app_path=app_path, virtualenv=virtualenv,
                     settings=module.params['settings'], pythonpath=module.params['pythonpath'])

275
# import module snippets
276
from ansible.module_utils.basic import *
277 278

main()