deploy.py 8.32 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
from fabric.api import task, env, local, run
from fabric.colors import green
Emmanuel Milou's avatar
Emmanuel Milou committed
22
from fabric.utils import abort
Ernesto Rodriguez Ortiz's avatar
Ernesto Rodriguez Ortiz committed
23
24

import helpers as h
25
26
import os
import glob
Ernesto Rodriguez Ortiz's avatar
Ernesto Rodriguez Ortiz committed
27
28


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

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


41
42
43
44
45
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
46
47
48


def _aegir_platform_name(target, environment):
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
    """
    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.')
    aegir_platform = target.get('aegir_platform')
    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)
    if _is_aegir_deployment(target):
        return target.get('root') + _aegir_platform_name(target, environment)
    return target.get('root')


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

Emmanuel Milou's avatar
Emmanuel Milou committed
77

78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
def _set_site_online(target, environment):
    """
    Helper function to set the site online.
    :param environment
    """
    run('drush --yes --root={} vset site_offline 0'.format(target.get('root')))
    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
    """
    run('drush --yes --root={} updatedb'.format(target.get('root')))
    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
    """
Emmanuel Milou's avatar
Emmanuel Milou committed
101

102
103
    run('drush --yes --root={} cache-clear all'.format(target.get('root')))
    print(green('The cache have been cleared on the target environment {}.'.format(environment)))
104

Emmanuel Milou's avatar
Emmanuel Milou committed
105

106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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')
    if len(files) == 0:
        abort('No tarball found in {}'.format(directory))
    if len(files) > 1:
        abort('More than one tarball has been found in {}. Can not decide which one to deploy.'.format(directory))
    return files[0]


def _rsync_platform(target, target_directory):
    """
    Helper function to rsync platform to server.
    """
    local('rsync -a src/drupal/ {}@{}:{}'.format(target.get('user'), target.get('host'), target_directory))


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))
    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')
    run('{}/remove-platform-wihout-sites {} {}'.format(aegir_path, environment, platform))
Emmanuel Milou's avatar
Emmanuel Milou committed
157
158
159
160


@task
def provision(environment, role='local'):
161
162
    """
    Provision a Jenkins deployment.
163
    This task loads the target environment and extract the archive to deploy.
164
165
    :param environment The environment to deploy the site DEV, STAGE, PROD
    :param role Tha fabric role to run the task.
166
    """
167
    _set_hosts(environment)
Emmanuel Milou's avatar
Emmanuel Milou committed
168

169
    artefact = _get_archive_from_dir(env.builddir)
Emmanuel Milou's avatar
Emmanuel Milou committed
170
171
172
173
174
175
176
177

    with h.fab_cd(role, '{}/src'.format(env.workspace)):

        # Clear the currently installed platform
        if h.fab_exists(role, env.site_root):
            h.fab_run(role, 'rm -rf {}'.format(env.site_root))
        # Extract the platform to deploy
        h.fab_run(role, 'tar -xzf {}/{}'.format(env.builddir, artefact))
178
179

        # Fast-check if the archive looks like a Drupal installation
Emmanuel Milou's avatar
Emmanuel Milou committed
180
181
182
183
184
185
        if not h.fab_exists(role, '{}/src/drupal'.format(env.workspace)):
            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.')

186
187
        print(green('The platform {} is now ready to be deployed to the target environment {}.'.format(artefact,
                                                                                                       environment)))
Emmanuel Milou's avatar
Emmanuel Milou committed
188
189
190


@task
191
192
193
194
def push(environment):
    """
    Push the platform to the target environment.
   :param environment: The target environment. It must match a valid Drush alias.
Emmanuel Milou's avatar
Emmanuel Milou committed
195
196
    """
    target = env.aliases.get(environment)
197
    target_directory = _target_dir(environment)
Emmanuel Milou's avatar
Emmanuel Milou committed
198

199
200
201
    if _is_aegir_deployment(target):
        # Push platform to Aegir Server
        _rsync_platform(target, target_directory)
202
203
        platform = _aegir_platform_name(target, environment)
        _aegir_provision_platform(platform, target.get('aegir_path'), target.get('aegir_destsrv'))
204
205
206
207
    else:
        # Push platform to Web Server
        _set_site_offline(target, environment)
        _rsync_platform(target, target_directory)
Emmanuel Milou's avatar
Emmanuel Milou committed
208
209
210


@task
211
212
213
214
def migrate(environment):
    """
    Migrate the Drupal database on the target environment.
    :param environment: The target environment. It must match a valid Drush alias.
215
216
    """
    target = env.aliases.get(environment)
217
218
    if _is_aegir_deployment(target):
        # Deploy to Aegir server.
219
        platform = _aegir_platform_name(target, environment)
220
221
        if env.get('migrate', "false") == "true":
            _aegir_migrate_sites(target, environment, platform)
Emmanuel Milou's avatar
Emmanuel Milou committed
222

223
224
225
226
227
228
229
        if env.get('remove_platform_without_sites', "false") == "true":
            _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)