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