IBM Cloud Docs
Adding test and build steps to pipelines

Adding test and build steps to pipelines

You can connect your existing test and build flow to the continuous integration pipeline by adding the results of your new or existing test and build scripts to the DevSecOps pipeline continuous integration flow.

You can use the following stages within the continuous integration pipeline to add test and build steps:

  • Setup stage
  • Test stage
  • Containerize stage (build)
  • Release stage

Setup

You can use the Setup stage to set up your test and build environment and pull information into the pipeline. For example, you can use multiple app-related repos in a single build. You can clone all of the repos that you need and make the pipeline aware of those repos in compliance-related checks and scans.

The default app repo cloned by the pipeline internally and added to the pipeline by using the save_repo pipelinectl interface with the reference name app-repo. The default repo is either provided by the repo pipeline UI parameter or selected by its toolchain binding name, if you set up the pipeline from the toolchain template.

If you want to use more repos, clone them in the setup stage and use the same save_repo interface to add them to the pipeline.

Example

#
# your scripts cloning the repositories
#
# make sure you prepare or export the following data from each cloned repository:
# - repository URL
# - path where it was cloned, relative to the $WORKSPACE path
# - cloned branch
# - latest commit hash
#
your_clone_scripts

#
# when cloning is complete
# use `save_repo` to add these information to the pipeline
# repo-reference-name can be any name, it is used to refer to the stored repo
#
save_repo <repo-reference-name> \
    url="${REPO_URL}" \
    path="${REPO_PATH}" \
    branch="${CLONED_BRANCH}" \
    commit="${LATEST_GIT_COMMIT}"

This way the rest of the pipeline can scan these repos for compliance violations and vulnerabilities.

Paths saved by using save_repo must be relative to the workspace path.

You don't need to install the pipelinectl tool for your scripts or base images, the reference pipeline provides the binaries for the context of your script.

Test

This stage is where you run your tests on your code repos. You can access your repos added in the setup stage using the list_repos and load_repo pipelinectl interfaces.

Example

exit_code=0

#
# `list_repos` returns the list of the reference names of saved repos
#
list_repos | while IFS= read -r repository ; do

    #
    # load_repo returns a property of a saved repository
    #
    # Usage: 
    # load_repo <repo-reference-name> <property> 
    # 
    url="$(load_repo "$repository" url)"
    sha="$(load_repo "$repository" commit)"
    branch="$(load_repo "$repository" branch)"
    path="$(load_repo "$repository" path)"

    #
    # use your repos to test, etc
    #
    run_tests 
    result=$?
    
    if [ $result != 0 ]; then
        exit_code=$result
    fi
done

exit $exit_code

The unit-test compliance control is determined by the exit code of the stage script itself. If your tests pass, exit with 0, if not, return a non-zero exit code at the end.

Saving results

Your tests might generate some report artifacts, such as a test results JSON or XML. To attach those to the created compliance evidence as evidence artifacts, use the save_result pipelinectl interface in this stage.

#
# run tests with some test suite runner, and save output to results.json
#
test_runner -o results.json

#
# save the result for the pipeline, so it can attach it to the unit test evidence
#
save_result test results.json

The first parameter of save_results must be the DevSecOps pipeline config stage name, like test, scan-artifact or acceptance-test. Otherwise the evidence collector won't be able to find it and attach it to the proper piece of evidence.

Using the save_result pipelinectl interface ensures that the pipeline finds your result artifacts, they are uploaded to the evidence locker, and attached to the compliance evidence that is created by the pipeline.

Example evidence created for the unit tests while using save_result:

{
  "evidence_type_id": "com.ibm.unit_tests",
  "evidence_type_version": "1.0.0",
  "date": "2021-03-31T07:41:31.881Z",
  "result": "success",
  "pipeline_id": "8c2b6750-91db-45fb-98ee-51684843b821",
  "pipeline_run_id": "89a04de9-2795-4e8e-be90-52a92ac7f9c1",
  "issues": [],
  "artifacts": [
    {
      "url": "https://s3.us-south.cloud-object-storage.appdomain.cloud/cos-bucket-name/ci/89a04de9-2795-4e8e-be90-52a92ac7f9c1/artifacts/compliance-app-COMPACT-20210218231513608/unit-tests-results.json_d9619521e7444fef0ff052e59fd54049",
      "hash": "d9619521e7444fef0ff052e59fd54049"
    }
  ],
  "toolchain_crn": "crn:v1:bluemix:public:toolchain:us-south:a/40111714589c4f7099032529b26a7a63:39d4f080-55e5-42ee-a787-26d936fb2b97::",
  "log": [
    {
      "url": "https://cloud.ibm.com/devops/pipelines/tekton/8c2b6750-91db-45fb-98ee-51684843b821/runs/89a04de9-2795-4e8e-be90-52a92ac7f9c1/code-unit-tests/run-stage?env_id=ibm:yp:us-south",
      "hash": null
    },
    {
      "url": "https://s3.us-south.cloud-object-storage.appdomain.cloud/cos-bucket-name/ci/89a04de9-2795-4e8e-be90-52a92ac7f9c1/artifacts/logs/code-unit-tests/run-stage.log_dae902fb1455b1fc9c565273aa4fe1bc",
      "hash": "dae902fb1455b1fc9c565273aa4fe1bc"
    }
  ]
}

Build or containerize

In this stage you can build your artifacts. The pipeline provides some default features for docker image type artifacts, but you're able to build any kind of artifacts here. What is important, is to save the created artifacts for the pipeline, so later it can run scans on it, or use them in your release stage.

To provide information on your built artifacts, use the save_artifact pipelinectl interface.

Example

#
# your scripts building the artifact
#
# make sure you prepare or export the following data from each built artifact:
# - type (image for docker images, package for rpms, npm tarballs, etc )
# - full artifact URL with version tag
# - artifact digest
#
your_build_scripts

#
# when the build is complete
# use `save_artifact` to add these information to the pipeline
# artifact-reference-name can be any name, it is used to refer to the stored artifact
#
save_artifact <artifact-reference-name> \
    type=image" \
    name="${IMAGE_URL}" \
    digest="${IMAGE_DIGEST}"

The preferred format for image name is image-URL:build-tag, for example, wcp-compliance-automation-team-docker-local.artifactory.swg-devops.com/compliance-baseimage:2.8.0. {: important]

If you build Docker images, use the save_artifact interface to send those images for the default built-in image signing and CR VA scanning tasks.

Release

At the end of the pipeline, the built artifacts must be added to the inventory, so they can be promoted to deployment. The release stage provides flexibility if you want to add other artifacts to the inventory, such as Helm charts.

In this stage, you can use the CLI cocoa inventory add command, and the data from pipelinectl commands, to create the inventory entries.

If there are problems in the pipeline run, you migh want to skip inventory update to avoid a problematic inventory. To skip inventory update use the following environment variables:

  • skip-inventory-update-on-failure Opt-in environment variable from pipeline to specify whether the inventory is updated.
  • one-pipeline-status Set to 1 if there is a stage failure in the pipeline run.

Check these variables before calling cocoa inventory add in this stage.

Example

# Check the status of pipeline and then release the artifacts to inventory

ONE_PIPELINE_STATUS=$(get_env one-pipeline-status 0)
if [ -n "$(get_env skip-inventory-update-on-failure "")" ]; then
    if [ $ONE_PIPELINE_STATUS -eq 1 ]; then
          echo "Skipping release stage as some of the pipeline stages are not successful."
          exit 1
    fi
fi

#
# `list_artifacts` returns the list of the reference names of saved artifacts
#
list_artifacts | while IFS= read -r artifact ; do
    #
    # Add a new value to the inventory repository. `cocoa inventory add` creates a new file with the name option, 
    # if does not exist otherwise overwrites it.
    #
    cocoa inventory add \
        --name="${artifact}" \
        --artifact="$(load_artifact $artifact name)" \
        --repository-url="$(load_repo app-repo url)" \
        --commit-sha="$(load_repo app-repo commit)" \
        --build-number="${BUILD_NUMBER}" \
        --pipeline-run-id="${PIPELINE_RUN_ID}" \
        --version="$(get_env version)" \
        --app-artifacts="{ \
            \"signature\": \"$(load_artifact $artifact signature)\", \
            \"provenance\": \"$(load_artifact $artifact name)\"\
        }"
done

To use the CLI, you must install it in your scripts, or use a base image that has the CLI pre-installed.