Commits (36)
......@@ -6,6 +6,19 @@ This project adheres to http://semver.org/[Semantic Versioning].
== [Unreleased]
* Add a "tags" argument to the "test" command in order to limit which scenarios are run by behat.
=== Fixed
* Site name can now have whitespaces without breaking the shell execution string
=== 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.
== 2.0.0 - 2016/05/09
=== Added
* Implements Aegir and standard deployments as Fabric tasks
......
......@@ -31,8 +31,9 @@ Those are global project parameters mandatory to Drupalizer.
|_workspace_
|Project root directory.
|_interactive_mode_
|If false, no prompt. Useful for Jenkins.
|_always_use_pty_
|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.
......@@ -88,6 +89,16 @@ Those are global project parameters mandatory to Drupalizer.
|===
=== Patternlab settings
|===
|Parameters |Description
|_patternlab_dir_
|The Patternlab directory. Default: _empty_
|===
=== Docker-related settings
|===
......@@ -194,3 +205,7 @@ CAUTION: This command will wipe all the modifications made in the working direct
* _Archive_ the full codebase and the database using drush archive_dump:
$ fab drush.archive_dump
* _Generate_ the guide style:
$ fab patternlab.build
......@@ -2,11 +2,13 @@ import docker
from deploy import *
import drush
import behat
import patternlab
from .environments import e
from fabric.api import task, env, execute
import helpers as h
from fabric.colors import red
from fabric.contrib.console import confirm
@task
def init():
......@@ -24,16 +26,19 @@ def init():
@task
def test():
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 tag Specific Behat tests tags to run.
:param tags Specific Behat tests tags to run.
"""
execute(behat.init)
execute(behat.run)
if not tags:
execute(behat.run)
else:
execute(behat.run, tags='{}'.format(tags))
......
......@@ -51,7 +51,7 @@ def install():
@task
@roles('docker')
def run():
def run(tags='~@wip&&~@disabled&&~@test'):
"""
Execute the complete Behat tests suite.
:param role Default 'role' where to run the task
......@@ -68,8 +68,5 @@ def run():
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 "~@wip&&~@disabled&&~@test" --colors')
# To run behat with only one test for example, comment previous line
# and uncomment next one
# fab_run(role, 'behat --format pretty --tags "~@wip&&~@disabled&&@yourTest" --colors')
h.fab_run(role, 'behat --format junit --format pretty --tags "{}" --colors'.format(tags))
......@@ -18,6 +18,7 @@ env.site_environment = 'local'
env.site_profile = ''
env.site_profile_repo = ''
env.site_profile_makefile = ''
env.site_profile_branch= '7.x'
env.site_db_user = 'dev'
env.site_db_pass = 'dev'
env.site_db_host = 'localhost'
......@@ -26,10 +27,18 @@ env.site_hostname = ''
env.site_admin_user = 'admin'
env.site_admin_pass = 'admin'
env.site_subdir = 'default'
env.site_languages = 'fr'
# PatternLab
# Specify the PatternLab dir is you want the style guide to be generated
env.patternlab_dir = ''
# Database dump
# To enable it, replace the boolean value with the absolute path of a gzipped SQL dump file.
env.db_dump = False
# Docker
......
......@@ -141,7 +141,7 @@ def container_start(role='local'):
# 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}}}}" '
'{}_container'.format(env.project_name), capture=True)
if env.get('interactive_mode', True) != 'false':
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. '
......@@ -165,7 +165,7 @@ def container_stop(role='local'):
"""
with h.fab_cd(role, env.workspace):
if '{}_container'.format(env.project_name) in docker_ps():
if env.get('interactive_mode', True) != 'false':
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))
print(green('Docker container {}_container was successful stopped'.format(env.project_name)))
......@@ -183,7 +183,7 @@ def container_remove(role='local'):
with h.fab_cd(role, env.workspace):
if '{}_container'.format(env.project_name) in docker_ps():
if env.get('interactive_mode', True) != 'false':
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))
......
......@@ -20,12 +20,15 @@ from __future__ import unicode_literals
from fabric.api import task, roles, env
from fabric.contrib.console import confirm
from fabric.colors import red, green
from fabric.utils import abort
from datetime import datetime
import helpers as h
import core as c
from git import isGitDirty
@task(alias='make')
@roles('local')
......@@ -34,6 +37,12 @@ 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 (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))
drush_opts = "--prepare-install " if action != 'update' else ''
# Update profile codebase
......@@ -41,20 +50,19 @@ def make(action='install'):
drush_opts += "--contrib-destination=profiles/{} ".format(env.site_profile)
h.update_profile()
if env.get('interactive_mode', True) == 'false':
drush_opts += "--translations=fr "
elif confirm(red('Say [Y] to {} the site at {} with the French translation, if you say [n] '
'the site will be installed in English only'.format(action, env.site_root))):
drush_opts += "--translations=fr "
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('interactive_mode', True) != 'false':
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))
with h.fab_cd('local', env.site_root):
h.fab_run('local', 'drush make {} {} -y'.format(drush_opts, env.makefile))
@task
@roles('local')
def aliases():
......@@ -117,7 +125,7 @@ def site_install():
with h.fab_cd(role, site_root):
locale = '--locale="fr"' if env.locale else ''
h.fab_run(role, 'sudo -u {} drush site-install {} {} --db-url=mysql://{}:{}@{}/{} --site-name={} '
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,
db_user, db_pass,
db_host, db_name, site_name,
......
from os import path
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
@task(alias='is_dirty')
def check_status():
"""
Check workspace's git repositories status.
"""
if (isGitDirty()):
print red('Your workspace is not clean.')
else:
print green('Your workspace is clean.')
def isGitDirty():
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)
return (nbWarnings > 0)
# STEP 2
# plutot que s'arreter ou continuer betement, on est intelligent et on demande quoi faire:
# - s'il y a du code non-stage, en faire un commit
# - si la Branch n'est pas trackee, la pusher
# - s'il y a des commits non pushes, les pusher
# - attention aux conflits, faire un pull d'abord et valider la fusion automatique
def _checkRepo(repoLocalPath):
nbWarnings = 0
with h.fab_cd('local', repoLocalPath):
print green('---')
print green('Verify repo in ' + repoLocalPath)
remoteName = local('git remote', capture=True)
filesStatusRawInfo = _getFilesStatusInformation()
print green('Verify local files status against current HEAD commit...')
nbWarnings += _checkFilesStatusVsHeadCommit(filesStatusRawInfo)
localBranchesRawInfo = _getLocalBranchesInformation()
print green('Verify local branches exist on remote "' + remoteName + '"...');
nbWarnings += _checkLocalBranchesExistOnRemote(localBranchesRawInfo, remoteName)
print green('Verify branches status against remote...');
nbWarnings += _checkLocalBranchesStatusVsRemote(localBranchesRawInfo, remoteName)
return nbWarnings
def _checkFilesStatusVsHeadCommit(filesStatusRawInfo):
nbWarnings = 0
addableFiles = []
if (len(filesStatusRawInfo) > 0):
for fileStatus in filesStatusRawInfo:
fileStatusData = fileStatus.split()
# 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.',
'??': 'is not indexed.',
}.get(fileStatusData[0], 'is in an unknown state (' + fileStatusData[0] + ')'))
return nbWarnings
def _checkLocalBranchesExistOnRemote(localBranchesRawInfo, remoteName):
nbWarnings = 0
pushableBranches = []
for localBranchRawInfo in localBranchesRawInfo:
localBranchName = _getBranchName(localBranchRawInfo)
if ((localBranchName is not None) and (not _remoteBranchExists(localBranchName))):
nbWarnings += 1
pushableBranches.append(localBranchName)
print yellow('Local branch "' + localBranchName + '" is not present on "' + remoteName + '" remote.')
# On suggere de pusher la(les) branche(s) qui sont seulement sur le local
if (nbWarnings > 0):
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):
nbWarnings = 0
pushableBranches = []
# on gere un git FR ou EN, a voir ce qu'on peut faire
# pour une internationalisation plus cool..
pattern = re.compile('.*\[.* (ahead|en avance de) .*\].*')
for localBranchRawInfo in localBranchesRawInfo:
if (pattern.match(localBranchRawInfo)):
localBranchName = _getBranchName(localBranchRawInfo)
nbWarnings += 1
pushableBranches.append(localBranchName)
print yellow('Local branch "' + localBranchName + '" is ahead of remote branch.');
# On suggere de pusher la(les) branche(s) qui sont seulement sur le local
if (nbWarnings > 0):
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
return nbWarnings
def _getLocalBranchesInformation():
return local('git branch --list -vv', capture=True).splitlines()
def _getBranchName(branchRawData):
branchName = branchRawData.replace('*', '').strip()
if (_isBranchDetached(branchName)):
return None
else:
return branchName.split()[0]
def _isBranchDetached(branchName):
pattern = re.compile('\(.*\)')
return pattern.match(branchName)
def _remoteBranchExists(branchName):
return (len(local('git branch --list --remote "*' + branchName + '"', capture=True).splitlines()) > 0)
def _getFilesStatusInformation():
return local('git status -s', capture=True).splitlines()
......@@ -169,7 +169,7 @@ def update_profile(role='local'):
print green('{} installation profile updated in {}/{}'.format(env.site_profile, env.builddir, env.site_profile))
else:
with fab_cd(role, env.builddir):
fab_run(role, 'git clone {} {}'.format(env.site_profile_repo, env.site_profile))
fab_run(role, 'git clone --branch={} {} {}'.format(env.site_profile_branch, env.site_profile_repo, env.site_profile))
print green('{} installation profile cloned in {}/{}'.format(env.site_profile, env.builddir, env.site_profile))
......
from __future__ import unicode_literals
from fabric.api import task, roles, env
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')