Getting started¶
Dry-run is the default
A run with no --no-dry-run deletes nothing. Read the safety model before
passing that flag for the first time.
Installation¶
Four routes, all producing the same binary: a release archive, Distillery and a GitHub Action, both of which install that archive for you, and a container image. None of them needs a credential.
1. Release archive¶
Every release publishes linux/amd64, linux/arm64 and darwin/arm64 archives named
oci-nuke-vX.Y.Z-<os>-<arch>.tar.gz, plus a checksums.txt covering them. darwin/amd64 is
deliberately not built — Intel macOS is not a supported target. Each archive also ships a
.sig/.pem pair produced by keyless cosign signing under the release workflow's own GitHub
OIDC identity, so an archive can be verified against Sigstore as well as against checksums.txt.
The binary sits at the root of the archive, alongside README.md, LICENSE and CHANGELOG.md
— there is no wrapping directory.
tag=v2.2.0
archive="oci-nuke-${tag}-linux-amd64.tar.gz"
base="https://github.com/naviteq/oci-nuke/releases/download/${tag}"
curl -fsSLO "${base}/${archive}"
curl -fsSLO "${base}/checksums.txt"
# Verify before extracting. On macOS use `shasum -a 256 -c -` instead of `sha256sum -c -`.
grep " ${archive}\$" checksums.txt | sha256sum -c -
tar -xzf "${archive}" oci-nuke
sudo install -m 0755 oci-nuke /usr/local/bin/oci-nuke
oci-nuke version
2. Distillery¶
Distillery (dist), by the author of libnuke, installs from this
repository's releases directly. It picks the archive for the host's OS and architecture, checks
it against checksums.txt and its cosign signature, and links the binary into
~/.distillery/bin, which you add to PATH yourself; the installer does not.
curl --proto '=https' --tlsv1.2 -LsSf https://get.dist.sh | sh
export PATH="$HOME/.distillery/bin:$PATH"
dist install naviteq/oci-nuke # the latest release
dist install naviteq/oci-nuke@v2.2.0 # an exact version
oci-nuke version
dist checks the signature against the certificate that ships next to it in the same release,
not against the identity the certificate was issued to. To confirm the archive was signed by this
repository's release workflow, run cosign on the release's checksums.txt once, then compare
the archive against it as in route 1:
cosign verify-blob checksums.txt \
--certificate checksums.txt.pem --signature checksums.txt.sig \
--certificate-identity-regexp '^https://github\.com/naviteq/oci-nuke/\.github/workflows/goreleaser\.yml@' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Intel macOS has no archive to pick, so dist install fails there.
3. GitHub Action¶
action.yml at the root of this repository is a composite action that does the download,
checksum verification and PATH setup above:
- uses: naviteq/oci-nuke@v2.2.0
with:
# An exact vX.Y.Z tag. "latest" is rejected by name: plan and apply are separate
# runs, and a moving ref would change the binary between them.
version: v2.2.0
# The release is public, but the action insists on a token; the job's own one is enough.
token: ${{ github.token }}
It verifies the archive's sha256 against the release's own checksums.txt and will not install
an archive it cannot verify. It does not verify the cosign signature: cosign is absent from
GitHub-hosted runners, and installing it would be a step of its own. That is a follow-up, and
the action's description says so rather than leaving the impression the signature was checked.
4. Container image¶
docker run --rm ghcr.io/naviteq/oci-nuke:v2.2.0 version
The tag is a multi-arch manifest covering linux/amd64 and linux/arm64, and the image is
cosign-signed by the same keyless workflow identity as the archives.
go install¶
go install github.com/naviteq/oci-nuke@latest
This builds the latest commit on main, not a release, and oci-nuke version prints dev. A tag
cannot be selected this way: from v2.0.0 on, Go expects the module path to end in /v2, and
it does not. Use one of the four routes above for a pinned version.
Exit codes¶
Scripting around oci-nuke means reading its exit code, not its output.
Exit codes¶
oci-nuke run exits one of four documented, distinct codes (pkg/commands/run.ExitError) —
never exit 0 after silently swallowing an error, the specific failure mode of the Python
script this tool replaces:
| Code | Meaning |
|---|---|
0 |
Clean: the run completed with nothing left unresolved that --fail-on-leftover would care about, or --fail-on-leftover was not set. |
1 |
Uncategorized internal error — the default for every error not explicitly classified below (an OCI API failure outside the safety-model gates, a plan-artifact marshaling failure, an unexplained *libnuke.Nuke.Run() failure with no corresponding leftover entry, ...). |
2 |
Refused: a pre-scan safety-model gate rejected the run before any resource was ever listed — the tenancy root as a target, an empty compartment blocklist, a tenancy mismatch, an invalid or unsubscribed regions entry, an unknown target compartment, or a target nested under a blocklisted ancestor. |
3 |
Unresolved leftovers, and --fail-on-leftover was set. |
A leftover-containing run without --fail-on-leftover still exits 0. This is
intentional, not a bug: the plan artifact and the printed table report every leftover
unconditionally, regardless of this flag — --fail-on-leftover only changes whether the
process's exit code reflects that fact, so an operator or CI pipeline can opt in to treating
leftovers as fatal without losing visibility into them either way. See
TestRunPipeline_LeftoverWithoutFailOnLeftoverExitsClean and
TestRunPipeline_FailOnLeftoverExitsNonZeroWithMatchingCode (pkg/commands/run).
When a single run's compartments produce more than one kind of outcome — say, one
compartment refused nothing but left a resource behind while another compartment's
*libnuke.Nuke.Run() failed for a reason unrelated to any leftover — the worst outcome wins,
with precedence internal (1) > leftovers (3) > clean (0): an unexplained internal failure
is never downgraded to a mere leftover just because --fail-on-leftover also happens to be
set. pkg/commands/run.classifyOutcome is the single function this decision is made in, and
TestClassifyOutcome_TableDriven proves every meaningful combination, including that exact
precedence case.