diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3453fa --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +keys/signing-key.pem diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1191f6c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,47 @@ +# AGENTS.md + +Guidance for AI coding agents working in `tektoncd-catalog/kaniko`. For full +detail see [DEVELOPMENT.md](DEVELOPMENT.md). + +## Repository structure + +| Path | Role | +|------|------| +| `task/kaniko/kaniko.yaml` | **Edit this.** The `kaniko` Task — the single source of truth. | +| `stepaction/kaniko/kaniko.yaml` | **Generated — never edit by hand.** Derived from the Task. | +| `hack/generate-stepaction.sh` | Wrapper around the Python generator. | +| `hack/generate-stepaction.py` | Derives the StepAction from the Task (workspaces → params). | +| `hack/release.sh` | Release automation. | +| `test/` | e2e runners (`e2e-tests.sh`, `e2e-bundle-test.sh`). | +| `.github/workflows/` | `build.yaml` (lint/e2e), `release.yaml` (bundle publish). | + +## Critical Rules + +1. **Never edit `stepaction/kaniko/kaniko.yaml` directly.** It is generated + from the Task. Edit `task/kaniko/kaniko.yaml`, then run + `./hack/generate-stepaction.sh`. CI's lint step diffs the committed file + against a freshly generated one and fails on mismatch. +2. **No `$(params.*)` in `script:` blocks.** For StepActions `$(params.*)` in + scripts is not supported. Pass values via `env:` and reference the shell + env var. +3. **Workspaces map to params in the StepAction.** `source` → `source-path`, + `dockerconfig` → `dockerconfig-path`. +4. **Sign off every commit** (DCO / EasyCLA): `git commit --signoff`. +5. **Use conventional commit prefixes** (`feat:`, `fix:`, `docs:`, `chore:`, + `ci:`) — the release changelog is derived from them. + +## Common commands + +```bash +./hack/generate-stepaction.sh # regenerate the StepAction from the Task +./hack/release.sh v0.2.0 --dry-run # preview a release +./test/e2e-tests.sh # e2e in a kind cluster +./test/e2e-bundle-test.sh # bundle-resolver e2e +``` + +## Validating changes locally + +1. After editing the Task, run `./hack/generate-stepaction.sh`. +2. Confirm `git status` shows only intended changes. +3. Run the relevant e2e script against a kind cluster. +4. Update `README.md` if you changed installation or usage. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..755358c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,60 @@ +# Contributing + +Thanks for your interest in contributing to `tektoncd-catalog/kaniko`! This +repository is part of the Tekton Catalog and follows the broader +[tektoncd-catalog contributing guide](https://github.com/tektoncd-catalog/.github/blob/main/CONTRIBUTING.md). + +For technical details on how the repo is structured and generated, see +[DEVELOPMENT.md](DEVELOPMENT.md). + +## Developer Certificate of Origin (DCO) / CLA + +All commits must be signed off to certify the +[Developer Certificate of Origin](https://developercertificate.org/). Add a +`Signed-off-by` trailer to every commit: + +```bash +git commit --signoff -m "fix: update kaniko image version" +``` + +The sign-off line must match the author's name and email. Contributions are +also covered by the Linux Foundation +[EasyCLA](https://github.com/tektoncd/community/blob/main/process.md#contributor-license-agreements) +check, which runs on pull requests — follow its prompt to sign the CLA the +first time you contribute. + +## Pull request workflow + +1. **Fork and branch** from `main`. +2. **Edit the Task** (`task/kaniko/kaniko.yaml`) — never edit the generated + `stepaction/kaniko/kaniko.yaml` directly. +3. **Regenerate** the StepAction and commit both files: + ```bash + ./hack/generate-stepaction.sh + git add task/ stepaction/ + ``` +4. **Test locally** (see [DEVELOPMENT.md](DEVELOPMENT.md#running-tests-locally)). +5. **Use conventional commit messages** (`feat:`, `fix:`, `docs:`, `chore:`, + `ci:`) — the release changelog is derived from these prefixes. +6. **Open a PR** with a clear description. + +Approvals are managed via `OWNERS` (Prow-based auto-merge). + +## CI expectations + +Every PR runs `.github/workflows/build.yaml`, which must pass: + +- **Lint** — validates YAML structure and verifies the StepAction is in sync + with the Task. +- **E2E** — installs the Task in a Kind cluster and builds a test image + across supported Tekton Pipelines LTS versions. + +> [!TIP] +> Before pushing, run `./hack/generate-stepaction.sh` and make sure +> `git status` is clean (apart from your intended changes). A stale StepAction +> is the most common CI failure. + +## Code of conduct + +This project follows the Tekton +[Code of Conduct](https://github.com/tektoncd/community/blob/main/code-of-conduct.md). diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..eb8cb28 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,115 @@ +# Development + +This document explains how the `tektoncd-catalog/kaniko` repository is +structured and how to develop, generate, test, and release its Task and +StepAction. + +> [!IMPORTANT] +> The `task/` directory is the **source of truth**. The `stepaction/` directory +> is **generated** from it. Never edit `stepaction/kaniko/kaniko.yaml` +> directly — edit the Task and run `./hack/generate-stepaction.sh`. + +## Architecture overview + +The repository ships a `kaniko` [Task](task/kaniko/) and a derived +[StepAction](stepaction/kaniko/) for Tekton Pipelines. Both use the +`ghcr.io/osscontainertools/kaniko` executor image (the community-maintained +fork of the archived Google kaniko project). + +``` +task/kaniko/kaniko.yaml ─────────── (source of truth) + │ + └─► hack/generate-stepaction.py ──► stepaction/kaniko/kaniko.yaml + (generated — do not edit) +``` + +Key files: + +| Path | Role | +|------|------| +| `task/kaniko/kaniko.yaml` | **Hand-edited.** The `kaniko` Task — the single source of truth. | +| `stepaction/kaniko/kaniko.yaml` | **Generated** from the Task. Do not edit. | +| `hack/generate-stepaction.sh` | Wrapper that runs the Python generator. | +| `hack/generate-stepaction.py` | Derives the StepAction from the Task (workspaces → params). | +| `hack/release.sh` | Release automation: bump version → regenerate → changelog → commit → tag → push. | +| `test/` | e2e runners (`e2e-tests.sh`, `e2e-bundle-test.sh`). | +| `.github/workflows/` | `build.yaml` (lint/e2e), `release.yaml` (bundle publish). | + +### Why generate the StepAction? + +- **Deterministic:** CI regenerates the StepAction and diffs it against what's + committed. The committed file must match exactly. +- **DRY:** The StepAction is a mechanical transform of the Task, so behaviour + stays in lockstep instead of being maintained by hand in two places. + +## How generation works + +Run: + +```bash +./hack/generate-stepaction.sh +``` + +Requirements: `python3` with **PyYAML**. If PyYAML isn't importable directly, +the wrapper falls back to `uv tool run --with pyyaml`. + +`generate-stepaction.py` parses the Task's build step and produces a StepAction: + +- **Workspaces become params.** `source` → `source-path`, `dockerconfig` → + `dockerconfig-path`. +- **Both steps merge into one.** The kaniko executor runs via a script, and the + URL result is written in the same step. +- **Script references use env vars** (never `$(params.*)`) because `$(params.*)` + substitution is not allowed in StepAction scripts. + +## Modifying the Task or StepAction + +1. Edit `task/kaniko/kaniko.yaml`. +2. Regenerate the StepAction: + ```bash + ./hack/generate-stepaction.sh + ``` +3. Review both files and commit them together. + +## Running tests locally + +E2e tests run against a real Tekton install in a local +[kind](https://kind.sigs.k8s.io/) cluster: + +```bash +kind create cluster +./test/e2e-tests.sh +./test/e2e-bundle-test.sh +``` + +Useful environment variables: + +| Var | Default | Meaning | +|-----|---------|---------| +| `PIPELINE_VERSION` | `v1.12.0` | Tekton Pipelines release to install | +| `TIMEOUT` | `180s` | Per-TaskRun timeout | +| `BUNDLE_REGISTRY` | `ttl.sh` | Registry the bundle test pushes to | + +## Release process + +Releases are driven by `hack/release.sh`: + +```bash +./hack/release.sh v0.2.0 --dry-run # preview the diff +./hack/release.sh v0.2.0 # bump, regenerate, commit, tag, push +``` + +What it does: + +1. Validates the version (`vX.Y.Z`) and that you're on an up-to-date `main`. +2. Bumps the `app.kubernetes.io/version` label in the Task and StepAction. +3. Regenerates the StepAction from the bumped Task. +4. Commits (`--signoff`), pushes `main`, creates an annotated tag, and pushes + the tag. + +The tag push triggers `.github/workflows/release.yaml`, which publishes a +Tekton bundle to `ghcr.io/tektoncd-catalog/kaniko`. + +## See also + +- [CONTRIBUTING.md](CONTRIBUTING.md) — contribution workflow and CI expectations. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000..7f8ecf6 --- /dev/null +++ b/OWNERS @@ -0,0 +1,6 @@ +# The OWNERS file is used by prow to automatically merge approved PRs. + +approvers: +- vdemeester +- vinamra28 +- QuanZhang-William diff --git a/README.md b/README.md new file mode 100644 index 0000000..3d1cbf0 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# Kaniko Task for Tekton + +[![Artifact Hub Tasks](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/kaniko)](https://artifacthub.io/packages/search?repo=kaniko) +[![Artifact Hub StepActions](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/kaniko-stepaction)](https://artifacthub.io/packages/search?repo=kaniko-stepaction) + +This repository contains the `kaniko` [Task](task/kaniko/) and [StepAction](stepaction/kaniko/) for [Tekton Pipelines](https://tekton.dev/), providing container image building capabilities using [kaniko](https://github.com/osscontainertools/kaniko). + +> **Note:** This uses the community-maintained fork of kaniko +> ([osscontainertools/kaniko](https://github.com/osscontainertools/kaniko)) +> since the original Google repository was archived. + +## Installation + +Install the Task directly: + +```bash +kubectl apply -f https://raw.githubusercontent.com/tektoncd-catalog/kaniko/main/task/kaniko/kaniko.yaml +``` + +Or use a [Tekton Bundle](https://tekton.dev/docs/pipelines/tekton-bundle-contracts/) with the bundle resolver: + +```yaml +taskRef: + resolver: bundles + params: + - name: bundle + value: ghcr.io/tektoncd-catalog/kaniko/bundle:v0.1.0 + - name: name + value: kaniko + - name: kind + value: task +``` + +## Quick Start + +```yaml +apiVersion: tekton.dev/v1 +kind: TaskRun +metadata: + generateName: kaniko-build- +spec: + taskRef: + name: kaniko + workspaces: + - name: source + persistentVolumeClaim: + claimName: my-source + params: + - name: IMAGE + value: registry.example.com/my-image:latest +``` + +## Documentation + +- **[Task reference](task/kaniko/README.md)** — full parameter, workspace, and authentication docs +- **[StepAction reference](stepaction/kaniko/README.md)** — composable step version +- **[DEVELOPMENT.md](DEVELOPMENT.md)** — architecture, generation, testing, and release process +- **[CONTRIBUTING.md](CONTRIBUTING.md)** — contribution workflow and CI expectations diff --git a/hack/generate-stepaction.py b/hack/generate-stepaction.py new file mode 100755 index 0000000..ad9534c --- /dev/null +++ b/hack/generate-stepaction.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 + +# Copyright 2025 The Tekton Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Derive a StepAction YAML from the kaniko Task YAML. + +The kaniko task has two steps: build-and-push (the core kaniko step) and +write-url (a trivial shell script). The StepAction merges both into a single +step: kaniko builds the image and a small script writes the URL result. + +Workspaces become params: + - source → source-path + - dockerconfig → dockerconfig-path (optional, empty string = not provided) + +Usage: generate-stepaction.py + +Requires PyYAML. +""" + +import copy +import re +import sys + +import yaml + + +# --- YAML dumper that uses block scalars for multiline strings --- +class StepActionDumper(yaml.SafeDumper): + pass + + +def str_representer(dumper, data): + if "\n" in data: + return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|") + if data == "" or re.match(r"^[\d.]+$", data): + return dumper.represent_scalar("tag:yaml.org,2002:str", data, style='"') + if data.lower() in ("true", "false", "yes", "no"): + return dumper.represent_scalar("tag:yaml.org,2002:str", data, style='"') + return dumper.represent_scalar("tag:yaml.org,2002:str", data) + + +StepActionDumper.add_representer(str, str_representer) + + +# --- Workspace → param mapping --- +WORKSPACE_PARAMS = [ + { + "name": "source-path", + "description": "Path to the source code containing the Dockerfile and build context.", + "type": "string", + }, + { + "name": "dockerconfig-path", + "description": "Path to a directory containing a docker config.json for registry auth. Empty string means no auth.", + "type": "string", + "default": "", + }, +] + + +def transform_description(desc: str) -> str: + d = desc.replace("This Task", "This StepAction") + d = d.replace("this Task", "this StepAction") + return d + + +def generate(task_file: str, output_file: str) -> None: + with open(task_file) as f: + task = yaml.safe_load(f) + + meta = task["metadata"] + spec = task["spec"] + build_step = spec["steps"][0] # build-and-push + + sa = { + "apiVersion": "tekton.dev/v1beta1", + "kind": "StepAction", + "metadata": { + "name": meta["name"], + "labels": { + "app.kubernetes.io/version": meta.get("labels", {}).get( + "app.kubernetes.io/version", "0.1" + ), + }, + "annotations": {}, + }, + "spec": {}, + } + + # Copy annotations (skip signature). + for k, v in meta.get("annotations", {}).items(): + if k == "tekton.dev/signature": + continue + sa["metadata"]["annotations"][k] = v + + sa["spec"]["description"] = transform_description(spec.get("description", "")) + + # Params: workspace replacements first, then task params (skip BUILDER_IMAGE, + # it becomes the step image directly). + sa["spec"]["params"] = [copy.deepcopy(p) for p in WORKSPACE_PARAMS] + for p in spec.get("params", []): + p2 = copy.deepcopy(p) + if "description" in p2 and isinstance(p2["description"], str): + p2["description"] = p2["description"].replace("this Task", "this StepAction") + sa["spec"]["params"].append(p2) + + # Image from the build step + sa["spec"]["image"] = build_step["image"] + + # Env: carry over from build step + add workspace path env vars + env = copy.deepcopy(build_step.get("env", [])) + env.append({"name": "SOURCE_PATH", "value": "$(params.source-path)"}) + env.append({"name": "DOCKERCONFIG_PATH", "value": "$(params.dockerconfig-path)"}) + env.append({"name": "IMAGE", "value": "$(params.IMAGE)"}) + env.append({"name": "DOCKERFILE", "value": "$(params.DOCKERFILE)"}) + env.append({"name": "CONTEXT", "value": "$(params.CONTEXT)"}) + sa["spec"]["env"] = env + + # Security context + if "securityContext" in build_step: + sa["spec"]["securityContext"] = copy.deepcopy(build_step["securityContext"]) + + # Results (step results use $(step.results.*)) + sa["spec"]["results"] = [] + for r in spec.get("results", []): + sa["spec"]["results"].append(copy.deepcopy(r)) + + # Combined script: run kaniko executor then write the URL result. + # In a StepAction we can't use args with $(params.*) substitution the same + # way, so we use a script with env vars. + sa["spec"]["script"] = """#!/busybox/sh +set -e + +# Set up docker config if provided +if [ -n "${DOCKERCONFIG_PATH}" ]; then + mkdir -p "${KANIKO_DIR}/.docker" + cp "${DOCKERCONFIG_PATH}/config.json" "${KANIKO_DIR}/.docker/config.json" 2>/dev/null || true +fi + +# Run kaniko executor +/kaniko/executor \\ + --dockerfile="${DOCKERFILE}" \\ + --context="${SOURCE_PATH}/${CONTEXT}" \\ + --destination="${IMAGE}" \\ + --digest-file="$(step.results.IMAGE_DIGEST.path)" \\ + "$@" + +# Write image URL result +printf "%s" "${IMAGE}" > "$(step.results.IMAGE_URL.path)" +""" + + # Args passthrough for EXTRA_ARGS + sa["spec"]["args"] = ["$(params.EXTRA_ARGS[*])"] + + header = f"# Generated from task/{meta['name']}/{meta['name']}.yaml \u2014 do not edit directly.\n" + + with open(output_file, "w") as f: + f.write(header) + yaml.dump( + sa, + f, + Dumper=StepActionDumper, + default_flow_style=False, + allow_unicode=True, + sort_keys=False, + ) + + +if __name__ == "__main__": + if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} ", file=sys.stderr) + sys.exit(1) + generate(sys.argv[1], sys.argv[2]) diff --git a/hack/generate-stepaction.sh b/hack/generate-stepaction.sh new file mode 100755 index 0000000..ea50237 --- /dev/null +++ b/hack/generate-stepaction.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# Copyright 2025 The Tekton Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Generate the StepAction from the Task. +# Usage: ./hack/generate-stepaction.sh + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +TASK_FILE="${ROOT_DIR}/task/kaniko/kaniko.yaml" +SA_FILE="${ROOT_DIR}/stepaction/kaniko/kaniko.yaml" + +mkdir -p "$(dirname "${SA_FILE}")" + +# Python runner: prefer uv (pulls in PyYAML), fall back to python3 with yaml. +if command -v uv &>/dev/null; then + PYRUN=(uv run --quiet --with pyyaml python3) +elif python3 -c 'import yaml' 2>/dev/null; then + PYRUN=(python3) +else + echo "Error: need either 'uv' or a python3 with PyYAML" >&2 + exit 1 +fi + +"${PYRUN[@]}" "${SCRIPT_DIR}/generate-stepaction.py" "${TASK_FILE}" "${SA_FILE}" +echo "Generated ${SA_FILE}" diff --git a/hack/release.sh b/hack/release.sh new file mode 100755 index 0000000..9dbf310 --- /dev/null +++ b/hack/release.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash + +# Copyright 2025 The Tekton Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Release script for tektoncd-catalog/kaniko. +# +# Usage: +# ./hack/release.sh v0.2.0 # bump, regenerate, commit, tag, push +# ./hack/release.sh v0.2.0 --dry-run # show what would change + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +VERSION="" +DRY_RUN=false + +for arg in "$@"; do + case "${arg}" in + --dry-run) DRY_RUN=true ;; + v*) VERSION="${arg}" ;; + *) echo "Unknown argument: ${arg}"; exit 1 ;; + esac +done + +if [[ -z "${VERSION}" ]]; then + echo "Usage: $0 [--dry-run]" + echo " Example: $0 v0.2.0" + exit 1 +fi + +if ! echo "${VERSION}" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "Error: version must match vX.Y.Z (got: ${VERSION})" + exit 1 +fi + +BARE_VERSION="${VERSION#v}" + +cd "${ROOT_DIR}" + +# Detect current version from the Task +CURRENT_VERSION=$(yq '.metadata.labels["app.kubernetes.io/version"]' task/kaniko/kaniko.yaml) + +echo "=== Release ${VERSION} ===" +echo " Current: ${CURRENT_VERSION}" +echo " Target: ${BARE_VERSION}" +echo "" + +# Ensure we're on main and up to date +BRANCH=$(git branch --show-current) +if [[ "${DRY_RUN}" != true ]]; then + if [[ "${BRANCH}" != "main" ]]; then + echo "Error: must be on main branch (currently on: ${BRANCH})" + exit 1 + fi + git fetch origin main + LOCAL=$(git rev-parse HEAD) + REMOTE=$(git rev-parse origin/main) + if [[ "${LOCAL}" != "${REMOTE}" ]]; then + echo "Error: local main is not up to date with origin/main" + exit 1 + fi +else + git fetch origin main 2>/dev/null || true +fi + +echo "--- Bumping version in Task" +yq -i ".metadata.labels[\"app.kubernetes.io/version\"] = \"${BARE_VERSION}\"" task/kaniko/kaniko.yaml + +echo "--- Regenerating StepAction" +"${SCRIPT_DIR}/generate-stepaction.sh" + +echo "--- Files changed:" +git --no-pager diff --stat + +if [[ "${DRY_RUN}" == true ]]; then + echo "" + echo "--- Dry run: showing changes ---" + git --no-pager diff -- task/ stepaction/ + echo "" + echo "--- Restoring working tree (dry run) ---" + git checkout -- task/ stepaction/ 2>/dev/null || true + echo "Dry run complete. Run without --dry-run to apply." + exit 0 +fi + +# Generate changelog from commits +COMMITS=$(git log --oneline "v${CURRENT_VERSION}..HEAD" --no-merges 2>/dev/null || git log --oneline -10) +TAG_MESSAGE="Release ${VERSION} + +Changes since v${CURRENT_VERSION}: +$(echo "${COMMITS}" | sed 's/^[a-f0-9]* /- /')" + +echo "--- Committing..." +git add task/ stepaction/ +git commit --signoff --message "chore: bump version to ${VERSION}" + +echo "--- Pushing to main..." +git push origin main:main + +echo "--- Tagging ${VERSION}..." +git tag -a "${VERSION}" -m "${TAG_MESSAGE}" + +echo "--- Pushing tag..." +git push origin "refs/tags/${VERSION}:refs/tags/${VERSION}" + +echo "" +echo "=== Release ${VERSION} initiated ===" +echo " Monitor: gh run list --workflow=release.yaml --limit 1" +echo " View: https://github.com/tektoncd-catalog/kaniko/releases/tag/${VERSION}" diff --git a/stepaction/artifacthub-repo.yaml b/stepaction/artifacthub-repo.yaml new file mode 100644 index 0000000..5534c7a --- /dev/null +++ b/stepaction/artifacthub-repo.yaml @@ -0,0 +1,4 @@ +repositoryID: kaniko-stepaction +owners: + - name: tektoncd-catalog + email: tekton-dev@googlegroups.com diff --git a/stepaction/kaniko/README.md b/stepaction/kaniko/README.md new file mode 100644 index 0000000..662cc85 --- /dev/null +++ b/stepaction/kaniko/README.md @@ -0,0 +1,54 @@ +# Kaniko StepAction + +This StepAction builds source into a container image using +[`kaniko`](https://github.com/osscontainertools/kaniko). It is a composable +step version of the [kaniko Task](../../task/kaniko/README.md). + +> **Note:** This file is **generated** from the Task. Do not edit it directly. +> Edit the Task and run `./hack/generate-stepaction.sh`. + +## Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `source-path` | Path to the source code containing the Dockerfile and build context. | _(required)_ | +| `dockerconfig-path` | Path to a directory containing a docker `config.json`. | `""` (no auth) | +| `IMAGE` | Name (reference) of the image to build. | _(required)_ | +| `DOCKERFILE` | Path to the Dockerfile to build. | `./Dockerfile` | +| `CONTEXT` | The build context used by Kaniko. | `./` | +| `EXTRA_ARGS` | Additional args to pass to the Kaniko executor. | `[]` | +| `BUILDER_IMAGE` | The Kaniko executor image to use. | `ghcr.io/osscontainertools/kaniko:v1.27.6` | +| `KANIKO_DIR` | Kaniko working directory (buildcontext, stages, layers, caches, docker config). | `/kaniko` | + +## Results + +| Result | Description | +|--------|-------------| +| `IMAGE_DIGEST` | Digest of the image just built. | +| `IMAGE_URL` | URL of the image just built. | + +## Usage + +```yaml +apiVersion: tekton.dev/v1 +kind: TaskRun +metadata: + name: kaniko-build +spec: + taskSpec: + steps: + - name: build + ref: + name: kaniko + params: + - name: source-path + value: $(workspaces.source.path) + - name: IMAGE + value: registry.example.com/my-image:latest + workspaces: + - name: source + workspaces: + - name: source + persistentVolumeClaim: + claimName: my-source +``` diff --git a/stepaction/kaniko/kaniko.yaml b/stepaction/kaniko/kaniko.yaml new file mode 100644 index 0000000..caa0d77 --- /dev/null +++ b/stepaction/kaniko/kaniko.yaml @@ -0,0 +1,87 @@ +# Generated from task/kaniko/kaniko.yaml — do not edit directly. +apiVersion: tekton.dev/v1beta1 +kind: StepAction +metadata: + name: kaniko + labels: + app.kubernetes.io/version: "0.1.0" + annotations: + tekton.dev/pipelines.minVersion: "0.43.0" + tekton.dev/categories: Image Build + tekton.dev/tags: image-build + tekton.dev/displayName: Build and upload container image using Kaniko + tekton.dev/platforms: linux/amd64,linux/arm64,linux/ppc64le +spec: + description: This StepAction builds a simple Dockerfile with kaniko and pushes to + a registry. This StepAction stores the image name and digest as results, allowing + Tekton Chains to pick up that an image was built & sign it. + params: + - name: source-path + description: Path to the source code containing the Dockerfile and build context. + type: string + - name: dockerconfig-path + description: Path to a directory containing a docker config.json for registry + auth. Empty string means no auth. + type: string + default: "" + - name: IMAGE + description: Name (reference) of the image to build. + - name: DOCKERFILE + description: Path to the Dockerfile to build. + default: ./Dockerfile + - name: CONTEXT + description: The build context used by Kaniko. + default: ./ + - name: EXTRA_ARGS + type: array + default: [] + - name: BUILDER_IMAGE + description: The kaniko executor image to use. + default: ghcr.io/osscontainertools/kaniko:v1.27.6@sha256:95779f52d460ca70b65a4f4679d4f5163ad6c33edea3303dc8bbff847de5e05c + - name: KANIKO_DIR + description: Specifies the kaniko working directory (used for buildcontext, stages, + layers, caches, and docker config). + default: /kaniko + image: $(params.BUILDER_IMAGE) + env: + - name: KANIKO_DIR + value: $(params.KANIKO_DIR) + - name: SOURCE_PATH + value: $(params.source-path) + - name: DOCKERCONFIG_PATH + value: $(params.dockerconfig-path) + - name: IMAGE + value: $(params.IMAGE) + - name: DOCKERFILE + value: $(params.DOCKERFILE) + - name: CONTEXT + value: $(params.CONTEXT) + securityContext: + runAsUser: 0 + results: + - name: IMAGE_DIGEST + description: Digest of the image just built. + - name: IMAGE_URL + description: URL of the image just built. + script: | + #!/busybox/sh + set -e + + # Set up docker config if provided + if [ -n "${DOCKERCONFIG_PATH}" ]; then + mkdir -p "${KANIKO_DIR}/.docker" + cp "${DOCKERCONFIG_PATH}/config.json" "${KANIKO_DIR}/.docker/config.json" 2>/dev/null || true + fi + + # Run kaniko executor + /kaniko/executor \ + --dockerfile="${DOCKERFILE}" \ + --context="${SOURCE_PATH}/${CONTEXT}" \ + --destination="${IMAGE}" \ + --digest-file="$(step.results.IMAGE_DIGEST.path)" \ + "$@" + + # Write image URL result + printf "%s" "${IMAGE}" > "$(step.results.IMAGE_URL.path)" + args: + - $(params.EXTRA_ARGS[*]) diff --git a/task/artifacthub-repo.yaml b/task/artifacthub-repo.yaml new file mode 100644 index 0000000..aa30f6e --- /dev/null +++ b/task/artifacthub-repo.yaml @@ -0,0 +1,4 @@ +repositoryID: kaniko +owners: + - name: tektoncd-catalog + email: tekton-dev@googlegroups.com diff --git a/task/kaniko/README.md b/task/kaniko/README.md index 9c9033b..84b0511 100644 --- a/task/kaniko/README.md +++ b/task/kaniko/README.md @@ -1,79 +1,69 @@ # Kaniko -This Task builds source into a container image using Google's -[`kaniko`](https://github.com/GoogleCloudPlatform/kaniko) tool. +This Task builds source into a container image using +[`kaniko`](https://github.com/osscontainertools/kaniko). ->kaniko doesn't depend on a Docker daemon and executes each command within a ->Dockerfile completely in userspace. This enables building container images in ->environments that can't easily or securely run a Docker daemon, such as a ->standard Kubernetes cluster. -> - [Kaniko website](https://github.com/GoogleCloudPlatform/kaniko) +> kaniko doesn't depend on a Docker daemon and executes each command within a +> Dockerfile completely in userspace. This enables building container images in +> environments that can't easily or securely run a Docker daemon, such as a +> standard Kubernetes cluster. -kaniko is meant to be run as an image, `gcr.io/kaniko-project/executor:v1.5.1`. This -makes it a perfect tool to be part of Tekton. This task can also be used with Tekton Chains to -attest and sign the image. - -## Changelog - -- Added `IMAGE_DIGEST` to the `Results` which get populated with the digest of a built image -- Added `IMAGE_URL` to the `Results` which get populated with the URL of a built image - -Both these results are needed in order for Chains to sign the image. See Chains documentation for more information: https://github.com/tektoncd/chains/blob/main/docs/config.md#chains-type-hinting - -## Install the Task - -``` -kubectl apply -f https://api.hub.tekton.dev/v1/resource/tekton/task/kaniko/0.6/raw -``` +This Task stores the image name and digest as results, allowing +[Tekton Chains](https://github.com/tektoncd/chains) to pick up that an image +was built & sign it. ## Parameters -* **IMAGE**: The name (reference) of the image to build. -* **DOCKERFILE**: The path to the `Dockerfile` to execute (_default:_ `./Dockerfile`) -* **CONTEXT**: The build context used by Kaniko (_default:_ `./`) -* **EXTRA_ARGS**: Additional args to pass to the Kaniko executor. -* **BUILDER_IMAGE**: The Kaniko executor image to use (_default:_ `gcr.io/kaniko-project/executor:v1.5.1`) +| Parameter | Description | Default | +|-----------|-------------|---------| +| `IMAGE` | Name (reference) of the image to build. | _(required)_ | +| `DOCKERFILE` | Path to the Dockerfile to build. | `./Dockerfile` | +| `CONTEXT` | The build context used by Kaniko. | `./` | +| `EXTRA_ARGS` | Additional args to pass to the Kaniko executor. | `[]` | +| `BUILDER_IMAGE` | The Kaniko executor image to use. | `ghcr.io/osscontainertools/kaniko:v1.27.6` | +| `KANIKO_DIR` | Kaniko working directory (buildcontext, stages, layers, caches, docker config). | `/kaniko` | ## Workspaces -* **source**: A [Workspace](https://github.com/tektoncd/pipeline/blob/master/docs/workspaces.md) containing the source to build. -* **dockerconfig**: An optional [Workspace](https://github.com/tektoncd/pipeline/blob/master/docs/workspaces.md) containing a Docker `config.json` +| Workspace | Description | Optional | +|-----------|-------------|----------| +| `source` | Holds the context and Dockerfile. | No | +| `dockerconfig` | Includes a docker `config.json` for registry auth. | Yes | ## Results -* **IMAGE_DIGEST**: The digest of the image just built. -* **IMAGE_URL**: URL of the image just built. - -These results are needed by chains to sign the created image. See Chains documentation for more information: https://github.com/tektoncd/chains/blob/main/docs/config.md#chains-type-hinting +| Result | Description | +|--------|-------------| +| `IMAGE_DIGEST` | Digest of the image just built. | +| `IMAGE_URL` | URL of the image just built. | -## Authentication to a Container Registry +## Authentication -kaniko builds an image and pushes it to the destination defined as a parameter. -In order to properly authenticate to the remote container registry, it needs to -have the proper credentials. This can achieved by using a workspace that contains -the docker `config.json`. +To authenticate to a remote container registry, use the `dockerconfig` +workspace bound to a Secret containing a `config.json` key: -When using a workspace, the workspace shall be bound to a secret that embeds the -configuration file in a key called `config.json`. +```yaml +workspaces: + - name: dockerconfig + secret: + secretName: my-docker-credentials +``` ## Usage -This TaskRun runs the Task to fetch a Git repo, and build and push a container -image using Kaniko - ```yaml -apiVersion: tekton.dev/v1beta1 +apiVersion: tekton.dev/v1 kind: TaskRun metadata: - name: example-run + name: kaniko-build spec: taskRef: name: kaniko workspaces: - - name: source - persistentVolumeClaim: - claimName: my-source - - name: dockerconfig - secret: - secretName: my-secret + - name: source + persistentVolumeClaim: + claimName: my-source + params: + - name: IMAGE + value: registry.example.com/my-image:latest ``` diff --git a/task/kaniko/kaniko.yaml b/task/kaniko/kaniko.yaml index 265ba2d..2847402 100644 --- a/task/kaniko/kaniko.yaml +++ b/task/kaniko/kaniko.yaml @@ -1,15 +1,15 @@ -apiVersion: tekton.dev/v1beta1 +apiVersion: tekton.dev/v1 kind: Task metadata: name: kaniko labels: - app.kubernetes.io/version: "0.6" + app.kubernetes.io/version: "0.1.0" annotations: - tekton.dev/pipelines.minVersion: "0.17.0" + tekton.dev/pipelines.minVersion: "0.43.0" tekton.dev/categories: Image Build tekton.dev/tags: image-build tekton.dev/displayName: "Build and upload container image using Kaniko" - tekton.dev/platforms: "linux/amd64" + tekton.dev/platforms: "linux/amd64,linux/arm64,linux/ppc64le" spec: description: >- This Task builds a simple Dockerfile with kaniko and pushes to a registry. @@ -28,8 +28,11 @@ spec: type: array default: [] - name: BUILDER_IMAGE - description: The image on which builds will run (default is v1.5.1) - default: gcr.io/kaniko-project/executor:v1.5.1@sha256:c6166717f7fe0b7da44908c986137ecfeab21f31ec3992f6e128fff8a94be8a5 + description: The kaniko executor image to use. + default: ghcr.io/osscontainertools/kaniko:v1.27.6@sha256:95779f52d460ca70b65a4f4679d4f5163ad6c33edea3303dc8bbff847de5e05c + - name: KANIKO_DIR + description: Specifies the kaniko working directory (used for buildcontext, stages, layers, caches, and docker config). + default: /kaniko workspaces: - name: source description: Holds the context and Dockerfile @@ -47,19 +50,19 @@ spec: workingDir: $(workspaces.source.path) image: $(params.BUILDER_IMAGE) args: - - $(params.EXTRA_ARGS) + - $(params.EXTRA_ARGS[*]) - --dockerfile=$(params.DOCKERFILE) - - --context=$(workspaces.source.path)/$(params.CONTEXT) # The user does not need to care the workspace and the source. + - --context=$(workspaces.source.path)/$(params.CONTEXT) - --destination=$(params.IMAGE) - --digest-file=$(results.IMAGE_DIGEST.path) - # kaniko assumes it is running as root, which means this example fails on platforms - # that default to run containers as random uid (like OpenShift). Adding this securityContext - # makes it explicit that it needs to run as root. + env: + - name: KANIKO_DIR + value: $(params.KANIKO_DIR) securityContext: runAsUser: 0 - name: write-url - image: docker.io/library/bash:5.1.4@sha256:b208215a4655538be652b2769d82e576bc4d0a2bb132144c060efc5be8c3f5d6 + image: docker.io/library/alpine:3.21@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c script: | set -e image="$(params.IMAGE)" - echo -n "${image}" | tee "$(results.IMAGE_URL.path)" + printf "%s" "${image}" | tee "$(results.IMAGE_URL.path)" diff --git a/task/kaniko/tests/pre-apply-task-hook.sh b/task/kaniko/tests/pre-apply-task-hook.sh deleted file mode 100755 index 892417d..0000000 --- a/task/kaniko/tests/pre-apply-task-hook.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# Add an internal registry as sidecar to the task so we can upload it directly -# from our tests withouth having to go to an external registry. -add_sidecar_registry ${TMPF} - -# Add git-clone -add_task git-clone latest diff --git a/task/kaniko/tests/resources.yaml b/task/kaniko/tests/resources.yaml deleted file mode 100644 index 195051e..0000000 --- a/task/kaniko/tests/resources.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: kaniko-source-pvc -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 500Mi diff --git a/task/kaniko/tests/run.yaml b/task/kaniko/tests/run.yaml deleted file mode 100644 index 57c90d9..0000000 --- a/task/kaniko/tests/run.yaml +++ /dev/null @@ -1,90 +0,0 @@ ---- -apiVersion: tekton.dev/v1beta1 -kind: Pipeline -metadata: - name: kaniko-test-pipeline -spec: - workspaces: - - name: shared-workspace - params: - - name: image - description: reference of the image to build - tasks: - - name: fetch-repository - taskRef: - name: git-clone - workspaces: - - name: output - workspace: shared-workspace - params: - - name: url - value: https://github.com/kelseyhightower/nocode - - name: subdirectory - value: "" - - name: deleteExisting - value: "true" - - name: kaniko - taskRef: - name: kaniko - runAfter: - - fetch-repository - workspaces: - - name: source - workspace: shared-workspace - params: - - name: IMAGE - value: $(params.image) - - name: EXTRA_ARGS - value: - - --skip-tls-verify - - name: verify-digest - runAfter: - - kaniko - params: - - name: digest - value: $(tasks.kaniko.results.IMAGE_DIGEST) - taskSpec: - params: - - name: digest - steps: - - name: bash - image: ubuntu - script: | - echo $(params.digest) - case .$(params.digest) in - ".sha"*) exit 0 ;; - *) echo "Digest value is not correct" && exit 1 ;; - esac - - name: verify-url - runAfter: - - kaniko - params: - - name: url - value: $(tasks.kaniko.results.IMAGE_URL) - taskSpec: - params: - - name: url - steps: - - name: bash - image: ubuntu - script: | - echo $(params.url) - case .$(params.url) in - *"/kaniko-nocode") exit 0 ;; - *) echo "URL value is not correct" && exit 1 ;; - esac ---- -apiVersion: tekton.dev/v1beta1 -kind: PipelineRun -metadata: - name: kaniko-test-pipeline-run -spec: - pipelineRef: - name: kaniko-test-pipeline - params: - - name: image - value: localhost:5000/kaniko-nocode - workspaces: - - name: shared-workspace - persistentvolumeclaim: - claimName: kaniko-source-pvc diff --git a/test/e2e-bundle-test.sh b/test/e2e-bundle-test.sh new file mode 100755 index 0000000..8dc1476 --- /dev/null +++ b/test/e2e-bundle-test.sh @@ -0,0 +1,169 @@ +#!/usr/bin/env bash + +# Copyright 2025 The Tekton Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# E2e test for Tekton Bundle publishing. +# Pushes the kaniko task as a bundle to ttl.sh, then runs a PipelineRun +# that references it via the bundle resolver. +# +# Environment variables: +# PIPELINE_VERSION - Tekton Pipelines version to install (default: v1.12.0) +# TIMEOUT - Timeout for PipelineRun (default: 180s) +# BUNDLE_REGISTRY - Registry to push bundles to (default: ttl.sh) + +set -euo pipefail + +PIPELINE_VERSION="${PIPELINE_VERSION:-v1.12.0}" +TIMEOUT="${TIMEOUT:-180s}" +BUNDLE_REGISTRY="${BUNDLE_REGISTRY:-ttl.sh}" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# Generate unique bundle reference (ttl.sh images expire after 1h) +BUNDLE_ID="kaniko-e2e-$(head -c 8 /proc/sys/kernel/random/uuid)" +BUNDLE_REF="${BUNDLE_REGISTRY}/${BUNDLE_ID}:1h" + +echo "--- Installing Tekton Pipelines ${PIPELINE_VERSION}" +kubectl apply --filename "https://github.com/tektoncd/pipeline/releases/download/${PIPELINE_VERSION}/release.yaml" +echo "--- Waiting for Tekton Pipelines to be ready" +kubectl wait --for=condition=available --timeout=300s \ + deployment --all -n tekton-pipelines +echo "--- Waiting for the admission webhook to serve" +for _ in $(seq 1 30); do + if [[ -n "$(kubectl get endpoints tekton-pipelines-webhook \ + -n tekton-pipelines -o jsonpath='{.subsets[*].addresses[*].ip}' 2>/dev/null)" ]]; then + break + fi + sleep 5 +done + +echo "--- Setting up in-cluster registry" +kubectl run registry --image=registry:2 --port=5000 +kubectl wait --for=condition=Ready --timeout=60s pod/registry +kubectl expose pod registry --port=5000 + +echo "--- Pushing Tekton Bundle" +echo " kaniko -> ${BUNDLE_REF}" +tkn bundle push "${BUNDLE_REF}" -f "${ROOT_DIR}/task/kaniko/kaniko.yaml" + +echo "--- Creating PipelineRun using bundle resolver" +cat < \$(workspaces.source.path)/Dockerfile < /hello.txt + CMD ["cat", "/hello.txt"] + DOCKERFILE + - name: build + runAfter: ["create-dockerfile"] + taskRef: + resolver: bundles + params: + - name: bundle + value: ${BUNDLE_REF} + - name: name + value: kaniko + - name: kind + value: task + workspaces: + - name: source + workspace: shared-workspace + params: + - name: IMAGE + value: registry:5000/kaniko-test:bundle + - name: EXTRA_ARGS + value: + - --insecure + workspaces: + - name: shared-workspace + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 256Mi +EOF + +pr="kaniko-bundle-test" + +SNAP_DIR="$(mktemp -d)" + +snapshot_run() { + kubectl get pipelinerun/"$1" -o yaml --show-managed-fields=false 2>/dev/null \ + | sed -e '/^status:/,$d' \ + -e '/^ resourceVersion:/d' \ + -e '/^ uid:/d' \ + -e '/^ creationTimestamp:/d' \ + -e '/^ generation:/d' \ + -e '/^ selfLink:/d' \ + > "${SNAP_DIR}/$1.yaml" +} + +wait_for_run() { + kubectl wait --for=condition=Succeeded --timeout="${TIMEOUT}" pipelinerun/"$1" 2>/dev/null +} + +dump_run() { + kubectl get pipelinerun/"$1" -o jsonpath='{.status.conditions[*].message}' 2>/dev/null || true + echo "" + for pod in $(kubectl get pods -l tekton.dev/pipelineRun="$1" -o name 2>/dev/null); do + echo " >> ${pod}" + kubectl logs "${pod}" --all-containers 2>/dev/null || true + done +} + +sleep 5 +snapshot_run "${pr}" + +echo "--- Waiting for PipelineRun to complete (timeout: ${TIMEOUT})" +echo -n " ${pr} ... " +if wait_for_run "${pr}"; then + echo "PASSED" +else + echo -n "FLAKY, retrying ... " + kubectl delete pipelinerun/"${pr}" --wait=true 2>/dev/null || true + kubectl apply -f "${SNAP_DIR}/${pr}.yaml" >/dev/null 2>&1 || true + sleep 5 + if wait_for_run "${pr}"; then + echo "PASSED" + else + echo "FAILED" + dump_run "${pr}" + exit 1 + fi +fi + +echo "" +echo "=== Bundle e2e test passed ===" diff --git a/test/e2e-tests.sh b/test/e2e-tests.sh new file mode 100755 index 0000000..820f353 --- /dev/null +++ b/test/e2e-tests.sh @@ -0,0 +1,162 @@ +#!/usr/bin/env bash + +# Copyright 2025 The Tekton Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# E2e test runner for the kaniko task. +# Installs the task, runs a test TaskRun that builds an image, and waits for +# completion. +# +# Environment variables: +# PIPELINE_VERSION - Tekton Pipelines version to install (default: v1.12.0) +# TIMEOUT - Timeout for each TaskRun (default: 180s) + +set -euo pipefail + +PIPELINE_VERSION="${PIPELINE_VERSION:-v1.12.0}" +TIMEOUT="${TIMEOUT:-180s}" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +echo "--- Installing Tekton Pipelines ${PIPELINE_VERSION}" +kubectl apply --filename "https://github.com/tektoncd/pipeline/releases/download/${PIPELINE_VERSION}/release.yaml" +echo "--- Waiting for Tekton Pipelines to be ready" +kubectl wait --for=condition=available --timeout=300s \ + deployment --all -n tekton-pipelines +echo "--- Waiting for the admission webhook to serve" +for _ in $(seq 1 30); do + if [[ -n "$(kubectl get endpoints tekton-pipelines-webhook \ + -n tekton-pipelines -o jsonpath='{.subsets[*].addresses[*].ip}' 2>/dev/null)" ]]; then + break + fi + sleep 5 +done + +echo "--- Installing kaniko task" +kubectl apply -f "${ROOT_DIR}/task/kaniko/kaniko.yaml" + +echo "--- Setting up in-cluster registry" +kubectl run registry --image=registry:2 --port=5000 +kubectl wait --for=condition=Ready --timeout=60s pod/registry +kubectl expose pod registry --port=5000 + +echo "--- Creating test PipelineRun" +cat <<'EOF' | kubectl apply -f - +apiVersion: tekton.dev/v1 +kind: PipelineRun +metadata: + name: kaniko-e2e-test +spec: + pipelineSpec: + workspaces: + - name: shared-workspace + tasks: + - name: create-dockerfile + workspaces: + - name: source + workspace: shared-workspace + taskSpec: + workspaces: + - name: source + steps: + - name: create + image: alpine:3.21 + script: | + cat > $(workspaces.source.path)/Dockerfile < /hello.txt + CMD ["cat", "/hello.txt"] + DOCKERFILE + - name: build + runAfter: ["create-dockerfile"] + taskRef: + name: kaniko + workspaces: + - name: source + workspace: shared-workspace + params: + - name: IMAGE + value: registry:5000/kaniko-test:e2e + - name: EXTRA_ARGS + value: + - --insecure + workspaces: + - name: shared-workspace + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 256Mi +EOF + +sleep 5 + +SNAP_DIR="$(mktemp -d)" +snapshot_run() { + kubectl get pipelinerun/"$1" -o yaml --show-managed-fields=false 2>/dev/null \ + | sed -e '/^status:/,$d' \ + -e '/^ resourceVersion:/d' \ + -e '/^ uid:/d' \ + -e '/^ creationTimestamp:/d' \ + -e '/^ generation:/d' \ + -e '/^ selfLink:/d' \ + > "${SNAP_DIR}/$1.yaml" +} + +wait_for_run() { + kubectl wait --for=condition=Succeeded --timeout="${TIMEOUT}" pipelinerun/"$1" 2>/dev/null +} + +dump_run() { + echo " --- PipelineRun status ---" + kubectl get pipelinerun/"$1" -o jsonpath='{.status.conditions[*].message}' 2>/dev/null || true + echo "" + echo " --- TaskRun details ---" + kubectl get taskrun -l tekton.dev/pipelineRun="$1" \ + -o custom-columns='NAME:.metadata.name,STATUS:.status.conditions[0].reason,MESSAGE:.status.conditions[0].message' 2>/dev/null || true + echo "" + echo " --- Pod logs ---" + for pod in $(kubectl get pods -l tekton.dev/pipelineRun="$1" -o name 2>/dev/null); do + echo " >> ${pod}" + kubectl logs "${pod}" --all-containers 2>/dev/null || true + done + echo " ---" +} + +pr="kaniko-e2e-test" +snapshot_run "${pr}" + +echo "--- Waiting for PipelineRun to complete (timeout: ${TIMEOUT})" +echo -n " ${pr} ... " +if wait_for_run "${pr}"; then + echo "PASSED" +else + # Retry once for transient flakes + echo -n "FLAKY, retrying ... " + kubectl delete pipelinerun/"${pr}" --wait=true 2>/dev/null || true + kubectl apply -f "${SNAP_DIR}/${pr}.yaml" >/dev/null 2>&1 || true + sleep 5 + if wait_for_run "${pr}"; then + echo "PASSED" + else + echo "FAILED" + dump_run "${pr}" + exit 1 + fi +fi + +echo "" +echo "=== E2E tests passed ==="