Skip to content

Latest commit

 

History

History
184 lines (136 loc) · 10.7 KB

authoring-tasks.adoc

File metadata and controls

184 lines (136 loc) · 10.7 KB

Authoring Tasks

This document will explain how to author custom Tekton tasks to use with ODS.

Prior to creating your own task, it is important to understand in general how Tekton and ODS work. The introduction provides a starting point.

Why and when to create custom tasks?

ODS provides companion tasks that can be used to build and deploy applications for a few popular technologies. However, they are opinionated and prescribe a certain way how the application is built for example. The behaviour of the task can only be adapted by setting pre-defined parameters. Typically, those parameters do not allow for a great deal of customization.

Tip
There is a notable exception to this in the build tasks such as ods-pipeline-go-build, ods-pipeline-gradle-build, and so on. These allow for a great deal of customization by providing the build-script param to exchange the build script used to build your application. For example, if your application build doesn’t pass linting as defined in the build script supplied by default, you can point the build-script param to a custom script in the repository of your application instead.

If you need more control than the official tasks offer, creating a custom Tekton task is the way to go. You may also use an already existing Tekton task provided by someone else, e.g. those from the Tekton Catalog.

Anatomy of a task

A task consists of one or more steps, and each step is a container that runs in a pod. Therefore, when you create your own task, you will need to define at least one step, which will need to define the container it will execute. For example, if you want to use the curl program in your custom task, then you will need to launch a container which has curl installed. This means that you either need to find a container image that has the binaries installed that you want to execute, or you need to build a container image first that suits your needs, before using it in your task definition.

Defining an ad-hoc task in the pipeline spec

Normally, a Tekton task is a Kubernetes resource in your OpenShift project. However, pipelines also allow for a lightweight inline task spec. This approach is helpful during experimentation or if you do not plan to reuse the task elsewhere. Below is an example of such an inline definition in the ods.yaml file:

ods.yaml
pipeline:
  - tasks:
    - name: hello-world
      taskSpec:
        steps:
          - name: say-hello
            image: busybox
            script: |
              echo hello world
      workspaces:
      - name: source
        workspace: shared-workspace

Creating your own task

If you want to reuse your task in other repositories, it is better to create a proper Task resource in your OpenShift project. Simply provide the YAML definition of the task in the OpenShift console interface. Then you are able to reference this task from your pipeline definition (ods.yaml) just like official ODS tasks.

The simplest YAML definition of a task looks like this:

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: hello-world
spec:
  description: Example task
  steps:
    - name: say-hello
      image: busybox
      script: |
        echo hello world

We define one step using the busybox image, which executes what is defined in script in a shell (sh). Since echo is available in sh, we can print the words hello world.

Your ods.yaml file would reference this task like this:

ods.yaml
pipeline:
  - tasks:
    - name: hello-world
      taskRef:
        kind: Task
        name: hello-world

Of course this doesn’t do anything useful in the CI pipeline. Typically, you’d need to mount a workspace (containing the Git repository you are working in) and maybe offer some parameters to the user of this task. Have a look at the official ODS tasks for more sophisticated examples of existing tasks. Later on in this document we’ll look at some example tasks you could create.

Using pipeline context

The pipeline generated by ODS is automatically fed with a few parameters which can be passed to each task. However, that would lead to a lot of boilerplate in ods.yaml as many parameters would need to be passed over and over again to each task. To avoid this, the parameters are cached by the ods-start task in the workspace in a .ods directory. For example, the repository parameter value is written to a file .ods/repository. This file and other similar files are forming the "pipeline context", which can be read by task authors to access this type of information easily. If you are writing your task logic in Go, you can conveniently read this cache into an ODSContext struct using ReadCache from the github.com/opendevstack/ods-pipeline/pkg/pipelinectxt package.

Handling artifacts

Pipelines may produce and consume artifacts such as xUnit test results, image digests, static code analysis reports, etc.

