Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cmd/krci/main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// Package main is the entry point for the krci CLI.
package main

import (
Expand Down
78 changes: 78 additions & 0 deletions docs/json-schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -477,3 +477,81 @@ Common messages:
| Unknown pull request (404) | `pull request <id> not found` |
| Upstream 5xx / network | `portal returned HTTP 500: <cause>` |
| Invalid flag value | Flag-specific message (e.g. enum list) |


## `krci pipelinerun start`

The start verb reuses the same column shape as `krci pipelinerun list`. Empty
cells render as `-` in table mode and as `""` in JSON mode (matches list).

### Success envelope

```json
{
"schemaVersion": "1",
"data": {
"name": "<apiserver-assigned name, e.g. foo-build-run-x9k2p>",
"status": "Pending|Running|Succeeded|Failed|Cancelled|Timeout",
"project": "<codebase or empty>",
"pr": "<pr number or empty>",
"author": "<git author or empty>",
"type": "<pipelinetype label or empty>",
"started": "<RFC3339 or empty>",
"duration": "<m+s or empty>"
}
}
```

### Error envelope

```json
{
"schemaVersion": "1",
"error": { "message": "pipeline 'ghost' not found" }
}
```

### Dry-run envelope (-o json)

`data` carries the rendered `PipelineRun` resource as a parsed JSON object —
not a string. Default and `-o yaml` modes emit the same resource as YAML
(suitable for piping to `kubectl apply -f -`).

```json
{
"schemaVersion": "1",
"data": {
"apiVersion": "tekton.dev/v1",
"kind": "PipelineRun",
"metadata": {
"generateName": "foo-build-run-",
"labels": { "app.edp.epam.com/codebase": "my-app" }
},
"spec": {
"params": [ { "name": "git-revision", "value": "main" } ]
}
}
}
```

### Common messages

User-facing messages on the not-found path are synthesised CLI-side from a
stable `error.reason` tag the Portal returns. The Portal deliberately does
not put resource-identifying text in `error.message` (cluster-hardening
policy applied uniformly to all REST routes), so the CLI builds the user
message from the pipeline name it already has plus the reason it received.

All errors exit `1` (per the global rule at the top of this document).

| Condition | Message |
| ------------------------------------------- | --------------------------------------------------------------------------------------------- |
| Pipeline not found | `pipeline '<name>' not found` |
| TriggerTemplate referenced but missing | `pipeline '<name>' references a TriggerTemplate that does not exist` |
| Malformed TriggerTemplate label | `platform rejected request: pipeline '<name>' has malformed TriggerTemplate label` |
| Platform admission rejection (400/422) | `platform rejected request: <HTTP status phrase>` (Portal does not echo K8s admission detail) |
| RBAC denied | `permission denied` |
| Portal upstream 5xx | `upstream service unavailable: <cause>` |
| Duplicate / malformed `--param` / `--label` | `duplicate parameter '<k>'` / `parameter must be key=value` / `label key must not be empty` |
| `--dry-run` with `-o table` | `--dry-run cannot use -o table (use -o json or -o yaml)` |

91 changes: 85 additions & 6 deletions docs/pipelinerun.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ deploy, release). Also surfaces logs and a focused failure-diagnosis view.

## Subcommands

| Command | Purpose |
|-------------------------------|------------------------------------------|
| `pipelinerun list` (`ls`) | List and filter runs |
| `pipelinerun get <name>` | Inspect a specific run |
| Command | Purpose |
|-------------------------------|-----------------------------------------------|
| `pipelinerun list` (`ls`) | List and filter runs |
| `pipelinerun get <name>` | Inspect a specific run |
| `pipelinerun start <pipeline>` | Create a new run from a Tekton pipeline name |

Both accept `-o, --output` (`table` | `json`), `--logs`, and `--reason`.
`list` and `get` accept `-o, --output` (`table` | `json`), `--logs`, and
`--reason`. `start` has its own flag set (`--param`, `--label`, `--dry-run`,
`-o`) — see below.

## `pipelinerun list`

Expand Down Expand Up @@ -73,6 +76,80 @@ Project: keycloak-operator

Add `--logs` for full logs or `--reason` for focused failure diagnosis.

## `pipelinerun start`

Create a new run of a Tekton pipeline by name. The pipeline name is the
required argument; everything else is optional. The new run uses Kubernetes
`metadata.generateName`, so the apiserver assigns the random suffix and the
resolved name is read back and printed.

```bash
krci pipelinerun start foo-build
```

```
NAME STATUS PROJECT PR AUTHOR TYPE STARTED DURATION
foo-build-run-zhqvj Pending - - - build 2026-05-07T06:14:04Z -
```

### Flags

| Flag | Description |
|----------------|------------------------------------------------------------------------------|
| `--param` | Pipeline parameter as `key=value` (repeatable; split on first `=`) |
| `--label` | Label to attach to the resulting PipelineRun as `key=value` (repeatable) |
| `--dry-run` | Render the would-be PipelineRun without creating it (needs `-o json`/`yaml`) |
| `-o, --output` | `table` (default), `json`, or `yaml` (yaml only with `--dry-run`) |

> **Params without a default** are submitted with `value: ""` (or `[]` for
> arrays). Pass `--param k=v` for any values your pipeline actually needs.

### Examples

```bash
# Override a single param
krci pipelinerun start foo-build --param git-revision=develop

# Multiple params plus a discoverability label
krci pipelinerun start foo-build --param k=v --param k2=v2 \
--label app.edp.epam.com/codebase=my-app

# Render the would-be PipelineRun without creating it
krci pipelinerun start foo-build --dry-run -o yaml

# JSON output (for AI agents / scripting)
krci pipelinerun start foo-build -o json
```

### JSON output

`start` uses a wrapped envelope (different from `list` / `get`):

```json
{
"schemaVersion": "1",
"data": {
"name": "foo-build-run-zhqvj",
"status": "Pending",
"project": "my-app",
"pr": "",
"author": "",
"type": "build",
"started": "2026-05-07T06:14:04Z",
"duration": ""
}
}
```

For `--dry-run`, `data` is the rendered PipelineRun manifest itself instead
of the result row.

### Finding the run you just started

```bash
krci pipelinerun list --project my-app
```

## Failure diagnosis (`--reason`)

Works on both `list` (targets the most recent matching run) and `get`:
Expand Down Expand Up @@ -100,7 +177,9 @@ Logs: sonar
[sonar-scanner] ERROR: QUALITY GATE STATUS: FAILED
```

## JSON output
## JSON output (`list` / `get`)

`start` uses a different envelope — see the [`start` section](#pipelinerun-start) above.

```bash
krci run list --project keycloak-operator -o json
Expand Down
57 changes: 49 additions & 8 deletions e2e/pipelinerun/fixtures.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,55 @@
# The /e2e command loads this file (if present) before running portal rows.
# If a row references a placeholder that has no value, the row is SKIPped.
#
# Discover good values with:
# ./dist/krci pipelinerun list -o json | jq '.pipelineRuns[0]'
# ./dist/krci pipelinerun list --status failed -o json | jq '.pipelineRuns[0]'
# All e2e rows are now self-contained: every value below either points at a
# Pipeline defined in fixtures/*.yaml, or at a label we attach to runs we
# create ourselves. There is no dependency on pre-existing cluster workload.
#
# Bootstrap once per cluster:
# kubectl apply -n <namespace> -f source/cli/e2e/pipelinerun/fixtures/
#
# Then run a few starts to populate RUN_NAME / FAILED_RUN_NAME (see below).

# ----- pipelinerun start fixtures -----
# Pipelines defined in fixtures/*.yaml.
PIPELINE_OK=krci-cli-e2e-noop
PIPELINE_REQUIRED_PARAM=krci-cli-e2e-required
PIPELINE_REQUIRED_PARAM_NAME=message
PIPELINE_BROKEN_TT=krci-cli-e2e-broken-tt
# Vanilla Tekton Pipeline with no KRCI labels at all — proves CLI start works
# regardless of the KubeRocketCI labelling convention (PR-S-BARE).
PIPELINE_BARE=krci-cli-e2e-bare
# Pipeline + TriggerTemplate fixture pair — exercises the TT branch of
# createPipelineRunDraftFromPipeline (placeholder resolution, label
# sanitization). The TT name is referenced via the Pipeline's
# app.edp.epam.com/triggertemplate label.
PIPELINE_WITH_TT=krci-cli-e2e-with-tt
TRIGGER_TEMPLATE=krci-cli-e2e-tt

# Param accepted by PIPELINE_OK whose default is "main" — PR-S-2 overrides it.
PARAM_KNOWN_KEY=git-revision
PARAM_KNOWN_VALUE=develop

PROJECT=my-project
PR=123
AUTHOR=john-doe
# Synthetic codebase label. Attached at start time via --label so the resulting
# PipelineRun is discoverable via `pipelinerun list --project krci-cli-e2e`.
PIPELINE_LABEL_KEY=app.edp.epam.com/codebase
PIPELINE_LABEL_VALUE=krci-cli-e2e

# ----- pipelinerun list / get fixtures -----
# Identifying values for runs created by the e2e fixtures themselves. The
# pipelinetype filter is the most useful selector since we own the value.
PROJECT=krci-cli-e2e
PR=1
AUTHOR=krci-cli-e2e-bot
BRANCH=main
RUN_NAME=build-my-project-main-abc12
FAILED_RUN_NAME=review-my-project-main-xyz34

# Concrete run names. After a fresh apply, populate these with one successful
# and one failed run name from your namespace, e.g.:
# ./dist/krci pipelinerun start krci-cli-e2e-noop -o json \
# | jq -r '.data.row.name'
# ./dist/krci pipelinerun start krci-cli-e2e-noop \
# --param should-fail=true -o json | jq -r '.data.row.name'
RUN_NAME=
FAILED_RUN_NAME=

NONCE=test-999zzz
125 changes: 125 additions & 0 deletions e2e/pipelinerun/fixtures/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# pipelinerun start — e2e fixtures

Self-contained Tekton manifests used by the `krci pipelinerun start` e2e rows
in `../test-cases.md`. Files are the source of truth — apply with
`kubectl apply -f` and the cluster matches what's checked in (no drift).

All pipelines run a single inline `taskSpec` that calls `busybox` and echoes
its inputs. None of them reach external systems, push artifacts, or write to
git. They are safe to start repeatedly. All resource names use a synthetic
`krci-cli-e2e-*` prefix and the manifests carry no organisation-specific
identifiers — drop them into any namespace on any KubeRocketCI cluster.

## Bundle

| File | Resource | Purpose | Test rows |
|---|---|---|---|
| `pipeline-noop.yaml` | Pipeline `krci-cli-e2e-noop` | Four params, all with defaults; `should-fail=true` makes the run exit non-zero. | `PR-S-1`, `PR-S-2`, `PR-S-3`, `PR-S-LABEL`, `PR-S-DRY-YAML`, `PR-S-DRY-JSON`, `PR-S-RACE`, `PR-S-GENNAME`, `PR-S-COL-EQ` |
| `pipeline-required.yaml` | Pipeline `krci-cli-e2e-required` | Declares a no-default param. Documents the portal's empty-string synthesis: omitting `--param message=...` produces `spec.params[0].value: ""`, **not** an admission rejection. The fixture's spec.description still references the old (incorrect) intent — left untouched to avoid drift; see `PR-S-PARAM-SYNTHESIZED` for the actual contract. | `PR-S-PARAM-SYNTHESIZED` |
| `pipeline-broken-tt.yaml` | Pipeline `krci-cli-e2e-broken-tt` | Carries an `app.edp.epam.com/triggertemplate` label pointing at a TT that does not exist. | `PR-S-TT-MISSING` |
| `pipeline-bare.yaml` | Pipeline `krci-cli-e2e-bare` | No KRCI labels at all. Proves the CLI can start any valid Tekton Pipeline regardless of the KubeRocketCI labelling convention. | `PR-S-BARE` |
| `pipeline-with-tt.yaml` | Pipeline `krci-cli-e2e-with-tt` | Paired with `trigger-template.yaml`. Exercises the TriggerTemplate branch of `createPipelineRunDraftFromPipeline` — placeholder resolution and label sanitization. | `PR-S-TT-OK`, `PR-S-TT-DRY` |
| `trigger-template.yaml` | TriggerTemplate `krci-cli-e2e-tt` | Resourcetemplate uses `$(tt.params.X)` placeholders that resolve against `pipeline-with-tt.yaml`'s param defaults. | (paired with above) |

## Apply

Substitute `<namespace>` with whatever namespace you target (e.g. the one your
portal session points at):

```sh
kubectl apply -n <namespace> -f source/cli/e2e/pipelinerun/fixtures/
```

Verify:

```sh
kubectl -n <namespace> get pipeline.tekton.dev,triggertemplate.triggers.tekton.dev \
-l app.edp.epam.com/pipelinetype=tests
```

