Skip to content
Draft
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
48 changes: 48 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,51 @@ jobs:
if resolved not in installed:
print(f"resolved {resolved!r} not found in installed {installed!r}")
sys.exit(1)

attest-docker:
name: Attest docker artifact (integration)
runs-on: ubuntu-latest
services:
registry:
image: registry:2
ports:
- 5000:5000
env:
KOSLI_ORG: dummy-org
KOSLI_FLOW: dummy-flow
KOSLI_TRAIL: dummy-trail
KOSLI_API_TOKEN: dummy-token
KOSLI_DRY_RUN: "true"
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Install deps and build
run: npm ci && npm run build

- name: Setup kosli CLI with docker attestation
uses: ./
with:
attest-docker-artifact: true

- name: docker resolves to the shim
run: |
resolved=$(command -v docker)
[[ "$resolved" == "$RUNNER_TEMP"/kosli-docker-shim-*/docker ]] || { echo "docker is $resolved, not the shim"; exit 1; }

- name: docker push attests the pushed tag
run: |
docker build -f test/integration/Dockerfile -t localhost:5000/kosli-shim-it:push .
docker push localhost:5000/kosli-shim-it:push 2>&1 | tee push.log
grep -q "kosli-shim: attesting localhost:5000/kosli-shim-it:push as 'kosli-shim-it'" push.log
grep -q "kosli-shim: attested localhost:5000/kosli-shim-it:push" push.log

- name: docker buildx build --push attests the pushed tag
run: |
docker buildx build --push -t localhost:5000/kosli-shim-it:bx -f test/integration/Dockerfile . 2>&1 | tee bx.log
grep -q "kosli-shim: attested localhost:5000/kosli-shim-it:bx" bx.log

- name: a build without --push does not attest
run: |
docker build -t localhost:5000/kosli-shim-it:nopush -f test/integration/Dockerfile . 2>&1 | tee nopush.log
! grep -q "kosli-shim: attest" nopush.log
82 changes: 82 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,79 @@ steps:
version: latest
```

## Automatically attest pushed Docker images

On Linux runners the action can attest Docker images to Kosli automatically, with no
explicit `kosli attest artifact` step. Set `attest-docker-artifact: true` and the action
installs shims for `docker`, `buildx`, and `docker-buildx` that shadow the real binaries
in subsequent steps. Whenever a later step pushes an image — `docker push`,
`docker build --push`, `docker buildx build --push`, or a standalone `buildx build --push`
— the shim runs

```
kosli attest artifact "<ref>" --artifact-type=oci --name "<name>"
```

for each pushed tag, after the push succeeds. `--artifact-type=oci` fingerprints the
image directly from the registry, so this works with buildx's default `docker-container`
driver, where the pushed image never enters the local image store.

```yaml
env:
KOSLI_API_TOKEN: ${{ secrets.KOSLI_API_TOKEN }}
KOSLI_ORG: my-org
KOSLI_FLOW: my-flow
KOSLI_TRAIL: ${{ github.sha }}

jobs:
build-image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- name: Setup kosli # must run BEFORE the build/push step
uses: kosli-dev/setup-cli-action@v5
with:
attest-docker-artifact: true

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
push: true
tags: my-registry/my-image:${{ github.sha }}
# no explicit attest step - the push is attested automatically
```

> **Ordering matters.** The shims are added to the `PATH` of *subsequent* steps, so this
> action must run before the step that builds or pushes.

By default the Kosli artifact `--name` is derived from each image ref (strip any digest,
take the last `/`-segment, strip the tag): `my-registry/my-image:1.2.3` → `my-image`.
That per-image default is what makes monorepos work — each image gets its own name. If
your Kosli flow template uses a different artifact name, set `artifact-name` (it applies
to every image pushed in the job, so it is intended for single-artifact repos).

The shim reads the usual `KOSLI_API_TOKEN` / `KOSLI_ORG` / `KOSLI_FLOW` / `KOSLI_TRAIL` /
`KOSLI_HOST` environment variables, and the CLI auto-detects `--commit`, `--build-url`,
etc. on GitHub Actions. `KOSLI_DRY_RUN` also flows through unchanged.

A failed attestation does not fail the push by default — it prints a warning and
continues. Set `fail-on-attest-error: true` to make it fail the step instead. A failed
`docker`/`buildx` command is never masked: its exit code is always propagated and nothing
is attested.

### Limitations

- Linux runners only; on other platforms the input is ignored with a warning.
- Only invocations that resolve the shimmed binaries via `PATH` are intercepted; tools
that call docker/buildx by absolute path bypass the shims.
- `docker push --all-tags` is skipped (the pushed tag set is not knowable from the
command line); a warning is printed.
- `attest-flags` is split on whitespace; flag values containing spaces are not supported.
- `docker buildx bake --push` and `docker compose push` are not intercepted.
- Shim and attestation output goes to stderr (so `$(docker push -q ...)` captures stay
clean), and appears as plain log lines rather than GitHub annotations.

## Inputs

The action supports the following inputs:
Expand All @@ -80,6 +153,15 @@ The action supports the following inputs:
Quote partial versions (see the note above). Defaults to `latest`.
- `github-token`: Token used to authenticate the GitHub API calls that resolve `latest` or a
major/minor pin. Defaults to `${{ github.token }}`; normally you do not need to set this.
- `attest-docker-artifact`: When `true` (Linux runners only), install the docker/buildx shims
described above so images pushed by subsequent steps are attested automatically.
Defaults to `false`.
- `artifact-name`: Kosli template artifact name used for every auto-attested image. Leave
empty (the default) to derive the name from each image ref.
- `attest-flags`: Extra flags appended verbatim to every `kosli attest artifact` call made by
the shim, e.g. `--annotate key=value`.
- `fail-on-attest-error`: When `true`, a failed auto-attestation fails the step that pushed
the image. Defaults to `false` (warn and continue).

## Outputs

Expand Down
16 changes: 16 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@ inputs:
description: Token used to authenticate GitHub API calls when resolving `latest` or a major/minor pin.
required: false
default: ${{ github.token }}
attest-docker-artifact:
description: When true (Linux runners only), install shims for docker/buildx so any image pushed by a subsequent step is automatically attested to Kosli with `kosli attest artifact --artifact-type=oci`. This action must run before the step that builds or pushes.
required: false
default: "false"
artifact-name:
description: Kosli template artifact name used for every auto-attested image. Leave empty (the default) to derive the name from each image ref, which is what makes monorepos work.
required: false
default: ""
attest-flags:
description: Extra flags appended verbatim to every `kosli attest artifact` call made by the docker shim (e.g. `--annotate key=value`).
required: false
default: ""
fail-on-attest-error:
description: When true, a failed auto-attestation fails the step that pushed the image. Default is to warn and continue, keeping pushes non-blocking.
required: false
default: "false"
outputs:
version:
description: The resolved Kosli CLI version that was installed.
Expand Down
4 changes: 3 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"dependencies": {
"@actions/core": "^3.0.1",
"@actions/github": "^9.1.1",
"@actions/io": "^3.0.2",
"@actions/tool-cache": "^4.0.0"
},
"devDependencies": {
Expand Down
11 changes: 11 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import os from "os";
import crypto from "crypto";
import fs from "fs";
import path from "path";
import * as core from "@actions/core";
import * as tc from "@actions/tool-cache";
import { getAssetFilename, getChecksumsUrl, getDownloadUrl, resolveVersion, verifyChecksum } from "./download.js";
import { withRetries } from "./retry.js";
import { installDockerShims } from "./wrapDocker.js";

async function setup() {
try {
Expand Down Expand Up @@ -36,6 +38,15 @@ async function setup() {
core.addPath(pathToCLI);
core.setOutput("version", resolvedVersion);
console.log(`installed Kosli CLI v${resolvedVersion} to ${pathToCLI}`);

if (core.getBooleanInput("attest-docker-artifact")) {
await installDockerShims({
kosliBin: path.join(pathToCLI, "kosli"),
artifactName: core.getInput("artifact-name"),
attestFlags: core.getInput("attest-flags"),
failOnAttestError: core.getBooleanInput("fail-on-attest-error")
});
}
} catch (e) {
core.setFailed(e);
}
Expand Down
Loading