Skip to content

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.