1. #DevOps

As a continuous integration platform, Travis CI supports your development process by automatically building and testing code changes, providing immediate feedback on the success of the change. Travis CI can also automate other parts of your development process by managing deployments and notifications.

Getting started with Travis CI

It’s easy to get started with Travis CI. Their documentation has a page describing it; Travis CI for Beginners. There is also a great Travis CI Tutorial. However, there are some important things to take note of:

  • You should sign into Travis CI using your GitHub login. This is because Travis CI will automatically show you repositories that you have read permissions to in GitHub, and it will restrict administrative control to those who also have admin permissions to a repo in GitHub.
  • Travis CI is free for public repositories. You can, and should, experiment and learn Travis using a public repo in your personal GitHub account. You can sandbox and break things without having any effect on business operations. Everyone should take advantage of this.
  • Travis CI is much more powerful, by a significant magnitude, than you are probably aware of. Do lot let perception control your reality. Travis CI can achieve the simplest of CI/CD plans with ease, but it is also capable of managing much more complex plans. Due to it’s excellent support for Docker, there really are few limits to what can be achieved.

Limitations of Travis CI

No tool is without limitations and negative aspects. It is important to note the following limitations of Travis CI, although continual improvements to the platform cause this list to shrink every year.

  • You cannot share build artifacts across jobs. It is important to note that jobs do not share storage, as each job runs in a fresh VM or container. If your jobs need to share files (e.g., using build artifacts from the “Test” stage for deployment in the subsequent “Deploy” stage), you need to use an external storage mechanism such as an image repository or a remote server.

Travis CI Configuration

Breaking down the Travis CI configuration file

This is a top-down examination of a typical Travis CI configuration file, .travis.yml, and a description of the best practices exhibited therein.

Configuration Description
sudo: required

The `sudo` property is no longer required and its use is now discouraged.

It had been `false` by default, and marking in as `required` was necessary when using Docker. However, `required` is now the default state and any other setting is deprecated. It is therefore no longer needed to have this line, but important to make note of it, as it can be found in many configuration examples.

language: node_js
The `language` property is the most typical method of selecting the image that will be used for builds. `node_js` is the most popular, not only because JavaScript is the most popular language but also because the image also contains other system dependencies, such as; Java, Ruby, etc. Making it ideal for most build requirements.
  - '8'
If `node_js` is the selected language/image, then you can specify which Node version(s) using the `node_js` property. As depicted, this property supports an array of values. This is useful when required to test code against multiple versions of Node.
    - ~/.npm
    - node_modules
  npm: true
The `cache` property is used to dictate which dependencies and/or directories should be cached across build jobs and stages. This is most helpful to avoid running dependency installations more than once, saving up to 2 minutes for each additional job.
  email: false
The `notifications` property is used to configure build notification settings. To avoid an overabundance of emails, it is common to always include this option and ensure the subsequent `email` property is marked as `false`.
  - docker
The `services` property is used to activate services to be made available to build jobs. The most common of these services is `docker` and this should be enabled on all plans.
    organization: sonar-org-name
The `addons` property is used to activate additional 3rd party applications and integrations. This is most commonly used for things like SonarCloud integration and Chrome support. As depicted here, every single Travis CI build plan should have `sonarcloud` as an add-on with your `sonar-org-name` as the `organization`. The SonarCloud add-on is required to ensure the `sonar-scanner` binary is available and connected to the matching SonarCloud profile. Please note that the Travis CI plan must also provide the `SONAR_TOKEN` as an environment variable so that `sonar-scanner` can authenticate the account.
  depth: false
The `git` property is used to control Git configuration. This is most commonly used to increase the default depth (50 commits) of GitHub pulls. When you want Travis CI to be able to analyze more than just the absolute latest commits to the master branch, you need increase this setting or disable it altogether. For example; if you want to automate your releases and changelogs, then you need to be able to analyze as many commits as are applicable to said release. In many cases, this can exceed the default. The more automation and contributors you have, the more this setting can interfere.
    - master
    - /^greenkeeper.*$/

The `branches` property is used to control which branches Travis CI will watch for commits. This should always include the primary development branch, i.e.; `master`. Additional branches should be activated on a case-by-case basis. For example, if the repository is integrated with Greenkeeper, then you must add support for `greenkeeper/...` branches so they can be processed.