## Smoke test (after apply)

```sh
# Dry-run — proves CLI <-> portal plumbing without creating a PipelineRun.
./dist/krci pipelinerun start krci-cli-e2e-noop --dry-run -o json | jq .

# Real run with default params.
./dist/krci pipelinerun start krci-cli-e2e-noop -o json

# Real run overriding a param.
./dist/krci pipelinerun start krci-cli-e2e-noop \
--param git-revision=develop --param count=3 -o json

# Deterministic failure path.
./dist/krci pipelinerun start krci-cli-e2e-noop --param should-fail=true -o json

# No-default-param path: succeeds with synthesised empty value (exit 0).
# Demonstrates that "missing required param" is not a reachable failure mode.
./dist/krci pipelinerun start krci-cli-e2e-required -o json

# TriggerTemplate-not-found path: synthesised "TT does not exist" message, exit 1.
./dist/krci pipelinerun start krci-cli-e2e-broken-tt

# Bare Tekton Pipeline (no KRCI labels) — must still start.
./dist/krci pipelinerun start krci-cli-e2e-bare -o json

# TriggerTemplate happy path — dry-run reveals resolved $(tt.params.X) values.
./dist/krci pipelinerun start krci-cli-e2e-with-tt --dry-run -o json | jq .

# TriggerTemplate happy path — real run.
./dist/krci pipelinerun start krci-cli-e2e-with-tt -o json
```

## Cleanup

The PipelineRun objects created by tests are subject to whatever GC the
cluster has configured (Tekton Results retention, operator pruning, etc.).
To remove the fixture resources themselves:

```sh
kubectl delete -n <namespace> -f source/cli/e2e/pipelinerun/fixtures/
```

To purge accumulated runs (label selector picks up runs from any of the
fixture pipelines because every fixture sets `pipelinetype: tests` on either
the Pipeline or — via TT-resolved labels — the resulting PipelineRun):

```sh
kubectl -n <namespace> delete pipelinerun.tekton.dev \
-l app.edp.epam.com/pipelinetype=tests
```

## Why these manifests look the way they do

- **Labelled `pipelinetype: tests`.** Distinguishes these fixtures from
KubeRocketCI's real pipelines (build/review/deploy/clean/security/release).
Filter via `kubectl get pipeline.tekton.dev
-l app.edp.epam.com/pipelinetype=tests` or the same selector in the portal
UI. `tests` is already a valid value in the portal's `pipelineTypeEnum`.
- **`app.edp.epam.com/*` label keys are upstream KubeRocketCI definitions**
and cannot be renamed — the portal reads exactly those keys to drive
TriggerTemplate lookup, codebase filters, and pipelinetype filters. The
values used in this bundle (`tests`, `krci-cli-e2e`, `""`) are synthetic
and carry no organisation-specific data.
- **`triggertemplate` label is empty** on the `noop` and `required` fixtures.
The portal's Pipeline Zod schema requires both labels to be present, but
`getTriggerTemplateLabel` treats an empty string as "absent" and skips the
TT lookup — so `start` works without us having to also create a TT.
`pipeline-broken-tt.yaml` is the deliberate exception: its label points at
a TT that does not exist, which is exactly the failure mode it tests.
- **`pipeline-with-tt.yaml` + `trigger-template.yaml` are a pair.** The
Pipeline carries `triggertemplate: krci-cli-e2e-tt`; the matching TT's
`resourcetemplates[0]` uses `$(tt.params.X)` placeholders. The portal's
draft builder resolves those placeholders against the Pipeline's param
defaults (not the TT's), so the resulting PipelineRun reflects values
declared on the Pipeline side. This is the only fixture that exercises
`createPipelineRunDraftFromPipeline`'s TT branch end-to-end.
- **Inline `taskSpec`, not `taskRef`.** Decouples the fixtures from
cluster-installed Tasks/ClusterTasks — apply works on any cluster with
Tekton Pipelines installed.
- **Params passed via env, not interpolated into shell.** Tekton substitutes
`$(params.X)` as text before the shell runs. Routing through `env:` means
param values land in shell variables, where `"$VAR"` does not re-expand
command substitutions — so a hostile `--param message='$(reboot)'` is
echoed literally rather than executed.
- **Pinned image tag (`busybox:1.36`).** Reproducible across runs.
Loading
Loading