We will cover how to make our git-based deployment into a production-ready CI solution, with staged deployment and support for multiple environments. Staging deployment to production was covered earlier. This is going to address how to deploy to several environments in a CI pipeline, and promote a package from one environment to the next from a build server.

Our environments’ instances are each gonna be a slot of a web-app. We’ll create a local git repository for each of the environments, then promote code from one to the next.

The advantages of this method are:

  • It’s generic enough that anything with a shell and a git client can run it
  • It’s promoting from environment to environment using binaries, increasing confidence of what’s on each environment. Bonus point: binaries are marked with the commit hash that generated them, so you can easily trace back to the code.
  • It maintains history of deployments. You can rollback to a given version by re-assigning master to the commit you want, then push to the environment.

The biggest drawback being that all your environments are hosted on the same webapp, which is not applicable to all cases.

Setting the pipeline up

You need to create something that ressembles this:

Environments

You can copy slot configuration from the staging slot, but make sure you deactivate auto swap for environments other than staging.

Slots

Then configure each slot to be deployed from local git repository, the same way that’s described here. The trick here is that each environment’s repository is named SERVICENAME-slotname.azurewebsites.net, and being on the same resource-group, they all use the same credentials. To deploy from one environment to the next, we really just need to add environments as remotes, and push the master branch from one to the next. We’re thus going to build and deploy to the first environment (e.g. dev), then push to the next remotes.

Finally, set the pipeline using the scripts that you can find here, you need to do the following:

  1. set the environment variables
  2. invoke prepare.sh to point the build directory to your seed environment (e.g. dev).
  3. build
  4. deploy to the seed environment using deploy.sh
  5. repeat the following process:
    • run required tests and operations
    • promote to the next environment with promote.sh.

An example of the whole orchestration looks like this:

#!/bin/bash 

export DEPLOYMENT_USER=user-you-configured-in-azure
export DEPLOYMENT_PASSWORD=some-password-that-is-cool
export DEPLOYMENT_URL='my-app-service-$ENVIRONMENT.scm.azurewebsites.net:443/my-app-service.git'
export DEPLOYMENT_FOLDER=bin

git config --global user.email "service@build.com"
git config --global user.name "Mr. Build Service"

chmod +x clean.sh
chmod +x build.sh
chmod +x deploy.sh
chmod +x prepare.sh
chmod +x promote.sh

./prepare.sh dev
./build.sh
./deploy.sh dev

Then run your unit tests. If they succeed, we promote to CI:

#!/bin/bash
./promote.sh dev ci

The run your integration tests. If they succeed, we promote to prod (through staging and autoswap):

# Run some other tests
./promote.sh ci staging

How it works

First off, we changed the way prepare.sh works from what was described here. It now takes an environment name, then:

  1. checks if the build folder exists, else creates it and initializes git.
  2. checks if the remote exists, adds it else - observe that the url is generated using the $ENVIRONMENT variable, the DEPLOYMENT_URL should be set accordingly (e.g. set to appservicename-$ENVIRONMENT.scm.azurewebsites.net)
  3. fetches the latest from the initial git repo1.
#!/bin/bash

ENVIRONMENT=$1
echo "Preparing environment for $ENVIRONMENT"

if [ ! -d "$DEPLOYMENT_FOLDER/.git" ]; then
    if [ ! -d "$DEPLOYMENT_FOLDER" ]; then
        mkdir "$DEPLOYMENT_FOLDER"
    fi
    cd "$DEPLOYMENT_FOLDER"
    git init
else
    cd "$DEPLOYMENT_FOLDER"
fi

if [ ! `git remote | grep $ENVIRONMENT` ]; then
    URL=`eval echo -n $DEPLOYMENT_URL`
    git remote add $ENVIRONMENT https://$DEPLOYMENT_USER:$DEPLOYMENT_PASSWORD@$URL
fi

git fetch $ENVIRONMENT
git clean -xdf
git checkout $ENVIRONMENT/master --force
git branch -f master $ENVIRONMENT/master
git checkout master

cd -

Then we build. This part is on you.

Then we deploy to the “seed” environment (e.g. dev), this is done through the deploy.sh script, which really just commits everything and push to the remote:

#!/bin/bash

ENVIRONMENT=$1
VERSION=`git describe --always`

echo "Deploying version $VERSION to $ENVIRONMENT"

cd $DEPLOYMENT_FOLDER

git add -A
git commit -m "Commit $VERSION - Build as of `date`"
git push $ENVIRONMENT master

cd -

Finally, we want to promote from one environment to the next based on your process (test results, manual process, etc.). This is done using promote.sh. This script is basically checking out the source environment, and pushing its master to the target environment:

#!/bin/sh

FROM=$1
TO=$2

echo "Promoting $FROM to $TO"

./prepare.sh $FROM
./deploy.sh $TO

And that is it!

Considerations

  • You need some form of cleanup to remove deleted files in your build. Drastically, you could just delete everything (except directory .git) as a first step of your build.
  • You need to prevent the situation where a merge conflict would happen. This means that you need to always build to the same environment (say dev)

Notes

  1. If you’re changing the initial build environment (say, go from staging to dev), you need to synchronize your staging repository to the dev repository before you do the first build to prevent merge conflicts.