Pull Requests are automatically processed if they are made against the branches listed in this configuration. In this example; any PR made against the `master` branch will be automatically processed. This means every PR will be able to exhibit accurate Status Checks in GitHub, capable of preventing PR's from being merged if they fail the CI plan.

    - PATH=$HOME/.local/bin:$PATH

The `env` property is used to supply environment variables which, for most plans, should only be stored in the Travis CI Build Settings area - only accessible to repository admins. Travis CI supports encryption of environment variables, but it is still best practice to keep these things out of the config file.

The primary value of the `env` property is modifying global variables, especially when needing to modify `PATH` variable. The example provided shows the best practice for ensuring globally installed packages are accessible at the command line, in addition to the current active directory (`PWD`).

  - npm install -g greenkeeper-lockfile
The `before_install` property is generally used for installing global dependencies. It can be ignored but it is best practice to use it for any system-level provisioning, and using the `install` property for application-level installation and provisioning.
  - travis_retry npm install

The `install` property is used to run all installation scripting, such as `npm install`. The caveat here, as exhibited in the example, is that the command should always be prefixed with the `travis_retry` command - which will ensure that any erroneous install failures will be retried before failing the build. This can be common during dependency installations when things like network drop-offs and registry blips affect the reliability of a plan.

Note: the `travis_retry` command can be used in front of any command to ensure that it is retried upon failure.

  - test
  - name: deploy
    if: repo = profile/repo
        AND type != pull_request
        AND branch = master

The `stages` property is used to segment jobs into individual stages. Whilst this is not a required property, it is best practice to ensure that subsequent jobs are not run if a previous job/stage has failed. This is because Travis CI will run all scripts within a job even if a failure occurs during the first script. The job would still fail, but not before all scripts run. This has a benefit of observing just how many scripts failed, instead of just the first one. But it has the unfortunate consequence of potentially running a script that should be run if previous failures occurred. This is obviously the case with things like deployments, but can also play a role in other areas, such as; ensuring a build doesn't run for 10 minutes if a failure occurred in the first 2 minutes. As Travis CI is priced based on concurrent builds, it is important to ensure you do not unnecessarily waste build time.

As exhibited in the example, stages are a great way to add conditionals to restrict if and when a stage is run. The example shows how to restrict a deployment stage to just the master branch of the explicit repo and not on any PR's.

Note: stages are processed in a linear fashion, meaning subsequent stages cannot run until a previous stage as completed successfully.

    - stage: test
      name: 'Checks and Tests'
        - npm run lint
        - npm run test
        - npm run build
    - stage: deploy
      name: 'Publish and Release'
        - npm run semantic-release
The `jobs` property is used to group all scripts/command into individual jobs. When using stages, you can have multiple jobs within the same stage. The example show one job per stage, but there is plenty of documentation describing the alternative.

Conditional scripting and statements

Travis CI provides a number of methods for wrapping build stages and jobs with conditional statements. There is also a page describing the available conditions, and a page describing how conditionals can be tested. You can also find a list of environment variables that can be used in conditional statements.

There are plenty of standard methods to learn therein. But there is little documentation about inline conditionals and the quirks of their behavior. This is a list of those quirks;

  • Inline conditional statements should be enclosed in single-quotes or they will not be processed properly; '[[ $TRAVIS_BRANCH = master ]] && echo "is master" || echo "is not master";'
  • All statements should end with a semicolon to ensure the statement is closed properly.
  • Any unmet inline conditional will trigger a premature fail/exit of the build plan. This is because Travis CI expects all scripts output to complete without error. If a conditional fails, this does not occur. It is therefore required to provide an “else” branch in the conditional statement. However, the “else” must also not fail. So it is common to simply “echo” some text that signifies the bypass.
  - '[[ $TRAVIS_BRANCH =~ ^greenkeeper.*$ ]] && greenkeeper-lockfile-update || echo "Skipping greenkeeper-lockfile-update";'
  - '[[ $TRAVIS_BRANCH =~ ^greenkeeper.*$ ]] && npm audit || echo "Skipping npm audit";'
  - commitlint-travis
  - npm run lint
  - npm run build
  - travis_retry github-label-sync -a "$GH_TOKEN" -l 'https://git.io/fAe5i' profile/repo
  - npm run test
  - '[[ $TRAVIS_BRANCH = master ]] && sonar-scanner || echo "Skipping sonar-scanner";'
  - '[[ $TRAVIS_BRANCH =~ ^greenkeeper.*$ ]] && greenkeeper-lockfile-upload || echo "Skipping greenkeeper-lockfile-upload";'