7.4 KiB
7.4 KiB
autoscale: true build-lists: true hide-footer: true
[fit] Deploying PHP
applications
*(and anything else)
*with Fabric
[.build-lists: false]
opdavies
- Web Developer
- System Administrator
- Senior Developer at Microserve
- Drupal, Symfony, Silex, Sculpin
^ Not a Python Developer
What is Fabric*?*
- Python CLI tool
- Runs commands on local and remote hosts
- Flexible
- Combine multiple scripts
^ Previously had multiple scripts (pre, post build) per project
Installing Fabric
$ pip install fabric
# macOS
$ brew install fabric
# Debian, Ubuntu
$ apt-get install fabric
$ apt-get install python-fabric
Writing your first fabfile
# fabfile.py
from fabric.api import env, run, cd
env.hosts = ['example.com']
env.use_ssh_config = True
project_dir = '/var/www/html'
# Do stuff...
# fabfile.py
from fabric.api import *
env.hosts = ['example.com']
env.use_ssh_config = True
project_dir = '/var/www/html'
# Do stuff...
# fabfile.py
from fabric.api import *
env.hosts = ['example.com']
env.use_ssh_config = True
project_dir = '/var/www/html'
# Do stuff...
# fabfile.py
from fabric.api import *
env.hosts = ['example.com']
env.use_ssh_config = True
project_dir = '/var/www/html'
# Do stuff...
# fabfile.py
from fabric.api import *
env.hosts = ['example.com']
env.use_ssh_config = True
project_dir = '/var/www/html'
# Do stuff...
Adding Tasks
Adding tasks
def build():
with cd('/var/www/html'):
run('git pull')
run('composer install')
Adding parameters to tasks
def build(run_composer=True):
with cd('/var/www/html'):
run('git pull')
if run_composer:
run('composer install')
Adding parameters to tasks
def build(run_composer=True, env='prod'):
with cd('/var/www/html'):
run('git pull')
if run_composer:
if env == 'prod':
run('composer install --no-dev')
else:
run('composer install')
Calling other tasks
@task
def build():
with cd('/var/www/html'):
run('git pull')
run('composer install')
drupal_post_install_tasks()
def drupal_post_install_tasks():
run('drush updatedb -y')
run('drush entup -y')
run('drush cache-rebuild')
Running Tasks
$ fab --list
$ fab <task>
$ fab <task> --fabfile=/path/to/fabfile
$ fab <task>:build_number=$BUILD_ID,build_type=drupal
^ fabfile.py in the current directory is found automatically.
Building Front-End Assets
def build_assets(run_gulp=True):
with cd('themes/custom/example'):
run('yarn --pure-lockfile')
if run_gulp:
run('node_modules/.bin/gulp --production')
^ lcd = change directory locally
Error reporting
print '===> Checking the site is alive.'
if run('drush status | egrep "Connected|Successful"').failed:
print 'Could not connect to the database.'
print '===> Building the site.'
if run('vendor/bin/sculpin generate').return_code == 0:
print 'Site built. Yay! :)'
Making Fabfiles smarter
Conditional tasks
def build_assets():
with cd('themes/custom/example'):
if exists('package.json') and not exists('node_modules'):
run('yarn --pure-lockfile')
if exists('gulpfile.js'):
print '===> Building front-end assets with Gulp...'
run('node_modules/.bin/gulp --production')
elif exists('gruntfile.js'):
print '===> Building front-end assets with Grunt...'
run('node_modules/.bin/grunt build')
Project settings file
# app.yml
drupal:
config: { import: yes, name: sync }
root: web
theme:
build:
npm: no
type: gulp
yarn: yes
path: 'themes/custom/drupalbristol'
version: 8
composer:
install: true
Project settings file
# fabfile.py
from fabric.api import *
import yaml
with open('app.yml', 'r') as file:
config = yaml.load(file.read())
Project settings file
# fabfile.py
...
theme_config = config['theme']
with cd(theme_config['path']):
if theme_config['build']['npm'] == True:
print '===> Installing dependencies via npm.'
run('npm install')
elif theme_config['build']['yarn'] == True:
print '===> Installing dependencies via yarn.'
run('yarn --pure-lockfile')
Project settings file v2
# app.yml
commands:
build: |
cd web/themes/custom/drupalbristol
yarn --pure-lockfile
node_modules/.bin/gulp --production
deploy: |
cd web
drush cache-rebuild -y
Project settings file v2
# fabfile.py
for hook in config['commands'].get('build', '').split("\n"):
run(hook)
...
for hook in config['commands'].get('deploy', '').split("\n"):
run(hook)
Building on Prod == :(
[.build-lists: false]
Not Building on Prod
- Build locally and deploy.
- Build in a separate directory and switch after build.
Running Tasks Locally
# Runs remotely.
from fabric.api import run
run('git pull')
run('composer install')
# Runs locally.
from fabric.api import local
local('git pull')
local('composer install')
rsync
from fabric.contrib.project import rsync_project
...
def deploy():
rsync_project(
local_dir='./',
remote_dir='/var/www/html'
default_opts='-vzcrSLh',
exclude=('.git', 'node_modules/', '.sass-cache/')
)
[.build-lists: false]
Not Building on Prod
Build locally and deploy.- Build in a separate directory and switch after build.
Deploying into a different directory
from fabric.api import *
from time import time
project_dir = '/var/www/html'
next_release = "%(time).0f" % { 'time': time() }
def init():
if not exists(project_dir):
run('mkdir -p %s/shared' % project_dir)
run('mkdir -p %s/releases' % project_dir)
Deploying into a different directory
current_release = '%s/%s' % (releases_dir, next_release)
run('git clone %s %s' % (git_repo, current_release))
def build():
with cd(current_release):
build_site_pre_tasks()
build_site()
build_site_post_tasks()
Deploying into a different directory
def update_symlinks():
run('ln -nfs %s/releases/%s %s/current'
% (project_dir, next_release, project_dir))
# /var/www/html/current
Other things
- Database backup pre-build
- Rollback on failure
- Run Drush/console/artisan commands
- Verify file permissions
- Restart services
- Anything you can do on the command line...
[.build-lists: false]
- https://www.oliverdavies.uk/talks/deploying-php-fabric
- http://fabfile.org
- https://github.com/opdavies/fabric-example-sculpin
- https://github.com/opdavies/fabric-example-drupal
- https://deploy.serversforhackers.com