This topic provides a step-by-step tutorial for setting up dispatch and getting your first successful cloud native CI build on your GitHub repository. It will leverage the Starlark front end language.
Prerequisites
- Some basic knowledge of git, bash, and Docker.
- A GitHub account.
- Owner permissions for a project hosted on GitHub or access to create one.
- Deploy access to a namespace in Kubernetes cluster.
- Dispatch CLI installed in the environment.
Setup a git repository
Initialize a git repository
Begin by creating a new repository on GitHub and clone it locally. You may skip this if you intend to use an existing repository on GitHub. Throughout the rest of this tutorial, this repository is named helloworld
.
For the purposes of this tutorial, focus on creating a CI check that uses a Docker image to test the source code. This tutorial assumes the Docker image is testing the golang
source code present in the repository but this can be replaced with any other Docker image trivially.
Go to the directory where you’ve cloned the repository
cd helloworld
Add some golang source and test files:
main.go
:
package main
import "fmt"
func main() {
fmt.Printf("Hello %v!", World())
}
func World() string {
return "World"
}
main_test.go
:
package main
import (
"testing"
)
func Test_World(t *testing.T) {
actual := World()
if actual != "World" {
t.Fail()
}
}
If you have go
in the environment, you can verify that the above code works using go test -v ./...
.
For the sake of this tutorial, we can add a simple Dockerfile
that builds from the base golang
image to run the tests. Create this dockerfile:
Dockerfile
:
FROM golang:1.14.0
ADD main.go /test/main.go
ADD main_test.go /test/main_test.go
In the later sections, we will setup CI infrastructure to run the tests on every pull request. As a side note, real life test scripts are much more complicated (obviously!) such as launching a kind cluster and running some tests on the cluster. The focus of this tutorial is to demonstrate the ability to run a simple CI build and this should be extendable to more complicated tests.
Install Dispatch
Refer to this guide for install options.
By default, Dispatch installs to dispatch
namespace and can be overridden during install time. This tutorial assumes Dispatch is installed to dispatch
namespace.
Rest of the tutorial assumes you are working with default
namespace for pipelines. Add a --namespace
flag to commands as applicable if you are working with a different namespace.
Setup credentials & Service accounts
Refer to this guide on setting up credentials.
For the purposes of this tutorial, you need at least Github & Docker credentials. At the bare minimum, you should have executed (with appropriate names):
-
Create a service account named team-1
dispatch serviceaccount create team-1
-
Create a Docker credential
dispatch login docker --service-account team-1
-
Create a github credential
dispatch login github --service-account team-1 --user $YOURGITHUBUSERNAME --token $YOURGITHUBTOKEN
-
Create a git SSH credential only if you want to be able to build locally
dispatch login git --service-account team-1 --private-key-path $SSH_KEY_PATH
Setup repository in Dispatch
Refer to this guide for repo setup.
At the least, you should execute the following:
dispatch ci repository create --service-account=team-1
In the next section, we are going to define the build specification in a file named Dispatchfile
Adding a Dispatchfile to git repository
In this tutorial, we are going to use Starlark and create a file named Dispatchfile
which holds our build specification. This is a step-by-step walk-through of creating our Dispatchfile
:
-
Declare the DSL (Domain Specific Language) syntax for our
Dispatchfile
using shebang:#!mesosphere/dispatch-starlark:v0.5
This specifies to use version
0.5
of Starlark DSL parser. -
Dispatch Catalog holds syntactic sugar for reusing various Starlark functions that makes your
Dispatchfile
smaller and lets you focus on actual testing aspects. Let’s import the following helpers:gitResource
: to declare the git repository as a resource that can be sent as input to taskspullRequest
: to declare a condition to trigger the builds whenever a pull request is updatedkaniko
: to build and publish the Docker image
# Import the gitResource, pullRequest, and kaniko helpers from dispatch catalog load("github.com/mesosphere/dispatch-catalog/starlark/stable/pipeline@0.0.4", "gitResource", "pullRequest") load("github.com/mesosphere/dispatch-catalog/starlark/stable/kaniko@0.0.4", "kaniko")
-
Declare the git resource:
git = gitResource("helloworld-git")
Any valid Kubernetes resource name can be chosen here.
-
Declare a task to build and push the Docker image using kaniko:
# Build and push the docker image simple_docker = kaniko(git, "$YOURDOCKERUSERNAME/helloworld")
-
Declare a task using above Docker image to run tests:
# This is a simple unit test that uses the base golang image to validate the go source code task("unit-test-simple", inputs=[simple_docker], steps=[k8s.corev1.Container( name="unit-test-simple", image="$YOURDOCKERUSERNAME/helloworld:$(context.build.name)", workingDir="/test", command=["go", "test", "./..."])])
We specified the
workingDir
as/test
as that is the location of our source code. Access to repo source code can be gained by usinggit
as one of the inputs and settingworkingDir
to"/workspace/{}".format(git)
. -
Define an Action to run the task on every pull request:
simpleTasks = ["unit-test-simple"] action(tasks=simpleTasks, on=pullRequest()) action(tasks=simpleTasks, on=pullRequest(chatops=["test"]))
The first action triggers the
unit-test-simple
task upon creating/updating a pull request. The second action triggersunit-test-simple
whenever a comment/test
is made on the pull request.
Hence, The entire Dispatchfile
becomes:
Dispatchfile
:
#!mesosphere/dispatch-starlark:v0.5
# vi:syntax=python
load("github.com/mesosphere/dispatch-catalog/starlark/stable/pipeline@0.0.4", "gitResource", "pullRequest")
load("github.com/mesosphere/dispatch-catalog/starlark/stable/kaniko@0.0.4", "kaniko")
git = gitResource("helloworld-git")
# Build and push the docker image
simple_docker = kaniko(git, "$YOURDOCKERUSERNAME/helloworld")
# Use the pushed docker image to run CI
task("unit-test-simple",
inputs=[simple_docker],
steps=[k8s.corev1.Container(
name="unit-test-docker",
image="$YOURDOCKERUSERNAME/helloworld:$(context.build.name)",
workingDir="/test",
command=["go", "test", "./..."])])
simpleTasks = ["unit-test-simple"]
action(tasks=simpleTasks, on=pullRequest())
action(tasks=simpleTasks, on=pullRequest(chatops=["build"]))
You can use the Dispatch CLI to validate above Dispatchfile:
dispatch ci render --file=Dispatchfile
which would result in an output similar to:
...
#!yaml
# vi:syntax=yaml
actions:
- "on":
pull_request: {}
tasks:
- unit-test-simple
- "on":
pull_request:
chatops:
- build
tasks:
- unit-test-simple
resource:
$YOURDOCKERUSERNAME-helloworld:
param:
digest: $(inputs.resources.$YOURDOCKERUSERNAME-helloworld.digest)
url: $YOURDOCKERUSERNAME/helloworld:$(context.build.name)
type: image
helloworld-git:
param:
revision: $(context.git.commit)
url: $(context.git.url)
type: git
task:
$YOURDOCKERUSERNAME-helloworld:
inputs:
- helloworld-git
outputs:
- $YOURDOCKERUSERNAME-helloworld
steps:
- args:
- --destination=$YOURDOCKERUSERNAME/helloworld:$(context.build.name)
- --context=/workspace/helloworld-git/
- --oci-layout-path=/workspace/output/$YOURDOCKERUSERNAME-helloworld
- --dockerfile=/workspace/helloworld-git/Dockerfile
image: gcr.io/kaniko-project/executor
name: docker-build
resources: {}
unit-test-simple:
inputs:
- $YOURDOCKERUSERNAME-helloworld
steps:
- command:
- go
- test
- ./...
image: $YOURDOCKERUSERNAME/helloworld:$(context.build.name)
name: unit-test-docker
resources: {}
workingDir: /test
See full reference of a Dispatchfile.
After setting up Dispatch, adding relevant credentials, and creating helloworld
repository, we can move on to running our CI.
Continuous Integration in Action
After creating your Dispatchfile
, you can push it to a branch of your choice and create a pull request against default branch (or any branch).
When you executed the dispatch ci create repository
command in earlier sections, Dispatch
repository controller created a webhook in your GitHub repository. This webhook enables Dispatch to receive events (such as pull request events) from GitHub. When you create a Pull Request, a PullRequest
event is posted to Dispatch
and this in turn triggers a pipeline to run unit-test-simple
task as declared in your Dispatchfile
. If you make a comment on the Pull Request that starts with /test
then this would have a similar effect (useful in cases where you want to rerun a flaky CI test). Make such a comment and the build status should be reflected shortly on your Pull Request as soon as the build is scheduled. See the troubleshooting guide if you are having problems.
You can look at logs of various Dispatch components as well as pipelines.
Advanced reading
There may be cases where you may need to have Docker runtime in your CI builds (e.g.: launch a kind cluster and run tests on it). The dindTask
provided in dispatch-catalog can be used exactly for this purpose. As an example, the unit-test-simple
task from earlier can be rewritten as follows:
#!mesosphere/dispatch-starlark:v0.5
# Import the predefined Docker-in-Docker task from Dispatch Catalog.
load("github.com/mesosphere/dispatch-catalog/starlark/stable/docker@0.0.4", "dindTask")
# This is the same test as above rewritten using Docker-in-Docker image
dindTask("unit-test-dind",
inputs=[git], steps=[k8s.corev1.Container(
name="go-test",
args=[
"docker",
"run",
"--volume", "/workspace/{}:/workspace/{}".format(git, git),
"--workdir", "/workspace/{}".format(git),
"golang:1.14.0",
"go", "test", "./..."])])
This is similar to previous task definition except that this runs mesosphere/dispatch-dind:1.0.0 Docker-in-Docker image. You can override the image by using image
field in Container
definition above and run any arbitrary scripts that require Docker. Explore various other predefined constructs from dispatch-catalog and contribute your own!