Concepts

Cogate is an end-to-end testing and gating system for container-based applications.

The idea is simple: each time you open a pull request for any of the git repos that make up your app (whether the repos are for app code, libraries, or even deployment) Cogate will build any necessary images, create an ephemeral Kubernetes cluster, deploy your app and run tests.

This simple idea allows a whole new way of working where:

  • Changes that span git repos and images can be tested together before any pull requests are merged.

  • Pull requests can be approved and automatically merged after testing (so you don’t have to worry about the tree moving under your changes).

  • You can have effective deployment testing for continuously deployed apps.

  • If any test fails, you can hold the ephemeral cluster and inspect the results.

We’ll go into detail below on the Cogate concepts and features that allow for all of this. Whenever you’re ready to get started and try it out, hop over to the Tutorial.

Git Repos

Cogate watches git repos that you configure and builds and deploys images when pull requests are opened. Each git repo you want to use with Cogate needs to be added to your account in the web app. Once it’s added, Cogate will look for configuration files in the repo and use those to determine what images to build and deploy.

Normally Cogate will watch and operate on all branches of a repo. In this way, you can have different branches create different versions of images (presumably each with its own set of tags). However, if you use development or feature branches on your repos, then you can tell Cogate to only operate on “protected branches”. Cogate will only operate on branches where you have enabled up GitHub’s branch protection support.

Cogate will not only operate on the config files that are already present in the repo, it will also use config files (or changes to them) that are in pull requests. This means that you can iterate on a new Cogate configuration file within a single (or even multiple – see Cross-Repo Dependencies below) pull request and get it perfect before you merge it.

Image Building

Cogate both builds and deploys images; naturally the first step is to build them. For any pull request to a repository, Cogate will build all of the images that have been configured for that repository. Cogate builds images with a straightforward docker build command. But there is an important behavior to note regarding base images, and to understand that, we need to understand cross-repo dependencies.

Cross-Repo Dependencies

Cogate is designed for you to express dependency relationships between images and it will use those dependencies when building related images for un-merged pull requests.

For example, let’s say you have a repo for a base image, and a repo for your app that consumes that base image using a FROM line.

First, you need to make sure to tell Cogate about the relationship between images it knows about. Unfortunately, it’s not quite smart enough to read your Dockerfile and figure this out automatically (but that’s a good idea we’re looking into). So first when you configure Cogate to build the app image, you need to tell it that it depends on the base image. That establishes the relationship between the git repos.

If you open a pull request for the base image, and then open a pull-request for the app repo which depends on the change to the base repo, Cogate will know that it should build the base image first and then make sure that version of the base image is in the layer cache when it builds the app image.

This is how Cogate lets you test changes to multiple images before you merge any PRs. You don’t need to do anything special to the Dockerfile in the app repo. If your Dockerfile normally looks like:

FROM example/base-image:latest

And Cogate knows how to build example/base-image:latest, then Cogate will make sure that the version of base-image that it built for the dependent PR is used in the build of the app image.

To tell Cogate that one pull request depends on another, add a Depends-On: footer to the pull request message. For example:

Depends-On: https://github.com/example/base-image/pull/1

If that is added to a pull request for the app image, it tells Cogate that it depends on PR #1 to the base-image repository.

You can stack cross-repo dependencies like these as deep as you want, and you can create circular dependencies (A -> B, B -> A) to indicate that they should always be tested together and merged as a unit.

Deployments

Deployments are configured similarly to images, and in the same way that you should declare dependencies between images so that Cogate can do its magic, you should also declare what images your deployments depend on. Again, this lets Cogate do some behind the scenes work to make sure that your deployment uses the right builds for un-merged pull requests.

For a deployment, Cogate will create an ephemeral Kubernetes cluster and either apply a Kubernetes YAML file or run an action container to direct the deployment. It uses its knowledge of dependent images to ensure that the versions of the images it builds are used in the deployment. Just like the magic with FROM lines in Dockerfiles, this means that your deployment tooling can refer to your actual image names (like example/app-image:latest) and Cogate will use its build of the app image rather than the one that is currently published in the container registry.

Check Results

Cogate runs its builds and deployments in two contexts: check and gate. Check refers to a build or deployment that is run when a pull request is first opened. It provides immediate feedback to developers about whether a change (or group of changes) is expected to work.

Gating

Gating is an additional mode of operation (and one that is highly recommended – that’s why we call it Cogate after all). When using gating, rather than pressing the Merge button on GitHub yourself, you instead approve the pull request in GitHub (or add a gate label) and then Cogate will run the tests a second time with the current state of the repositories and any dependent changes), and if the deployment succeeds, it will merge the pull request automatically.

This frees us humans from having to worry about whether we sequenced everything correctly or if some intermediate change broke the change we’re about to merge. It lets computers handle that, so we can do more important things.

Publishing Images

If you’re using gating you should also make sure to configure your target image repositories with Cogate. That way each time Cogate merges a pull request, it will promote the image that it just built as part of the gating process. It will fetch the image, re-tag it, and upload it to the production registry with its final tag(s).

At the end of this process, Cogate will have built and deployed images in an ephemeral production-like deployment, and then published those exact images for use in your production deployment. Test fidelity doesn’t get any better than that!

Holds

Cogate can hold the test environment after deployment, which is useful if the tests fail and you need to investigate why, or you would like to perform interactive testing or evaluation in the environment.

Note

A held environment continues to accumulate charges at the same rate as when the job is running.

To hold a deployment environment, navigate to the deployment build in Cogate while it’s running: click on Builds and then select the appropirate build. On the build page, click the Hold switch to tell Cogate to hold the build when it finishes (click it again to toggle it off).

Once the build finishes, an additional button will appear: Kubeconfig. Click this button to download a kubeconfig file to use with kubectl:

kubectl --kubeconfig /path/to/kubeconfig version

This will allow you to interact with the Kubernetes cluster directly.

When you are finished with the enviroment, click the Delete Hold button to tell Cogate to remove the cluster.