detached f8212fb

feat: scaffold native registry push package with go-containerregistry dep

commit
f8212fb2546d4df8582f639df02fdae084326d4b
author
brice · · 2026-03-14 13:53 UTC
parent
d1b6a91
signature
unsigned
4 files · +105 -13
M go.mod +3 -4
@@ -10,6 +10,7 @@ require (
1010 github.com/docker/docker v28.5.2+incompatible
1111 github.com/docker/go-connections v0.5.0
1212 github.com/golang-jwt/jwt/v5 v5.3.1
13+ github.com/google/go-containerregistry v0.21.2
1314 github.com/google/uuid v1.6.0
1415 github.com/mattn/go-runewidth v0.0.21
1516 github.com/rivo/uniseg v0.4.7
@@ -43,6 +44,7 @@ require (
4344 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
4445 github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
4546 github.com/mattn/go-localereader v0.0.1 // indirect
47+ github.com/moby/sys/sequential v0.6.0 // indirect
4648 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
4749 github.com/muesli/cancelreader v0.2.2 // indirect
4850 github.com/muesli/termenv v0.16.0 // indirect
@@ -68,10 +70,9 @@ require (
6870 )
6971
7072 require (
71- github.com/Microsoft/go-winio v0.4.14 // indirect
73+ github.com/Microsoft/go-winio v0.6.2 // indirect
7274 github.com/containerd/errdefs v1.0.0
7375 github.com/containerd/errdefs/pkg v0.3.0 // indirect
74- github.com/containerd/log v0.1.0 // indirect
7576 github.com/distribution/reference v0.6.0 // indirect
7677 github.com/docker/go-units v0.5.0 // indirect
7778 github.com/felixge/httpsnoop v1.0.4 // indirect
@@ -83,9 +84,7 @@ require (
8384 github.com/mattn/go-colorable v0.1.14 // indirect
8485 github.com/mattn/go-isatty v0.0.20 // indirect
8586 github.com/moby/docker-image-spec v1.3.1 // indirect
86- github.com/moby/sys/atomicwriter v0.1.0 // indirect
8787 github.com/moby/term v0.5.2 // indirect
88- github.com/morikuni/aec v1.0.0 // indirect
8988 github.com/opencontainers/go-digest v1.0.0 // indirect
9089 github.com/opencontainers/image-spec v1.1.1 // indirect
9190 github.com/pelletier/go-toml/v2 v2.2.4
@@ -10,6 +10,7 @@ require (
10 github.com/docker/docker v28.5.2+incompatible10 github.com/docker/docker v28.5.2+incompatible
11 github.com/docker/go-connections v0.5.011 github.com/docker/go-connections v0.5.0
12 github.com/golang-jwt/jwt/v5 v5.3.112 github.com/golang-jwt/jwt/v5 v5.3.1
13+ github.com/google/go-containerregistry v0.21.2
13 github.com/google/uuid v1.6.014 github.com/google/uuid v1.6.0
14 github.com/mattn/go-runewidth v0.0.2115 github.com/mattn/go-runewidth v0.0.21
15 github.com/rivo/uniseg v0.4.716 github.com/rivo/uniseg v0.4.7
@@ -43,6 +44,7 @@ require (
43 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect44 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
44 github.com/lucasb-eyer/go-colorful v1.3.0 // indirect45 github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
45 github.com/mattn/go-localereader v0.0.1 // indirect46 github.com/mattn/go-localereader v0.0.1 // indirect
47+ github.com/moby/sys/sequential v0.6.0 // indirect
46 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect48 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
47 github.com/muesli/cancelreader v0.2.2 // indirect49 github.com/muesli/cancelreader v0.2.2 // indirect
48 github.com/muesli/termenv v0.16.0 // indirect50 github.com/muesli/termenv v0.16.0 // indirect
@@ -68,10 +70,9 @@ require (
68 )70 )
69 71
70 require (72 require (
71- github.com/Microsoft/go-winio v0.4.14 // indirect73+ github.com/Microsoft/go-winio v0.6.2 // indirect
72 github.com/containerd/errdefs v1.0.074 github.com/containerd/errdefs v1.0.0
73 github.com/containerd/errdefs/pkg v0.3.0 // indirect75 github.com/containerd/errdefs/pkg v0.3.0 // indirect
74- github.com/containerd/log v0.1.0 // indirect
75 github.com/distribution/reference v0.6.0 // indirect76 github.com/distribution/reference v0.6.0 // indirect
76 github.com/docker/go-units v0.5.0 // indirect77 github.com/docker/go-units v0.5.0 // indirect
77 github.com/felixge/httpsnoop v1.0.4 // indirect78 github.com/felixge/httpsnoop v1.0.4 // indirect
@@ -83,9 +84,7 @@ require (
83 github.com/mattn/go-colorable v0.1.14 // indirect84 github.com/mattn/go-colorable v0.1.14 // indirect
84 github.com/mattn/go-isatty v0.0.20 // indirect85 github.com/mattn/go-isatty v0.0.20 // indirect
85 github.com/moby/docker-image-spec v1.3.1 // indirect86 github.com/moby/docker-image-spec v1.3.1 // indirect
86- github.com/moby/sys/atomicwriter v0.1.0 // indirect
87 github.com/moby/term v0.5.2 // indirect87 github.com/moby/term v0.5.2 // indirect
88- github.com/morikuni/aec v1.0.0 // indirect
89 github.com/opencontainers/go-digest v1.0.0 // indirect88 github.com/opencontainers/go-digest v1.0.0 // indirect
90 github.com/opencontainers/image-spec v1.1.1 // indirect89 github.com/opencontainers/image-spec v1.1.1 // indirect
91 github.com/pelletier/go-toml/v2 v2.2.490 github.com/pelletier/go-toml/v2 v2.2.4
M go.sum +4 -9 generated file: collapsed
A pkg/registrypush/push.go +79 -0
@@ -0,0 +1,79 @@
1+package registrypush
2+
3+import (
4+ "context"
5+ "io"
6+ "net/http"
7+
8+ v1 "github.com/google/go-containerregistry/pkg/v1"
9+)
10+
11+// DefaultChunkSize is 50MB - safely under Cloudflare's 100MB per-request limit
12+// and Gordon's default max_blob_chunk_size of 95MB.
13+const DefaultChunkSize = 50 * 1024 * 1024
14+
15+// ImageSource provides a v1.Image for a given ref.
16+// The default reads from the local Docker/Podman daemon.
17+// Override with WithImageSource for testing.
18+type ImageSource func(ctx context.Context, ref string) (v1.Image, error)
19+
20+// Pusher uploads container images to a registry using chunked blob uploads.
21+type Pusher struct {
22+ chunkSize int64
23+ transport http.RoundTripper
24+ imageSource ImageSource
25+ progress io.Writer
26+}
27+
28+// Option configures a Pusher.
29+type Option func(*Pusher)
30+
31+// WithChunkSize sets the maximum size of each blob upload chunk.
32+func WithChunkSize(size int64) Option {
33+ return func(p *Pusher) { p.chunkSize = size }
34+}
35+
36+// WithTransport sets a custom HTTP transport (for testing).
37+func WithTransport(t http.RoundTripper) Option {
38+ return func(p *Pusher) { p.transport = t }
39+}
40+
41+// WithImageSource overrides the default daemon-based image reader.
42+func WithImageSource(src ImageSource) Option {
43+ return func(p *Pusher) { p.imageSource = src }
44+}
45+
46+// WithProgress sets a writer for progress messages.
47+func WithProgress(w io.Writer) Option {
48+ return func(p *Pusher) { p.progress = w }
49+}
50+
51+// New creates a Pusher with the given options.
52+func New(opts ...Option) *Pusher {
53+ p := &Pusher{chunkSize: DefaultChunkSize}
54+ for _, o := range opts {
55+ o(p)
56+ }
57+ if p.transport == nil {
58+ p.transport = http.DefaultTransport
59+ }
60+ if p.imageSource == nil {
61+ p.imageSource = defaultImageSource
62+ }
63+ return p
64+}
65+
66+// Push reads a local image (by ref) from the Docker/Podman daemon and uploads
67+// it to the registry encoded in the ref, using chunked blob uploads.
68+//
69+// The ref must be fully qualified: registry.example.com/name:tag
70+func (p *Pusher) Push(ctx context.Context, ref string) error {
71+ // Implementation in subsequent tasks
72+ return nil
73+}
74+
75+// defaultImageSource reads an image from the local Docker/Podman daemon.
76+func defaultImageSource(ctx context.Context, ref string) (v1.Image, error) {
77+ // Implementation in Task 3
78+ return nil, nil
79+}
@@ -0,0 +1,79 @@
1+package registrypush
2+
3+import (
4+ "context"
5+ "io"
6+ "net/http"
7+
8+ v1 "github.com/google/go-containerregistry/pkg/v1"
9+)
10+
11+// DefaultChunkSize is 50MB - safely under Cloudflare's 100MB per-request limit
12+// and Gordon's default max_blob_chunk_size of 95MB.
13+const DefaultChunkSize = 50 * 1024 * 1024
14+
15+// ImageSource provides a v1.Image for a given ref.
16+// The default reads from the local Docker/Podman daemon.
17+// Override with WithImageSource for testing.
18+type ImageSource func(ctx context.Context, ref string) (v1.Image, error)
19+
20+// Pusher uploads container images to a registry using chunked blob uploads.
21+type Pusher struct {
22+ chunkSize int64
23+ transport http.RoundTripper
24+ imageSource ImageSource
25+ progress io.Writer
26+}
27+
28+// Option configures a Pusher.
29+type Option func(*Pusher)
30+
31+// WithChunkSize sets the maximum size of each blob upload chunk.
32+func WithChunkSize(size int64) Option {
33+ return func(p *Pusher) { p.chunkSize = size }
34+}
35+
36+// WithTransport sets a custom HTTP transport (for testing).
37+func WithTransport(t http.RoundTripper) Option {
38+ return func(p *Pusher) { p.transport = t }
39+}
40+
41+// WithImageSource overrides the default daemon-based image reader.
42+func WithImageSource(src ImageSource) Option {
43+ return func(p *Pusher) { p.imageSource = src }
44+}
45+
46+// WithProgress sets a writer for progress messages.
47+func WithProgress(w io.Writer) Option {
48+ return func(p *Pusher) { p.progress = w }
49+}
50+
51+// New creates a Pusher with the given options.
52+func New(opts ...Option) *Pusher {
53+ p := &Pusher{chunkSize: DefaultChunkSize}
54+ for _, o := range opts {
55+ o(p)
56+ }
57+ if p.transport == nil {
58+ p.transport = http.DefaultTransport
59+ }
60+ if p.imageSource == nil {
61+ p.imageSource = defaultImageSource
62+ }
63+ return p
64+}
65+
66+// Push reads a local image (by ref) from the Docker/Podman daemon and uploads
67+// it to the registry encoded in the ref, using chunked blob uploads.
68+//
69+// The ref must be fully qualified: registry.example.com/name:tag
70+func (p *Pusher) Push(ctx context.Context, ref string) error {
71+ // Implementation in subsequent tasks
72+ return nil
73+}
74+
75+// defaultImageSource reads an image from the local Docker/Podman daemon.
76+func defaultImageSource(ctx context.Context, ref string) (v1.Image, error) {
77+ // Implementation in Task 3
78+ return nil, nil
79+}
A pkg/registrypush/push_test.go +19 -0
@@ -0,0 +1,19 @@
1+package registrypush_test
2+
3+import (
4+ "testing"
5+
6+ "github.com/stretchr/testify/assert"
7+
8+ "github.com/bnema/gordon/pkg/registrypush"
9+)
10+
11+func TestNew_defaults(t *testing.T) {
12+ p := registrypush.New()
13+ assert.NotNil(t, p)
14+}
15+
16+func TestNew_withChunkSize(t *testing.T) {
17+ p := registrypush.New(registrypush.WithChunkSize(10 * 1024 * 1024))
18+ assert.NotNil(t, p)
19+}
@@ -0,0 +1,19 @@
1+package registrypush_test
2+
3+import (
4+ "testing"
5+
6+ "github.com/stretchr/testify/assert"
7+
8+ "github.com/bnema/gordon/pkg/registrypush"
9+)
10+
11+func TestNew_defaults(t *testing.T) {
12+ p := registrypush.New()
13+ assert.NotNil(t, p)
14+}
15+
16+func TestNew_withChunkSize(t *testing.T) {
17+ p := registrypush.New(registrypush.WithChunkSize(10 * 1024 * 1024))
18+ assert.NotNil(t, p)
19+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close