1package cli
2
3import (
4 "context"
5 "fmt"
6 "io"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11
12 climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks"
13 "github.com/bnema/zerowrap"
14 v1 "github.com/google/go-containerregistry/pkg/v1"
15 "github.com/google/go-containerregistry/pkg/v1/random"
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/mock"
18 "github.com/stretchr/testify/require"
19
20 registryhttp "github.com/bnema/gordon/internal/adapters/in/http/registry"
21 "github.com/bnema/gordon/internal/adapters/out/filesystem"
22 "github.com/bnema/gordon/internal/domain"
23 registrysvc "github.com/bnema/gordon/internal/usecase/registry"
24 "github.com/bnema/gordon/pkg/registrypush"
25)
26
27func TestIntegration_TagAndPush_NativeRegistry(t *testing.T) {
28 t.Parallel()
29
30 ctx := context.Background()
31 harness := newCLITestRegistry(t, 95*1024*1024)
32 defer harness.Close()
33
34 img, err := random.Image(512, 1)
35 require.NoError(t, err)
36
37 registry := strings.TrimPrefix(harness.server.URL, "http://")
38 versionRef := registry + "/testapp:v1.0.0"
39 latestRef := registry + "/testapp:latest"
40
41 ops := climocks.NewMockpushImageOps(t)
42 ops.On("Tag", mock.Anything, mock.Anything, mock.Anything).Return(nil)
43 ops.On("Exists", mock.Anything, registry+"/testapp:v1.0.0").Return(true, nil)
44 ops.EXPECT().Push(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, ref string) error {
45 pusher := registrypush.New(
46 registrypush.WithChunkSize(256),
47 registrypush.WithTransport(harness.server.Client().Transport),
48 registrypush.WithImageSource(func(context.Context, string) (v1.Image, error) {
49 return img, nil
50 }),
51 )
52 return pusher.Push(ctx, ref)
53 })
54
55 err = tagAndPush(ctx, io.Discard, ops, imagePush{
56 Registry: registry,
57 ImageName: "testapp",
58 SourceRef: versionRef,
59 Version: "v1.0.0",
60 VersionRef: versionRef,
61 LatestRef: latestRef,
62 })
63 require.NoError(t, err)
64
65 harness.RequireManifest(t, "v1.0.0")
66 harness.RequireManifest(t, "latest")
67 harness.RequireBlobs(ctx, t, img)
68}
69
70type cliTestRegistry struct {
71 server *httptest.Server
72}
73
74func newCLITestRegistry(t *testing.T, maxBlobChunkSize int64) *cliTestRegistry {
75 t.Helper()
76
77 log := zerowrap.Default()
78 rootDir := t.TempDir()
79
80 blobStore, err := filesystem.NewBlobStorage(rootDir, log)
81 require.NoError(t, err)
82
83 manifestStore, err := filesystem.NewManifestStorage(rootDir, log)
84 require.NoError(t, err)
85
86 svc := registrysvc.NewService(blobStore, manifestStore, cliNoopEventPublisher{})
87 handler := registryhttp.NewHandler(svc, log, maxBlobChunkSize)
88
89 return &cliTestRegistry{server: httptest.NewServer(handler)}
90}
91
92func (h *cliTestRegistry) Close() {
93 h.server.Close()
94}
95
96func (h *cliTestRegistry) RequireManifest(t *testing.T, tag string) {
97 t.Helper()
98
99 req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/testapp/manifests/%s", h.server.URL, tag), nil)
100 require.NoError(t, err)
101
102 resp, err := h.server.Client().Do(req)
103 require.NoError(t, err)
104 defer resp.Body.Close()
105
106 assert.Equal(t, http.StatusOK, resp.StatusCode)
107}
108
109func (h *cliTestRegistry) RequireBlobs(ctx context.Context, t *testing.T, img v1.Image) {
110 t.Helper()
111
112 layers, err := img.Layers()
113 require.NoError(t, err)
114
115 for _, layer := range layers {
116 digest, digestErr := layer.Digest()
117 require.NoError(t, digestErr)
118
119 req, reqErr := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/v2/testapp/blobs/%s", h.server.URL, digest.String()), nil)
120 require.NoError(t, reqErr)
121
122 resp, respErr := h.server.Client().Do(req)
123 require.NoError(t, respErr)
124 assert.Equal(t, http.StatusOK, resp.StatusCode)
125 require.NoError(t, resp.Body.Close())
126 }
127}
128
129type cliNoopEventPublisher struct{}
130
131func (cliNoopEventPublisher) Publish(domain.EventType, any) error {
132 return nil
133}