Poorman's Pantheon and Wunderflow

Submitted by Calvin on Tue, 01/01/2019 - 14:58

This is a rough guide to set up and support a "wunderflow" style branching strategy with code sync to various environments with git. It will show how to create a Pantheon-like multidev experience on any server, using git hooks, as well as how to bypass Pantheon's "serial" code workflow and use a more robust "parallel" workflow. See: Parallel Principle of Git Workflow

The Env Setup below illustrates how this might be done on a single server with subdomains, plus additional notes on using MultiDev on https://pantheon.io

ENV SETUP

GIT REPOS

Create a Bare Repo

This will vary for the chosen hosting service. On Pantheon for example, we'd just create "multidev" environments for develop and stage branches, using the main master branch for live.

If you're configuring your own server, set up vhosts for your 3 environments and configure subdomains: dev.[project].tld, stg.[project].tld. Our master branch will deploy code for [project].tld.

vhosts should point to doc roots for each environment something like:

  • /var/www/dev = Development Testing
  • /var/www/stage = Staging for review and approval prior to release.
  • /var/www/html = Live

Now create a bare git repo to dispatch code to your "work trees".

  • mkdir /var/www/[project].git
  • cd /var/www/[project].git
  • git init --bare

So all your project directories will look like this:

  • /var/www/[project].git = The Bare git repo.
  • /var/www/dev = Development testing.
  • /var/www/stage = Staging for review and approval prior to release.
  • /var/www/html = Live

Create a post-receive hook that will push to a detached work tree.

/var/www/[project].git/hooks/post-receive

#!/bin/bash

# The path to the bare git repo on the test/prod server.
GIT_DIR="/var/www/[project].git"

# The following checks which branch is received (master, stage, or develop)
# and sets some variables for code syncing to the correct work tree.

while read oldrev newrev ref
do
  if [[ $ref = refs/heads/master ]];
    then
      echo "Ref $ref received. Deploying MASTER branch to production..."
      WORK_TREE="/var/www/html"
      BRANCH="master"
  elif [[ $ref = refs/heads/stage ]];
    then
      echo "Ref $ref received. Deploying STAGE branch to stage environment..."
      WORK_TREE="/var/www/stage"
      BRANCH="stage"
  elif [[ $ref = refs/heads/develop ]];
    then
      echo "Ref $ref received. Deploying DEVELOP branch to test environment..."
      WORK_TREE="/var/www/dev"
      BRANCH="develop"
  else
    echo "Not a valid environment branch, no code sync."
    exit 1
  fi
done

echo
echo "Syncing Code to $WORK_TREE"
git --work-tree=$WORK_TREE --git-dir=$GIT_DIR checkout $BRANCH -f

echo
echo "Cleaning up. (git clean -df)"
git --work-tree=$WORK_TREE --git-dir=$GIT_DIR clean -df


# Run any post code sync up tasks.
# Example below for Drupal 8 updates, config sync, and cache clear.

echo
echo "Updating and Importing"
cd $WORK_TREE
# Run Drupal database updates.
drush updb -y
# Import updated configuration.
drush cim -y
# Clear caches.
drush cr

echo
echo
echo "Done deploy and update."
echo

Make the git hook executable.

chmod +x /var/www/[project].git/hooks/post-receive

Set Up Your Git Remotes

While code deployment can, and probably should, be automated, I describe steps below as if they were being done manually, to illustrate the process.

For Manual Deployment: Add remotes for your working repo and host.

_NOTE: for simplicity, I'm focusing only on deploying the whole repo to a host. A more advanced process would be to have a separate "working repo" with tooling for development along with the codebase. The deployment process would create a "build artifact" of production code that would be committed to a separate repo that is deployed to the host. This process would allow for a light weight working repo with dev tools and dependency files, while only committing compiled dependencies and production code to a host.

Because the repo on the host is only meant for managing branches for dispatch to our environments, and the host has limited access, we host a working copy of the repo somewhere else like Github or Bitbucket. This is where developers can checkout code and push changes to be merged.

  • git clone [your project] -- working copy from Bitbucket/Github
  • cd [your project]
  • git remote add host [host uri]:/var/www/[project].git

Now you can use git commands to sync code from your working repo to your host repo for each environment. Simply git pull the desired environment branch from remote: origin (your working repo), and git push the same environment branch to remote: host. (See NOTE about simplicity, above).

If new work was merged into develop on Bitbucket, to get it to your development environment:

  • git pull origin develop
  • git push host develop

(Or configure an automated task to do this at intervals, or on merge to an environment branch.)

Deploying a Release

During a development cycle, all PRs make their way through Development Testing and then to stage for final review and sign off.

Each release branch should have a version number i.e 8.2.x.0.0 D.d.f.p (Drupal major version, Drupal minor version, Custom feature release, patch/bug).

Code that has final approval for deployment can be merged into the master branch. Master can be tagged with the version number, and synced to the live environment.

  • git checkout master
  • git pull origin master
  • git tag release-D.d.x.f.p
  • git push host
  • git push host release-D.d.x.f.p

Pantheon: A secret trick for parallel git workflow.

We have an extra step when deploying to Pantheon. We need to add an additional tag in the tag step above.

Pantheon's standard workflow is "serial" not "parallel". We must use multidev instances on Pantheon for a parallel workflow. The standard workflow uses only the master branch with specific tags to indicate which commit to checkout for the test and live environments. We only care about pantheon_live_N because we ignore pantheon dev and test, in favour of our own develop and stage multisites. Read the linked post to find out why.

The following command will list the tags on the rep, filter them to pantheon_live_ with grep, and show only the highest increment.

git tag -l --sort=v:refname $pantheon_prefix* | tail -1

Once we have the current latest tag, we increment by one and tag our new latest commit on master:

  • git checkout master
  • git pull origin master
  • git tag release-D.d.x.f.p
  • git tag pantheon_live_N -- whatever the next pantheon_live_ value is.
  • git push host
  • git push host --tags

Pushing the tag pantheon_live_N will trigger a code sync to Live if N is greater than any other pantheon_live_N tag. More info: https://pantheon.io/docs/hotfixes

REFERENCES: