Commits (41)
......@@ -9,11 +9,11 @@ health-check:
- fab --version
- fab -f . --list-format=nested --list
tags:
- docker-images
- docker-generic
sonarqube:
image: ntdt/sonar-scanner:2.7
stage: quality
script:
- cd $CI_PROJECT_DIR && /opt/sonar-scanner/bin/sonar-scanner -Dsonar.host.url=$SONAR_HOST -Dsonar.login=$SONAR_TOKEN
tags:
- docker-images
- docker-generic
......@@ -10,12 +10,15 @@ This project adheres to http://semver.org/[Semantic Versioning].
=== Fixed
* Use Fabric built-in *always_use_pty* variable to set interactive mode
* Site name can now have whitespaces without breaking the shell execution string
* Add a new variable in conf file to select one or more languages (PO files) when building sites. Default: _fr_.
=== Added
* Add a Patternlab command to build the static style guide: `fab patternlab.build`.
* Add a Git command to verify repositories states and warn user if changes might be lost.
* Add a docker.clean command to clean docker's workspace (removes container and image).
== 2.0.0 - 2016/05/09
......
......@@ -35,9 +35,6 @@ Those are global project parameters mandatory to Drupalizer.
|This env var is defined by Fabric itself, and it's defaut value is True.
|You can easily pass it to False, addinf --no-pty to your fab command line. Useful in Jenkins jobs.
|_locale_
|If True, install the site in French.
|===
=== Site settings
......@@ -60,6 +57,9 @@ Those are global project parameters mandatory to Drupalizer.
|_site_profile_repo_
|The installation profile git repository, if applicable.
|_site_profile_branch_
|Branch you want to use to access to site makefile.
|_site_profile_makefile_
|The Drush Makefile to run to build the platform.
......@@ -88,7 +88,7 @@ Those are global project parameters mandatory to Drupalizer.
|The Drupal site directory (only for multisite). Default: _default_.
|_site_languages_
|Translation files you need to download when building new Drupal installation (separate with comma). Default: _fr_.
|Translation files that must be downloaded when building Drupal and activated during installation (separate with comma). Default: _fr_.
|===
......
from fabric.api import task, env, execute
from fabric.colors import red
from fabric.contrib.console import confirm
import behat
import deploy
import docker
from deploy import *
import drush
import behat
import patternlab
from .environments import e
from fabric.api import task, env, execute
from .environments import e
from deploy import migrate, provision, push
from fabric.colors import red
from fabric.contrib.console import confirm
@task
def init():
"""
Complete local installation process, used generally when building the docker image for install and configure Drupal.
"""
execute(docker.image_create)
execute(docker.container_start)
execute(drush.make, 'install')
execute(drush.site_install, host='root@{}'.format(env.container_ip))
execute(drush.aliases)
execute(behat.init, host='root@{}'.format(env.container_ip))
@task
def test(tags=''):
"""
Setup Behat and run the complete tests suite. Default output formatters: pretty and JUnit.
The JUnit report file is specified in the Behat configuration file. Default: tests/behat/out/behat.junit.xml.
:param tags Specific Behat tests tags to run.
"""
execute(behat.init)
if not tags:
execute(behat.run)
else:
execute(behat.run, tags='{}'.format(tags))
execute(behat.init, host='root@{}'.format(env.container_ip))
@task
def install():
"""
Run the full installation process.
"""
execute(drush.make, 'install')
execute(drush.site_install)
execute(behat.init)
@task
def update():
def release():
"""
Update the full codebase and run the availabe database updates.
Generate all artefacts related to a release process or a deployment process.
"""
execute(drush.make, 'update')
execute(drush.updatedb)
execute(behat.init)
execute(drush.archive_dump)
execute(drush.gen_doc)
@task
def release():
def test(tags=''):
"""
Generate all artefacts related to a release process or a deployment process.
Setup Behat and run the complete tests suite. Default output formatters: pretty and JUnit.
The JUnit report file is specified in the Behat configuration file. Default: tests/behat/out/behat.junit.xml.
:param tags Specific Behat tests tags to run.
"""
execute(behat.init)
execute(drush.archive_dump)
execute(drush.gen_doc)
if not tags:
execute(behat.run)
else:
execute(behat.run, tags='{}'.format(tags))
@task
def deploy(environment):
"""Deploy code and run database updates on a target Drupal environment.
def update():
"""
execute(provision, environment)
execute(push, environment, hosts=env.hosts)
execute(migrate, environment, hosts=env.hosts)
Update the full codebase and run the availabe database updates.
"""
execute(drush.make, 'update')
execute(drush.updatedb)
execute(behat.init)
from __future__ import unicode_literals
from fabric.api import task, roles, env
from fabric.colors import green
import helpers as h
@task
@roles('docker')
def init(rewrite=True):
......@@ -12,7 +14,6 @@ def init(rewrite=True):
:param role Default 'role' where to run the task
:param rewrite If the behat.yml file should be rewrited or not.
"""
role = 'docker'
workspace = env.docker_workspace
host = env.site_hostname
......@@ -24,18 +25,18 @@ def init(rewrite=True):
h.fab_run(role, 'sed -i "s@%DRUPAL_ROOT@{}@g" behat.yml'.format(site_root))
h.fab_run(role, 'sed -i "s@%URL@http://{}@g" behat.yml'.format(host))
h.fab_run(role, 'echo "127.0.0.1 {}" >> /etc/hosts'.format(host))
print green('Behat is now properly configured. The configuration file is {}/tests/behat/behat.yml'.format(workspace))
else:
print green('{}/tests/behat/behat.yml is already created.'.format(workspace))
print green('{}/tests/behat/behat.yml is already created.'.format(workspace))
@task()
@task
@roles('docker')
def install():
"""
Install behat
"""
role = 'docker'
workspace = env.docker_workspace
......@@ -44,6 +45,7 @@ def install():
h.fab_run(role, 'curl -s https://getcomposer.org/installer | php')
h.fab_run(role, 'php composer.phar install')
h.fab_run(role, 'ln -s bin/behat /usr/local/bin/behat')
print green('Behat has been properly installed with Composer in /usr/local/bin')
else:
print(green('Behat is already installed, no need for a new installation'))
......@@ -56,7 +58,6 @@ def run(tags='~@wip&&~@disabled&&~@test'):
Execute the complete Behat tests suite.
:param role Default 'role' where to run the task
"""
role = 'docker'
workspace = env.docker_workspace
......@@ -64,9 +65,10 @@ def run(tags='~@wip&&~@disabled&&~@test'):
# In the container behat is installed globaly, so check before install it inside the tests directory
if not h.fab_exists(role, '/usr/local/bin/behat') or not h.fab_exists(role, '../tests/behat/bin/behat'):
install()
# If the configuration file behat.yml doesn't exist, call behat_init before run the test.
if not h.fab_exists(role, '{}/tests/behat/behat.yml'.format(workspace)):
init()
with h.fab_cd(role, '{}/tests/behat'.format(workspace)):
h.fab_run(role, 'behat --format junit --format pretty --tags "{}" --colors'.format(tags))
from __future__ import unicode_literals
from fabric.api import task, roles, env
from fabric.colors import red, green
import helpers as h
@task
@roles('docker')
def db_import(filename, role='docker'):
......@@ -13,7 +15,6 @@ def db_import(filename, role='docker'):
:param filename: a full path to a gzipped sql dump.
"""
if h.fab_exists(role, filename):
print green('Database dump {} found.'.format(filename))
h.fab_run(role, 'zcat {} | mysql -u{} -p{} {}'.format(filename, env.site_db_user, env.site_db_pass, env.site_db_name))
......
......@@ -5,34 +5,34 @@ from fabric.api import env
# Project settings
env.project_name = ''
env.project_name = 'drupalizer-default'
env.workspace = path.join(path.dirname(__file__), path.pardir)
env.locale = False
# Site
env.site_root = '{}/src/drupal'.format(env.workspace)
env.site_name = ''
env.site_name = 'Default Drupalizer'
env.site_environment = 'local'
env.site_profile = ''
env.site_profile = 'standard'
env.site_profile_repo = ''
env.site_profile_makefile = ''
env.site_profile_makefile = 'drupalizer.make'
env.site_profile_branch= '7.x'
env.site_db_user = 'dev'
env.site_db_pass = 'dev'
env.site_db_host = 'localhost'
env.site_db_name = ''
env.site_hostname = ''
env.site_db_name = 'dev'
env.site_hostname = 'drupalizer.dev.local'
env.site_admin_user = 'admin'
env.site_admin_pass = 'admin'
env.site_subdir = 'default'
env.site_languages = 'fr'
env.site_languages = ''
# PatternLab
# Specify the PatternLab dir is you want the style guide to be generated
env.patternlab_dir = ''
......@@ -41,6 +41,7 @@ env.patternlab_dir = ''
env.db_dump = False
# Docker
env.docker_workspace = '/opt/sfl'
......@@ -49,13 +50,17 @@ env.bind_port = 8001
env.apache_user = 'www-data'
# Docker auto-added container IP
env.container_ip = '172.17.0.0'
env.container_ip = '172.17.0.2'
# Hook commands
# Example:
# env.hook_post_install = ['drush fra -y', 'drush cc all']
# env.hook_post_update = ['drush fra -y', 'drush cc all']
env.hook_post_install = ['drush fra -y', 'drush cc all']
env.hook_post_update = ['drush fra -y', 'drush cc all']
env.hook_post_install = []
env.hook_post_update = []
# Target environments definition
......
......@@ -17,13 +17,15 @@
#
from __future__ import unicode_literals
from fabric.api import task, env, local, run
import glob
import os
from fabric.api import task, env, local, run, execute, lcd
from fabric.colors import green
from fabric.utils import abort
import helpers as h
import os
import glob
def _set_hosts(environment):
......@@ -51,7 +53,9 @@ def _aegir_platform_name(target, environment):
"""
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)
......@@ -61,8 +65,10 @@ def _target_dir(environment):
: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')
......@@ -98,7 +104,6 @@ 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,10 +114,13 @@ def _get_archive_from_dir(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]
......@@ -120,7 +128,7 @@ 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))
local('rsync -a --exclude sites/*/settings.php --exclude sites/*/files src/drupal/ {}@{}:{}'.format(target.get('user'), target.get('host'), target_directory))
def _aegir_provision_platform(platform, aegir_path, aegir_destsrv):
......@@ -132,6 +140,7 @@ def _aegir_provision_platform(platform, aegir_path, aegir_destsrv):
"""
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')
......@@ -156,28 +165,36 @@ def _aegir_remove_platform_without_sites(target, environment, platform):
run('{}/remove-platforms {} {}'.format(aegir_path, environment, platform))
@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)
@task
def provision(environment, role='local'):
def provision(environment):
"""
Provision a Jenkins deployment.
This task loads the target environment and extract the archive to deploy.
:param environment The environment to deploy the site DEV, STAGE, PROD
:param role Tha fabric role to run the task.
"""
_set_hosts(environment)
artefact = _get_archive_from_dir(env.builddir)
with h.fab_cd(role, '{}/src'.format(env.workspace)):
with lcd('{}/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))
if os.path.exists(env.site_root):
local('rm -rf {}'.format(env.site_root))
# Extract the platform to deploy
h.fab_run(role, 'tar -xzf {}/{}'.format(env.builddir, artefact))
local('tar -xzf {}/{}'.format(env.builddir, artefact))
# Fast-check if the archive looks like a Drupal installation
if not h.fab_exists(role, '{}/src/drupal'.format(env.workspace)):
if not os.path.exists('{}/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)):
......@@ -186,12 +203,11 @@ def provision(environment, role='local'):
print(green('The platform {} is now ready to be deployed to the target environment {}.'.format(artefact,
environment)))
@task
def push(environment):
"""
Push the platform to the target environment.
:param environment: The target environment. It must match a valid Drush alias.
:param environment: The target environment. It must match a valid Drush alias.
"""
target = env.aliases.get(environment)
target_directory = _target_dir(environment)
......@@ -217,6 +233,7 @@ def migrate(environment):
if _is_aegir_deployment(target):
# Deploy to Aegir server.
platform = _aegir_platform_name(target, environment)
if env.get('migrate', "false") == "true":
_aegir_migrate_sites(target, environment, platform)
......
from __future__ import unicode_literals
from fabric.api import task, roles, env, local, run
from fabric.api import task, roles, env, local, run, lcd, execute
from fabric.colors import red, green
from fabric.contrib.console import confirm
import helpers as h
###########################################################
# Helper functions to manage docker images and containers #
###########################################################
def docker_ps(running_only=False):
args = '' if running_only else '-a'
result = local('docker ps %s' % args, capture=True)
......@@ -22,9 +26,11 @@ def docker_tryrun(imgname, containername=None, opts='', mounts=None, cmd='', res
# Returns True if the container was effectively ran (false if it was restarted or aborted)
if not mounts:
mounts = []
if containername and containername in docker_ps(running_only=True):
print green("%s already running" % containername)
return False
if containername and containername in docker_ps(running_only=False):
if restart:
print green("%s already exists and is stopped. Restarting!" % containername)
......@@ -34,16 +40,20 @@ def docker_tryrun(imgname, containername=None, opts='', mounts=None, cmd='', res
print red("There's a dangling container %s! That's not supposed to happen. Aborting" % containername)
print "Run 'docker rm %s' to remove that container" % containername
return False
for from_path, to_path, canwrite in mounts:
abspath = from_path
opt = ' -v %s:%s' % (abspath, to_path)
if not canwrite:
opt += ':ro'
opts += opt
if containername:
containername_opt = '--name %s' % containername
else:
containername_opt = ''
local('docker run %s %s %s %s' % (opts, containername_opt, imgname, cmd))
return True
......@@ -70,8 +80,10 @@ def docker_ensure_data_container(containername, volume_paths=None, base_image='b
volume_args = ' '.join('-v %s' % volpath for volpath in volume_paths)
else:
volume_args = ''
local('docker create %s --name %s %s' % (volume_args, containername, base_image))
return True
return False
......@@ -86,69 +98,66 @@ def docker_isrunning(containername):
def docker_images():
result = local('docker images', capture=True)
lines = result.stdout.splitlines()
# image name is supposed to be the first column
assert lines[0].strip().startswith('REPOSITORY')
return [line.strip().split(' ')[0] for line in lines]
@task
@roles('local')
def connect(role='local'):
def connect():
"""
Connect to a docker container using "docker -it exec <name> bash".
This is a better way to connect to the container than using ssh'
:param role Default 'role' where to run the task
"""
with h.fab_cd(role, env.workspace):
with lcd(env.workspace):
if docker_isrunning('{}_container'.format(env.project_name)):
h.fab_run(role, 'docker exec -it {}_container bash'.format(env.project_name))
local('docker exec -it {}_container bash'.format(env.project_name))
else:
print(red('Docker container {}_container is not running, it should be running to be able to connect.'))
@task
@roles('local')
def image_create(role='local'):
def image_create():
"""
Create docker images
:param role Default 'role' where to run the task
"""
with h.fab_cd(role, env.workspace):
with lcd(env.workspace):
if '{}/drupal'.format(env.project_name) in docker_images():
print(red('Docker image {}/drupal was found, you has already build this image'.format(env.project_name)))
else:
h.copy_public_ssh_keys(role)
h.fab_run(role, 'docker build -t {}/drupal .'.format(env.project_name))
h.copy_public_ssh_keys('local')
local('docker build -t {}/drupal .'.format(env.project_name))
print(green('Docker image {}/drupal was build successful'.format(env.project_name)))
@task
@roles('local')
def container_start(role='local'):
def container_start():
"""
Run docker containers
:param role Default 'role' where to run the task
"""
with h.fab_cd(role, env.workspace):
with lcd(env.workspace):
if '{}/drupal'.format(env.project_name) in docker_images():
if docker_tryrun('{}/drupal'.format(env.project_name),
'{}_container'.format(env.project_name),
'-d -p {}:80'.format(env.bind_port),
mounts=[(env.workspace, env.docker_workspace, True)]):
# If container was successful build, get the IP address and show it to the user.
env.container_ip = h.fab_run(role, 'docker inspect -f "{{{{.NetworkSettings.IPAddress}}}}" '
env.container_ip = local('docker inspect -f "{{{{.NetworkSettings.IPAddress}}}}" '
'{}_container'.format(env.project_name), capture=True)
if env.get('always_use_pty', True):
h.fab_update_hosts(env.container_ip, env.site_hostname)
print(green('Docker container {}_container was build successful. '
'To visit the Website open a web browser in http://{} or '
'http://localhost:{}.'.format(env.project_name, env.site_hostname, env.bind_port)))
h.fab_update_container_ip(env.container_ip)
else:
print(red('Docker image {}/drupal not found and is a requirement to run the {}_container.'
'Please, run first "fab create" in order to build the {}/drupal '
......@@ -157,16 +166,16 @@ def container_start(role='local'):
@task
@roles('local')
def container_stop(role='local'):
def container_stop():
"""
Stop docker containers
:param role Default 'role' where to run the task
"""
with h.fab_cd(role, env.workspace):
with lcd(env.workspace):
if '{}_container'.format(env.project_name) in docker_ps():
if env.get('always_use_pty', True):
h.fab_remove_from_hosts(env.site_hostname)
h.fab_run(role, 'docker stop {}_container'.format(env.project_name))
local('docker stop {}_container'.format(env.project_name))
print(green('Docker container {}_container was successful stopped'.format(env.project_name)))
else:
print(red('Docker container {}_container was not running or paused'.format(env.project_name)))
......@@ -174,55 +183,69 @@ def container_stop(role='local'):
@task
@roles('local')
def container_remove(role='local'):
def container_remove():
"""
Stop docker containers
:param role Default 'role' where to run the task
"""
with h.fab_cd(role, env.workspace):
with lcd(env.workspace):
if '{}_container'.format(env.project_name) in docker_ps():
if env.get('always_use_pty', True):
h.fab_remove_from_hosts(env.site_hostname)
h.fab_run(role, 'docker rm -f {}_container'.format(env.project_name))
local('docker rm -f {}_container'.format(env.project_name))
print(green('Docker container {}_container was successful removed'.format(env.project_name)))
else:
print(red('Docker container {}_container was already removed'.format(env.project_name)))
@task()
@task
@roles('local')
def image_remove(role='local'):
def image_remove():
"""
Remove docker container and images
:param role Default 'role' where to run the task
"""
with h.fab_cd(role, env.workspace):
with lcd(env.workspace):
if docker_isrunning('{}_container'.format(env.project_name)):
print(red('Docker container {}_container is running, '
'you should stopped it after remove the image {}/drupal'.format(env.project_name,
env.project_name)))
if '{}/drupal'.format(env.project_name) in docker_images():
h.fab_run(role, 'docker rmi -f {}/drupal'.format(env.project_name))
local('docker rmi -f {}/drupal'.format(env.project_name))
# Remove dangling docker images to free space.
if '<none>' in docker_images():
h.fab_run(role, 'docker images --filter="dangling=true" -q | xargs docker rmi -f')
local('docker images --filter="dangling=true" -q | xargs docker rmi -f')
print(green('Docker image {}/drupal was successful removed'.format(env.project_name)))
else:
print(red('Docker image {}/drupal was not found'.format(env.project_name)))
@task
@roles('local')
def clean():
"""
Clean docker workspace (removes container & images)
"""
if (confirm(
red('This will stop, remove container and delete docker image ' +
'related to this project. Do you want to continue?'),
default=False)):
execute(container_stop)
execute(container_remove)
execute(image_remove)
@task
@roles('docker')
def update_host():
"""
Update hostname resolution in the container.
"""
site_hostname = run("hostname")
run("sed '/{}/c\{} {} localhost.domainlocal' "
"/etc/hosts > /root/hosts.backup".format(env.container_ip, env.container_ip, site_hostname))
run("cat /root/hosts.backup > /etc/hosts")
h.fab_update_container_ip()
......@@ -17,17 +17,17 @@
#
from __future__ import unicode_literals
from fabric.api import task, roles, env
from fabric.contrib.console import confirm
from datetime import datetime
from fabric.api import task, roles, env, local
from fabric.colors import red, green
from fabric.contrib.console import confirm
from fabric.utils import abort
from datetime import datetime
import helpers as h
import core as c
import helpers as h
from git import isGitDirty
from git import is_git_dirty
@task(alias='make')
......@@ -36,9 +36,8 @@ def make(action='install'):
"""
Build the platform by running the Makefile specified in the local_vars.py configuration file.
"""
if env.get('always_use_pty', True):
if (isGitDirty()):
if (is_git_dirty()):
if (not confirm(red('There are warnings on status of your repositories. '
'Do you want to continue and reset all changes to remote repositories'' states?'), default=False)):
abort('Aborting "drush {}" since there might be a risk of loosing local data.'.format(action))
......@@ -47,21 +46,24 @@ def make(action='install'):
# Update profile codebase
if env.site_profile and env.site_profile != '' and not h.is_core_profile(env.site_profile):
drush_opts += "--contrib-destination=profiles/{} ".format(env.site_profile)
h.update_profile()
if not env.get('always_use_pty', True):
drush_opts += "--translations=" + env.site_languages + " "
elif confirm(red('Say [Y] to {} the site at {} with the specified translation(s): {}. If you say [n] '
'the site will be installed in English only'.format(action, env.site_root, env.site_languages))):
drush_opts += "--translations=" + env.site_languages + " "
if env.site_languages:
if not env.get('always_use_pty', True):
drush_opts += "--translations=" + env.site_languages + " "
elif confirm(red('Say [Y] to {} the site at {} with the specified translation(s): {}. If you say [n] '
'the site will be installed in English only'.format(action, env.site_root, env.site_languages))):
drush_opts += "--translations=" + env.site_languages + " "
if env.get('always_use_pty', True):
drush_opts += " --working-copy --no-gitinfofile"
if not h.fab_exists('local', env.site_root):
h.fab_run('local', "mkdir {}".format(env.site_root))
local('mkdir {}'.format(env.site_root))
with h.fab_cd('local', env.site_root):
h.fab_run('local', 'drush make {} {} -y'.format(drush_opts, env.makefile))
local('drush make {} {} -y'.format(drush_opts, env.makefile))
@task
......@@ -70,17 +72,18 @@ def aliases():
"""
Copy conf/aliases.drushrc.php in the site environment.
"""
role = 'docker'
drush_aliases = env.site_drush_aliases
workspace = env.docker_workspace
if not h.fab_exists(role, drush_aliases):
h.fab_run(role, 'mkdir -p {}'.format(drush_aliases))
with h.fab_cd(role, drush_aliases):
# Create aliases
if h.fab_exists(role, '{}/aliases.drushrc.php'.format(drush_aliases)):
h.fab_run(role, 'rm {}/aliases.drushrc.php'.format(drush_aliases))
h.fab_run(role, 'cp {}/conf/aliases.drushrc.php .'.format(workspace))
print(green('Drush aliases have been copied to {} directory.'.format(drush_aliases)))
......@@ -92,9 +95,7 @@ def updatedb():
"""
Run the available database updates. Similar to drush updatedb.
"""
role = 'docker'
with h.fab_cd(role, env.docker_site_root):
h.fab_run(role, 'drush updatedb -y')
h.hook_execute(env.hook_post_update, role)
......@@ -106,7 +107,6 @@ def site_install():
"""
Run the site installation procedure.
"""
role = 'docker'
site_root = env.docker_site_root
apache = env.apache_user
......@@ -127,7 +127,10 @@ def site_install():
h.fab_run(role, 'chown {}:{} ./sites'.format(env.apache_user, env.apache_user))
h.fab_run(role, 'chmod ug+w ./sites/default'.format(env.docker_site_root))
locale = '--locale="fr"' if env.locale else ''
if env.site_languages:
locale = '--locale="{}"'.format(env.site_languages.split(',')[0])
else:
locale = ''
h.fab_run(role, 'sudo -u {} drush site-install {} {} --db-url=mysql://{}:{}@{}/{} --site-name="{}" '
'--account-name={} --account-pass={} --sites-subdir={} -y'.format(apache, profile, locale,
......@@ -153,18 +156,15 @@ def archive_dump(role='docker'):
Archive the platform for release or deployment.
:param role Default 'role' where to run the task
"""
with h.fab_cd(role, env.docker_site_root):
platform = '{}-{}.tar.gz'.format(env.project_name, datetime.now().strftime('%Y%m%d_%H%M%S'))
h.fab_run(role, 'rm -f {}/build/*.tar.gz'.format(env.docker_workspace))
print(green('All tar.gz archives found in {}/build have been deleted.'.format(env.docker_workspace)))
h.fab_run(
role,
'drush archive-dump --destination={}/build/{} --tags="sflinux {}" --generatorversion="2.x" '
'--generator="Drupalizer::fab drush.archive_dump" --tar-options="--exclude=.git"'
''.format(env.docker_workspace, platform, env.project_name)
)
h.fab_run(role,
'drush archive-dump --destination={}/build/{} --tags="sflinux {}" --generatorversion="2.x" '
'--generator="Drupalizer::fab drush.archive_dump" --tar-options="--exclude=.git"'
''.format(env.docker_workspace, platform, env.project_name))
@task
......@@ -174,13 +174,14 @@ def gen_doc(role='docker'):
Generate README file
:param role Default 'role' where to run the task
"""
if h.fab_exists(role, '{}/README.adoc'.format(env.docker_workspace)):
h.fab_run(role, 'asciidoctor -d book -b html5 -o {}/README.html {}/README.adoc'.
format(env.docker_workspace, env.docker_workspace))
print(green('README.html generated in {}'.format(env.docker_workspace)))
if h.fab_exists(role, '{}/CHANGELOG.adoc'.format(env.docker_workspace)):
h.fab_run(role, 'asciidoctor -d book -b html5 -o {}/CHANGELOG.html {}/CHANGELOG.adoc'.
format(env.docker_workspace, env.docker_workspace))
print(green('CHANGELOG.html generated in {}'.format(env.docker_workspace)))
......@@ -2,6 +2,7 @@ from importlib import import_module
from fabric.utils import abort
def e(name):
"""
Set your environment before running other commands
......
import re
import time
from os import path
from fabric.api import task, env, execute
from fabric.api import task, env, local
from fabric.colors import red, green, yellow
from fabric.api import task, env, execute
from fabric.contrib.console import confirm
from .environments import e
import helpers as h
import time
import re
from .environments import e
@task(alias='is_dirty')
def check_status():
"""
Check workspace's git repositories status.
"""
if (isGitDirty()):
if (is_git_dirty()):
print red('Your workspace is not clean.')
else:
print green('Your workspace is clean.')
def isGitDirty():
def is_git_dirty():
repos = local('find ' + path.normpath(env.workspace) + ' -name ".git"', capture=True).splitlines()
nbWarnings = 0
for repo in repos:
repoLocalPath = path.normpath(path.join(repo, '..'))
nbWarnings += _checkRepo(repoLocalPath)
nbWarnings += _check_repo(repoLocalPath)
return (nbWarnings > 0)
......@@ -39,7 +42,7 @@ def isGitDirty():
# - attention aux conflits, faire un pull d'abord et valider la fusion automatique
def _checkRepo(repoLocalPath):
def _check_repo(repoLocalPath):
nbWarnings = 0
with h.fab_cd('local', repoLocalPath):
print green('---')
......@@ -47,21 +50,21 @@ def _checkRepo(repoLocalPath):
remoteName = local('git remote', capture=True)
filesStatusRawInfo = _getFilesStatusInformation()
filesStatusRawInfo = _get_files_status_information()
print green('Verify local files status against current HEAD commit...')
nbWarnings += _checkFilesStatusVsHeadCommit(filesStatusRawInfo)
nbWarnings += _check_files_status_vs_head_commit(filesStatusRawInfo)
localBranchesRawInfo = _getLocalBranchesInformation()
localBranchesRawInfo = _get_local_branches_information()
print green('Verify local branches exist on remote "' + remoteName + '"...');
nbWarnings += _checkLocalBranchesExistOnRemote(localBranchesRawInfo, remoteName)
nbWarnings += _check_local_branches_exist_on_remote(localBranchesRawInfo, remoteName)
print green('Verify branches status against remote...');
nbWarnings += _checkLocalBranchesStatusVsRemote(localBranchesRawInfo, remoteName)
nbWarnings += _check_local_branches_status_vs_remote(localBranchesRawInfo, remoteName)
return nbWarnings
def _checkFilesStatusVsHeadCommit(filesStatusRawInfo):
def _check_files_status_vs_head_commit(filesStatusRawInfo):
nbWarnings = 0
addableFiles = []
if (len(filesStatusRawInfo) > 0):
......@@ -70,8 +73,10 @@ def _checkFilesStatusVsHeadCommit(filesStatusRawInfo):
# Break loop if filename is "fabfile"
if fileStatusData[1] == 'fabfile':
break
nbWarnings += 1
addableFiles.append(fileStatusData[1])
print yellow('File "' + fileStatusData[1] + '" ' + {
'M': 'has un-commited modifications.',
'D': 'has been deleted.',
......@@ -80,12 +85,13 @@ def _checkFilesStatusVsHeadCommit(filesStatusRawInfo):
return nbWarnings
def _checkLocalBranchesExistOnRemote(localBranchesRawInfo, remoteName):
def _check_local_branches_exist_on_remote(localBranchesRawInfo, remoteName):
nbWarnings = 0
pushableBranches = []
for localBranchRawInfo in localBranchesRawInfo:
localBranchName = _getBranchName(localBranchRawInfo)
if ((localBranchName is not None) and (not _remoteBranchExists(localBranchName))):
localBranchName = _get_branch_name(localBranchRawInfo)
if ((localBranchName is not None) and (not _remote_branch_exists(localBranchName))):
nbWarnings += 1
pushableBranches.append(localBranchName)
print yellow('Local branch "' + localBranchName + '" is not present on "' + remoteName + '" remote.')
......@@ -95,12 +101,14 @@ def _checkLocalBranchesExistOnRemote(localBranchesRawInfo, remoteName):
if (confirm(red('There are many local branches not present on remote. Do you want to sync theses?'), default=False)):
for branchName in pushableBranches:
local('git push --set-upstream ' + remoteName + ' ' + branchName)
# Do not alert with diff as local branches are now pushed
nbWarnings = 0
return nbWarnings
def _checkLocalBranchesStatusVsRemote(localBranchesRawInfo, remoteName):
def _check_local_branches_status_vs_remote(localBranchesRawInfo, remoteName):
nbWarnings = 0
pushableBranches = []
# on gere un git FR ou EN, a voir ce qu'on peut faire
......@@ -108,7 +116,7 @@ def _checkLocalBranchesStatusVsRemote(localBranchesRawInfo, remoteName):
pattern = re.compile('.*\[.* (ahead|en avance de) .*\].*')
for localBranchRawInfo in localBranchesRawInfo:
if (pattern.match(localBranchRawInfo)):
localBranchName = _getBranchName(localBranchRawInfo)
localBranchName = _get_branch_name(localBranchRawInfo)
nbWarnings += 1
pushableBranches.append(localBranchName)
print yellow('Local branch "' + localBranchName + '" is ahead of remote branch.');
......@@ -118,31 +126,36 @@ def _checkLocalBranchesStatusVsRemote(localBranchesRawInfo, remoteName):
if (confirm(red('There are many local branches which are ahead of remote branch. Do you want to sync theses?'), default=False)):
for branchName in pushableBranches:
local('git push ' + remoteName + ' ' + branchName)
# Do not alert with diff as local branches are now pushed
nbWarnings = 0
# TO DO : PUSHER LA BRANCHE SUR LE REPO
# TODO
# PUSHER LA BRANCHE SUR LE REPO
return nbWarnings
def _getLocalBranchesInformation():
def _get_local_branches_information():
return local('git branch --list -vv', capture=True).splitlines()
def _getBranchName(branchRawData):
def _get_branch_name(branchRawData):
branchName = branchRawData.replace('*', '').strip()
if (_isBranchDetached(branchName)):
if (_is_branch_detached(branchName)):
return None
else:
return branchName.split()[0]
def _isBranchDetached(branchName):
def _is_branch_detached(branchName):
pattern = re.compile('\(.*\)')
return pattern.match(branchName)
def _remoteBranchExists(branchName):
def _remote_branch_exists(branchName):
return (len(local('git branch --list --remote "*' + branchName + '"', capture=True).splitlines()) > 0)
def _getFilesStatusInformation():
def _get_files_status_information():
return local('git status -s', capture=True).splitlines()
......@@ -17,19 +17,18 @@
#
from __future__ import unicode_literals
import socket
from getpass import getuser
from fabric.api import lcd, cd, roles, local, run
from fabric.colors import green
from fabric.contrib.console import confirm
from fabric.contrib.files import exists
# Import socket to find the localhost IP address
import socket
# Import default variables
from default_vars import *
# Import local variables' overrides, if they exist
if path.exists(path.join(path.dirname(__file__), 'local_vars.py')):
from local_vars import *
......@@ -58,16 +57,16 @@ else:
env.makefile = path.join(env.builddir, env.site_profile, env.site_profile_makefile)
env.site_drush_aliases = path.join(env.docker_site_root, 'sites/all/drush')
def fab_run(role="local", cmd="", capture=False):
def fab_run(role="local", cmd=""):
"""
Helper function to run the task locally or remotely
:param role: the role to use for define the host
:param cmd: the command to execute
:param capture: if it should return or not the output of the command
:return: the function to execute the command locally or remotely
"""
if role == "local":
return local(cmd, capture)
return local(cmd)
else:
return run(cmd)
......@@ -105,9 +104,13 @@ def fab_add_to_hosts(ip, site_hostname):
:param site_hostname:
:return:
"""
if not site_hostname:
return
if confirm(green('Do you want add to the /etc/hosts the line "{} {}"? '
'If you say yes you will be able to visit the site using a more frienldy url '
'"http://{}".'.format(ip, site_hostname, site_hostname))):
# Add if not find the comment "# Docker auto-added host" to the file /etc/hosts
local('grep "# Docker auto-added host" /etc/hosts > /dev/null || '
'sudo sed -i "$ a # Docker auto-added host" /etc/hosts')
......@@ -122,6 +125,9 @@ def fab_remove_from_hosts(site_hostname):
:param site_hostname:
:return:
"""
if not site_hostname:
return
print(green('Enter your password to remove the {} from your /etc/hosts file'.format(site_hostname)))
local('sudo sed -i "/{}/d" /etc/hosts'.format(site_hostname))
......@@ -155,12 +161,10 @@ def hook_execute(cmds=env.hook_post_install, role='docker'):
def copy_public_ssh_keys(role='local'):
"""
Copy your public SSH keys to use it in the docker container to connect to it using ssh protocol.
:param role Default 'role' where to run the task
"""
with fab_cd(role, env.workspace):
fab_run(role, 'cp ~/.ssh/id_rsa.pub conf/')
print green('Public SSH key copied successful to {}/conf directory'.format(env.workspace))
......@@ -183,13 +187,12 @@ def update_profile(role='local'):
@roles('docker')
def init_db(role='docker'):
"""
Create a database and a user that can access it.
"""
container_ip = fab_run('local', 'docker inspect -f "{{{{.NetworkSettings.IPAddress}}}}" '
container_ip = local('docker inspect -f "{{{{.NetworkSettings.IPAddress}}}}" '
'{}_container'.format(env.project_name), capture=True)
docker_iface_ip = [(s.connect((container_ip, 80)), s.getsockname()[0], s.close())
for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]
......
from __future__ import unicode_literals
from fabric.api import task, roles, env
from fabric.api import task, roles, env, local, lcd
from fabric.colors import green
import helpers as h
@task
@roles('local')
def build():
"""
Generate the PatternLab static site
"""
role = 'local'
workspace = env.docker_workspace
host = env.site_hostname
site_root = env.docker_site_root
with h.fab_cd(role, env.patternlab_dir):
h.fab_run(role, 'touch public/styleguide/html/styleguide.html')
h.fab_run(role, 'php core/builder.php -g')
with lcd(env.patternlab_dir):
local('touch public/styleguide/html/styleguide.html')
local('php core/builder.php -g')