These artifacts are stored under .ods/artifacts, for example .ods/artifacts/xunit-reports/report.xml. The ods-finish task at the end of each pipeline automatically uploads all artifacts stored under .ods/artifacts to Nexus. Conversely, the ods-start task searches for any artifacts in Nexus belonging to the checked out commit (this may happen e.g. if the pipeline was triggered again or the same commit is pushed to another branch). If artifacts are detected, they will be downloaded and subsequent tasks may change their behaviour dependening on the existance of such artifacts.

If your custom task produces artifacts and stores them in a subfolder under .ods/artifacts, they will also be uploaded to Nexus like any other artifacts produced by an ODS task. In the same way, your custom artifacts will also be downloaded in pipelines running for commits that have already produced artifacts.

FAQ

Which programming language should I use? Do I need to use Go?

You may use any programming language you wish to implement the logic of your task, since you provide both the container image to use, and the script to execute in that image. Therefore, you can write the task in any way you want: shell scripts, Go, Python, Ruby, Java, …​ you name it. That said, using languages with a fast boot time and a low memory footprint is advisable. If you plan to write automated tests for your task (which can also be run locally), then you may use the Go test framework provided by ods-pipeline (see the documentation of the tektontaskrun and odstasktest packages), but even then you may use a language other than Go for your actual task.

Which images can I use in my tasks?

In theory you can use pretty much any image that works in OpenShift (e.g. the image needs to adhere to limitations around user permissions). For nitty-gritty details, see the Container Contract. This means you can also build your own image in OpenShift and use it in a task, as explained in the next section.

How do I create my own container image to use in a task?

In OpenShift, the easiest way is by creating an ImageStream and a BuildConfig. See the OpenShift documentation on builds for more information.

How can I test my tasks?

Official ODS tasks are provided with automated tests. These tests are written in Go, and can be executed locally (in a KinD cluster) via make test. Each test creates a TaskRun with certain parameters and then checks the result of the run and the state of the workspace after the run. This allows to test each task in isolation and before using the task in a pipeline in an actual OpenShift cluster. If you want, you should be able to make use of this task testing framework for your own custom tasks. However, this has not been documented yet and likely needs a few adjustments to work well.

Examples

Building Ruby applications

ODS does not offer a task to build Ruby applications at the moment. How would you create a task that builds a Ruby application in your OpenShift project?

For this example, we will consider a very basic application like this puma-test-app. The task to build such an application could look like this:

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: build-ruby
spec:
  description: Ruby build task
  steps:
    - name: build-ruby
      image: 'registry.access.redhat.com/ubi8/ruby-25'
      script: |
        bundle install --path ./bundle
        # run tests
        # copy files to docker directory
        # etc
      workingDir: $(workspaces.source.path)
  workspaces:
    - name: source

This task uses the registry.access.redhat.com/ubi8/ruby-25 image, but as explained above in the FAQ you can use other images as well.

Once you have created the task in your namespace (in the web console under "Pipelines > Tasks > Create Task"), it can be referenced from a repository in the ods.yaml file like this:

pipeline:
  tasks:
  - name: build-ruby
    taskRef:
      kind: Task
      name: build-ruby
    workspaces:
    - name: source
      workspace: shared-workspace

Customizing how Go applications are built

While ODS offers a task to build Go applications, that task is quite opinionated and does not offer a lot of control for you as a user. For example, it will lint your code with golangci-lint and you cannot disable this step. This is by design to allow the platform to make certain assumptions about software created by ODS tasks. However, imagine you have some legacy code that will not pass linting and you are unable to change this (quickly). How would you create a task that does not run the linter?

As a first step, copy the YAML from ods-pipeline-go-build task and adjust as necessary. A simple starting point for your own task building Go could look like this:

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: build-go
spec:
  description: Custom Go (module) applications build task.
  steps:
    - name: build-go-binary
      image: 'registry.example.com:5000/my-namespace/my-go-toolset:latest'
      env:
        - name: HOME
          value: '/tekton/home'
      resources: {}
      script: |
        go build -o docker/app
      workingDir: $(workspaces.source.path)
  workspaces:
    - name: source
Note
You’ll need to adjust the image value to point to an image e.g. in an OpenShift image stream or in an external registry.