Skip to content

copy: Add text-based progress output for non-TTY environments#1

Open
oshe-gi wants to merge 1 commit intomainfrom
658-text-progress-for-non-tty
Open

copy: Add text-based progress output for non-TTY environments#1
oshe-gi wants to merge 1 commit intomainfrom
658-text-progress-for-non-tty

Conversation

@oshe-gi
Copy link
Owner

@oshe-gi oshe-gi commented Feb 17, 2026

Relates-to: containers/skopeo#658

Problem

When copying images in non-TTY environments (CI/CD pipelines, redirected output, piped commands), the visual mpb progress bars are discarded via io.Discard, leaving users with no visibility into transfer progress. This makes it difficult to:

  • Detect stalled transfers in CI/CD pipelines
  • Monitor progress of long-running copies
  • Distinguish between a slow transfer and a hung process

Currently, only a single "Copying blob..." message is printed via printCopyInfo() when non-TTY, with no subsequent progress updates.

Solution

This change automatically enables text-based progress output for non-TTY environments by leveraging the existing Options.Progress channel mechanism.

Design decisions

  1. Automatic for non-TTY: When output is not a TTY and the caller hasn't already provided a buffered Progress channel, we automatically set up text-based progress output. No opt-in flag needed.

  2. Aggregate progress instead of per-blob: Rather than printing a line for each blob (which would be verbose for multi-layer images), we track total bytes across all blobs and print a single aggregate progress line. This keeps CI logs clean while still providing visibility.

  3. Reuse existing Progress channel: The progressReader in blob.go already sends ProgressEventNewArtifact, ProgressEventRead, and ProgressEventDone events. We simply consume these events and format them as text output.

  4. Buffered channel: We create a buffered channel to prevent blocking senders during parallel blob downloads. Callers who need custom consumption should provide a properly buffered channel.

  5. Sensible interval defaults: If ProgressInterval is not set, we default to 500ms. If the caller sets a larger interval, we respect that. The interval is clamped to a minimum of 500ms to prevent log spam.

Output example

Progress: 13.1 MiB / 52.3 MiB
Progress: 26.2 MiB / 52.3 MiB
Progress: 52.3 MiB / 52.3 MiB

Files changed

  • copy/progress_nontty.go - New file with nonTTYProgressWriter implementation
  • copy/copy.go - Initialize text progress writer in non-TTY mode

Testing

Build and unit tests:

go build ./copy/...  #
go test ./copy/...   # ✓ all existing tests pass

Manual testing with skopeo (pipe to cat to force non-TTY):

go run ./cmd/skopeo \
  --policy ./default-policy.json \
  --override-os linux \
  --override-arch amd64 \
  copy \
  --image-parallel-copies 8 \
  docker://docker.io/library/golang:1.24-bookworm \
  dir:/tmp/golang-copy | cat
Getting image source signatures
Copying blob sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1
Copying blob sha256:89edcaae7ec479668d9bf0891145726173a305c809a8c4165723ceaf15b5a05f
Copying blob sha256:bbceb003542957cee7df7b79249eaf0a71d21c5203d086969b565fb6dec85d86
Copying blob sha256:d1ae0a59862a60fde488eccaa7f764b5f5cb60746b7adf2335f9cc05ce1ed745
Copying blob sha256:6bc9f599b3efabc64230fd3b969d7654fcd6c6c98ad7cf7470093fe85274a7fc
Copying blob sha256:ee9e6246b78f3a784fefa655e89ccdf2271e396000b7624d6a785cb2c2580001
Copying blob sha256:f7bdfd728ac2ad72d43b82689890dc698260d3a1049845f48fb3fb942df6c581
Progress: 1.5MiB / 271.3MiB
Progress: 5.8MiB / 294.2MiB
Progress: 10.9MiB / 294.2MiB
Progress: 15.7MiB / 294.2MiB
Progress: 20.8MiB / 294.2MiB
Progress: 26.3MiB / 294.2MiB
Progress: 31.7MiB / 294.2MiB
Progress: 37.9MiB / 294.2MiB
Progress: 43.6MiB / 294.2MiB
Progress: 49.9MiB / 294.2MiB
Progress: 56.0MiB / 294.2MiB
Progress: 62.3MiB / 294.2MiB
Progress: 68.6MiB / 294.2MiB
Progress: 75.0MiB / 294.2MiB
Progress: 80.7MiB / 294.2MiB
Progress: 87.2MiB / 294.2MiB
Progress: 91.6MiB / 294.2MiB
Progress: 95.4MiB / 294.2MiB
Progress: 98.1MiB / 294.2MiB
Progress: 101.5MiB / 294.2MiB
Progress: 104.9MiB / 294.2MiB
Progress: 108.8MiB / 294.2MiB
Progress: 112.9MiB / 294.2MiB
Progress: 117.0MiB / 294.2MiB
Progress: 120.5MiB / 294.2MiB
Progress: 125.1MiB / 294.2MiB
Progress: 129.7MiB / 294.2MiB
Progress: 134.6MiB / 294.2MiB
Progress: 139.6MiB / 294.2MiB
Progress: 144.6MiB / 294.2MiB
Progress: 149.6MiB / 294.2MiB
Progress: 154.7MiB / 294.2MiB
Progress: 160.1MiB / 294.2MiB
Progress: 165.8MiB / 294.2MiB
Progress: 172.1MiB / 294.2MiB
Progress: 177.9MiB / 294.2MiB
Progress: 184.2MiB / 294.2MiB
Progress: 190.3MiB / 294.2MiB
Progress: 196.4MiB / 294.2MiB
Progress: 203.4MiB / 294.2MiB
Progress: 210.8MiB / 294.2MiB
Progress: 218.8MiB / 294.2MiB
Progress: 227.1MiB / 294.2MiB
Progress: 236.7MiB / 294.2MiB
Progress: 243.8MiB / 294.2MiB
Progress: 249.7MiB / 294.2MiB
Progress: 255.9MiB / 294.2MiB
Progress: 262.3MiB / 294.2MiB
Progress: 270.8MiB / 294.2MiB
Progress: 284.7MiB / 294.2MiB
Copying config sha256:e0cffc405270b9114fac7706d07c373727d1b42b0e47c525b9cd1ab1097779ff
Writing manifest to image destination

Output may be shorter, if ProgressInterval is set longer.
Signed-off-by: Oleksandr Shestopal ar.shestopal@gmail.com

@oshe-gi oshe-gi force-pushed the 658-text-progress-for-non-tty branch from 7c68cfa to 89c1f7b Compare February 17, 2026 15:57
When copying images in non-TTY environments (CI/CD pipelines, redirected
output, piped commands), the visual mpb progress bars are discarded,
leaving users with no visibility into transfer progress. This makes it
difficult to detect stalled transfers or monitor long-running copies.

This change adds a nonTTYProgressWriter that consumes progress events
from the existing Progress channel and prints periodic aggregate progress
lines suitable for log output:

    Progress: 13.1 MiB / 52.3 MiB
    Progress: 26.2 MiB / 52.3 MiB
    Progress: 52.3 MiB / 52.3 MiB

The feature is enabled when, output is not a TTY, we check if option.Progress is set,
otherwise create a new buffered channel for progress events.

Note: Unbuffered channels are replaced with buffered ones to prevent
blocking during parallel blob downloads. Callers who need custom
consumption should provide a properly buffered channel.

Relates-to: containers/skopeo#658
Signed-off-by: Oleksandr Shestopal <ar.shestopal-oshegithub@gmail.com>
@oshe-gi oshe-gi force-pushed the 658-text-progress-for-non-tty branch from 89c1f7b to 00a1e73 Compare February 18, 2026 10:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants