fix(registry): keep fqdn tags for internal pulls
- commit
- 1a89de94a7bf2a7e680cb99730e361fcf3d63c27
- author
- bnema · · 2026-01-13 20:48 UTC
- parent
- 67b31fc
- signature
- unsigned
7 files · +241 -16
M internal/adapters/out/docker/runtime.go +19 -0
| @@ -425,6 +425,25 @@ func (r *Runtime) PullImageWithAuth(ctx context.Context, imageRef, username, pas | |||
| 425 | 425 | return nil | |
| 426 | 426 | } | |
| 427 | 427 | ||
| 428 | + | // TagImage tags a locally available image with a new reference. | |
| 429 | + | func (r *Runtime) TagImage(ctx context.Context, sourceRef, targetRef string) error { | |
| 430 | + | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |
| 431 | + | zerowrap.FieldLayer: "adapter", | |
| 432 | + | zerowrap.FieldAdapter: "docker", | |
| 433 | + | zerowrap.FieldAction: "TagImage", | |
| 434 | + | "source": sourceRef, | |
| 435 | + | "target": targetRef, | |
| 436 | + | }) | |
| 437 | + | log := zerowrap.FromCtx(ctx) | |
| 438 | + | ||
| 439 | + | if err := r.client.ImageTag(ctx, sourceRef, targetRef); err != nil { | |
| 440 | + | return log.WrapErr(err, "failed to tag image") | |
| 441 | + | } | |
| 442 | + | ||
| 443 | + | log.Info().Msg("image tagged successfully") | |
| 444 | + | return nil | |
| 445 | + | } | |
| 446 | + | ||
| 428 | 447 | // RemoveImage removes an image. | |
| 429 | 448 | func (r *Runtime) RemoveImage(ctx context.Context, imageRef string, force bool) error { | |
| 430 | 449 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |
| @@ -425,6 +425,25 @@ func (r *Runtime) PullImageWithAuth(ctx context.Context, imageRef, username, pas | |||||
| 425 | return nil | 425 | return nil | ||
| 426 | } | 426 | } | ||
| 427 | 427 | ||||
| 428 | + | // TagImage tags a locally available image with a new reference. | |||
| 429 | + | func (r *Runtime) TagImage(ctx context.Context, sourceRef, targetRef string) error { | |||
| 430 | + | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |||
| 431 | + | zerowrap.FieldLayer: "adapter", | |||
| 432 | + | zerowrap.FieldAdapter: "docker", | |||
| 433 | + | zerowrap.FieldAction: "TagImage", | |||
| 434 | + | "source": sourceRef, | |||
| 435 | + | "target": targetRef, | |||
| 436 | + | }) | |||
| 437 | + | log := zerowrap.FromCtx(ctx) | |||
| 438 | + | ||||
| 439 | + | if err := r.client.ImageTag(ctx, sourceRef, targetRef); err != nil { | |||
| 440 | + | return log.WrapErr(err, "failed to tag image") | |||
| 441 | + | } | |||
| 442 | + | ||||
| 443 | + | log.Info().Msg("image tagged successfully") | |||
| 444 | + | return nil | |||
| 445 | + | } | |||
| 446 | + | ||||
| 428 | // RemoveImage removes an image. | 447 | // RemoveImage removes an image. | ||
| 429 | func (r *Runtime) RemoveImage(ctx context.Context, imageRef string, force bool) error { | 448 | func (r *Runtime) RemoveImage(ctx context.Context, imageRef string, force bool) error { | ||
| 430 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | 449 | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | ||
M internal/boundaries/out/mocks/mock_container_runtime.go +63 -0
| @@ -1892,6 +1892,69 @@ func (_c *MockContainerRuntime_StopContainer_Call) RunAndReturn(run func(ctx con | |||
| 1892 | 1892 | return _c | |
| 1893 | 1893 | } | |
| 1894 | 1894 | ||
| 1895 | + | // TagImage provides a mock function for the type MockContainerRuntime | |
| 1896 | + | func (_mock *MockContainerRuntime) TagImage(ctx context.Context, sourceRef string, targetRef string) error { | |
| 1897 | + | ret := _mock.Called(ctx, sourceRef, targetRef) | |
| 1898 | + | ||
| 1899 | + | if len(ret) == 0 { | |
| 1900 | + | panic("no return value specified for TagImage") | |
| 1901 | + | } | |
| 1902 | + | ||
| 1903 | + | var r0 error | |
| 1904 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) error); ok { | |
| 1905 | + | r0 = returnFunc(ctx, sourceRef, targetRef) | |
| 1906 | + | } else { | |
| 1907 | + | r0 = ret.Error(0) | |
| 1908 | + | } | |
| 1909 | + | return r0 | |
| 1910 | + | } | |
| 1911 | + | ||
| 1912 | + | // MockContainerRuntime_TagImage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TagImage' | |
| 1913 | + | type MockContainerRuntime_TagImage_Call struct { | |
| 1914 | + | *mock.Call | |
| 1915 | + | } | |
| 1916 | + | ||
| 1917 | + | // TagImage is a helper method to define mock.On call | |
| 1918 | + | // - ctx context.Context | |
| 1919 | + | // - sourceRef string | |
| 1920 | + | // - targetRef string | |
| 1921 | + | func (_e *MockContainerRuntime_Expecter) TagImage(ctx interface{}, sourceRef interface{}, targetRef interface{}) *MockContainerRuntime_TagImage_Call { | |
| 1922 | + | return &MockContainerRuntime_TagImage_Call{Call: _e.mock.On("TagImage", ctx, sourceRef, targetRef)} | |
| 1923 | + | } | |
| 1924 | + | ||
| 1925 | + | func (_c *MockContainerRuntime_TagImage_Call) Run(run func(ctx context.Context, sourceRef string, targetRef string)) *MockContainerRuntime_TagImage_Call { | |
| 1926 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 1927 | + | var arg0 context.Context | |
| 1928 | + | if args[0] != nil { | |
| 1929 | + | arg0 = args[0].(context.Context) | |
| 1930 | + | } | |
| 1931 | + | var arg1 string | |
| 1932 | + | if args[1] != nil { | |
| 1933 | + | arg1 = args[1].(string) | |
| 1934 | + | } | |
| 1935 | + | var arg2 string | |
| 1936 | + | if args[2] != nil { | |
| 1937 | + | arg2 = args[2].(string) | |
| 1938 | + | } | |
| 1939 | + | run( | |
| 1940 | + | arg0, | |
| 1941 | + | arg1, | |
| 1942 | + | arg2, | |
| 1943 | + | ) | |
| 1944 | + | }) | |
| 1945 | + | return _c | |
| 1946 | + | } | |
| 1947 | + | ||
| 1948 | + | func (_c *MockContainerRuntime_TagImage_Call) Return(err error) *MockContainerRuntime_TagImage_Call { | |
| 1949 | + | _c.Call.Return(err) | |
| 1950 | + | return _c | |
| 1951 | + | } | |
| 1952 | + | ||
| 1953 | + | func (_c *MockContainerRuntime_TagImage_Call) RunAndReturn(run func(ctx context.Context, sourceRef string, targetRef string) error) *MockContainerRuntime_TagImage_Call { | |
| 1954 | + | _c.Call.Return(run) | |
| 1955 | + | return _c | |
| 1956 | + | } | |
| 1957 | + | ||
| 1895 | 1958 | // Version provides a mock function for the type MockContainerRuntime | |
| 1896 | 1959 | func (_mock *MockContainerRuntime) Version(ctx context.Context) (string, error) { | |
| 1897 | 1960 | ret := _mock.Called(ctx) | |
| @@ -1892,6 +1892,69 @@ func (_c *MockContainerRuntime_StopContainer_Call) RunAndReturn(run func(ctx con | |||||
| 1892 | return _c | 1892 | return _c | ||
| 1893 | } | 1893 | } | ||
| 1894 | 1894 | ||||
| 1895 | + | // TagImage provides a mock function for the type MockContainerRuntime | |||
| 1896 | + | func (_mock *MockContainerRuntime) TagImage(ctx context.Context, sourceRef string, targetRef string) error { | |||
| 1897 | + | ret := _mock.Called(ctx, sourceRef, targetRef) | |||
| 1898 | + | ||||
| 1899 | + | if len(ret) == 0 { | |||
| 1900 | + | panic("no return value specified for TagImage") | |||
| 1901 | + | } | |||
| 1902 | + | ||||
| 1903 | + | var r0 error | |||
| 1904 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) error); ok { | |||
| 1905 | + | r0 = returnFunc(ctx, sourceRef, targetRef) | |||
| 1906 | + | } else { | |||
| 1907 | + | r0 = ret.Error(0) | |||
| 1908 | + | } | |||
| 1909 | + | return r0 | |||
| 1910 | + | } | |||
| 1911 | + | ||||
| 1912 | + | // MockContainerRuntime_TagImage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TagImage' | |||
| 1913 | + | type MockContainerRuntime_TagImage_Call struct { | |||
| 1914 | + | *mock.Call | |||
| 1915 | + | } | |||
| 1916 | + | ||||
| 1917 | + | // TagImage is a helper method to define mock.On call | |||
| 1918 | + | // - ctx context.Context | |||
| 1919 | + | // - sourceRef string | |||
| 1920 | + | // - targetRef string | |||
| 1921 | + | func (_e *MockContainerRuntime_Expecter) TagImage(ctx interface{}, sourceRef interface{}, targetRef interface{}) *MockContainerRuntime_TagImage_Call { | |||
| 1922 | + | return &MockContainerRuntime_TagImage_Call{Call: _e.mock.On("TagImage", ctx, sourceRef, targetRef)} | |||
| 1923 | + | } | |||
| 1924 | + | ||||
| 1925 | + | func (_c *MockContainerRuntime_TagImage_Call) Run(run func(ctx context.Context, sourceRef string, targetRef string)) *MockContainerRuntime_TagImage_Call { | |||
| 1926 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 1927 | + | var arg0 context.Context | |||
| 1928 | + | if args[0] != nil { | |||
| 1929 | + | arg0 = args[0].(context.Context) | |||
| 1930 | + | } | |||
| 1931 | + | var arg1 string | |||
| 1932 | + | if args[1] != nil { | |||
| 1933 | + | arg1 = args[1].(string) | |||
| 1934 | + | } | |||
| 1935 | + | var arg2 string | |||
| 1936 | + | if args[2] != nil { | |||
| 1937 | + | arg2 = args[2].(string) | |||
| 1938 | + | } | |||
| 1939 | + | run( | |||
| 1940 | + | arg0, | |||
| 1941 | + | arg1, | |||
| 1942 | + | arg2, | |||
| 1943 | + | ) | |||
| 1944 | + | }) | |||
| 1945 | + | return _c | |||
| 1946 | + | } | |||
| 1947 | + | ||||
| 1948 | + | func (_c *MockContainerRuntime_TagImage_Call) Return(err error) *MockContainerRuntime_TagImage_Call { | |||
| 1949 | + | _c.Call.Return(err) | |||
| 1950 | + | return _c | |||
| 1951 | + | } | |||
| 1952 | + | ||||
| 1953 | + | func (_c *MockContainerRuntime_TagImage_Call) RunAndReturn(run func(ctx context.Context, sourceRef string, targetRef string) error) *MockContainerRuntime_TagImage_Call { | |||
| 1954 | + | _c.Call.Return(run) | |||
| 1955 | + | return _c | |||
| 1956 | + | } | |||
| 1957 | + | ||||
| 1895 | // Version provides a mock function for the type MockContainerRuntime | 1958 | // Version provides a mock function for the type MockContainerRuntime | ||
| 1896 | func (_mock *MockContainerRuntime) Version(ctx context.Context) (string, error) { | 1959 | func (_mock *MockContainerRuntime) Version(ctx context.Context) (string, error) { | ||
| 1897 | ret := _mock.Called(ctx) | 1960 | ret := _mock.Called(ctx) | ||
M internal/boundaries/out/runtime.go +1 -0
| @@ -29,6 +29,7 @@ type ContainerRuntime interface { | |||
| 29 | 29 | // Image operations | |
| 30 | 30 | PullImage(ctx context.Context, image string) error | |
| 31 | 31 | PullImageWithAuth(ctx context.Context, image, username, password string) error | |
| 32 | + | TagImage(ctx context.Context, sourceRef, targetRef string) error | |
| 32 | 33 | RemoveImage(ctx context.Context, image string, force bool) error | |
| 33 | 34 | ListImages(ctx context.Context) ([]string, error) | |
| 34 | 35 | ||
| @@ -29,6 +29,7 @@ type ContainerRuntime interface { | |||||
| 29 | // Image operations | 29 | // Image operations | ||
| 30 | PullImage(ctx context.Context, image string) error | 30 | PullImage(ctx context.Context, image string) error | ||
| 31 | PullImageWithAuth(ctx context.Context, image, username, password string) error | 31 | PullImageWithAuth(ctx context.Context, image, username, password string) error | ||
| 32 | + | TagImage(ctx context.Context, sourceRef, targetRef string) error | |||
| 32 | RemoveImage(ctx context.Context, image string, force bool) error | 33 | RemoveImage(ctx context.Context, image string, force bool) error | ||
| 33 | ListImages(ctx context.Context) ([]string, error) | 34 | ListImages(ctx context.Context) ([]string, error) | ||
| 34 | 35 | ||||
M internal/usecase/container/events.go +19 -1
| @@ -77,9 +77,13 @@ func (h *ImagePushedHandler) CanHandle(eventType domain.EventType) bool { | |||
| 77 | 77 | func (h *ImagePushedHandler) findRoutesForImage(imageName string) []domain.Route { | |
| 78 | 78 | var routes []domain.Route | |
| 79 | 79 | ||
| 80 | + | registryDomain := h.configSvc.GetRegistryDomain() | |
| 81 | + | normalizedImage := normalizeRegistryImage(imageName, registryDomain) | |
| 82 | + | ||
| 80 | 83 | configuredRoutes := h.configSvc.GetRoutes(h.ctx) | |
| 81 | 84 | for _, route := range configuredRoutes { | |
| 82 | - | if strings.EqualFold(route.Image, imageName) { | |
| 85 | + | normalizedRouteImage := normalizeRegistryImage(route.Image, registryDomain) | |
| 86 | + | if strings.EqualFold(normalizedRouteImage, normalizedImage) { | |
| 83 | 87 | routes = append(routes, route) | |
| 84 | 88 | } | |
| 85 | 89 | } | |
| @@ -87,6 +91,20 @@ func (h *ImagePushedHandler) findRoutesForImage(imageName string) []domain.Route | |||
| 87 | 91 | return routes | |
| 88 | 92 | } | |
| 89 | 93 | ||
| 94 | + | func normalizeRegistryImage(imageName, registryDomain string) string { | |
| 95 | + | registryDomain = strings.TrimSuffix(registryDomain, "/") | |
| 96 | + | if registryDomain == "" { | |
| 97 | + | return imageName | |
| 98 | + | } | |
| 99 | + | ||
| 100 | + | prefix := registryDomain + "/" | |
| 101 | + | if strings.HasPrefix(imageName, prefix) { | |
| 102 | + | return strings.TrimPrefix(imageName, prefix) | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | return imageName | |
| 106 | + | } | |
| 107 | + | ||
| 90 | 108 | // ConfigReloadHandler handles config.reload events. | |
| 91 | 109 | type ConfigReloadHandler struct { | |
| 92 | 110 | containerSvc in.ContainerService | |
| @@ -77,9 +77,13 @@ func (h *ImagePushedHandler) CanHandle(eventType domain.EventType) bool { | |||||
| 77 | func (h *ImagePushedHandler) findRoutesForImage(imageName string) []domain.Route { | 77 | func (h *ImagePushedHandler) findRoutesForImage(imageName string) []domain.Route { | ||
| 78 | var routes []domain.Route | 78 | var routes []domain.Route | ||
| 79 | 79 | ||||
| 80 | + | registryDomain := h.configSvc.GetRegistryDomain() | |||
| 81 | + | normalizedImage := normalizeRegistryImage(imageName, registryDomain) | |||
| 82 | + | ||||
| 80 | configuredRoutes := h.configSvc.GetRoutes(h.ctx) | 83 | configuredRoutes := h.configSvc.GetRoutes(h.ctx) | ||
| 81 | for _, route := range configuredRoutes { | 84 | for _, route := range configuredRoutes { | ||
| 82 | - | if strings.EqualFold(route.Image, imageName) { | 85 | + | normalizedRouteImage := normalizeRegistryImage(route.Image, registryDomain) |
| 86 | + | if strings.EqualFold(normalizedRouteImage, normalizedImage) { | |||
| 83 | routes = append(routes, route) | 87 | routes = append(routes, route) | ||
| 84 | } | 88 | } | ||
| 85 | } | 89 | } | ||
| @@ -87,6 +91,20 @@ func (h *ImagePushedHandler) findRoutesForImage(imageName string) []domain.Route | |||||
| 87 | return routes | 91 | return routes | ||
| 88 | } | 92 | } | ||
| 89 | 93 | ||||
| 94 | + | func normalizeRegistryImage(imageName, registryDomain string) string { | |||
| 95 | + | registryDomain = strings.TrimSuffix(registryDomain, "/") | |||
| 96 | + | if registryDomain == "" { | |||
| 97 | + | return imageName | |||
| 98 | + | } | |||
| 99 | + | ||||
| 100 | + | prefix := registryDomain + "/" | |||
| 101 | + | if strings.HasPrefix(imageName, prefix) { | |||
| 102 | + | return strings.TrimPrefix(imageName, prefix) | |||
| 103 | + | } | |||
| 104 | + | ||||
| 105 | + | return imageName | |||
| 106 | + | } | |||
| 107 | + | ||||
| 90 | // ConfigReloadHandler handles config.reload events. | 108 | // ConfigReloadHandler handles config.reload events. | ||
| 91 | type ConfigReloadHandler struct { | 109 | type ConfigReloadHandler struct { | ||
| 92 | containerSvc in.ContainerService | 110 | containerSvc in.ContainerService | ||
M internal/usecase/container/events_test.go +36 -0
| @@ -36,6 +36,8 @@ func TestImagePushedHandler_Handle_DeploysMatchingRoutes(t *testing.T) { | |||
| 36 | 36 | ||
| 37 | 37 | handler := NewImagePushedHandler(testCtx(), containerSvc, configSvc) | |
| 38 | 38 | ||
| 39 | + | configSvc.EXPECT().GetRegistryDomain().Return("") | |
| 40 | + | ||
| 39 | 41 | // Configure routes that match the pushed image | |
| 40 | 42 | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | |
| 41 | 43 | {Domain: "app1.example.com", Image: "myapp:latest"}, | |
| @@ -72,6 +74,8 @@ func TestImagePushedHandler_Handle_NoMatchingRoutes(t *testing.T) { | |||
| 72 | 74 | ||
| 73 | 75 | handler := NewImagePushedHandler(testCtx(), containerSvc, configSvc) | |
| 74 | 76 | ||
| 77 | + | configSvc.EXPECT().GetRegistryDomain().Return("") | |
| 78 | + | ||
| 75 | 79 | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | |
| 76 | 80 | {Domain: "other.example.com", Image: "otherapp:latest"}, | |
| 77 | 81 | }) | |
| @@ -114,6 +118,8 @@ func TestImagePushedHandler_Handle_DefaultTag(t *testing.T) { | |||
| 114 | 118 | ||
| 115 | 119 | handler := NewImagePushedHandler(testCtx(), containerSvc, configSvc) | |
| 116 | 120 | ||
| 121 | + | configSvc.EXPECT().GetRegistryDomain().Return("") | |
| 122 | + | ||
| 117 | 123 | // Image name "myapp" with empty tag should become "myapp:latest" | |
| 118 | 124 | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | |
| 119 | 125 | {Domain: "app.example.com", Image: "myapp:latest"}, | |
| @@ -142,6 +148,8 @@ func TestImagePushedHandler_Handle_DeployError(t *testing.T) { | |||
| 142 | 148 | ||
| 143 | 149 | handler := NewImagePushedHandler(testCtx(), containerSvc, configSvc) | |
| 144 | 150 | ||
| 151 | + | configSvc.EXPECT().GetRegistryDomain().Return("") | |
| 152 | + | ||
| 145 | 153 | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | |
| 146 | 154 | {Domain: "app.example.com", Image: "myapp:latest"}, | |
| 147 | 155 | }) | |
| @@ -161,6 +169,34 @@ func TestImagePushedHandler_Handle_DeployError(t *testing.T) { | |||
| 161 | 169 | assert.NoError(t, err) | |
| 162 | 170 | } | |
| 163 | 171 | ||
| 172 | + | func TestImagePushedHandler_Handle_StripsRegistryDomain(t *testing.T) { | |
| 173 | + | containerSvc := inmocks.NewMockContainerService(t) | |
| 174 | + | configSvc := inmocks.NewMockConfigService(t) | |
| 175 | + | ||
| 176 | + | handler := NewImagePushedHandler(testCtx(), containerSvc, configSvc) | |
| 177 | + | ||
| 178 | + | configSvc.EXPECT().GetRegistryDomain().Return("registry.example.com") | |
| 179 | + | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | |
| 180 | + | {Domain: "app.example.com", Image: "registry.example.com/docker.io/library/nginx:latest"}, | |
| 181 | + | }) | |
| 182 | + | ||
| 183 | + | containerSvc.EXPECT().Deploy(mock.Anything, domain.Route{ | |
| 184 | + | Domain: "app.example.com", | |
| 185 | + | Image: "registry.example.com/docker.io/library/nginx:latest", | |
| 186 | + | }).Return(&domain.Container{ID: "container-1"}, nil) | |
| 187 | + | ||
| 188 | + | event := domain.Event{ | |
| 189 | + | ID: "event-123", | |
| 190 | + | Type: domain.EventImagePushed, | |
| 191 | + | ImageName: "docker.io/library/nginx", | |
| 192 | + | Tag: "latest", | |
| 193 | + | } | |
| 194 | + | ||
| 195 | + | err := handler.Handle(event) | |
| 196 | + | ||
| 197 | + | assert.NoError(t, err) | |
| 198 | + | } | |
| 199 | + | ||
| 164 | 200 | // ConfigReloadHandler tests | |
| 165 | 201 | ||
| 166 | 202 | func TestConfigReloadHandler_CanHandle(t *testing.T) { | |
| @@ -36,6 +36,8 @@ func TestImagePushedHandler_Handle_DeploysMatchingRoutes(t *testing.T) { | |||||
| 36 | 36 | ||||
| 37 | handler := NewImagePushedHandler(testCtx(), containerSvc, configSvc) | 37 | handler := NewImagePushedHandler(testCtx(), containerSvc, configSvc) | ||
| 38 | 38 | ||||
| 39 | + | configSvc.EXPECT().GetRegistryDomain().Return("") | |||
| 40 | + | ||||
| 39 | // Configure routes that match the pushed image | 41 | // Configure routes that match the pushed image | ||
| 40 | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | 42 | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | ||
| 41 | {Domain: "app1.example.com", Image: "myapp:latest"}, | 43 | {Domain: "app1.example.com", Image: "myapp:latest"}, | ||
| @@ -72,6 +74,8 @@ func TestImagePushedHandler_Handle_NoMatchingRoutes(t *testing.T) { | |||||
| 72 | 74 | ||||
| 73 | handler := NewImagePushedHandler(testCtx(), containerSvc, configSvc) | 75 | handler := NewImagePushedHandler(testCtx(), containerSvc, configSvc) | ||
| 74 | 76 | ||||
| 77 | + | configSvc.EXPECT().GetRegistryDomain().Return("") | |||
| 78 | + | ||||
| 75 | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | 79 | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | ||
| 76 | {Domain: "other.example.com", Image: "otherapp:latest"}, | 80 | {Domain: "other.example.com", Image: "otherapp:latest"}, | ||
| 77 | }) | 81 | }) | ||
| @@ -114,6 +118,8 @@ func TestImagePushedHandler_Handle_DefaultTag(t *testing.T) { | |||||
| 114 | 118 | ||||
| 115 | handler := NewImagePushedHandler(testCtx(), containerSvc, configSvc) | 119 | handler := NewImagePushedHandler(testCtx(), containerSvc, configSvc) | ||
| 116 | 120 | ||||
| 121 | + | configSvc.EXPECT().GetRegistryDomain().Return("") | |||
| 122 | + | ||||
| 117 | // Image name "myapp" with empty tag should become "myapp:latest" | 123 | // Image name "myapp" with empty tag should become "myapp:latest" | ||
| 118 | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | 124 | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | ||
| 119 | {Domain: "app.example.com", Image: "myapp:latest"}, | 125 | {Domain: "app.example.com", Image: "myapp:latest"}, | ||
| @@ -142,6 +148,8 @@ func TestImagePushedHandler_Handle_DeployError(t *testing.T) { | |||||
| 142 | 148 | ||||
| 143 | handler := NewImagePushedHandler(testCtx(), containerSvc, configSvc) | 149 | handler := NewImagePushedHandler(testCtx(), containerSvc, configSvc) | ||
| 144 | 150 | ||||
| 151 | + | configSvc.EXPECT().GetRegistryDomain().Return("") | |||
| 152 | + | ||||
| 145 | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | 153 | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | ||
| 146 | {Domain: "app.example.com", Image: "myapp:latest"}, | 154 | {Domain: "app.example.com", Image: "myapp:latest"}, | ||
| 147 | }) | 155 | }) | ||
| @@ -161,6 +169,34 @@ func TestImagePushedHandler_Handle_DeployError(t *testing.T) { | |||||
| 161 | assert.NoError(t, err) | 169 | assert.NoError(t, err) | ||
| 162 | } | 170 | } | ||
| 163 | 171 | ||||
| 172 | + | func TestImagePushedHandler_Handle_StripsRegistryDomain(t *testing.T) { | |||
| 173 | + | containerSvc := inmocks.NewMockContainerService(t) | |||
| 174 | + | configSvc := inmocks.NewMockConfigService(t) | |||
| 175 | + | ||||
| 176 | + | handler := NewImagePushedHandler(testCtx(), containerSvc, configSvc) | |||
| 177 | + | ||||
| 178 | + | configSvc.EXPECT().GetRegistryDomain().Return("registry.example.com") | |||
| 179 | + | configSvc.EXPECT().GetRoutes(mock.Anything).Return([]domain.Route{ | |||
| 180 | + | {Domain: "app.example.com", Image: "registry.example.com/docker.io/library/nginx:latest"}, | |||
| 181 | + | }) | |||
| 182 | + | ||||
| 183 | + | containerSvc.EXPECT().Deploy(mock.Anything, domain.Route{ | |||
| 184 | + | Domain: "app.example.com", | |||
| 185 | + | Image: "registry.example.com/docker.io/library/nginx:latest", | |||
| 186 | + | }).Return(&domain.Container{ID: "container-1"}, nil) | |||
| 187 | + | ||||
| 188 | + | event := domain.Event{ | |||
| 189 | + | ID: "event-123", | |||
| 190 | + | Type: domain.EventImagePushed, | |||
| 191 | + | ImageName: "docker.io/library/nginx", | |||
| 192 | + | Tag: "latest", | |||
| 193 | + | } | |||
| 194 | + | ||||
| 195 | + | err := handler.Handle(event) | |||
| 196 | + | ||||
| 197 | + | assert.NoError(t, err) | |||
| 198 | + | } | |||
| 199 | + | ||||
| 164 | // ConfigReloadHandler tests | 200 | // ConfigReloadHandler tests | ||
| 165 | 201 | ||||
| 166 | func TestConfigReloadHandler_CanHandle(t *testing.T) { | 202 | func TestConfigReloadHandler_CanHandle(t *testing.T) { | ||
M internal/usecase/container/service.go +57 -15
| @@ -88,8 +88,8 @@ func (s *Service) Deploy(ctx context.Context, route domain.Route) (*domain.Conta | |||
| 88 | 88 | log.WrapErr(err, "failed to cleanup orphaned containers") | |
| 89 | 89 | } | |
| 90 | 90 | ||
| 91 | - | // Build image reference and ensure it's available | |
| 92 | - | // ensureImage returns the actual image reference used (may be rewritten for internal pulls) | |
| 91 | + | // Build image reference and ensure it's available. | |
| 92 | + | // ensureImage returns the canonical reference; internal pulls may use the local registry. | |
| 93 | 93 | imageRef := s.buildImageRef(route.Image) | |
| 94 | 94 | actualImageRef, err := s.ensureImage(ctx, imageRef) | |
| 95 | 95 | if err != nil { | |
| @@ -469,33 +469,45 @@ func (s *Service) buildImageRef(image string) string { | |||
| 469 | 469 | } | |
| 470 | 470 | ||
| 471 | 471 | // ensureImage ensures the image is available locally, pulling if needed. | |
| 472 | - | // Returns the actual image reference to use for container operations (may differ | |
| 473 | - | // from input if localhost rewrite was applied for internal pulls). | |
| 472 | + | // Returns the canonical image reference to use for container operations. | |
| 474 | 473 | func (s *Service) ensureImage(ctx context.Context, imageRef string) (string, error) { | |
| 475 | 474 | ctx = zerowrap.CtxWithField(ctx, "image", imageRef) | |
| 476 | 475 | log := zerowrap.FromCtx(ctx) | |
| 477 | 476 | ||
| 478 | - | // Determine if this is an internal deploy and what reference to use | |
| 477 | + | // Determine if this is an internal deploy and what reference to use for pulls. | |
| 479 | 478 | pullRef := imageRef | |
| 480 | 479 | isInternal := domain.IsInternalDeploy(ctx) | |
| 481 | - | if isInternal && s.config.RegistryDomain != "" { | |
| 482 | - | pullRef = rewriteToRegistryDomain(imageRef, s.config.RegistryDomain) | |
| 480 | + | if isInternal { | |
| 481 | + | pullRef = rewriteToLocalRegistry(imageRef, s.config.RegistryDomain, s.config.RegistryPort) | |
| 482 | + | } | |
| 483 | + | if pullRef != imageRef { | |
| 483 | 484 | log.Info(). | |
| 484 | 485 | Str("original_ref", imageRef). | |
| 485 | - | Str("registry_ref", pullRef). | |
| 486 | - | Msg("internal deploy: using registry domain for pull") | |
| 486 | + | Str("pull_ref", pullRef). | |
| 487 | + | Msg("internal deploy: using localhost registry for pull") | |
| 487 | 488 | } | |
| 488 | 489 | ||
| 489 | - | // Check local images using the reference we'll actually use | |
| 490 | + | // Check local images using the canonical reference or pull reference. | |
| 490 | 491 | localImages, err := s.runtime.ListImages(ctx) | |
| 491 | 492 | if err != nil { | |
| 492 | 493 | log.WrapErr(err, "failed to list local images, will attempt pull") | |
| 493 | 494 | } else { | |
| 494 | - | normalizedRef := normalizeImageRef(pullRef) | |
| 495 | + | normalizedRef := normalizeImageRef(imageRef) | |
| 496 | + | normalizedPullRef := normalizeImageRef(pullRef) | |
| 495 | 497 | for _, img := range localImages { | |
| 496 | - | if normalizeImageRef(img) == normalizedRef { | |
| 498 | + | normalizedImage := normalizeImageRef(img) | |
| 499 | + | if normalizedImage == normalizedRef { | |
| 500 | + | log.Info().Msg("image found locally, skipping pull") | |
| 501 | + | return imageRef, nil | |
| 502 | + | } | |
| 503 | + | if normalizedImage == normalizedPullRef { | |
| 504 | + | if pullRef != imageRef { | |
| 505 | + | if err := s.runtime.TagImage(ctx, pullRef, imageRef); err != nil { | |
| 506 | + | return "", log.WrapErr(err, "failed to tag image from pull reference") | |
| 507 | + | } | |
| 508 | + | } | |
| 497 | 509 | log.Info().Msg("image found locally, skipping pull") | |
| 498 | - | return pullRef, nil | |
| 510 | + | return imageRef, nil | |
| 499 | 511 | } | |
| 500 | 512 | } | |
| 501 | 513 | } | |
| @@ -525,8 +537,14 @@ func (s *Service) ensureImage(ctx context.Context, imageRef string) (string, err | |||
| 525 | 537 | } | |
| 526 | 538 | } | |
| 527 | 539 | ||
| 540 | + | if pullRef != imageRef { | |
| 541 | + | if err := s.runtime.TagImage(ctx, pullRef, imageRef); err != nil { | |
| 542 | + | return "", log.WrapErr(err, "failed to tag image from pull reference") | |
| 543 | + | } | |
| 544 | + | } | |
| 545 | + | ||
| 528 | 546 | log.Info().Msg("image pulled successfully") | |
| 529 | - | return pullRef, nil | |
| 547 | + | return imageRef, nil | |
| 530 | 548 | } | |
| 531 | 549 | ||
| 532 | 550 | func (s *Service) loadEnvironment(ctx context.Context, domainName, imageRef string) ([]string, error) { | |
| @@ -726,7 +744,7 @@ func (s *Service) deployAttachedService(ctx context.Context, ownerDomain, servic | |||
| 726 | 744 | ||
| 727 | 745 | log.Info().Str(zerowrap.FieldService, serviceImage).Msg("deploying attached service") | |
| 728 | 746 | ||
| 729 | - | // Ensure image (get actual image ref, may be rewritten for internal pulls) | |
| 747 | + | // Ensure image (canonical ref is returned; internal pulls may use the local registry). | |
| 730 | 748 | imageRef := s.buildImageRef(serviceImage) | |
| 731 | 749 | actualImageRef, err := s.ensureImage(ctx, imageRef) | |
| 732 | 750 | if err != nil { | |
| @@ -993,3 +1011,27 @@ func rewriteToRegistryDomain(imageRef, registryDomain string) string { | |||
| 993 | 1011 | ||
| 994 | 1012 | return prefix + imageRef | |
| 995 | 1013 | } | |
| 1014 | + | ||
| 1015 | + | // rewriteToLocalRegistry rewrites an image reference to use the local registry address. | |
| 1016 | + | // e.g., "registry.example.com/myapp:latest" -> "localhost:5000/myapp:latest" | |
| 1017 | + | func rewriteToLocalRegistry(imageRef, registryDomain string, registryPort int) string { | |
| 1018 | + | if imageRef == "" { | |
| 1019 | + | return imageRef | |
| 1020 | + | } | |
| 1021 | + | ||
| 1022 | + | localRegistry := fmt.Sprintf("localhost:%d", registryPort) | |
| 1023 | + | localPrefix := localRegistry + "/" | |
| 1024 | + | if strings.HasPrefix(imageRef, localPrefix) { | |
| 1025 | + | return imageRef | |
| 1026 | + | } | |
| 1027 | + | ||
| 1028 | + | registryDomain = strings.TrimSuffix(registryDomain, "/") | |
| 1029 | + | if registryDomain != "" { | |
| 1030 | + | prefix := registryDomain + "/" | |
| 1031 | + | if strings.HasPrefix(imageRef, prefix) { | |
| 1032 | + | imageRef = strings.TrimPrefix(imageRef, prefix) | |
| 1033 | + | } | |
| 1034 | + | } | |
| 1035 | + | ||
| 1036 | + | return localPrefix + imageRef | |
| 1037 | + | } | |
| @@ -88,8 +88,8 @@ func (s *Service) Deploy(ctx context.Context, route domain.Route) (*domain.Conta | |||||
| 88 | log.WrapErr(err, "failed to cleanup orphaned containers") | 88 | log.WrapErr(err, "failed to cleanup orphaned containers") | ||
| 89 | } | 89 | } | ||
| 90 | 90 | ||||
| 91 | - | // Build image reference and ensure it's available | 91 | + | // Build image reference and ensure it's available. |
| 92 | - | // ensureImage returns the actual image reference used (may be rewritten for internal pulls) | 92 | + | // ensureImage returns the canonical reference; internal pulls may use the local registry. |
| 93 | imageRef := s.buildImageRef(route.Image) | 93 | imageRef := s.buildImageRef(route.Image) | ||
| 94 | actualImageRef, err := s.ensureImage(ctx, imageRef) | 94 | actualImageRef, err := s.ensureImage(ctx, imageRef) | ||
| 95 | if err != nil { | 95 | if err != nil { | ||
| @@ -469,33 +469,45 @@ func (s *Service) buildImageRef(image string) string { | |||||
| 469 | } | 469 | } | ||
| 470 | 470 | ||||
| 471 | // ensureImage ensures the image is available locally, pulling if needed. | 471 | // ensureImage ensures the image is available locally, pulling if needed. | ||
| 472 | - | // Returns the actual image reference to use for container operations (may differ | 472 | + | // Returns the canonical image reference to use for container operations. |
| 473 | - | // from input if localhost rewrite was applied for internal pulls). | |||
| 474 | func (s *Service) ensureImage(ctx context.Context, imageRef string) (string, error) { | 473 | func (s *Service) ensureImage(ctx context.Context, imageRef string) (string, error) { | ||
| 475 | ctx = zerowrap.CtxWithField(ctx, "image", imageRef) | 474 | ctx = zerowrap.CtxWithField(ctx, "image", imageRef) | ||
| 476 | log := zerowrap.FromCtx(ctx) | 475 | log := zerowrap.FromCtx(ctx) | ||
| 477 | 476 | ||||
| 478 | - | // Determine if this is an internal deploy and what reference to use | 477 | + | // Determine if this is an internal deploy and what reference to use for pulls. |
| 479 | pullRef := imageRef | 478 | pullRef := imageRef | ||
| 480 | isInternal := domain.IsInternalDeploy(ctx) | 479 | isInternal := domain.IsInternalDeploy(ctx) | ||
| 481 | - | if isInternal && s.config.RegistryDomain != "" { | 480 | + | if isInternal { |
| 482 | - | pullRef = rewriteToRegistryDomain(imageRef, s.config.RegistryDomain) | 481 | + | pullRef = rewriteToLocalRegistry(imageRef, s.config.RegistryDomain, s.config.RegistryPort) |
| 482 | + | } | |||
| 483 | + | if pullRef != imageRef { | |||
| 483 | log.Info(). | 484 | log.Info(). | ||
| 484 | Str("original_ref", imageRef). | 485 | Str("original_ref", imageRef). | ||
| 485 | - | Str("registry_ref", pullRef). | 486 | + | Str("pull_ref", pullRef). |
| 486 | - | Msg("internal deploy: using registry domain for pull") | 487 | + | Msg("internal deploy: using localhost registry for pull") |
| 487 | } | 488 | } | ||
| 488 | 489 | ||||
| 489 | - | // Check local images using the reference we'll actually use | 490 | + | // Check local images using the canonical reference or pull reference. |
| 490 | localImages, err := s.runtime.ListImages(ctx) | 491 | localImages, err := s.runtime.ListImages(ctx) | ||
| 491 | if err != nil { | 492 | if err != nil { | ||
| 492 | log.WrapErr(err, "failed to list local images, will attempt pull") | 493 | log.WrapErr(err, "failed to list local images, will attempt pull") | ||
| 493 | } else { | 494 | } else { | ||
| 494 | - | normalizedRef := normalizeImageRef(pullRef) | 495 | + | normalizedRef := normalizeImageRef(imageRef) |
| 496 | + | normalizedPullRef := normalizeImageRef(pullRef) | |||
| 495 | for _, img := range localImages { | 497 | for _, img := range localImages { | ||
| 496 | - | if normalizeImageRef(img) == normalizedRef { | 498 | + | normalizedImage := normalizeImageRef(img) |
| 499 | + | if normalizedImage == normalizedRef { | |||
| 500 | + | log.Info().Msg("image found locally, skipping pull") | |||
| 501 | + | return imageRef, nil | |||
| 502 | + | } | |||
| 503 | + | if normalizedImage == normalizedPullRef { | |||
| 504 | + | if pullRef != imageRef { | |||
| 505 | + | if err := s.runtime.TagImage(ctx, pullRef, imageRef); err != nil { | |||
| 506 | + | return "", log.WrapErr(err, "failed to tag image from pull reference") | |||
| 507 | + | } | |||
| 508 | + | } | |||
| 497 | log.Info().Msg("image found locally, skipping pull") | 509 | log.Info().Msg("image found locally, skipping pull") | ||
| 498 | - | return pullRef, nil | 510 | + | return imageRef, nil |
| 499 | } | 511 | } | ||
| 500 | } | 512 | } | ||
| 501 | } | 513 | } | ||
| @@ -525,8 +537,14 @@ func (s *Service) ensureImage(ctx context.Context, imageRef string) (string, err | |||||
| 525 | } | 537 | } | ||
| 526 | } | 538 | } | ||
| 527 | 539 | ||||
| 540 | + | if pullRef != imageRef { | |||
| 541 | + | if err := s.runtime.TagImage(ctx, pullRef, imageRef); err != nil { | |||
| 542 | + | return "", log.WrapErr(err, "failed to tag image from pull reference") | |||
| 543 | + | } | |||
| 544 | + | } | |||
| 545 | + | ||||
| 528 | log.Info().Msg("image pulled successfully") | 546 | log.Info().Msg("image pulled successfully") | ||
| 529 | - | return pullRef, nil | 547 | + | return imageRef, nil |
| 530 | } | 548 | } | ||
| 531 | 549 | ||||
| 532 | func (s *Service) loadEnvironment(ctx context.Context, domainName, imageRef string) ([]string, error) { | 550 | func (s *Service) loadEnvironment(ctx context.Context, domainName, imageRef string) ([]string, error) { | ||
| @@ -726,7 +744,7 @@ func (s *Service) deployAttachedService(ctx context.Context, ownerDomain, servic | |||||
| 726 | 744 | ||||
| 727 | log.Info().Str(zerowrap.FieldService, serviceImage).Msg("deploying attached service") | 745 | log.Info().Str(zerowrap.FieldService, serviceImage).Msg("deploying attached service") | ||
| 728 | 746 | ||||
| 729 | - | // Ensure image (get actual image ref, may be rewritten for internal pulls) | 747 | + | // Ensure image (canonical ref is returned; internal pulls may use the local registry). |
| 730 | imageRef := s.buildImageRef(serviceImage) | 748 | imageRef := s.buildImageRef(serviceImage) | ||
| 731 | actualImageRef, err := s.ensureImage(ctx, imageRef) | 749 | actualImageRef, err := s.ensureImage(ctx, imageRef) | ||
| 732 | if err != nil { | 750 | if err != nil { | ||
| @@ -993,3 +1011,27 @@ func rewriteToRegistryDomain(imageRef, registryDomain string) string { | |||||
| 993 | 1011 | ||||
| 994 | return prefix + imageRef | 1012 | return prefix + imageRef | ||
| 995 | } | 1013 | } | ||
| 1014 | + | ||||
| 1015 | + | // rewriteToLocalRegistry rewrites an image reference to use the local registry address. | |||
| 1016 | + | // e.g., "registry.example.com/myapp:latest" -> "localhost:5000/myapp:latest" | |||
| 1017 | + | func rewriteToLocalRegistry(imageRef, registryDomain string, registryPort int) string { | |||
| 1018 | + | if imageRef == "" { | |||
| 1019 | + | return imageRef | |||
| 1020 | + | } | |||
| 1021 | + | ||||
| 1022 | + | localRegistry := fmt.Sprintf("localhost:%d", registryPort) | |||
| 1023 | + | localPrefix := localRegistry + "/" | |||
| 1024 | + | if strings.HasPrefix(imageRef, localPrefix) { | |||
| 1025 | + | return imageRef | |||
| 1026 | + | } | |||
| 1027 | + | ||||
| 1028 | + | registryDomain = strings.TrimSuffix(registryDomain, "/") | |||
| 1029 | + | if registryDomain != "" { | |||
| 1030 | + | prefix := registryDomain + "/" | |||
| 1031 | + | if strings.HasPrefix(imageRef, prefix) { | |||
| 1032 | + | imageRef = strings.TrimPrefix(imageRef, prefix) | |||
| 1033 | + | } | |||
| 1034 | + | } | |||
| 1035 | + | ||||
| 1036 | + | return localPrefix + imageRef | |||
| 1037 | + | } | |||
M internal/usecase/container/service_test.go +46 -0
| @@ -650,3 +650,49 @@ func TestRewriteToRegistryDomain(t *testing.T) { | |||
| 650 | 650 | }) | |
| 651 | 651 | } | |
| 652 | 652 | } | |
| 653 | + | ||
| 654 | + | func TestRewriteToLocalRegistry(t *testing.T) { | |
| 655 | + | tests := []struct { | |
| 656 | + | name string | |
| 657 | + | imageRef string | |
| 658 | + | registryDomain string | |
| 659 | + | registryPort int | |
| 660 | + | wantRef string | |
| 661 | + | }{ | |
| 662 | + | { | |
| 663 | + | name: "rewrites registry domain prefix", | |
| 664 | + | imageRef: "registry.example.com/myapp:latest", | |
| 665 | + | registryDomain: "registry.example.com", | |
| 666 | + | registryPort: 5000, | |
| 667 | + | wantRef: "localhost:5000/myapp:latest", | |
| 668 | + | }, | |
| 669 | + | { | |
| 670 | + | name: "prefixes local registry when no domain", | |
| 671 | + | imageRef: "myapp:v1.0", | |
| 672 | + | registryDomain: "registry.example.com", | |
| 673 | + | registryPort: 5000, | |
| 674 | + | wantRef: "localhost:5000/myapp:v1.0", | |
| 675 | + | }, | |
| 676 | + | { | |
| 677 | + | name: "keeps existing localhost prefix", | |
| 678 | + | imageRef: "localhost:5000/myapp:latest", | |
| 679 | + | registryDomain: "registry.example.com", | |
| 680 | + | registryPort: 5000, | |
| 681 | + | wantRef: "localhost:5000/myapp:latest", | |
| 682 | + | }, | |
| 683 | + | { | |
| 684 | + | name: "prefixes external image path", | |
| 685 | + | imageRef: "docker.io/library/nginx:latest", | |
| 686 | + | registryDomain: "registry.example.com", | |
| 687 | + | registryPort: 5000, | |
| 688 | + | wantRef: "localhost:5000/docker.io/library/nginx:latest", | |
| 689 | + | }, | |
| 690 | + | } | |
| 691 | + | ||
| 692 | + | for _, tt := range tests { | |
| 693 | + | t.Run(tt.name, func(t *testing.T) { | |
| 694 | + | gotRef := rewriteToLocalRegistry(tt.imageRef, tt.registryDomain, tt.registryPort) | |
| 695 | + | assert.Equal(t, tt.wantRef, gotRef, "unexpected rewritten reference") | |
| 696 | + | }) | |
| 697 | + | } | |
| 698 | + | } | |
| @@ -650,3 +650,49 @@ func TestRewriteToRegistryDomain(t *testing.T) { | |||||
| 650 | }) | 650 | }) | ||
| 651 | } | 651 | } | ||
| 652 | } | 652 | } | ||
| 653 | + | ||||
| 654 | + | func TestRewriteToLocalRegistry(t *testing.T) { | |||
| 655 | + | tests := []struct { | |||
| 656 | + | name string | |||
| 657 | + | imageRef string | |||
| 658 | + | registryDomain string | |||
| 659 | + | registryPort int | |||
| 660 | + | wantRef string | |||
| 661 | + | }{ | |||
| 662 | + | { | |||
| 663 | + | name: "rewrites registry domain prefix", | |||
| 664 | + | imageRef: "registry.example.com/myapp:latest", | |||
| 665 | + | registryDomain: "registry.example.com", | |||
| 666 | + | registryPort: 5000, | |||
| 667 | + | wantRef: "localhost:5000/myapp:latest", | |||
| 668 | + | }, | |||
| 669 | + | { | |||
| 670 | + | name: "prefixes local registry when no domain", | |||
| 671 | + | imageRef: "myapp:v1.0", | |||
| 672 | + | registryDomain: "registry.example.com", | |||
| 673 | + | registryPort: 5000, | |||
| 674 | + | wantRef: "localhost:5000/myapp:v1.0", | |||
| 675 | + | }, | |||
| 676 | + | { | |||
| 677 | + | name: "keeps existing localhost prefix", | |||
| 678 | + | imageRef: "localhost:5000/myapp:latest", | |||
| 679 | + | registryDomain: "registry.example.com", | |||
| 680 | + | registryPort: 5000, | |||
| 681 | + | wantRef: "localhost:5000/myapp:latest", | |||
| 682 | + | }, | |||
| 683 | + | { | |||
| 684 | + | name: "prefixes external image path", | |||
| 685 | + | imageRef: "docker.io/library/nginx:latest", | |||
| 686 | + | registryDomain: "registry.example.com", | |||
| 687 | + | registryPort: 5000, | |||
| 688 | + | wantRef: "localhost:5000/docker.io/library/nginx:latest", | |||
| 689 | + | }, | |||
| 690 | + | } | |||
| 691 | + | ||||
| 692 | + | for _, tt := range tests { | |||
| 693 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 694 | + | gotRef := rewriteToLocalRegistry(tt.imageRef, tt.registryDomain, tt.registryPort) | |||
| 695 | + | assert.Equal(t, tt.wantRef, gotRef, "unexpected rewritten reference") | |||
| 696 | + | }) | |||
| 697 | + | } | |||
| 698 | + | } | |||