Skip to content

Commit

Permalink
feat: add task validation in CI
Browse files Browse the repository at this point in the history
  • Loading branch information
tisutisu committed Nov 27, 2024
1 parent b0a4fea commit a897d76
Show file tree
Hide file tree
Showing 9 changed files with 469 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .github/actions/install-tkn/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# yamllint disable-file
---
name: Install tkn
runs:
using: "composite"
steps:
- run: |
curl -LO "https://github.com/tektoncd/cli/releases/download/v${TKN_CLI_VERSION}/tektoncd-cli-${TKN_CLI_VERSION}_Linux-64bit.deb"
sudo dpkg -i ./tektoncd-cli-${TKN_CLI_VERSION}_Linux-64bit.deb
shell: bash
env:
TKN_CLI_VERSION: 0.38.1
7 changes: 7 additions & 0 deletions .github/resources/workspace-template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Mi
187 changes: 187 additions & 0 deletions .github/scripts/test_tekton_tasks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
#!/bin/bash

set -e
# This script will run task tests for all task directories
# provided either via TEST_ITEMS env var, or as arguments
# when running the script.
#
# Requirements:
# - Connection to a running k8s cluster (e.g. kind)
# - upstream konflux-ci installed on the cluster ( Follow steps from: https://github.com/konflux-ci/konflux-ci?tab=readme-ov-file#bootstrapping-the-cluster)
# - tkn installed
#
# Examples of usage:
# export TEST_ITEMS="task/git-clone/0.1 some/other/dir"
# ./test_tekton_tasks.sh
#
# or
#
# ./test_tekton_tasks.sh task/git-clone/0.1 some/other/dir

# Define a custom kubectl path if you like
KUBECTL_CMD=${KUBECTL_CMD:-kubectl}

# yield empty strings for unmatched patterns
shopt -s nullglob

WORKSPACE_TEMPLATE=${BASH_SOURCE%/*/*}/resources/workspace-template.yaml

if [[ -z $@ || ${1} == "-h" ]]; then
cat <<EOF
Error: No task directories.
Usage:
$0 [item1] [item2] [...]
Example: ./.github/scripts/test_tekton_tasks.sh task/git-clone/0.1
or
export TEST_ITEMS="item1 item2 ..."
$0
Items can be task directories including version or paths to task test yaml files (useful when working on a single test)
EOF
exit 1
fi

if [ $# -gt 0 ]; then
TEST_ITEMS=$@
fi

# Check that all directories or test yamls exist. If not, fail
for ITEM in $TEST_ITEMS; do
if [[ "$ITEM" == *tests/test-*.yaml && -f "$ITEM" ]]; then
true
elif [[ -d "$ITEM" ]]; then
true
else
echo "Error: Invalid test yaml file or task directory: $ITEM"
exit 1
fi
done

for ITEM in $TEST_ITEMS; do
echo "Test item: $ITEM"
TASK_DIR=$(echo $ITEM | cut -d '/' -f -3)
TASK_NAME=$(echo $ITEM | cut -d '/' -f 2)
TASK_VERSION=$(echo $ITEM | cut -d '/' -f 3)
echo "DEBUG: Task name: $TASK_NAME"
echo "DEBUG: Task version: $TASK_VERSION"

TASK_VERSION_WITH_HYPHEN="$(echo $TASK_VERSION | tr '.' '-')"
TEST_NS="${TASK_NAME}-${TASK_VERSION_WITH_HYPHEN}"

TASK_PATH=${TASK_DIR}/${TASK_NAME}.yaml
# check if task file exists or not
if [ ! -f $TASK_PATH ]; then
echo "ERROR: Task file does not exist: $TASK_PATH"
exit 1
fi

# Check if tests dir exists under task dir
TESTS_DIR=${TASK_DIR}/tests
if [ ! -d $TESTS_DIR ]; then
echo "ERROR: tests dir does not exist: $TESTS_DIR"
exit 1
fi

# check if tests yamls exists
if [[ "$ITEM" == *tests/test-*.yaml ]]; then
TEST_PATHS=($ITEM)
else
TEST_PATHS=($TESTS_DIR/test-*.yaml)
fi
if [ ${#TEST_PATHS[@]} -eq 0 ]; then
echo "WARNING: No tests for test item $ITEM ... Skipping..."
continue
fi

# Use a copy of the task file to prevent modifying the original task file
TASK_COPY=$(mktemp /tmp/task.XXXXXX)
clean() { rm -f ${TASK_COPY}; }
trap clean EXIT

cp "$TASK_PATH" "$TASK_COPY"

# run the pre-apply-task-hook.sh if exists
if [ -f ${TESTS_DIR}/pre-apply-task-hook.sh ]
then
echo "Found pre-apply-task-hook.sh file in dir: $TESTS_DIR. Executing..."
${TESTS_DIR}/pre-apply-task-hook.sh "$TASK_COPY"
fi

# Create test namespace
${KUBECTL_CMD} create namespace ${TEST_NS}

# Create the service account appstudio-pipeline (konflux spedific requirement)
$KUBECTL_CMD create sa appstudio-pipeline -n ${TEST_NS}

# dry-run this YAML to validate and also get formatting side-effects.
${KUBECTL_CMD} -n ${TEST_NS} create -f ${TASK_COPY} --dry-run=client -o yaml

echo "INFO: Installing task"
${KUBECTL_CMD} apply -f "$TASK_COPY" -n "$TEST_NS"

for TEST_PATH in ${TEST_PATHS[@]}; do
echo "========== Starting Test Pipeline: $TEST_PATH =========="
echo "INFO: Installing test pipeline: $TEST_PATH"
${KUBECTL_CMD} -n ${TEST_NS} apply -f $TEST_PATH
TEST_NAME=$(yq '.metadata.name' $TEST_PATH)

# Sometimes the pipeline is not available immediately
while ! ${KUBECTL_CMD} -n ${TEST_NS} get pipeline $TEST_NAME > /dev/null 2>&1; do
echo "DEBUG: Pipeline $TEST_NAME not ready. Waiting 5s..."
sleep 5
done

PIPELINERUN=$(tkn p start $TEST_NAME -n ${TEST_NS} -w name=tests-workspace,volumeClaimTemplateFile=$WORKSPACE_TEMPLATE -o json | jq -r '.metadata.name')
echo "INFO: Started pipelinerun: $PIPELINERUN"
sleep 1 # allow a second for the prun object to appear (including a status condition)
while [ "$(${KUBECTL_CMD} get pr $PIPELINERUN -n ${TEST_NS} -o=jsonpath='{.status.conditions[0].status}')" == "Unknown" ]; do
echo "DEBUG: PipelineRun $PIPELINERUN is in progress (status Unknown). Waiting for update..."
sleep 5
done
tkn pr logs $PIPELINERUN -n ${TEST_NS}

PR_STATUS=$(${KUBECTL_CMD} get pr $PIPELINERUN -n ${TEST_NS} -o=jsonpath='{.status.conditions[0].status}')

ASSERT_TASK_FAILURE=$(yq '.metadata.annotations.test/assert-task-failure' < $TEST_PATH)
if [ "$ASSERT_TASK_FAILURE" != "null" ]; then
if [ "$PR_STATUS" == "True" ]; then
echo "INFO: Pipeline $TEST_NAME is succeeded but was expected to fail"
exit 1
else
echo "DEBUG: Pipeline $TEST_NAME failed (expected). Checking that it failed in task ${ASSERT_TASK_FAILURE}..."

# Check that the pipelinerun failed on the tested task and not somewhere else
TASKRUN=$(${KUBECTL_CMD} get pr $PIPELINERUN -n ${TEST_NS} -o json|jq -r ".status.childReferences[] | select(.pipelineTaskName == \"${ASSERT_TASK_FAILURE}\") | .name")
if [ -z "$TASKRUN" ]; then
echo "ERROR: Unable to find task $ASSERT_TASK_FAILURE in childReferences of pipelinerun $PIPELINERUN. Pipelinerun failed earlier?"
exit 1
else
echo "DEBUG: Found taskrun $TASKRUN"
fi
if [ $(${KUBECTL_CMD} get tr $TASKRUN -n ${TEST_NS} -o=jsonpath='{.status.conditions[0].status}') != "False" ]; then
echo "ERROR: Taskrun did not fail - pipelinerun failed later on?"
exit 1
else
echo "INFO: Taskrun failed as expected"
fi

fi
else
if [ "$PR_STATUS" == "True" ]; then
echo "INFO: Pipelinerun $TEST_NAME succeeded"
else
echo "ERROR: Pipelinerun $TEST_NAME failed"
exit 1
fi
fi

echo "========== Completed: $TEST_PATH =========="
done

done
90 changes: 90 additions & 0 deletions .github/workflows/run-task-tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Run Task Tests

"on":
pull_request:
types:
- opened
- synchronize
- reopened

jobs:
run-task-tests:
runs-on: ubuntu-22.04
steps:
- name: Get all changed files in the PR from task directory
id: changed-dirs
uses: tj-actions/changed-files@v45
with:
files: |
task/**
dir_names: "true"
dir_names_max_depth: "3"

- name: Checkout build-defintions Repository
if: steps.changed-dirs.outputs.any_changed == 'true'
uses: actions/checkout@v3
with:
ref: "${{ github.event.pull_request.head.sha }}"
path: build-definitions

- name: Install tkn
if: steps.changed-dirs.outputs.any_changed == 'true'
uses: ./build-definitions/.github/actions/install-tkn

- name: Checkout konflux-ci/konflux-ci Repository
if: steps.changed-dirs.outputs.any_changed == 'true'
uses: actions/checkout@v3
with:
repository: 'konflux-ci/konflux-ci'
path: konflux-ci
ref: c4630e684e81f71bfd920fff0c9bb2956d3265f4

- name: Create k8s Kind Cluster
if: steps.changed-dirs.outputs.any_changed == 'true'
uses: helm/kind-action@v1
with:
config: konflux-ci/kind-config.yaml

- name: Show version information
if: steps.changed-dirs.outputs.any_changed == 'true'
run: |
kubectl version
kind version
- name: Deploying Dependencies
if: steps.changed-dirs.outputs.any_changed == 'true'
run: |
cd $GITHUB_WORKSPACE/konflux-ci
./deploy-deps.sh
- name: Wait for the dependencies to be ready
if: steps.changed-dirs.outputs.any_changed == 'true'
run: |
cd $GITHUB_WORKSPACE/konflux-ci
./wait-for-all.sh
- name: Deploying Konflux
if: steps.changed-dirs.outputs.any_changed == 'true'
run: |
cd $GITHUB_WORKSPACE/konflux-ci
./deploy-konflux.sh
- name: List namespaces
if: steps.changed-dirs.outputs.any_changed == 'true'
run: |
kubectl get namespace
- name: Deploy test resources
if: steps.changed-dirs.outputs.any_changed == 'true'
run: |
cd $GITHUB_WORKSPACE/konflux-ci
./deploy-test-resources.sh
- name: Run the task tests
if: steps.changed-dirs.outputs.any_changed == 'true'
env:
CHANGED_DIRS: ${{ steps.changed-dirs.outputs.all_changed_files }}
run: |
echo "Task Dirs changed in PR: ${CHANGED_DIRS}"
cd $GITHUB_WORKSPACE/build-definitions
./.github/scripts/test_tekton_tasks.sh ${CHANGED_DIRS}
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,76 @@ Specify the Quay repository using the `QUAY_NAMESPACE` environment variable in t
./hack/test-shellspec.sh`
```
## Testing Tasks
When updating tasks, if the tasks doesn't have tests, try to add a few tests. Currently it is not mandatory, but is recommended.
When a pull request is opened, CI will run the tests (if it exists) for the task directories that are being modified.
[Github workflow](https://github.com/konflux-ci/build-definitions/blob/main/.github/workflows/run-task-tests.yaml) runs the tests.
Tests are defined as Tekton Pipelines inside the `tests` subdirectory of the task directory. The test filenames must match `test-*.yaml` format and
a test file should contain a single Pipeline.
E.g. to add a test pipeline for `task/git-clone/0.1` task, you can add a pipeline such as `task/git-clone/0.1/tests/test-git-clone-run-with-tag.yaml`
Refer the task under test in a test pipeline by task name. For example:
```
- name: run-task
taskRef:
name: git-clone
```
### Testing scenarios where the Task is expected to fail
When testing Tasks, most tests will test a positive outcome. But sometimes it's desirable to test that a Task fails, for example when invalid data is supplied as input for the Task. But if the Task under test fails in the test Pipeline, the whole Pipeline will fail too. So we need a way to tell the test script that the given test Pipeline is expected to fail.
You can do this by adding the annotation `test/assert-task-failure` to the test pipeline object. This annotation will specify which task `(.spec.tasks[*].name)` in the pipeline is expected to fail. For example:
```
---
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: test-git-clone-fail-for-wrong-url
annotations:
test/assert-task-failure: "run-task"
```
When this annotation is present, the test script will test that the pipeline fails and also that it fails in the expected task.
### Adding Workspaces
Some tasks require one or multiple workspaces. This means that the test pipeline will also have to declare a workspace and bind it to the workspace(s) required by the task under test.
Currently, the test script will pass a single workspace named `tests-workspace` mapping to a 10Mi volume when starting the pipelinerun.
This workspace can be used in the test pipeline.
### Test Setup
Some task tests will require setup on the kind cluster before the test pipeline can be run. Certain things can be done in a setup task step as part of the test pipeline, but others cannot.
In order to achieve this, a `pre-apply-task-hook.sh` script can be created in the `tests` directory for a task. When the CI runs the testing, it will first check for this file. If it is found, it is executed before the test pipeline.
### Mocking commands executed in task scripts
Mocking commands is possible similar to the release service catalog repository.
For more details and example, refer [here](https://github.com/konflux-ci/release-service-catalog/blob/development/CONTRIBUTING.md#mocking-commands-executed-in-task-scripts).
### Prerequisites for running task test locally
- Upstream [konflux-ci installed](https://github.com/konflux-ci/konflux-ci?tab=readme-ov-file#bootstrapping-the-cluster) on a kind cluster
- [tkn](https://github.com/tektoncd/cli) installed
- jq installed
You can run the test script locally and to run tests for a particular task, pass the task directories as arguments, e.g.
```
./.github/scripts/test_tekton_tasks.sh task/git-clone/0.1
```
This will install the task and run all test pipelines matching `tests/test-*.yaml` under task directory.
Another option is to run one or more tests directly by specifying them as arguments:
```
./.github/scripts/test_tekton_tasks.sh tasks/git-clone/tests/test-git-clone-run-with-tag.yaml
```
It will then run only the specified test pipeline.
### Compliance
Task definitions must comply with the [Enterprise Contract](https://enterprisecontract.dev/) policies.
Expand Down
9 changes: 9 additions & 0 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@
],
"autoReplaceStringTemplate": "value: {{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
"datasourceTemplate": "docker"
},
{
"customType": "regex",
"fileMatch": [".github/workflows/run-task-tests.yaml"],
"matchStrings": ["ref:\\s+(?<currentDigest>[a-f0-9]{40})"],
"currentValueTemplate": "main",
"depNameTemplate": "konflux-ci",
"packageNameTemplate": "https://github.com/konflux-ci/konflux-ci",
"datasourceTemplate": "git-refs"
}
]
}
Loading

0 comments on commit a897d76

Please sign in to comment.