Geoff Garbers

Husband. Programmer. Tinkerer.

Setting environment variables per branch in Travis CI

Oct 13, 2017

I’m a fan of trying to keep continuous deployments as similar as possible between environments. Ideally, the only things that should be changing are various credentials: hostnames, usernames, passwords, etc. I haven’t yet come across a built-in method for managing these various credentials.

There’s a neat trick I’ve started using that allows me to define environment variables on a per-environment basis, thus enabling me to ensure that my deployment process is the same between environments.

The problem, illustrated.

Let me illustrate the problem that I’ve encountered with an example .travis.yml file; one that decrypts some kind of JSON file, depending on the environment:

language: generic
sudo: false
before_install:
  - |
      if [ "$TRAVIS_BRANCH" = "testing" ]; then
        openssl aes-256-cbc -d \
          -K "$encrypted_1234567890_key" \
          -iv "$encrypted_1234567890_iv" \
          -in $TRAVIS_BUILD_DIR/encrypted-file-testing.json.enc \
          -out $TRAVIS_BUILD_DIR/decrypted-file.json
      elif [ "$TRAVIS_BRANCH" = "master" ]; then
        openssl aes-256-cbc -d \
          -K "$encrypted_0987654321_key" \
          -iv "$encrypted_0987654321_iv" \
          -in $TRAVIS_BUILD_DIR/encrypted-file-prod.json.enc \
          -out $TRAVIS_BUILD_DIR/decrypted-file.json
      fi

As you can see from the example, normally you need to duplicate the logic you’re trying to execute. It becomes very easy to mistype an environment variable, or forget a specific flag. This becomes even more complicated when you’re updating a deployment method, and having to remember to change it for each environment.

Prefixing your environment variables.

The solution I’ve started using is fairly simple - you simply prefix your variables with your environment, in the main env declaration. The real magic happens in the before_install stage:

language: generic
sudo: false
env:
  global:
    # These variables are for the "testing" branch.
    - TESTING_ENCFILE="encrypted-file-testing.json.enc"
    - TESTING_DECRYPT_KEY="decrypt-key-testing"
    - TESTING_DECRYPT_IV="decrypt-iv-testing"
    
    # These are for the "production" branch.
    - PROD_ENCFILE="encrypted-file-prod.json.enc"
    - PROD_DECRYPT_KEY="decrypt-key-prod"
    - PROD_DECRYPT_IV="decrypt-iv-prod"
before_install:
  - |
      if [ "$TRAVIS_BRANCH" = "testing" ]; then
        for prefixed_envvar in ${!TESTING_*}; do
          eval export ${prefixed_envvar#TESTING_}="${!prefixed_envvar}"
        done
      elif [ "$TRAVIS_BRANCH" = "master" ]; then
        for prefixed_envvar in ${!PROD_*}; do
          eval export ${prefixed_envvar#PROD_}="${!prefixed_envvar}"
        done
      fi
  
  - openssl aes-256-cbc -d \
      -K $DECRYPT_KEY \
      -iv $DECRYPT_IV \
      -in $TRAVIS_BUILD_DIR/$ENCFILE \
      -out $TRAVIS_BUILD_DIR/decrypted-file.json

A simple explanation of what is happening here is that the required prefix (PROD_ or TESTING_ in this case) is removed from the initial variable name, and set as a new variable. This means you can now easily reference the “stripped” variable without the initial prefix: $PROD_ENCFILE becomes $ENCFILE, $PROD_DECRYPT_KEY becomes $DECRYPT_KEY, etc.

Note: The caveat to this is that you need to make use of eval. This is not normally something you should be doing, so use this method with caution; as it can potentially open up security vulnerabilities.