Skip to content

Releases: Ghostwritten/harpoon

v2.0.3

19 Mar 08:47

Choose a tag to compare

Harpoon v2.0.3 β€” Release Notes

Released: 2026-03-14


Summary

v2.0.3 is a quality and security hardening release. It contains no new user-facing
features but resolves all critical and high-priority issues identified in the v2.0.x
code review, along with targeted refactoring that eliminates code duplication and
improves long-term maintainability.


Bug Fixes

readImageList reported wrong line numbers after deduplication (cmd/hpn/common.go)

Before: Image validation ran on the deduplicated slice, so the reported line number
referred to the post-dedup position rather than the original line in the file.

After: Validation runs on the raw (pre-dedup) lines with original line numbers
tracked per entry. Error messages now correctly reference the actual line in the
image list file.

Skopeo Pull() discarded downloaded data immediately (internal/runtime/skopeo.go)

Before: Pull() copied the image to a /tmp directory and then immediately called
defer os.RemoveAll(tempDir), making the entire operation a no-op.

After: Pull() copies the image directly into the first available daemon store
(Docker via docker-daemon:, or Podman via containers-storage:). If neither daemon
is available a clear error is returned directing the user to hpn save instead.

Duplicate --output flag binding in save command (cmd/hpn/save.go)

Before: Both --path and --output were bound to the same variable with identical
defaults. Cobra resolved them silently, producing confusing behaviour when both were
supplied.

After: --path is the canonical flag. --output is marked deprecated via Cobra's
MarkDeprecated so users receive an upgrade notice while backwards compatibility is
preserved during a transition period.

trimWhitespace reimplemented strings.TrimSpace (internal/runtime/checksum.go)

Before: A private trimWhitespace function with a manual loop existed alongside a
usage of strings.TrimSpace in the same file.

After: The redundant function is deleted; all callers use strings.TrimSpace
directly.

verifyChecksum duplicated VerifyTarChecksum (internal/runtime/checksum.go)

Before: Two nearly-identical functions existed (VerifyTarChecksum exported,
verifyChecksum unexported). The unexported version was never called outside the file.

After: The dead function is removed. All callers use the single exported
VerifyTarChecksum.

Nerdctl added --insecure-registry to every pull and push (internal/runtime/nerdctl.go)

Before: Pull() and Push() unconditionally appended --insecure-registry,
silently disabling TLS verification for all nerdctl operations.

After: The flag is removed from the defaults. Users who need it can pass it via
ExtraArgs (e.g. hpn pull -f list.txt -- --insecure-registry).


Security Fixes

Password exposed in process table via -p flag (all runtimes)

Severity: High

Before: When a password was supplied programmatically (e.g. from an env var or
--password flag), it was passed as a -p <value> argument to the underlying CLI
command. This made the credential visible to all local users via ps.

After: All runtimes (docker, podman, nerdctl, skopeo) now unconditionally
use --password-stdin and pipe the password to the command's stdin. The credential
never appears in the process table.

Predictable /tmp paths in Skopeo enabled TOCTOU / symlink attacks (internal/runtime/skopeo.go)

Severity: High

Before: Temporary directories were constructed with deterministic names such as
/tmp/skopeo-pull-<image> and /tmp/skopeo-oci-<image>-<unix-timestamp>. A local
attacker could pre-create these paths as symlinks before the process ran.

After: All temporary directories are created with os.MkdirTemp("", "hpn-skopeo-*")
which produces a cryptographically random suffix and is atomic with the directory
creation, eliminating the TOCTOU window.

Save directory created world-readable (cmd/hpn/save.go)

Severity: Medium

Before: os.MkdirAll(saveDir, 0755) allowed other local users to read image
archives stored in the save directory.

After: os.MkdirAll(saveDir, 0700) β€” only the owner can access the directory.

Checksum files written world-readable (internal/runtime/checksum.go)

Severity: Low

Before: .sha256 files were written with 0644 permissions.

After: Written with 0600 (owner read/write only).

Auth directory created with broad permissions (internal/runtime/auth.go)

Severity: Low

Before: os.MkdirAll(dir, 0755) for the credential config directory.

After: os.MkdirAll(dir, 0700) β€” credential directories must be private.


Interface Changes

ContainerRuntime.Load() now accepts an imageName parameter

