From aa012e4dbc8538f012e910eec2ad84c98b9de12e Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Fri, 3 Jul 2026 15:16:56 +0200 Subject: [PATCH 1/3] feat: modernize kaniko repo on par with git-clone and golang - Upgrade Task from v1beta1 to v1 API - Switch from archived GoogleContainerTools/kaniko to community fork osscontainertools/kaniko (v1.27.6) - Add multi-arch support (amd64, arm64, ppc64le) - Add KANIKO_DIR param for bootstrapping kaniko with itself - Create derived StepAction with generation script - Add repo scaffolding: README, CONTRIBUTING, DEVELOPMENT, AGENTS.md, LICENSE, OWNERS, .gitignore - Add GitHub workflows: build (lint + e2e matrix), release (Tekton bundle publish) - Add ArtifactHub metadata for task and stepaction - Add e2e tests and bundle e2e test - Add release script (hack/release.sh) - Add dependabot for GitHub Actions - Remove old v1beta1 test fixtures Signed-off-by: Vincent Demeester --- .gitignore | 1 + AGENTS.md | 47 +++++ CONTRIBUTING.md | 60 +++++++ DEVELOPMENT.md | 115 +++++++++++++ LICENSE | 202 ++++++++++++++++++++++ OWNERS | 6 + README.md | 58 +++++++ hack/generate-stepaction.py | 185 ++++++++++++++++++++ hack/generate-stepaction.sh | 41 +++++ hack/release.sh | 123 ++++++++++++++ stepaction/artifacthub-repo.yaml | 4 + stepaction/kaniko/README.md | 54 ++++++ stepaction/kaniko/kaniko.yaml | 86 ++++++++++ task/artifacthub-repo.yaml | 4 + task/kaniko/README.md | 94 +++++----- task/kaniko/kaniko.yaml | 29 ++-- task/kaniko/tests/pre-apply-task-hook.sh | 8 - task/kaniko/tests/resources.yaml | 11 -- task/kaniko/tests/run.yaml | 90 ---------- test/e2e-bundle-test.sh | 169 ++++++++++++++++++ test/e2e-tests.sh | 208 +++++++++++++++++++++++ 21 files changed, 1421 insertions(+), 174 deletions(-) create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 CONTRIBUTING.md create mode 100644 DEVELOPMENT.md create mode 100644 LICENSE create mode 100644 OWNERS create mode 100644 README.md create mode 100755 hack/generate-stepaction.py create mode 100755 hack/generate-stepaction.sh create mode 100755 hack/release.sh create mode 100644 stepaction/artifacthub-repo.yaml create mode 100644 stepaction/kaniko/README.md create mode 100644 stepaction/kaniko/kaniko.yaml create mode 100644 task/artifacthub-repo.yaml delete mode 100755 task/kaniko/tests/pre-apply-task-hook.sh delete mode 100644 task/kaniko/tests/resources.yaml delete mode 100644 task/kaniko/tests/run.yaml create mode 100755 test/e2e-bundle-test.sh create mode 100755 test/e2e-tests.sh 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..3c8ea10 --- /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/.docker + cp "${DOCKERCONFIG_PATH}/config.json" /kaniko/.docker/config.json 2>/dev/null || true +fi + +# Run kaniko executor +${KANIKO_DIR}/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..100ce1b --- /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` | Specifies the location of the kaniko binaries. | `/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..11900b4 --- /dev/null +++ b/stepaction/kaniko/kaniko.yaml @@ -0,0 +1,86 @@ +# 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" + 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 location of the kaniko binaries. + 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/.docker + cp "${DOCKERCONFIG_PATH}/config.json" /kaniko/.docker/config.json 2>/dev/null || true + fi + + # Run kaniko executor + ${KANIKO_DIR}/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..80465ba 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` | Specifies the location of the kaniko binaries. | `/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..9c5e0f9 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" 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 location of the kaniko binaries. + 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..1a14027 --- /dev/null +++ b/test/e2e-tests.sh @@ -0,0 +1,208 @@ +#!/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 TaskRun" +cat <<'EOF' | kubectl apply -f - +apiVersion: tekton.dev/v1 +kind: TaskRun +metadata: + name: kaniko-e2e-test +spec: + taskRef: + name: kaniko + timeout: "0h3m0s" + workspaces: + - name: source + emptyDir: {} + params: + - name: IMAGE + value: registry:5000/kaniko-test:e2e + - name: EXTRA_ARGS + value: + - --insecure + stepTemplate: + volumeMounts: + - name: dockerfile + mountPath: /workspace/source + volumes: + - name: dockerfile + emptyDir: {} + podTemplate: + securityContext: + runAsUser: 0 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: kaniko-test-dockerfile +data: + Dockerfile: | + FROM alpine:3.21 + RUN echo "hello from kaniko e2e test" > /hello.txt + CMD ["cat", "/hello.txt"] +EOF + +# Patch the TaskRun to create the Dockerfile in the workspace via an init step +# Since we can't easily inject files into emptyDir, we use a simpler approach: +# create a PipelineRun that first creates the Dockerfile, then runs kaniko. +kubectl delete taskrun kaniko-e2e-test --ignore-not-found 2>/dev/null || true + +echo "--- Creating test PipelineRun with source prep" +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 ===" From e3aed0cc0cb9bc20a68a49ed6538e0aac851e67d Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Fri, 3 Jul 2026 15:29:06 +0200 Subject: [PATCH 2/3] fix: remove dead TaskRun code from e2e test The test script had a stale TaskRun with invalid fields (stepTemplate, volumes) that failed strict decoding. Only the PipelineRun is needed. Signed-off-by: Vincent Demeester --- test/e2e-tests.sh | 48 +---------------------------------------------- 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/test/e2e-tests.sh b/test/e2e-tests.sh index 1a14027..820f353 100755 --- a/test/e2e-tests.sh +++ b/test/e2e-tests.sh @@ -51,53 +51,7 @@ 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 TaskRun" -cat <<'EOF' | kubectl apply -f - -apiVersion: tekton.dev/v1 -kind: TaskRun -metadata: - name: kaniko-e2e-test -spec: - taskRef: - name: kaniko - timeout: "0h3m0s" - workspaces: - - name: source - emptyDir: {} - params: - - name: IMAGE - value: registry:5000/kaniko-test:e2e - - name: EXTRA_ARGS - value: - - --insecure - stepTemplate: - volumeMounts: - - name: dockerfile - mountPath: /workspace/source - volumes: - - name: dockerfile - emptyDir: {} - podTemplate: - securityContext: - runAsUser: 0 ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: kaniko-test-dockerfile -data: - Dockerfile: | - FROM alpine:3.21 - RUN echo "hello from kaniko e2e test" > /hello.txt - CMD ["cat", "/hello.txt"] -EOF - -# Patch the TaskRun to create the Dockerfile in the workspace via an init step -# Since we can't easily inject files into emptyDir, we use a simpler approach: -# create a PipelineRun that first creates the Dockerfile, then runs kaniko. -kubectl delete taskrun kaniko-e2e-test --ignore-not-found 2>/dev/null || true - -echo "--- Creating test PipelineRun with source prep" +echo "--- Creating test PipelineRun" cat <<'EOF' | kubectl apply -f - apiVersion: tekton.dev/v1 kind: PipelineRun From 8601c1c1c2ba0aa83545c665c1d8b2575178a993 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Fri, 3 Jul 2026 15:46:34 +0200 Subject: [PATCH 3/3] fix: correct KANIKO_DIR semantics and version label - KANIKO_DIR is the kaniko working directory, not binaries location - Docker config path uses ${KANIKO_DIR}/.docker (respects override) - Executor binary hardcoded to /kaniko/executor (image-determined) - Version label aligned to semver 0.1.0 (matching git-clone/golang) Signed-off-by: Vincent Demeester --- hack/generate-stepaction.py | 6 +++--- stepaction/kaniko/README.md | 2 +- stepaction/kaniko/kaniko.yaml | 11 ++++++----- task/kaniko/README.md | 2 +- task/kaniko/kaniko.yaml | 4 ++-- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/hack/generate-stepaction.py b/hack/generate-stepaction.py index 3c8ea10..ad9534c 100755 --- a/hack/generate-stepaction.py +++ b/hack/generate-stepaction.py @@ -145,12 +145,12 @@ def generate(task_file: str, output_file: str) -> None: # Set up docker config if provided if [ -n "${DOCKERCONFIG_PATH}" ]; then - mkdir -p /kaniko/.docker - cp "${DOCKERCONFIG_PATH}/config.json" /kaniko/.docker/config.json 2>/dev/null || true + mkdir -p "${KANIKO_DIR}/.docker" + cp "${DOCKERCONFIG_PATH}/config.json" "${KANIKO_DIR}/.docker/config.json" 2>/dev/null || true fi # Run kaniko executor -${KANIKO_DIR}/executor \\ +/kaniko/executor \\ --dockerfile="${DOCKERFILE}" \\ --context="${SOURCE_PATH}/${CONTEXT}" \\ --destination="${IMAGE}" \\ diff --git a/stepaction/kaniko/README.md b/stepaction/kaniko/README.md index 100ce1b..662cc85 100644 --- a/stepaction/kaniko/README.md +++ b/stepaction/kaniko/README.md @@ -18,7 +18,7 @@ step version of the [kaniko Task](../../task/kaniko/README.md). | `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` | Specifies the location of the kaniko binaries. | `/kaniko` | +| `KANIKO_DIR` | Kaniko working directory (buildcontext, stages, layers, caches, docker config). | `/kaniko` | ## Results diff --git a/stepaction/kaniko/kaniko.yaml b/stepaction/kaniko/kaniko.yaml index 11900b4..caa0d77 100644 --- a/stepaction/kaniko/kaniko.yaml +++ b/stepaction/kaniko/kaniko.yaml @@ -4,7 +4,7 @@ kind: StepAction metadata: name: kaniko labels: - app.kubernetes.io/version: "0.1" + app.kubernetes.io/version: "0.1.0" annotations: tekton.dev/pipelines.minVersion: "0.43.0" tekton.dev/categories: Image Build @@ -39,7 +39,8 @@ spec: description: The kaniko executor image to use. default: ghcr.io/osscontainertools/kaniko:v1.27.6@sha256:95779f52d460ca70b65a4f4679d4f5163ad6c33edea3303dc8bbff847de5e05c - name: KANIKO_DIR - description: Specifies the location of the kaniko binaries. + description: Specifies the kaniko working directory (used for buildcontext, stages, + layers, caches, and docker config). default: /kaniko image: $(params.BUILDER_IMAGE) env: @@ -68,12 +69,12 @@ spec: # Set up docker config if provided if [ -n "${DOCKERCONFIG_PATH}" ]; then - mkdir -p /kaniko/.docker - cp "${DOCKERCONFIG_PATH}/config.json" /kaniko/.docker/config.json 2>/dev/null || true + mkdir -p "${KANIKO_DIR}/.docker" + cp "${DOCKERCONFIG_PATH}/config.json" "${KANIKO_DIR}/.docker/config.json" 2>/dev/null || true fi # Run kaniko executor - ${KANIKO_DIR}/executor \ + /kaniko/executor \ --dockerfile="${DOCKERFILE}" \ --context="${SOURCE_PATH}/${CONTEXT}" \ --destination="${IMAGE}" \ diff --git a/task/kaniko/README.md b/task/kaniko/README.md index 80465ba..84b0511 100644 --- a/task/kaniko/README.md +++ b/task/kaniko/README.md @@ -21,7 +21,7 @@ was built & sign it. | `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` | Specifies the location of the kaniko binaries. | `/kaniko` | +| `KANIKO_DIR` | Kaniko working directory (buildcontext, stages, layers, caches, docker config). | `/kaniko` | ## Workspaces diff --git a/task/kaniko/kaniko.yaml b/task/kaniko/kaniko.yaml index 9c5e0f9..2847402 100644 --- a/task/kaniko/kaniko.yaml +++ b/task/kaniko/kaniko.yaml @@ -3,7 +3,7 @@ kind: Task metadata: name: kaniko labels: - app.kubernetes.io/version: "0.1" + app.kubernetes.io/version: "0.1.0" annotations: tekton.dev/pipelines.minVersion: "0.43.0" tekton.dev/categories: Image Build @@ -31,7 +31,7 @@ spec: description: The kaniko executor image to use. default: ghcr.io/osscontainertools/kaniko:v1.27.6@sha256:95779f52d460ca70b65a4f4679d4f5163ad6c33edea3303dc8bbff847de5e05c - name: KANIKO_DIR - description: Specifies the location of the kaniko binaries. + description: Specifies the kaniko working directory (used for buildcontext, stages, layers, caches, and docker config). default: /kaniko workspaces: - name: source