deploy.py 8.6 KB
Newer Older
Emmanuel Milou's avatar
Emmanuel Milou committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# coding: utf-8
#
# Copyright (C) 2016 Savoir-faire Linux Inc. (<www.savoirfairelinux.com>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program 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 Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

Ernesto Rodriguez Ortiz's avatar
Ernesto Rodriguez Ortiz committed
19
from __future__ import unicode_literals
20
21
22
23

import glob
import os

24
from fabric.api import task, env, local, run, execute, lcd
25
from fabric.colors import green
Emmanuel Milou's avatar
Emmanuel Milou committed
26
from fabric.utils import abort
Ernesto Rodriguez Ortiz's avatar
Ernesto Rodriguez Ortiz committed
27
28
29

import helpers as h

30

31
def _set_hosts(environment):
32
33
    """
    Set the hosts Fabric environment variable with the target environment.
34
    :param environment This is the host that will be used to run SSH commands on.
35
    """
Emmanuel Milou's avatar
Emmanuel Milou committed
36
37
38
    if environment not in env.aliases:
        abort('Environment {} could not be found in the aliases definition.'.format(environment))

39
40
    target = env.aliases.get(environment)
    env.hosts = ['{}@{}'.format(target.get('user'), target.get('host'))]
Emmanuel Milou's avatar
Emmanuel Milou committed
41
42


43
44
45
46
47
def _is_aegir_deployment(target):
    """
    Check if the target environment is an Aegir server.
    """
    return False if 'aegir' not in target or target.get('aegir') is False else True
48
49
50


def _aegir_platform_name(target, environment):
51
52
53
54
55
    """
    Build the platform needed by Aegir, from the placeholder in configuration file.
    """
    if 'aegir_platform' not in target:
        abort('Aegir needs a unique platform name to function properly. Check your aegir_platform key in your aliases.')
56

57
    aegir_platform = target.get('aegir_platform')
58

59
60
61
62
63
64
65
66
67
    return aegir_platform.format(name=env.project_name, env=environment, build=env.build_number)


def _target_dir(environment):
    """
    Return the target directory to deploy the site.
    :param environment
    """
    target = env.aliases.get(environment)
68

69
70
    if _is_aegir_deployment(target):
        return target.get('root') + _aegir_platform_name(target, environment)
71

72
73
74
75
76
77
78
79
    return target.get('root')


def _set_site_offline(target, environment):
    """
    Helper function to set the site in maintenance.
    :param environment
    """
80
    run('drush --yes --root={}  vset site_offline 1 || true'.format(target.get('root')))
81
82
    print(green('The is in maintenance mode on the target environment {}.'.format(environment)))

Emmanuel Milou's avatar
Emmanuel Milou committed
83

84
85
86
87
88
def _set_site_online(target, environment):
    """
    Helper function to set the site online.
    :param environment
    """
89
    run('drush --yes --root={} vset site_offline 0 || true'.format(target.get('root')))
90
91
92
93
94
95
96
97
    print(green('The site is online on the target environment {}.'.format(environment)))


def _update_site_database(target, environment):
    """
    Helper function to update site database.
    :param environment
    """
98
    run('drush --yes --root={} updatedb || true'.format(target.get('root')))
99
100
101
102
103
104
105
106
107
108
    print(green('The target environment {} is up-to-date.'.format(environment)))


def _clear_site_cache(target, environment):
    """
    Helper function to clear site cache.
    :param environment
    """
    run('drush --yes --root={} cache-clear all'.format(target.get('root')))
    print(green('The cache have been cleared on the target environment {}.'.format(environment)))
109

Emmanuel Milou's avatar
Emmanuel Milou committed
110

111
112
113
114
115
116
def _get_archive_from_dir(directory):
    """
    List tarball archives in a directory.
    :param directory The directory used to untar the artefact.
    """
    files = glob.glob1(directory, '*.tar.gz')
117

118
119
    if len(files) == 0:
        abort('No tarball found in {}'.format(directory))
120

121
122
    if len(files) > 1:
        abort('More than one tarball has been found in {}. Can not decide which one to deploy.'.format(directory))
123

124
125
126
127
128
129
130
    return files[0]


def _rsync_platform(target, target_directory):
    """
    Helper function to rsync platform to server.
    """
131
    local('rsync -e "ssh -o StrictHostKeyChecking=no" -a --delete --exclude sites/*/settings.php --exclude sites/*/files src/drupal/ {}@{}:{}'.format(target.get('user'), target.get('host'), target_directory))
132
133
134
135
136
137
138
139
140
141
142


def _aegir_provision_platform(platform, aegir_path, aegir_destsrv):
    """
    Provision the platform on Aegir
    :param platform The platform name
    :param aegir_path The path to the home of aegir, usually in /var/aegir
    :param aegir_destsrv The destination webserver for the platform.
    """
    run('drush --root="{}/platforms/{}" provision-save "@platform_{}" --context_type="platform" --web_server=@{}'
        .format(aegir_path, platform, platform, aegir_destsrv))
143

144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
    run('drush @hostmaster hosting-import platform_{}'.format(platform))
    run('drush @hostmaster hosting-dispatch')


def _aegir_migrate_sites(target, environment, platform):
    """
    Helper funtion to migrage sites in aegir after a deployment.
    :param environment
    :param platform The patern name of the platform in wich the sites will be migrated on
    """
    aegir_path = target.get('aegir_path')
    run('{}/migrate-sites {} {}'.format(aegir_path, environment, platform))


def _aegir_remove_platform_without_sites(target, environment, platform):
    """
    Helper funtion to remove platforms without sites in aegir after a deployment.
    :param environment
    :param platform The patern name of the platform in wich the sites will be migrated on
    """
    aegir_path = target.get('aegir_path')
165
    run('{}/remove-platforms {} {}'.format(aegir_path, environment, platform))
Emmanuel Milou's avatar
Emmanuel Milou committed
166
167


168
169
170
171
172
173
174
175
176
@task(default=True)
def deploy(environment):
    """Deploy code and run database updates on a target Drupal environment.
    """
    execute(provision, environment)
    execute(push, environment, hosts=env.hosts)
    execute(migrate, environment, hosts=env.hosts)


Emmanuel Milou's avatar
Emmanuel Milou committed
177
@task
178
def provision(environment):
179
180
    """
    Provision a Jenkins deployment.
181
    This task loads the target environment and extract the archive to deploy.
182
    :param environment The environment to deploy the site DEV, STAGE, PROD
183
    """
184
    _set_hosts(environment)
Emmanuel Milou's avatar
Emmanuel Milou committed
185

186
    artefact = _get_archive_from_dir(env.builddir)
Emmanuel Milou's avatar
Emmanuel Milou committed
187

188
    with lcd('{}/src'.format(env.workspace)):
Emmanuel Milou's avatar
Emmanuel Milou committed
189
        # Clear the currently installed platform
190
191
        if os.path.exists(env.site_root):
            local('rm -rf {}'.format(env.site_root))
192

Emmanuel Milou's avatar
Emmanuel Milou committed
193
        # Extract the platform to deploy
194
        local('tar -xzf {}/{}'.format(env.builddir, artefact))
195
196

        # Fast-check if the archive looks like a Drupal installation
197
        if not os.path.exists('{}/src/drupal'.format(env.workspace)):
Emmanuel Milou's avatar
Emmanuel Milou committed
198
199
200
201
202
            abort('The archive to deploy does not contain a drupal directory.')

        if not os.path.isfile('{}/src/drupal/cron.php'.format(env.workspace)):
            abort('The archive to deploy does not seem to contain a valid Drupal installation.')

203
204
        print(green('The platform {} is now ready to be deployed to the target environment {}.'.format(artefact,
                                                                                                       environment)))
Emmanuel Milou's avatar
Emmanuel Milou committed
205
206

@task
207
208
209
def push(environment):
    """
    Push the platform to the target environment.
210
    :param environment: The target environment. It must match a valid Drush alias.
Emmanuel Milou's avatar
Emmanuel Milou committed
211
212
    """
    target = env.aliases.get(environment)
213
    target_directory = _target_dir(environment)
Emmanuel Milou's avatar
Emmanuel Milou committed
214

215
216
217
    if _is_aegir_deployment(target):
        # Push platform to Aegir Server
        _rsync_platform(target, target_directory)
218
219
        platform = _aegir_platform_name(target, environment)
        _aegir_provision_platform(platform, target.get('aegir_path'), target.get('aegir_destsrv'))
220
221
222
223
    else:
        # Push platform to Web Server
        _set_site_offline(target, environment)
        _rsync_platform(target, target_directory)
Emmanuel Milou's avatar
Emmanuel Milou committed
224
225
226


@task
227
228
229
230
def migrate(environment):
    """
    Migrate the Drupal database on the target environment.
    :param environment: The target environment. It must match a valid Drush alias.
231
232
    """
    target = env.aliases.get(environment)
233
234
    if _is_aegir_deployment(target):
        # Deploy to Aegir server.
235
        platform = _aegir_platform_name(target, environment)
236

Philippe Mouchel's avatar
Philippe Mouchel committed
237
        if env.get('migrate', "false") == "true":
238
            _aegir_migrate_sites(target, environment, platform)
Emmanuel Milou's avatar
Emmanuel Milou committed
239

240
        if env.get('remove_platforms', "false") == "true":
241
242
243
244
245
246
            _aegir_remove_platform_without_sites(target, environment, platform)
    else:
        # Deploy to a Web Server
        _update_site_database(target, environment)
        _clear_site_cache(target, environment)
        _set_site_online(target, environment)