// Before
Load(ctx context.Context, tarPath string) error

// After
Load(ctx context.Context, tarPath string, imageName string) error

Docker, Podman, and Nerdctl implementations ignore imageName (they parse the image
reference from the tar manifest automatically). The Skopeo implementation uses it as
the destination reference in skopeo copy docker-archive:tarPath docker-daemon:imageName
so it no longer has to fall back to calling docker load directly.

New ContainerRuntime.Logout() method

Logout(ctx context.Context, registry string) error

All four runtimes implement logout <registry>. A new hpn logout <registry> command
is available.


New Features

hpn logout command

hpn logout registry.example.com

Logs out from the specified registry using the active container runtime.


Refactoring

Generic concurrent worker pool (cmd/hpn/worker.go)

The goroutine+channel worker pool pattern was duplicated verbatim across pull.go,
push.go, save.go, and load.go (~50 lines Γ— 4 = ~200 lines of duplication).

A single generic function now serves all four commands:

func runWorkerPool[J any](ctx context.Context, jobs []J, maxWorkers int, fn func(J) error) []workerResult[J]

The implementation honours context cancellation: when the context is done, remaining
queued jobs are marked with ctx.Err() instead of spawning new sub-processes.

Retry-with-backoff utility (cmd/hpn/retry.go)

pullImageWithRetry and pushImageWithRetry were identical 40-line loops.

Extracted to:

func retryWithBackoff(ctx context.Context, fn func() error, cfg RetryConfig) error

Correctly uses select { case <-ctx.Done(): ... case <-time.After(delay): } so that a
Ctrl+C during a backoff sleep returns immediately instead of blocking for up to 30 s.

Shared runtime helpers extracted to docker.go

Two helper functions shared by all four runtimes are now defined once in docker.go:

  • applyProxyEnv(cmd, proxy) β€” sets http_proxy / https_proxy when proxy is enabled
  • buildErrMsg(base, stderr, debug) β€” constructs error messages from stderr output

~20 occurrences of the raw debug-output boilerplate are replaced with calls to these
helpers, reducing per-runtime complexity significantly.

pull.go, push.go, save.go, load.go refactored

Each command now:

  1. Calls runWorkerPool instead of maintaining its own goroutine pool
  2. Calls retryWithBackoff instead of its own retry loop
  3. Derives per-operation contexts from cmd.Context() so SIGINT propagates correctly

CI/CD Improvements

Race detector added to CI (.github/workflows/test.yml)

go test -race ./... is now a required check for all PRs.

golangci-lint added to CI

The existing .golangci.yml configuration was never invoked in CI. The workflow now
runs golangci-lint using the official action on every push and PR.

actions/setup-go upgraded to v5 with caching enabled


Signal Handling

SIGINT / SIGTERM cancels the active operation (cmd/hpn/main.go)

Before: Pressing Ctrl+C sent SIGINT to hpn, but the process would continue running
until the current sub-process (docker pull, etc.) finished.

After: signal.NotifyContext creates a top-level context that is cancelled
immediately on SIGINT or SIGTERM. This context is threaded through
cobra.ExecuteContext β†’ cmd.Context() β†’ runWorkerPool β†’ retryWithBackoff β†’ each context.WithTimeout. In-progress sub-processes receive a cancelled context on their
next retry boundary, and queued jobs are skipped immediately.


Files Changed

File Change type
internal/runtime/checksum.go Bug fix, dead-code removal, permission fix
internal/runtime/interface.go Interface change (Load, Logout)
internal/runtime/docker.go Security fix, interface update, helper extraction
internal/runtime/podman.go Security fix, interface update
internal/runtime/nerdctl.go Security fix, interface update, unconditional flag removal
internal/runtime/skopeo.go Bug fix, security fix, interface update, os.MkdirTemp
internal/runtime/auth.go Permission fix
cmd/hpn/common.go Bug fix (line numbers), restore validateImageList
cmd/hpn/save.go Flag dedup fix, permission fix, worker pool adoption
cmd/hpn/load.go Worker pool adoption, imageName parameter
cmd/hpn/pull.go Worker pool + retry adoption, context propagation
cmd/hpn/push.go Worker pool + retry adoption, context propagation
cmd/hpn/main.go SIGINT / SIGTERM handling
cmd/hpn/root.go Register logoutCmd
cmd/hpn/worker.go New β€” generic worker pool
cmd/hpn/retry.go New β€” retry-with-backoff utility
cmd/hpn/logout.go New β€” hpn logout command
cmd/hpn/worker_test.go New β€” worker pool tests (8 cases)
cmd/hpn/retry_test.go New β€” retry tests (7 cases)
.github/workflows/test.yml Race detector, golangci-lint, setup-go v5

Test Results

Run: go test -v -race ./... | Go 1.25.0 darwin/ar...

Read more

v2.0.2

13 Feb 12:18

Choose a tag to compare

Harpoon v2.0.2 Release Notes

Release Date: February 13, 2025
Version: v2.0.2

What's New in v2.0.2

This release focuses on code quality and professionalism: consistent exit codes for scripting, clearer usage errors, a contributing guide, additional tests, and CI improvements. It also adds the project logo and updates .gitignore for the default save/load directory.

Exit codes and usage errors

  • Exit codes: The CLI now uses exit code 0 (success), 1 (runtime or operational error), and 2 (usage error). Scripts can rely on exit code 2 when required flags are missing or arguments are invalid.
  • Usage errors: When you omit required flags (e.g. -f, --chart, --registry), the error message no longer appends ": usage"; only the clear message is shown (e.g. "missing required --file parameter").

Contributing and code quality

  • CONTRIBUTING.md: New guide covering clone, build (go build ./cmd/hpn), tests (go test ./...), go vet, code style (gofmt, English comments), and PR process. Exit code and logging conventions are documented.
  • Package doc comments: pkg/errors and internal/runtime now have package-level doc comments for go doc.
  • Cmd tests: Table-driven tests for readImageList (empty file, comments, valid lines, trim/skip) and mergeExtraArgs (nil/empty, config only, passthrough, order) in cmd/hpn/common_test.go.
  • CI: The test workflow now runs go mod verify, a go mod tidy check, and go vet ./....

Other code changes: Subcommand registration is centralized in root.go. All Go comments in runtime packages are in English.

Project logo

  • New project logo logo.png at the repository root, used in the README.

Other

  • .gitignore: Added images/ so the default save/load output directory is not committed.

Download

Download the latest release from GitHub Releases:

For the full changelog, see Changelog.

v2.0.1

12 Feb 22:44

Choose a tag to compare

Harpoon v2.0.1 Release Notes

Release Date: February 13, 2025
Version: v2.0.1

What's New in v2.0.1

This release adds three new subcommands (list-images, ls, rmi), a Helm chart image extraction pipeline, and improved filtering for chart-derived image lists.

New Features

list-images β€” Extract image list from Helm charts

Get a list of container images from a Helm chart (remote or local) for use with hpn pull -f or hpn push -f.

Requirements: Helm CLI must be installed.

Examples:

# From a remote chart
hpn list-images --chart bitnami/nginx --version 15.0.0 -o images.txt
hpn pull -f images.txt

# From a local chart directory or .tgz
hpn list-images --chart ./mychart -o images.txt
hpn list-images --chart ./mychart.tgz -f values-prod.yaml -o images.txt

Options: --chart, --version, -f/--values, -o/--output, --release-name.

ls β€” List images (runtime, path, or check)

Three modes:

  1. Runtime: hpn ls β€” list images in the local runtime (Docker/Podman/Nerdctl). Alias: hpn list.
  2. Path: hpn ls --path ./images β€” list saved tar files in a directory with IMAGE, SIZE, CHECKSUM.
  3. Check: hpn ls -f images.txt β€” verify images from a file exist in the runtime or in --path.

Examples:

hpn ls
hpn ls --path ./images
hpn ls -f images.txt
hpn ls -f images.txt -o plain

Skopeo is not supported (it does not store images locally).

rmi β€” Remove images from local storage

Remove images listed in a file from the local runtime. No interactive confirmation.

Examples:

hpn rmi -f images.txt
# Force remove (e.g. Docker) when images are in use
hpn rmi -f images.txt -- -f

Supported runtimes: Docker, Podman, Nerdctl. Skopeo returns an error (no local image store).

Improved

  • list-images filtering: Image extraction now excludes URLs, Prometheus-style metric names, unix:// / tcp:// / fd:// addresses, and port-only tags to reduce false positives in chart image lists.

Download

Download the latest release from GitHub Releases:

For the full changelog, see Changelog.

v2.0.0

21 Jan 08:36

Choose a tag to compare

Harpoon v2.0 Release Notes

Release Date: January 21, 2025
Version: v2.0.0

πŸŽ‰ What's New in v2.0

Harpoon v2.0 is a major release that introduces significant improvements to the CLI architecture, adds unified registry authentication, integrates Skopeo support, and enhances the overall developer experience.

πŸš€ Key Features

Unified Registry Authentication

The new hpn login command provides a unified interface for authenticating with container registries across all supported runtimes (Docker, Podman, Skopeo, Nerdctl).

Features:

  • Interactive password input (most secure)
  • Stdin password support for CI/CD pipelines
  • Environment variable support
  • Insecure registry support for private environments
  • Automatic runtime detection

Example:

# Interactive login
hpn login harbor.company.com

# CI/CD usage
echo "password" | hpn login harbor.company.com -u admin --password-stdin

Modern CLI Architecture

The CLI has been completely refactored to use subcommands, following industry standards (similar to docker, kubectl).

Before (v1.x):

hpn -a pull -f images.txt
hpn -a save -f images.txt --save-mode 2

After (v2.0):

hpn pull -f images.txt
hpn save -f images.txt --path ./images

Skopeo Integration

Skopeo is now supported as a first-class runtime, enabling daemonless container image operations.

Benefits:

  • No Docker daemon required
  • Multi-architecture image support
  • Better suited for CI/CD environments
  • Automatic detection and fallback

Enhanced Developer Experience

  • Debug Mode: --debug flag for detailed troubleshooting
  • Progress Bars: Visual progress indicators for all operations
  • Better Error Messages: More actionable error messages
  • Improved Help: Subcommand-specific help information

πŸ“‹ Breaking Changes

CLI Syntax Changes

All commands now use subcommands instead of the -a flag:

Old Syntax New Syntax
hpn -a pull -f images.txt hpn pull -f images.txt
hpn -a save -f images.txt --save-mode 2 hpn save -f images.txt --path ./images
hpn -a load --load-mode 2 hpn load --path ./images
hpn -a push -f images.txt -r registry.com --push-mode 2 hpn push -f images.txt --registry registry.com --project project

Configuration File Changes

The modes section has been replaced with paths:

Old config.yaml:

modes:
  save_mode: 2
  load_mode: 2
  push_mode: 2

New config.yaml:

paths:
  save_path: ./images
  load_path: ./images

πŸ”„ Migration Guide

For detailed migration instructions, see the Migration Guide in the changelog.

πŸ“¦ Installation

Download Pre-built Binaries

Download the latest release from GitHub Releases:

  • Linux AMD64: hpn-linux-amd64
  • Linux ARM64: hpn-linux-arm64
  • macOS Intel: hpn-darwin-amd64
  • macOS Apple Silicon: hpn-darwin-arm64
  • Windows AMD64: hpn-windows-amd64.exe

Build from Source

git clone https://github.com/Ghostwritten/harpoon.git
cd harpoon
make build-all

πŸ“š Documentation

πŸ™ Acknowledgments

Thank you to all contributors and users who provided feedback and helped improve Harpoon.

πŸ”— Links


For the complete list of changes, see the Changelog.

v1.1

29 Jul 08:35

Choose a tag to compare

Full Changelog: v1.0...v1.1

v1.0

23 Jul 12:21

Choose a tag to compare

Harpoon v1.0

What's New

This is the first stable release of Harpoon - a powerful cloud-native container image management tool.

Key Features

  • Multi-container runtime support (Docker, Podman, Nerdctl)
  • Flexible operation modes for different deployment scenarios
  • Cross-platform support (Linux, macOS, Windows)
  • Configuration management with YAML files
  • Proxy support for corporate environments
  • Batch operations for bulk image processing

Download

Choose the appropriate binary for your platform:

Platform Architecture Download
Linux AMD64 hpn-v1.0-linux-amd64.tar.gz
Linux ARM64 hpn-v1.0-linux-arm64.tar.gz
macOS Intel hpn-v1.0-darwin-amd64.tar.gz
macOS Apple Silicon hpn-v1.0-darwin-arm64.tar.gz
Windows AMD64 hpn-v1.0-windows-amd64.zip