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
107 changes: 106 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
- "*/v*"

permissions:
contents: read
contents: write
packages: write
id-token: write
attestations: write
Expand Down Expand Up @@ -108,6 +108,7 @@ jobs:
sbom: true

- name: Attest image
id: attest
uses: actions/attest@v4
with:
subject-name: ${{ steps.image.outputs.name }}
Expand All @@ -134,3 +135,107 @@ jobs:
echo
echo "\`${IMAGE}@${DIGEST}\`"
} >> "$GITHUB_STEP_SUMMARY"

- name: Publish GitHub release
uses: actions/github-script@v9
env:
TOOL: ${{ steps.meta.outputs.name }}
VERSION: ${{ steps.meta.outputs.version }}
UPSTREAM: ${{ steps.meta.outputs.upstream }}
IMAGE: ${{ steps.image.outputs.name }}
DIGEST: ${{ steps.build.outputs.digest }}
ATTESTATION_URL: ${{ steps.attest.outputs.attestation-url }}
with:
script: |
const tag = context.ref.replace("refs/tags/", "");
const tool = process.env.TOOL;
const version = process.env.VERSION;
const upstream = process.env.UPSTREAM;
const image = process.env.IMAGE;
const digest = process.env.DIGEST;
const attestationUrl = process.env.ATTESTATION_URL;
const imageTag = `${image}:${version}`;
const imageDigest = `${image}@${digest}`;
const workflowUrl =
`https://github.com/${context.repo.owner}/${context.repo.repo}` +
`/actions/runs/${context.runId}`;
const upstreamUrl =
`https://github.com/${upstream}/releases/tag/v${version}`;
const startMarker = "<!-- tiny-cli-images:container:start -->";
const endMarker = "<!-- tiny-cli-images:container:end -->";
const links = [
`[Upstream](${upstreamUrl})`,
`[Build](${workflowUrl})`,
];
if (attestationUrl) {
links.push(`[Attestation](${attestationUrl})`);
}

const details = [
startMarker,
`Minimal ${tool} v${version} image for \`linux/amd64\` and \`linux/arm64\`.`,
"",
`**Image:** \`${imageTag}\``,
`**Digest:** \`${digest}\``,
"",
"```sh",
`docker run --rm ${imageDigest} --version`,
"```",
"",
links.join(" · "),
endMarker,
].join("\n");

let release = null;
try {
const response = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag,
});
release = response.data;
} catch (error) {
if (error.status !== 404) {
throw error;
}
}

if (!release) {
await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tag,
target_commitish: context.sha,
name: `${tool} v${version}`,
body: details,
draft: false,
prerelease: false,
make_latest: "false",
});
return;
}

const currentBody = release.body || "";
const start = currentBody.indexOf(startMarker);
const end = currentBody.indexOf(endMarker);
let body;

if (start !== -1 && end > start) {
body =
currentBody.slice(0, start) +
details +
currentBody.slice(end + endMarker.length);
} else {
body = [currentBody.trim(), details].filter(Boolean).join("\n\n");
}

await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.id,
name: release.name || `${tool} v${version}`,
body,
draft: false,
prerelease: release.prerelease,
make_latest: "false",
});
2 changes: 2 additions & 0 deletions docs/PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ The release workflow must:
4. Publish SBOM and build provenance attestations.
5. Sign the resulting digest using keyless OIDC signing.
6. Expose the immutable OCI digest.
7. Create or update a GitHub Release with the image reference, digest,
supported platforms, upstream release link, and supply-chain details.

Release workflows use minimal GitHub Actions permissions. Third-party actions
should ultimately be pinned to full commit SHAs.
Expand Down
2 changes: 1 addition & 1 deletion images/xh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ manager, and its entrypoint is `/xh`.

Each release publishes only its full upstream version tag, such as `0.26.2`.
The image does not publish `latest` or shortened version tags. For immutable
deployments, use the digest shown by the release workflow:
deployments, use the digest shown by the GitHub Release and release workflow:

```text
ghcr.io/unitmatrix/xh@sha256:<digest>
Expand Down