fix: honor TLS insecure settings in native registry push and refactor docker shell-outs
Replace docker CLI shell-outs (tag, inspect) with Docker SDK calls and introduce pushImageOps interface for testability. The native registry push now resolves insecure TLS from --insecure flag, env, and client config — matching the remote client behavior. - Add WithInsecureTLS option to registrypush.Pusher with safe transport fallback - Add pushImageOps interface + dockerImageOps implementation (Tag, Push, Exists, Build) - Replace dockerTagFn/dockerPushFn/dockerImageExistsFn function variables - Build method returns actionable error for non-Docker runtimes (Podman) - Generate mockery mock for pushImageOps, use in all tests Closes #107
- commit
- 8fce1c332815171e1487c0acaee89f9f5aa24304
- author
- brice · · 2026-03-14 17:14 UTC
- parent
- d176775
- signature
- unsigned
10 files · +551 -112
M .mockery.yaml +3 -0
| @@ -35,3 +35,6 @@ packages: | |||
| 35 | 35 | HTTPProber: | |
| 36 | 36 | SecretService: | |
| 37 | 37 | LogService: | |
| 38 | + | github.com/bnema/gordon/internal/adapters/in/cli: | |
| 39 | + | interfaces: | |
| 40 | + | pushImageOps: | |
| @@ -35,3 +35,6 @@ packages: | |||||
| 35 | HTTPProber: | 35 | HTTPProber: | ||
| 36 | SecretService: | 36 | SecretService: | ||
| 37 | LogService: | 37 | LogService: | ||
| 38 | + | github.com/bnema/gordon/internal/adapters/in/cli: | |||
| 39 | + | interfaces: | |||
| 40 | + | pushImageOps: | |||
M internal/adapters/in/cli/attachments_push.go +9 -6
| @@ -15,8 +15,6 @@ import ( | |||
| 15 | 15 | ||
| 16 | 16 | var ( | |
| 17 | 17 | resolveControlPlaneFn = resolveControlPlane | |
| 18 | - | tagAndPushFn = tagAndPush | |
| 19 | - | buildAndPushFn = buildAndPush | |
| 20 | 18 | determineVersionFn = determineVersion | |
| 21 | 19 | ) | |
| 22 | 20 | ||
| @@ -77,11 +75,16 @@ func runAttachmentsPush(ctx context.Context, imageArg, tag string, build bool, p | |||
| 77 | 75 | ||
| 78 | 76 | versionRef, latestRef := resolveImageRefs(registry, imageName, version) | |
| 79 | 77 | ||
| 78 | + | imageOps, err := newImageOpsFn() | |
| 79 | + | if err != nil { | |
| 80 | + | return err | |
| 81 | + | } | |
| 82 | + | ||
| 80 | 83 | if err := printAttachmentPushInfo(out, imageArg, targets, versionRef, latestRef, version); err != nil { | |
| 81 | 84 | return err | |
| 82 | 85 | } | |
| 83 | 86 | ||
| 84 | - | if err := performAttachmentPush(ctx, build, version, platform, dockerfile, buildArgs, registry, imageName, versionRef, latestRef); err != nil { | |
| 87 | + | if err := performAttachmentPush(ctx, imageOps, build, version, platform, dockerfile, buildArgs, registry, imageName, versionRef, latestRef); err != nil { | |
| 85 | 88 | return err | |
| 86 | 89 | } | |
| 87 | 90 | ||
| @@ -106,11 +109,11 @@ func printAttachmentPushInfo(out io.Writer, imageArg string, targets []string, v | |||
| 106 | 109 | return nil | |
| 107 | 110 | } | |
| 108 | 111 | ||
| 109 | - | func performAttachmentPush(ctx context.Context, build bool, version, platform, dockerfile string, buildArgs []string, registry, imageName, versionRef, latestRef string) error { | |
| 112 | + | func performAttachmentPush(ctx context.Context, ops pushImageOps, build bool, version, platform, dockerfile string, buildArgs []string, registry, imageName, versionRef, latestRef string) error { | |
| 110 | 113 | if build { | |
| 111 | - | return buildAndPushFn(ctx, version, platform, dockerfile, buildArgs, versionRef, latestRef) | |
| 114 | + | return buildAndPush(ctx, ops, version, platform, dockerfile, buildArgs, versionRef, latestRef) | |
| 112 | 115 | } | |
| 113 | - | return tagAndPushFn(ctx, registry, imageName, version, versionRef, latestRef) | |
| 116 | + | return tagAndPush(ctx, ops, registry, imageName, version, versionRef, latestRef) | |
| 114 | 117 | } | |
| 115 | 118 | ||
| 116 | 119 | func resolveAttachmentImage(ctx context.Context, cp ControlPlane, imageArg string) (registry, imageName string, targets []string, err error) { | |
| @@ -15,8 +15,6 @@ import ( | |||||
| 15 | 15 | ||||
| 16 | var ( | 16 | var ( | ||
| 17 | resolveControlPlaneFn = resolveControlPlane | 17 | resolveControlPlaneFn = resolveControlPlane | ||
| 18 | - | tagAndPushFn = tagAndPush | |||
| 19 | - | buildAndPushFn = buildAndPush | |||
| 20 | determineVersionFn = determineVersion | 18 | determineVersionFn = determineVersion | ||
| 21 | ) | 19 | ) | ||
| 22 | 20 | ||||
| @@ -77,11 +75,16 @@ func runAttachmentsPush(ctx context.Context, imageArg, tag string, build bool, p | |||||
| 77 | 75 | ||||
| 78 | versionRef, latestRef := resolveImageRefs(registry, imageName, version) | 76 | versionRef, latestRef := resolveImageRefs(registry, imageName, version) | ||
| 79 | 77 | ||||
| 78 | + | imageOps, err := newImageOpsFn() | |||
| 79 | + | if err != nil { | |||
| 80 | + | return err | |||
| 81 | + | } | |||
| 82 | + | ||||
| 80 | if err := printAttachmentPushInfo(out, imageArg, targets, versionRef, latestRef, version); err != nil { | 83 | if err := printAttachmentPushInfo(out, imageArg, targets, versionRef, latestRef, version); err != nil { | ||
| 81 | return err | 84 | return err | ||
| 82 | } | 85 | } | ||
| 83 | 86 | ||||
| 84 | - | if err := performAttachmentPush(ctx, build, version, platform, dockerfile, buildArgs, registry, imageName, versionRef, latestRef); err != nil { | 87 | + | if err := performAttachmentPush(ctx, imageOps, build, version, platform, dockerfile, buildArgs, registry, imageName, versionRef, latestRef); err != nil { |
| 85 | return err | 88 | return err | ||
| 86 | } | 89 | } | ||
| 87 | 90 | ||||
| @@ -106,11 +109,11 @@ func printAttachmentPushInfo(out io.Writer, imageArg string, targets []string, v | |||||
| 106 | return nil | 109 | return nil | ||
| 107 | } | 110 | } | ||
| 108 | 111 | ||||
| 109 | - | func performAttachmentPush(ctx context.Context, build bool, version, platform, dockerfile string, buildArgs []string, registry, imageName, versionRef, latestRef string) error { | 112 | + | func performAttachmentPush(ctx context.Context, ops pushImageOps, build bool, version, platform, dockerfile string, buildArgs []string, registry, imageName, versionRef, latestRef string) error { |
| 110 | if build { | 113 | if build { | ||
| 111 | - | return buildAndPushFn(ctx, version, platform, dockerfile, buildArgs, versionRef, latestRef) | 114 | + | return buildAndPush(ctx, ops, version, platform, dockerfile, buildArgs, versionRef, latestRef) |
| 112 | } | 115 | } | ||
| 113 | - | return tagAndPushFn(ctx, registry, imageName, version, versionRef, latestRef) | 116 | + | return tagAndPush(ctx, ops, registry, imageName, version, versionRef, latestRef) |
| 114 | } | 117 | } | ||
| 115 | 118 | ||||
| 116 | func resolveAttachmentImage(ctx context.Context, cp ControlPlane, imageArg string) (registry, imageName string, targets []string, err error) { | 119 | func resolveAttachmentImage(ctx context.Context, cp ControlPlane, imageArg string) (registry, imageName string, targets []string, err error) { | ||
M internal/adapters/in/cli/attachments_push_test.go +29 -13
| @@ -6,9 +6,11 @@ import ( | |||
| 6 | 6 | "testing" | |
| 7 | 7 | ||
| 8 | 8 | "github.com/bnema/gordon/internal/adapters/dto" | |
| 9 | + | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" | |
| 9 | 10 | "github.com/bnema/gordon/internal/adapters/in/cli/remote" | |
| 10 | 11 | "github.com/bnema/gordon/internal/domain" | |
| 11 | 12 | "github.com/stretchr/testify/assert" | |
| 13 | + | "github.com/stretchr/testify/mock" | |
| 12 | 14 | "github.com/stretchr/testify/require" | |
| 13 | 15 | ) | |
| 14 | 16 | ||
| @@ -212,12 +214,12 @@ func TestResolveAttachmentImage_NotConfigured(t *testing.T) { | |||
| 212 | 214 | ||
| 213 | 215 | func TestAttachmentPushCmd_NoDeploy(t *testing.T) { | |
| 214 | 216 | origResolveControlPlane := resolveControlPlaneFn | |
| 215 | - | origTagAndPush := tagAndPushFn | |
| 216 | 217 | origDetermineVersion := determineVersionFn | |
| 218 | + | origNewImageOps := newImageOpsFn | |
| 217 | 219 | t.Cleanup(func() { | |
| 218 | 220 | resolveControlPlaneFn = origResolveControlPlane | |
| 219 | - | tagAndPushFn = origTagAndPush | |
| 220 | 221 | determineVersionFn = origDetermineVersion | |
| 222 | + | newImageOpsFn = origNewImageOps | |
| 221 | 223 | }) | |
| 222 | 224 | ||
| 223 | 225 | cp := &attachmentPushTestControlPlane{ | |
| @@ -233,10 +235,15 @@ func TestAttachmentPushCmd_NoDeploy(t *testing.T) { | |||
| 233 | 235 | return &controlPlaneHandle{plane: cp}, nil | |
| 234 | 236 | } | |
| 235 | 237 | determineVersionFn = func(context.Context, string) string { return "v1.2.3" } | |
| 236 | - | called := false | |
| 237 | - | tagAndPushFn = func(context.Context, string, string, string, string, string) error { | |
| 238 | - | called = true | |
| 239 | - | return nil | |
| 238 | + | pushCalled := false | |
| 239 | + | newImageOpsFn = func() (pushImageOps, error) { | |
| 240 | + | m := climocks.NewMockpushImageOps(t) | |
| 241 | + | m.On("Tag", mock.Anything, mock.Anything, mock.Anything).Return(nil) | |
| 242 | + | m.On("Exists", mock.Anything, mock.Anything).Return(true, nil) | |
| 243 | + | m.On("Push", mock.Anything, mock.Anything).Run(func(mock.Arguments) { | |
| 244 | + | pushCalled = true | |
| 245 | + | }).Return(nil) | |
| 246 | + | return m, nil | |
| 240 | 247 | } | |
| 241 | 248 | ||
| 242 | 249 | cmd := newAttachmentsPushCmd() | |
| @@ -248,18 +255,18 @@ func TestAttachmentPushCmd_NoDeploy(t *testing.T) { | |||
| 248 | 255 | err := cmd.Execute() | |
| 249 | 256 | ||
| 250 | 257 | require.NoError(t, err) | |
| 251 | - | assert.True(t, called) | |
| 258 | + | assert.True(t, pushCalled) | |
| 252 | 259 | assert.Contains(t, out.String(), "Push complete") | |
| 253 | 260 | } | |
| 254 | 261 | ||
| 255 | 262 | func TestAttachmentPushCmd_TaggedImageInputBuildsValidRefs(t *testing.T) { | |
| 256 | 263 | origResolveControlPlane := resolveControlPlaneFn | |
| 257 | - | origTagAndPush := tagAndPushFn | |
| 258 | 264 | origDetermineVersion := determineVersionFn | |
| 265 | + | origNewImageOps := newImageOpsFn | |
| 259 | 266 | t.Cleanup(func() { | |
| 260 | 267 | resolveControlPlaneFn = origResolveControlPlane | |
| 261 | - | tagAndPushFn = origTagAndPush | |
| 262 | 268 | determineVersionFn = origDetermineVersion | |
| 269 | + | newImageOpsFn = origNewImageOps | |
| 263 | 270 | }) | |
| 264 | 271 | ||
| 265 | 272 | cp := &attachmentPushTestControlPlane{ | |
| @@ -278,10 +285,19 @@ func TestAttachmentPushCmd_TaggedImageInputBuildsValidRefs(t *testing.T) { | |||
| 278 | 285 | ||
| 279 | 286 | var gotVersionRef string | |
| 280 | 287 | var gotLatestRef string | |
| 281 | - | tagAndPushFn = func(_ context.Context, registry, imageName, version, versionRef, latestRef string) error { | |
| 282 | - | gotVersionRef = versionRef | |
| 283 | - | gotLatestRef = latestRef | |
| 284 | - | return nil | |
| 288 | + | newImageOpsFn = func() (pushImageOps, error) { | |
| 289 | + | m := climocks.NewMockpushImageOps(t) | |
| 290 | + | m.On("Tag", mock.Anything, mock.Anything, mock.Anything).Return(nil) | |
| 291 | + | m.On("Exists", mock.Anything, mock.Anything).Return(true, nil) | |
| 292 | + | m.On("Push", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { | |
| 293 | + | ref := args.Get(1).(string) | |
| 294 | + | if gotVersionRef == "" { | |
| 295 | + | gotVersionRef = ref | |
| 296 | + | } else if gotLatestRef == "" { | |
| 297 | + | gotLatestRef = ref | |
| 298 | + | } | |
| 299 | + | }).Return(nil) | |
| 300 | + | return m, nil | |
| 285 | 301 | } | |
| 286 | 302 | ||
| 287 | 303 | cmd := newAttachmentsPushCmd() | |
| @@ -6,9 +6,11 @@ import ( | |||||
| 6 | "testing" | 6 | "testing" | ||
| 7 | 7 | ||||
| 8 | "github.com/bnema/gordon/internal/adapters/dto" | 8 | "github.com/bnema/gordon/internal/adapters/dto" | ||
| 9 | + | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" | |||
| 9 | "github.com/bnema/gordon/internal/adapters/in/cli/remote" | 10 | "github.com/bnema/gordon/internal/adapters/in/cli/remote" | ||
| 10 | "github.com/bnema/gordon/internal/domain" | 11 | "github.com/bnema/gordon/internal/domain" | ||
| 11 | "github.com/stretchr/testify/assert" | 12 | "github.com/stretchr/testify/assert" | ||
| 13 | + | "github.com/stretchr/testify/mock" | |||
| 12 | "github.com/stretchr/testify/require" | 14 | "github.com/stretchr/testify/require" | ||
| 13 | ) | 15 | ) | ||
| 14 | 16 | ||||
| @@ -212,12 +214,12 @@ func TestResolveAttachmentImage_NotConfigured(t *testing.T) { | |||||
| 212 | 214 | ||||
| 213 | func TestAttachmentPushCmd_NoDeploy(t *testing.T) { | 215 | func TestAttachmentPushCmd_NoDeploy(t *testing.T) { | ||
| 214 | origResolveControlPlane := resolveControlPlaneFn | 216 | origResolveControlPlane := resolveControlPlaneFn | ||
| 215 | - | origTagAndPush := tagAndPushFn | |||
| 216 | origDetermineVersion := determineVersionFn | 217 | origDetermineVersion := determineVersionFn | ||
| 218 | + | origNewImageOps := newImageOpsFn | |||
| 217 | t.Cleanup(func() { | 219 | t.Cleanup(func() { | ||
| 218 | resolveControlPlaneFn = origResolveControlPlane | 220 | resolveControlPlaneFn = origResolveControlPlane | ||
| 219 | - | tagAndPushFn = origTagAndPush | |||
| 220 | determineVersionFn = origDetermineVersion | 221 | determineVersionFn = origDetermineVersion | ||
| 222 | + | newImageOpsFn = origNewImageOps | |||
| 221 | }) | 223 | }) | ||
| 222 | 224 | ||||
| 223 | cp := &attachmentPushTestControlPlane{ | 225 | cp := &attachmentPushTestControlPlane{ | ||
| @@ -233,10 +235,15 @@ func TestAttachmentPushCmd_NoDeploy(t *testing.T) { | |||||
| 233 | return &controlPlaneHandle{plane: cp}, nil | 235 | return &controlPlaneHandle{plane: cp}, nil | ||
| 234 | } | 236 | } | ||
| 235 | determineVersionFn = func(context.Context, string) string { return "v1.2.3" } | 237 | determineVersionFn = func(context.Context, string) string { return "v1.2.3" } | ||
| 236 | - | called := false | 238 | + | pushCalled := false |
| 237 | - | tagAndPushFn = func(context.Context, string, string, string, string, string) error { | 239 | + | newImageOpsFn = func() (pushImageOps, error) { |
| 238 | - | called = true | 240 | + | m := climocks.NewMockpushImageOps(t) |
| 239 | - | return nil | 241 | + | m.On("Tag", mock.Anything, mock.Anything, mock.Anything).Return(nil) |
| 242 | + | m.On("Exists", mock.Anything, mock.Anything).Return(true, nil) | |||
| 243 | + | m.On("Push", mock.Anything, mock.Anything).Run(func(mock.Arguments) { | |||
| 244 | + | pushCalled = true | |||
| 245 | + | }).Return(nil) | |||
| 246 | + | return m, nil | |||
| 240 | } | 247 | } | ||
| 241 | 248 | ||||
| 242 | cmd := newAttachmentsPushCmd() | 249 | cmd := newAttachmentsPushCmd() | ||
| @@ -248,18 +255,18 @@ func TestAttachmentPushCmd_NoDeploy(t *testing.T) { | |||||
| 248 | err := cmd.Execute() | 255 | err := cmd.Execute() | ||
| 249 | 256 | ||||
| 250 | require.NoError(t, err) | 257 | require.NoError(t, err) | ||
| 251 | - | assert.True(t, called) | 258 | + | assert.True(t, pushCalled) |
| 252 | assert.Contains(t, out.String(), "Push complete") | 259 | assert.Contains(t, out.String(), "Push complete") | ||
| 253 | } | 260 | } | ||
| 254 | 261 | ||||
| 255 | func TestAttachmentPushCmd_TaggedImageInputBuildsValidRefs(t *testing.T) { | 262 | func TestAttachmentPushCmd_TaggedImageInputBuildsValidRefs(t *testing.T) { | ||
| 256 | origResolveControlPlane := resolveControlPlaneFn | 263 | origResolveControlPlane := resolveControlPlaneFn | ||
| 257 | - | origTagAndPush := tagAndPushFn | |||
| 258 | origDetermineVersion := determineVersionFn | 264 | origDetermineVersion := determineVersionFn | ||
| 265 | + | origNewImageOps := newImageOpsFn | |||
| 259 | t.Cleanup(func() { | 266 | t.Cleanup(func() { | ||
| 260 | resolveControlPlaneFn = origResolveControlPlane | 267 | resolveControlPlaneFn = origResolveControlPlane | ||
| 261 | - | tagAndPushFn = origTagAndPush | |||
| 262 | determineVersionFn = origDetermineVersion | 268 | determineVersionFn = origDetermineVersion | ||
| 269 | + | newImageOpsFn = origNewImageOps | |||
| 263 | }) | 270 | }) | ||
| 264 | 271 | ||||
| 265 | cp := &attachmentPushTestControlPlane{ | 272 | cp := &attachmentPushTestControlPlane{ | ||
| @@ -278,10 +285,19 @@ func TestAttachmentPushCmd_TaggedImageInputBuildsValidRefs(t *testing.T) { | |||||
| 278 | 285 | ||||
| 279 | var gotVersionRef string | 286 | var gotVersionRef string | ||
| 280 | var gotLatestRef string | 287 | var gotLatestRef string | ||
| 281 | - | tagAndPushFn = func(_ context.Context, registry, imageName, version, versionRef, latestRef string) error { | 288 | + | newImageOpsFn = func() (pushImageOps, error) { |
| 282 | - | gotVersionRef = versionRef | 289 | + | m := climocks.NewMockpushImageOps(t) |
| 283 | - | gotLatestRef = latestRef | 290 | + | m.On("Tag", mock.Anything, mock.Anything, mock.Anything).Return(nil) |
| 284 | - | return nil | 291 | + | m.On("Exists", mock.Anything, mock.Anything).Return(true, nil) |
| 292 | + | m.On("Push", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { | |||
| 293 | + | ref := args.Get(1).(string) | |||
| 294 | + | if gotVersionRef == "" { | |||
| 295 | + | gotVersionRef = ref | |||
| 296 | + | } else if gotLatestRef == "" { | |||
| 297 | + | gotLatestRef = ref | |||
| 298 | + | } | |||
| 299 | + | }).Return(nil) | |||
| 300 | + | return m, nil | |||
| 285 | } | 301 | } | ||
| 286 | 302 | ||||
| 287 | cmd := newAttachmentsPushCmd() | 303 | cmd := newAttachmentsPushCmd() | ||
A internal/adapters/in/cli/mocks/mock_push_image_ops.go +281 -0
| @@ -0,0 +1,281 @@ | |||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |
| 2 | + | // github.com/vektra/mockery | |
| 3 | + | // template: testify | |
| 4 | + | ||
| 5 | + | package mocks | |
| 6 | + | ||
| 7 | + | import ( | |
| 8 | + | "context" | |
| 9 | + | ||
| 10 | + | mock "github.com/stretchr/testify/mock" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | // NewMockpushImageOps creates a new instance of MockpushImageOps. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 14 | + | // The first argument is typically a *testing.T value. | |
| 15 | + | func NewMockpushImageOps(t interface { | |
| 16 | + | mock.TestingT | |
| 17 | + | Cleanup(func()) | |
| 18 | + | }) *MockpushImageOps { | |
| 19 | + | mock := &MockpushImageOps{} | |
| 20 | + | mock.Mock.Test(t) | |
| 21 | + | ||
| 22 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |
| 23 | + | ||
| 24 | + | return mock | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | // MockpushImageOps is an autogenerated mock type for the pushImageOps type | |
| 28 | + | type MockpushImageOps struct { | |
| 29 | + | mock.Mock | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | type MockpushImageOps_Expecter struct { | |
| 33 | + | mock *mock.Mock | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | func (_m *MockpushImageOps) EXPECT() *MockpushImageOps_Expecter { | |
| 37 | + | return &MockpushImageOps_Expecter{mock: &_m.Mock} | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | // Build provides a mock function for the type MockpushImageOps | |
| 41 | + | func (_mock *MockpushImageOps) Build(ctx context.Context, args []string) error { | |
| 42 | + | ret := _mock.Called(ctx, args) | |
| 43 | + | ||
| 44 | + | if len(ret) == 0 { | |
| 45 | + | panic("no return value specified for Build") | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | var r0 error | |
| 49 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, []string) error); ok { | |
| 50 | + | r0 = returnFunc(ctx, args) | |
| 51 | + | } else { | |
| 52 | + | r0 = ret.Error(0) | |
| 53 | + | } | |
| 54 | + | return r0 | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | // MockpushImageOps_Build_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Build' | |
| 58 | + | type MockpushImageOps_Build_Call struct { | |
| 59 | + | *mock.Call | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | // Build is a helper method to define mock.On call | |
| 63 | + | // - ctx context.Context | |
| 64 | + | // - args []string | |
| 65 | + | func (_e *MockpushImageOps_Expecter) Build(ctx interface{}, args interface{}) *MockpushImageOps_Build_Call { | |
| 66 | + | return &MockpushImageOps_Build_Call{Call: _e.mock.On("Build", ctx, args)} | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | func (_c *MockpushImageOps_Build_Call) Run(run func(ctx context.Context, args []string)) *MockpushImageOps_Build_Call { | |
| 70 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 71 | + | var arg0 context.Context | |
| 72 | + | if args[0] != nil { | |
| 73 | + | arg0 = args[0].(context.Context) | |
| 74 | + | } | |
| 75 | + | var arg1 []string | |
| 76 | + | if args[1] != nil { | |
| 77 | + | arg1 = args[1].([]string) | |
| 78 | + | } | |
| 79 | + | run( | |
| 80 | + | arg0, | |
| 81 | + | arg1, | |
| 82 | + | ) | |
| 83 | + | }) | |
| 84 | + | return _c | |
| 85 | + | } | |
| 86 | + | ||
| 87 | + | func (_c *MockpushImageOps_Build_Call) Return(err error) *MockpushImageOps_Build_Call { | |
| 88 | + | _c.Call.Return(err) | |
| 89 | + | return _c | |
| 90 | + | } | |
| 91 | + | ||
| 92 | + | func (_c *MockpushImageOps_Build_Call) RunAndReturn(run func(ctx context.Context, args []string) error) *MockpushImageOps_Build_Call { | |
| 93 | + | _c.Call.Return(run) | |
| 94 | + | return _c | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | // Exists provides a mock function for the type MockpushImageOps | |
| 98 | + | func (_mock *MockpushImageOps) Exists(ctx context.Context, imageRef string) (bool, error) { | |
| 99 | + | ret := _mock.Called(ctx, imageRef) | |
| 100 | + | ||
| 101 | + | if len(ret) == 0 { | |
| 102 | + | panic("no return value specified for Exists") | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | var r0 bool | |
| 106 | + | var r1 error | |
| 107 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { | |
| 108 | + | return returnFunc(ctx, imageRef) | |
| 109 | + | } | |
| 110 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) bool); ok { | |
| 111 | + | r0 = returnFunc(ctx, imageRef) | |
| 112 | + | } else { | |
| 113 | + | r0 = ret.Get(0).(bool) | |
| 114 | + | } | |
| 115 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { | |
| 116 | + | r1 = returnFunc(ctx, imageRef) | |
| 117 | + | } else { | |
| 118 | + | r1 = ret.Error(1) | |
| 119 | + | } | |
| 120 | + | return r0, r1 | |
| 121 | + | } | |
| 122 | + | ||
| 123 | + | // MockpushImageOps_Exists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Exists' | |
| 124 | + | type MockpushImageOps_Exists_Call struct { | |
| 125 | + | *mock.Call | |
| 126 | + | } | |
| 127 | + | ||
| 128 | + | // Exists is a helper method to define mock.On call | |
| 129 | + | // - ctx context.Context | |
| 130 | + | // - imageRef string | |
| 131 | + | func (_e *MockpushImageOps_Expecter) Exists(ctx interface{}, imageRef interface{}) *MockpushImageOps_Exists_Call { | |
| 132 | + | return &MockpushImageOps_Exists_Call{Call: _e.mock.On("Exists", ctx, imageRef)} | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | func (_c *MockpushImageOps_Exists_Call) Run(run func(ctx context.Context, imageRef string)) *MockpushImageOps_Exists_Call { | |
| 136 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 137 | + | var arg0 context.Context | |
| 138 | + | if args[0] != nil { | |
| 139 | + | arg0 = args[0].(context.Context) | |
| 140 | + | } | |
| 141 | + | var arg1 string | |
| 142 | + | if args[1] != nil { | |
| 143 | + | arg1 = args[1].(string) | |
| 144 | + | } | |
| 145 | + | run( | |
| 146 | + | arg0, | |
| 147 | + | arg1, | |
| 148 | + | ) | |
| 149 | + | }) | |
| 150 | + | return _c | |
| 151 | + | } | |
| 152 | + | ||
| 153 | + | func (_c *MockpushImageOps_Exists_Call) Return(b bool, err error) *MockpushImageOps_Exists_Call { | |
| 154 | + | _c.Call.Return(b, err) | |
| 155 | + | return _c | |
| 156 | + | } | |
| 157 | + | ||
| 158 | + | func (_c *MockpushImageOps_Exists_Call) RunAndReturn(run func(ctx context.Context, imageRef string) (bool, error)) *MockpushImageOps_Exists_Call { | |
| 159 | + | _c.Call.Return(run) | |
| 160 | + | return _c | |
| 161 | + | } | |
| 162 | + | ||
| 163 | + | // Push provides a mock function for the type MockpushImageOps | |
| 164 | + | func (_mock *MockpushImageOps) Push(ctx context.Context, ref string) error { | |
| 165 | + | ret := _mock.Called(ctx, ref) | |
| 166 | + | ||
| 167 | + | if len(ret) == 0 { | |
| 168 | + | panic("no return value specified for Push") | |
| 169 | + | } | |
| 170 | + | ||
| 171 | + | var r0 error | |
| 172 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok { | |
| 173 | + | r0 = returnFunc(ctx, ref) | |
| 174 | + | } else { | |
| 175 | + | r0 = ret.Error(0) | |
| 176 | + | } | |
| 177 | + | return r0 | |
| 178 | + | } | |
| 179 | + | ||
| 180 | + | // MockpushImageOps_Push_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Push' | |
| 181 | + | type MockpushImageOps_Push_Call struct { | |
| 182 | + | *mock.Call | |
| 183 | + | } | |
| 184 | + | ||
| 185 | + | // Push is a helper method to define mock.On call | |
| 186 | + | // - ctx context.Context | |
| 187 | + | // - ref string | |
| 188 | + | func (_e *MockpushImageOps_Expecter) Push(ctx interface{}, ref interface{}) *MockpushImageOps_Push_Call { | |
| 189 | + | return &MockpushImageOps_Push_Call{Call: _e.mock.On("Push", ctx, ref)} | |
| 190 | + | } | |
| 191 | + | ||
| 192 | + | func (_c *MockpushImageOps_Push_Call) Run(run func(ctx context.Context, ref string)) *MockpushImageOps_Push_Call { | |
| 193 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 194 | + | var arg0 context.Context | |
| 195 | + | if args[0] != nil { | |
| 196 | + | arg0 = args[0].(context.Context) | |
| 197 | + | } | |
| 198 | + | var arg1 string | |
| 199 | + | if args[1] != nil { | |
| 200 | + | arg1 = args[1].(string) | |
| 201 | + | } | |
| 202 | + | run( | |
| 203 | + | arg0, | |
| 204 | + | arg1, | |
| 205 | + | ) | |
| 206 | + | }) | |
| 207 | + | return _c | |
| 208 | + | } | |
| 209 | + | ||
| 210 | + | func (_c *MockpushImageOps_Push_Call) Return(err error) *MockpushImageOps_Push_Call { | |
| 211 | + | _c.Call.Return(err) | |
| 212 | + | return _c | |
| 213 | + | } | |
| 214 | + | ||
| 215 | + | func (_c *MockpushImageOps_Push_Call) RunAndReturn(run func(ctx context.Context, ref string) error) *MockpushImageOps_Push_Call { | |
| 216 | + | _c.Call.Return(run) | |
| 217 | + | return _c | |
| 218 | + | } | |
| 219 | + | ||
| 220 | + | // Tag provides a mock function for the type MockpushImageOps | |
| 221 | + | func (_mock *MockpushImageOps) Tag(ctx context.Context, sourceRef string, targetRef string) error { | |
| 222 | + | ret := _mock.Called(ctx, sourceRef, targetRef) | |
| 223 | + | ||
| 224 | + | if len(ret) == 0 { | |
| 225 | + | panic("no return value specified for Tag") | |
| 226 | + | } | |
| 227 | + | ||
| 228 | + | var r0 error | |
| 229 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) error); ok { | |
| 230 | + | r0 = returnFunc(ctx, sourceRef, targetRef) | |
| 231 | + | } else { | |
| 232 | + | r0 = ret.Error(0) | |
| 233 | + | } | |
| 234 | + | return r0 | |
| 235 | + | } | |
| 236 | + | ||
| 237 | + | // MockpushImageOps_Tag_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Tag' | |
| 238 | + | type MockpushImageOps_Tag_Call struct { | |
| 239 | + | *mock.Call | |
| 240 | + | } | |
| 241 | + | ||
| 242 | + | // Tag is a helper method to define mock.On call | |
| 243 | + | // - ctx context.Context | |
| 244 | + | // - sourceRef string | |
| 245 | + | // - targetRef string | |
| 246 | + | func (_e *MockpushImageOps_Expecter) Tag(ctx interface{}, sourceRef interface{}, targetRef interface{}) *MockpushImageOps_Tag_Call { | |
| 247 | + | return &MockpushImageOps_Tag_Call{Call: _e.mock.On("Tag", ctx, sourceRef, targetRef)} | |
| 248 | + | } | |
| 249 | + | ||
| 250 | + | func (_c *MockpushImageOps_Tag_Call) Run(run func(ctx context.Context, sourceRef string, targetRef string)) *MockpushImageOps_Tag_Call { | |
| 251 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 252 | + | var arg0 context.Context | |
| 253 | + | if args[0] != nil { | |
| 254 | + | arg0 = args[0].(context.Context) | |
| 255 | + | } | |
| 256 | + | var arg1 string | |
| 257 | + | if args[1] != nil { | |
| 258 | + | arg1 = args[1].(string) | |
| 259 | + | } | |
| 260 | + | var arg2 string | |
| 261 | + | if args[2] != nil { | |
| 262 | + | arg2 = args[2].(string) | |
| 263 | + | } | |
| 264 | + | run( | |
| 265 | + | arg0, | |
| 266 | + | arg1, | |
| 267 | + | arg2, | |
| 268 | + | ) | |
| 269 | + | }) | |
| 270 | + | return _c | |
| 271 | + | } | |
| 272 | + | ||
| 273 | + | func (_c *MockpushImageOps_Tag_Call) Return(err error) *MockpushImageOps_Tag_Call { | |
| 274 | + | _c.Call.Return(err) | |
| 275 | + | return _c | |
| 276 | + | } | |
| 277 | + | ||
| 278 | + | func (_c *MockpushImageOps_Tag_Call) RunAndReturn(run func(ctx context.Context, sourceRef string, targetRef string) error) *MockpushImageOps_Tag_Call { | |
| 279 | + | _c.Call.Return(run) | |
| 280 | + | return _c | |
| 281 | + | } | |
| @@ -0,0 +1,281 @@ | |||||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |||
| 2 | + | // github.com/vektra/mockery | |||
| 3 | + | // template: testify | |||
| 4 | + | ||||
| 5 | + | package mocks | |||
| 6 | + | ||||
| 7 | + | import ( | |||
| 8 | + | "context" | |||
| 9 | + | ||||
| 10 | + | mock "github.com/stretchr/testify/mock" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // NewMockpushImageOps creates a new instance of MockpushImageOps. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 14 | + | // The first argument is typically a *testing.T value. | |||
| 15 | + | func NewMockpushImageOps(t interface { | |||
| 16 | + | mock.TestingT | |||
| 17 | + | Cleanup(func()) | |||
| 18 | + | }) *MockpushImageOps { | |||
| 19 | + | mock := &MockpushImageOps{} | |||
| 20 | + | mock.Mock.Test(t) | |||
| 21 | + | ||||
| 22 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |||
| 23 | + | ||||
| 24 | + | return mock | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | // MockpushImageOps is an autogenerated mock type for the pushImageOps type | |||
| 28 | + | type MockpushImageOps struct { | |||
| 29 | + | mock.Mock | |||
| 30 | + | } | |||
| 31 | + | ||||
| 32 | + | type MockpushImageOps_Expecter struct { | |||
| 33 | + | mock *mock.Mock | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | func (_m *MockpushImageOps) EXPECT() *MockpushImageOps_Expecter { | |||
| 37 | + | return &MockpushImageOps_Expecter{mock: &_m.Mock} | |||
| 38 | + | } | |||
| 39 | + | ||||
| 40 | + | // Build provides a mock function for the type MockpushImageOps | |||
| 41 | + | func (_mock *MockpushImageOps) Build(ctx context.Context, args []string) error { | |||
| 42 | + | ret := _mock.Called(ctx, args) | |||
| 43 | + | ||||
| 44 | + | if len(ret) == 0 { | |||
| 45 | + | panic("no return value specified for Build") | |||
| 46 | + | } | |||
| 47 | + | ||||
| 48 | + | var r0 error | |||
| 49 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, []string) error); ok { | |||
| 50 | + | r0 = returnFunc(ctx, args) | |||
| 51 | + | } else { | |||
| 52 | + | r0 = ret.Error(0) | |||
| 53 | + | } | |||
| 54 | + | return r0 | |||
| 55 | + | } | |||
| 56 | + | ||||
| 57 | + | // MockpushImageOps_Build_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Build' | |||
| 58 | + | type MockpushImageOps_Build_Call struct { | |||
| 59 | + | *mock.Call | |||
| 60 | + | } | |||
| 61 | + | ||||
| 62 | + | // Build is a helper method to define mock.On call | |||
| 63 | + | // - ctx context.Context | |||
| 64 | + | // - args []string | |||
| 65 | + | func (_e *MockpushImageOps_Expecter) Build(ctx interface{}, args interface{}) *MockpushImageOps_Build_Call { | |||
| 66 | + | return &MockpushImageOps_Build_Call{Call: _e.mock.On("Build", ctx, args)} | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | func (_c *MockpushImageOps_Build_Call) Run(run func(ctx context.Context, args []string)) *MockpushImageOps_Build_Call { | |||
| 70 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 71 | + | var arg0 context.Context | |||
| 72 | + | if args[0] != nil { | |||
| 73 | + | arg0 = args[0].(context.Context) | |||
| 74 | + | } | |||
| 75 | + | var arg1 []string | |||
| 76 | + | if args[1] != nil { | |||
| 77 | + | arg1 = args[1].([]string) | |||
| 78 | + | } | |||
| 79 | + | run( | |||
| 80 | + | arg0, | |||
| 81 | + | arg1, | |||
| 82 | + | ) | |||
| 83 | + | }) | |||
| 84 | + | return _c | |||
| 85 | + | } | |||
| 86 | + | ||||
| 87 | + | func (_c *MockpushImageOps_Build_Call) Return(err error) *MockpushImageOps_Build_Call { | |||
| 88 | + | _c.Call.Return(err) | |||
| 89 | + | return _c | |||
| 90 | + | } | |||
| 91 | + | ||||
| 92 | + | func (_c *MockpushImageOps_Build_Call) RunAndReturn(run func(ctx context.Context, args []string) error) *MockpushImageOps_Build_Call { | |||
| 93 | + | _c.Call.Return(run) | |||
| 94 | + | return _c | |||
| 95 | + | } | |||
| 96 | + | ||||
| 97 | + | // Exists provides a mock function for the type MockpushImageOps | |||
| 98 | + | func (_mock *MockpushImageOps) Exists(ctx context.Context, imageRef string) (bool, error) { | |||
| 99 | + | ret := _mock.Called(ctx, imageRef) | |||
| 100 | + | ||||
| 101 | + | if len(ret) == 0 { | |||
| 102 | + | panic("no return value specified for Exists") | |||
| 103 | + | } | |||
| 104 | + | ||||
| 105 | + | var r0 bool | |||
| 106 | + | var r1 error | |||
| 107 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { | |||
| 108 | + | return returnFunc(ctx, imageRef) | |||
| 109 | + | } | |||
| 110 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) bool); ok { | |||
| 111 | + | r0 = returnFunc(ctx, imageRef) | |||
| 112 | + | } else { | |||
| 113 | + | r0 = ret.Get(0).(bool) | |||
| 114 | + | } | |||
| 115 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { | |||
| 116 | + | r1 = returnFunc(ctx, imageRef) | |||
| 117 | + | } else { | |||
| 118 | + | r1 = ret.Error(1) | |||
| 119 | + | } | |||
| 120 | + | return r0, r1 | |||
| 121 | + | } | |||
| 122 | + | ||||
| 123 | + | // MockpushImageOps_Exists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Exists' | |||
| 124 | + | type MockpushImageOps_Exists_Call struct { | |||
| 125 | + | *mock.Call | |||
| 126 | + | } | |||
| 127 | + | ||||
| 128 | + | // Exists is a helper method to define mock.On call | |||
| 129 | + | // - ctx context.Context | |||
| 130 | + | // - imageRef string | |||
| 131 | + | func (_e *MockpushImageOps_Expecter) Exists(ctx interface{}, imageRef interface{}) *MockpushImageOps_Exists_Call { | |||
| 132 | + | return &MockpushImageOps_Exists_Call{Call: _e.mock.On("Exists", ctx, imageRef)} | |||
| 133 | + | } | |||
| 134 | + | ||||
| 135 | + | func (_c *MockpushImageOps_Exists_Call) Run(run func(ctx context.Context, imageRef string)) *MockpushImageOps_Exists_Call { | |||
| 136 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 137 | + | var arg0 context.Context | |||
| 138 | + | if args[0] != nil { | |||
| 139 | + | arg0 = args[0].(context.Context) | |||
| 140 | + | } | |||
| 141 | + | var arg1 string | |||
| 142 | + | if args[1] != nil { | |||
| 143 | + | arg1 = args[1].(string) | |||
| 144 | + | } | |||
| 145 | + | run( | |||
| 146 | + | arg0, | |||
| 147 | + | arg1, | |||
| 148 | + | ) | |||
| 149 | + | }) | |||
| 150 | + | return _c | |||
| 151 | + | } | |||
| 152 | + | ||||
| 153 | + | func (_c *MockpushImageOps_Exists_Call) Return(b bool, err error) *MockpushImageOps_Exists_Call { | |||
| 154 | + | _c.Call.Return(b, err) | |||
| 155 | + | return _c | |||
| 156 | + | } | |||
| 157 | + | ||||
| 158 | + | func (_c *MockpushImageOps_Exists_Call) RunAndReturn(run func(ctx context.Context, imageRef string) (bool, error)) *MockpushImageOps_Exists_Call { | |||
| 159 | + | _c.Call.Return(run) | |||
| 160 | + | return _c | |||
| 161 | + | } | |||
| 162 | + | ||||
| 163 | + | // Push provides a mock function for the type MockpushImageOps | |||
| 164 | + | func (_mock *MockpushImageOps) Push(ctx context.Context, ref string) error { | |||
| 165 | + | ret := _mock.Called(ctx, ref) | |||
| 166 | + | ||||
| 167 | + | if len(ret) == 0 { | |||
| 168 | + | panic("no return value specified for Push") | |||
| 169 | + | } | |||
| 170 | + | ||||
| 171 | + | var r0 error | |||
| 172 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok { | |||
| 173 | + | r0 = returnFunc(ctx, ref) | |||
| 174 | + | } else { | |||
| 175 | + | r0 = ret.Error(0) | |||
| 176 | + | } | |||
| 177 | + | return r0 | |||
| 178 | + | } | |||
| 179 | + | ||||
| 180 | + | // MockpushImageOps_Push_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Push' | |||
| 181 | + | type MockpushImageOps_Push_Call struct { | |||
| 182 | + | *mock.Call | |||
| 183 | + | } | |||
| 184 | + | ||||
| 185 | + | // Push is a helper method to define mock.On call | |||
| 186 | + | // - ctx context.Context | |||
| 187 | + | // - ref string | |||
| 188 | + | func (_e *MockpushImageOps_Expecter) Push(ctx interface{}, ref interface{}) *MockpushImageOps_Push_Call { | |||
| 189 | + | return &MockpushImageOps_Push_Call{Call: _e.mock.On("Push", ctx, ref)} | |||
| 190 | + | } | |||
| 191 | + | ||||
| 192 | + | func (_c *MockpushImageOps_Push_Call) Run(run func(ctx context.Context, ref string)) *MockpushImageOps_Push_Call { | |||
| 193 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 194 | + | var arg0 context.Context | |||
| 195 | + | if args[0] != nil { | |||
| 196 | + | arg0 = args[0].(context.Context) | |||
| 197 | + | } | |||
| 198 | + | var arg1 string | |||
| 199 | + | if args[1] != nil { | |||
| 200 | + | arg1 = args[1].(string) | |||
| 201 | + | } | |||
| 202 | + | run( | |||
| 203 | + | arg0, | |||
| 204 | + | arg1, | |||
| 205 | + | ) | |||
| 206 | + | }) | |||
| 207 | + | return _c | |||
| 208 | + | } | |||
| 209 | + | ||||
| 210 | + | func (_c *MockpushImageOps_Push_Call) Return(err error) *MockpushImageOps_Push_Call { | |||
| 211 | + | _c.Call.Return(err) | |||
| 212 | + | return _c | |||
| 213 | + | } | |||
| 214 | + | ||||
| 215 | + | func (_c *MockpushImageOps_Push_Call) RunAndReturn(run func(ctx context.Context, ref string) error) *MockpushImageOps_Push_Call { | |||
| 216 | + | _c.Call.Return(run) | |||
| 217 | + | return _c | |||
| 218 | + | } | |||
| 219 | + | ||||
| 220 | + | // Tag provides a mock function for the type MockpushImageOps | |||
| 221 | + | func (_mock *MockpushImageOps) Tag(ctx context.Context, sourceRef string, targetRef string) error { | |||
| 222 | + | ret := _mock.Called(ctx, sourceRef, targetRef) | |||
| 223 | + | ||||
| 224 | + | if len(ret) == 0 { | |||
| 225 | + | panic("no return value specified for Tag") | |||
| 226 | + | } | |||
| 227 | + | ||||
| 228 | + | var r0 error | |||
| 229 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) error); ok { | |||
| 230 | + | r0 = returnFunc(ctx, sourceRef, targetRef) | |||
| 231 | + | } else { | |||
| 232 | + | r0 = ret.Error(0) | |||
| 233 | + | } | |||
| 234 | + | return r0 | |||
| 235 | + | } | |||
| 236 | + | ||||
| 237 | + | // MockpushImageOps_Tag_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Tag' | |||
| 238 | + | type MockpushImageOps_Tag_Call struct { | |||
| 239 | + | *mock.Call | |||
| 240 | + | } | |||
| 241 | + | ||||
| 242 | + | // Tag is a helper method to define mock.On call | |||
| 243 | + | // - ctx context.Context | |||
| 244 | + | // - sourceRef string | |||
| 245 | + | // - targetRef string | |||
| 246 | + | func (_e *MockpushImageOps_Expecter) Tag(ctx interface{}, sourceRef interface{}, targetRef interface{}) *MockpushImageOps_Tag_Call { | |||
| 247 | + | return &MockpushImageOps_Tag_Call{Call: _e.mock.On("Tag", ctx, sourceRef, targetRef)} | |||
| 248 | + | } | |||
| 249 | + | ||||
| 250 | + | func (_c *MockpushImageOps_Tag_Call) Run(run func(ctx context.Context, sourceRef string, targetRef string)) *MockpushImageOps_Tag_Call { | |||
| 251 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 252 | + | var arg0 context.Context | |||
| 253 | + | if args[0] != nil { | |||
| 254 | + | arg0 = args[0].(context.Context) | |||
| 255 | + | } | |||
| 256 | + | var arg1 string | |||
| 257 | + | if args[1] != nil { | |||
| 258 | + | arg1 = args[1].(string) | |||
| 259 | + | } | |||
| 260 | + | var arg2 string | |||
| 261 | + | if args[2] != nil { | |||
| 262 | + | arg2 = args[2].(string) | |||
| 263 | + | } | |||
| 264 | + | run( | |||
| 265 | + | arg0, | |||
| 266 | + | arg1, | |||
| 267 | + | arg2, | |||
| 268 | + | ) | |||
| 269 | + | }) | |||
| 270 | + | return _c | |||
| 271 | + | } | |||
| 272 | + | ||||
| 273 | + | func (_c *MockpushImageOps_Tag_Call) Return(err error) *MockpushImageOps_Tag_Call { | |||
| 274 | + | _c.Call.Return(err) | |||
| 275 | + | return _c | |||
| 276 | + | } | |||
| 277 | + | ||||
| 278 | + | func (_c *MockpushImageOps_Tag_Call) RunAndReturn(run func(ctx context.Context, sourceRef string, targetRef string) error) *MockpushImageOps_Tag_Call { | |||
| 279 | + | _c.Call.Return(run) | |||
| 280 | + | return _c | |||
| 281 | + | } | |||
M internal/adapters/in/cli/push.go +23 -44
| @@ -18,15 +18,10 @@ import ( | |||
| 18 | 18 | "github.com/bnema/gordon/internal/adapters/in/cli/ui/components" | |
| 19 | 19 | "github.com/bnema/gordon/internal/adapters/in/cli/ui/styles" | |
| 20 | 20 | "github.com/bnema/gordon/internal/domain" | |
| 21 | - | "github.com/bnema/gordon/pkg/registrypush" | |
| 22 | 21 | "github.com/bnema/gordon/pkg/validation" | |
| 23 | 22 | ) | |
| 24 | 23 | ||
| 25 | - | var ( | |
| 26 | - | dockerTagFn = dockerTag | |
| 27 | - | dockerPushFn = dockerPush | |
| 28 | - | dockerImageExistsFn = dockerImageExists | |
| 29 | - | ) | |
| 24 | + | var newImageOpsFn = newImageOpsFromFlags | |
| 30 | 25 | ||
| 31 | 26 | func newPushCmd() *cobra.Command { | |
| 32 | 27 | var ( | |
| @@ -155,6 +150,11 @@ func runPush(ctx context.Context, imageArg, domainFlag, tag string, build bool, | |||
| 155 | 150 | ||
| 156 | 151 | versionRef, latestRef := resolveImageRefs(registry, imageName, version) | |
| 157 | 152 | ||
| 153 | + | imageOps, err := newImageOpsFn() | |
| 154 | + | if err != nil { | |
| 155 | + | return err | |
| 156 | + | } | |
| 157 | + | ||
| 158 | 158 | fmt.Printf("Image: %s\n", styles.Theme.Bold.Render(versionRef)) | |
| 159 | 159 | if version != "latest" { | |
| 160 | 160 | fmt.Printf("Also: %s\n", styles.Theme.Bold.Render(latestRef)) | |
| @@ -171,11 +171,11 @@ func runPush(ctx context.Context, imageArg, domainFlag, tag string, build bool, | |||
| 171 | 171 | } | |
| 172 | 172 | ||
| 173 | 173 | if build { | |
| 174 | - | if err := buildAndPush(ctx, version, platform, dockerfile, buildArgs, versionRef, latestRef); err != nil { | |
| 174 | + | if err := buildAndPush(ctx, imageOps, version, platform, dockerfile, buildArgs, versionRef, latestRef); err != nil { | |
| 175 | 175 | return err | |
| 176 | 176 | } | |
| 177 | 177 | } else { | |
| 178 | - | if err := tagAndPush(ctx, registry, imageName, version, versionRef, latestRef); err != nil { | |
| 178 | + | if err := tagAndPush(ctx, imageOps, registry, imageName, version, versionRef, latestRef); err != nil { | |
| 179 | 179 | return err | |
| 180 | 180 | } | |
| 181 | 181 | } | |
| @@ -474,7 +474,7 @@ func parseTagRef(ref string) string { | |||
| 474 | 474 | return tag | |
| 475 | 475 | } | |
| 476 | 476 | ||
| 477 | - | func buildAndPush(ctx context.Context, version, platform, dockerfile string, buildArgs []string, versionRef, latestRef string) error { | |
| 477 | + | func buildAndPush(ctx context.Context, ops pushImageOps, version, platform, dockerfile string, buildArgs []string, versionRef, latestRef string) error { | |
| 478 | 478 | if _, err := os.Stat(dockerfile); os.IsNotExist(err) { | |
| 479 | 479 | return fmt.Errorf("dockerfile not found: %s", dockerfile) | |
| 480 | 480 | } | |
| @@ -483,20 +483,16 @@ func buildAndPush(ctx context.Context, version, platform, dockerfile string, bui | |||
| 483 | 483 | // The native registry push client handles chunked uploads to stay | |
| 484 | 484 | // within Cloudflare's 100MB per-request limit. | |
| 485 | 485 | fmt.Println("\nBuilding image...") | |
| 486 | - | buildCmd := exec.CommandContext(ctx, "docker", buildImageArgs(ctx, version, platform, dockerfile, buildArgs, versionRef, latestRef)...) // #nosec G204 | |
| 487 | - | buildCmd.Env = os.Environ() // VERSION is now passed as --build-arg VERSION=<value> | |
| 488 | - | buildCmd.Stdout = os.Stdout | |
| 489 | - | buildCmd.Stderr = os.Stderr | |
| 490 | - | if err := buildCmd.Run(); err != nil { | |
| 491 | - | return fmt.Errorf("docker buildx build failed: %w", err) | |
| 486 | + | if err := ops.Build(ctx, buildImageArgs(ctx, version, platform, dockerfile, buildArgs, versionRef, latestRef)); err != nil { | |
| 487 | + | return err | |
| 492 | 488 | } | |
| 493 | 489 | ||
| 494 | 490 | fmt.Println("Pushing...") | |
| 495 | - | if err := dockerPushFn(ctx, latestRef); err != nil { | |
| 491 | + | if err := ops.Push(ctx, latestRef); err != nil { | |
| 496 | 492 | return fmt.Errorf("failed to push %s: %w", latestRef, err) | |
| 497 | 493 | } | |
| 498 | 494 | if version != "latest" { | |
| 499 | - | if err := dockerPushFn(ctx, versionRef); err != nil { | |
| 495 | + | if err := ops.Push(ctx, versionRef); err != nil { | |
| 500 | 496 | return fmt.Errorf("failed to push %s: %w", versionRef, err) | |
| 501 | 497 | } | |
| 502 | 498 | } | |
| @@ -554,30 +550,34 @@ func buildImageArgs(ctx context.Context, version, platform, dockerfile string, b | |||
| 554 | 550 | return args | |
| 555 | 551 | } | |
| 556 | 552 | ||
| 557 | - | func tagAndPush(ctx context.Context, registry, imageName, version, versionRef, latestRef string) error { | |
| 553 | + | func tagAndPush(ctx context.Context, ops pushImageOps, registry, imageName, version, versionRef, latestRef string) error { | |
| 558 | 554 | localImage := fmt.Sprintf("%s/%s", registry, imageName) | |
| 559 | 555 | ||
| 560 | 556 | fmt.Println("\nChecking local image...") | |
| 561 | - | if !dockerImageExistsFn(ctx, localImage) { | |
| 557 | + | exists, err := ops.Exists(ctx, localImage) | |
| 558 | + | if err != nil { | |
| 559 | + | return fmt.Errorf("failed to inspect local image %s: %w", localImage, err) | |
| 560 | + | } | |
| 561 | + | if !exists { | |
| 562 | 562 | return fmt.Errorf("local image %s not found; build and tag it before pushing", localImage) | |
| 563 | 563 | } | |
| 564 | 564 | ||
| 565 | 565 | fmt.Println("Tagging...") | |
| 566 | - | if err := dockerTagFn(ctx, localImage, versionRef); err != nil { | |
| 566 | + | if err := ops.Tag(ctx, localImage, versionRef); err != nil { | |
| 567 | 567 | return fmt.Errorf("failed to tag %s: %w", versionRef, err) | |
| 568 | 568 | } | |
| 569 | 569 | if version != "latest" { | |
| 570 | - | if err := dockerTagFn(ctx, localImage, latestRef); err != nil { | |
| 570 | + | if err := ops.Tag(ctx, localImage, latestRef); err != nil { | |
| 571 | 571 | return fmt.Errorf("failed to tag %s: %w", latestRef, err) | |
| 572 | 572 | } | |
| 573 | 573 | } | |
| 574 | 574 | ||
| 575 | 575 | fmt.Println("Pushing...") | |
| 576 | - | if err := dockerPushFn(ctx, versionRef); err != nil { | |
| 576 | + | if err := ops.Push(ctx, versionRef); err != nil { | |
| 577 | 577 | return fmt.Errorf("failed to push %s: %w", versionRef, err) | |
| 578 | 578 | } | |
| 579 | 579 | if version != "latest" { | |
| 580 | - | if err := dockerPushFn(ctx, latestRef); err != nil { | |
| 580 | + | if err := ops.Push(ctx, latestRef); err != nil { | |
| 581 | 581 | return fmt.Errorf("failed to push %s: %w", latestRef, err) | |
| 582 | 582 | } | |
| 583 | 583 | } | |
| @@ -742,27 +742,6 @@ func getGitVersion(ctx context.Context) string { | |||
| 742 | 742 | return strings.TrimSpace(string(out)) | |
| 743 | 743 | } | |
| 744 | 744 | ||
| 745 | - | func dockerTag(ctx context.Context, src, dst string) error { | |
| 746 | - | cmd := exec.CommandContext(ctx, "docker", "tag", src, dst) //nolint:gosec // binary is constant; image refs validated by OCI ref parser | |
| 747 | - | cmd.Stdout = os.Stdout | |
| 748 | - | cmd.Stderr = os.Stderr | |
| 749 | - | return cmd.Run() | |
| 750 | - | } | |
| 751 | - | ||
| 752 | - | func dockerPush(ctx context.Context, ref string) error { | |
| 753 | - | pusher := registrypush.New( | |
| 754 | - | registrypush.WithProgress(os.Stderr), | |
| 755 | - | ) | |
| 756 | - | return pusher.Push(ctx, ref) | |
| 757 | - | } | |
| 758 | - | ||
| 759 | - | func dockerImageExists(ctx context.Context, image string) bool { | |
| 760 | - | cmd := exec.CommandContext(ctx, "docker", "image", "inspect", image) // #nosec G204 | |
| 761 | - | cmd.Stdout = nil | |
| 762 | - | cmd.Stderr = nil | |
| 763 | - | return cmd.Run() == nil | |
| 764 | - | } | |
| 765 | - | ||
| 766 | 745 | // buildArgPattern matches KEY=VALUE where KEY starts with a letter or underscore. | |
| 767 | 746 | var buildArgPattern = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*=.*$`) | |
| 768 | 747 | ||
| @@ -18,15 +18,10 @@ import ( | |||||
| 18 | "github.com/bnema/gordon/internal/adapters/in/cli/ui/components" | 18 | "github.com/bnema/gordon/internal/adapters/in/cli/ui/components" | ||
| 19 | "github.com/bnema/gordon/internal/adapters/in/cli/ui/styles" | 19 | "github.com/bnema/gordon/internal/adapters/in/cli/ui/styles" | ||
| 20 | "github.com/bnema/gordon/internal/domain" | 20 | "github.com/bnema/gordon/internal/domain" | ||
| 21 | - | "github.com/bnema/gordon/pkg/registrypush" | |||
| 22 | "github.com/bnema/gordon/pkg/validation" | 21 | "github.com/bnema/gordon/pkg/validation" | ||
| 23 | ) | 22 | ) | ||
| 24 | 23 | ||||
| 25 | - | var ( | 24 | + | var newImageOpsFn = newImageOpsFromFlags |
| 26 | - | dockerTagFn = dockerTag | |||
| 27 | - | dockerPushFn = dockerPush | |||
| 28 | - | dockerImageExistsFn = dockerImageExists | |||
| 29 | - | ) | |||
| 30 | 25 | ||||
| 31 | func newPushCmd() *cobra.Command { | 26 | func newPushCmd() *cobra.Command { | ||
| 32 | var ( | 27 | var ( | ||
| @@ -155,6 +150,11 @@ func runPush(ctx context.Context, imageArg, domainFlag, tag string, build bool, | |||||
| 155 | 150 | ||||
| 156 | versionRef, latestRef := resolveImageRefs(registry, imageName, version) | 151 | versionRef, latestRef := resolveImageRefs(registry, imageName, version) | ||
| 157 | 152 | ||||
| 153 | + | imageOps, err := newImageOpsFn() | |||
| 154 | + | if err != nil { | |||
| 155 | + | return err | |||
| 156 | + | } | |||
| 157 | + | ||||
| 158 | fmt.Printf("Image: %s\n", styles.Theme.Bold.Render(versionRef)) | 158 | fmt.Printf("Image: %s\n", styles.Theme.Bold.Render(versionRef)) | ||
| 159 | if version != "latest" { | 159 | if version != "latest" { | ||
| 160 | fmt.Printf("Also: %s\n", styles.Theme.Bold.Render(latestRef)) | 160 | fmt.Printf("Also: %s\n", styles.Theme.Bold.Render(latestRef)) | ||
| @@ -171,11 +171,11 @@ func runPush(ctx context.Context, imageArg, domainFlag, tag string, build bool, | |||||
| 171 | } | 171 | } | ||
| 172 | 172 | ||||
| 173 | if build { | 173 | if build { | ||
| 174 | - | if err := buildAndPush(ctx, version, platform, dockerfile, buildArgs, versionRef, latestRef); err != nil { | 174 | + | if err := buildAndPush(ctx, imageOps, version, platform, dockerfile, buildArgs, versionRef, latestRef); err != nil { |
| 175 | return err | 175 | return err | ||
| 176 | } | 176 | } | ||
| 177 | } else { | 177 | } else { | ||
| 178 | - | if err := tagAndPush(ctx, registry, imageName, version, versionRef, latestRef); err != nil { | 178 | + | if err := tagAndPush(ctx, imageOps, registry, imageName, version, versionRef, latestRef); err != nil { |
| 179 | return err | 179 | return err | ||
| 180 | } | 180 | } | ||
| 181 | } | 181 | } | ||
| @@ -474,7 +474,7 @@ func parseTagRef(ref string) string { | |||||
| 474 | return tag | 474 | return tag | ||
| 475 | } | 475 | } | ||
| 476 | 476 | ||||
| 477 | - | func buildAndPush(ctx context.Context, version, platform, dockerfile string, buildArgs []string, versionRef, latestRef string) error { | 477 | + | func buildAndPush(ctx context.Context, ops pushImageOps, version, platform, dockerfile string, buildArgs []string, versionRef, latestRef string) error { |
| 478 | if _, err := os.Stat(dockerfile); os.IsNotExist(err) { | 478 | if _, err := os.Stat(dockerfile); os.IsNotExist(err) { | ||
| 479 | return fmt.Errorf("dockerfile not found: %s", dockerfile) | 479 | return fmt.Errorf("dockerfile not found: %s", dockerfile) | ||
| 480 | } | 480 | } | ||
| @@ -483,20 +483,16 @@ func buildAndPush(ctx context.Context, version, platform, dockerfile string, bui | |||||
| 483 | // The native registry push client handles chunked uploads to stay | 483 | // The native registry push client handles chunked uploads to stay | ||
| 484 | // within Cloudflare's 100MB per-request limit. | 484 | // within Cloudflare's 100MB per-request limit. | ||
| 485 | fmt.Println("\nBuilding image...") | 485 | fmt.Println("\nBuilding image...") | ||
| 486 | - | buildCmd := exec.CommandContext(ctx, "docker", buildImageArgs(ctx, version, platform, dockerfile, buildArgs, versionRef, latestRef)...) // #nosec G204 | 486 | + | if err := ops.Build(ctx, buildImageArgs(ctx, version, platform, dockerfile, buildArgs, versionRef, latestRef)); err != nil { |
| 487 | - | buildCmd.Env = os.Environ() // VERSION is now passed as --build-arg VERSION=<value> | 487 | + | return err |
| 488 | - | buildCmd.Stdout = os.Stdout | |||
| 489 | - | buildCmd.Stderr = os.Stderr | |||
| 490 | - | if err := buildCmd.Run(); err != nil { | |||
| 491 | - | return fmt.Errorf("docker buildx build failed: %w", err) | |||
| 492 | } | 488 | } | ||
| 493 | 489 | ||||
| 494 | fmt.Println("Pushing...") | 490 | fmt.Println("Pushing...") | ||
| 495 | - | if err := dockerPushFn(ctx, latestRef); err != nil { | 491 | + | if err := ops.Push(ctx, latestRef); err != nil { |
| 496 | return fmt.Errorf("failed to push %s: %w", latestRef, err) | 492 | return fmt.Errorf("failed to push %s: %w", latestRef, err) | ||
| 497 | } | 493 | } | ||
| 498 | if version != "latest" { | 494 | if version != "latest" { | ||
| 499 | - | if err := dockerPushFn(ctx, versionRef); err != nil { | 495 | + | if err := ops.Push(ctx, versionRef); err != nil { |
| 500 | return fmt.Errorf("failed to push %s: %w", versionRef, err) | 496 | return fmt.Errorf("failed to push %s: %w", versionRef, err) | ||
| 501 | } | 497 | } | ||
| 502 | } | 498 | } | ||
| @@ -554,30 +550,34 @@ func buildImageArgs(ctx context.Context, version, platform, dockerfile string, b | |||||
| 554 | return args | 550 | return args | ||
| 555 | } | 551 | } | ||
| 556 | 552 | ||||
| 557 | - | func tagAndPush(ctx context.Context, registry, imageName, version, versionRef, latestRef string) error { | 553 | + | func tagAndPush(ctx context.Context, ops pushImageOps, registry, imageName, version, versionRef, latestRef string) error { |
| 558 | localImage := fmt.Sprintf("%s/%s", registry, imageName) | 554 | localImage := fmt.Sprintf("%s/%s", registry, imageName) | ||
| 559 | 555 | ||||
| 560 | fmt.Println("\nChecking local image...") | 556 | fmt.Println("\nChecking local image...") | ||
| 561 | - | if !dockerImageExistsFn(ctx, localImage) { | 557 | + | exists, err := ops.Exists(ctx, localImage) |
| 558 | + | if err != nil { | |||
| 559 | + | return fmt.Errorf("failed to inspect local image %s: %w", localImage, err) | |||
| 560 | + | } | |||
| 561 | + | if !exists { | |||
| 562 | return fmt.Errorf("local image %s not found; build and tag it before pushing", localImage) | 562 | return fmt.Errorf("local image %s not found; build and tag it before pushing", localImage) | ||
| 563 | } | 563 | } | ||
| 564 | 564 | ||||
| 565 | fmt.Println("Tagging...") | 565 | fmt.Println("Tagging...") | ||
| 566 | - | if err := dockerTagFn(ctx, localImage, versionRef); err != nil { | 566 | + | if err := ops.Tag(ctx, localImage, versionRef); err != nil { |
| 567 | return fmt.Errorf("failed to tag %s: %w", versionRef, err) | 567 | return fmt.Errorf("failed to tag %s: %w", versionRef, err) | ||
| 568 | } | 568 | } | ||
| 569 | if version != "latest" { | 569 | if version != "latest" { | ||
| 570 | - | if err := dockerTagFn(ctx, localImage, latestRef); err != nil { | 570 | + | if err := ops.Tag(ctx, localImage, latestRef); err != nil { |
| 571 | return fmt.Errorf("failed to tag %s: %w", latestRef, err) | 571 | return fmt.Errorf("failed to tag %s: %w", latestRef, err) | ||
| 572 | } | 572 | } | ||
| 573 | } | 573 | } | ||
| 574 | 574 | ||||
| 575 | fmt.Println("Pushing...") | 575 | fmt.Println("Pushing...") | ||
| 576 | - | if err := dockerPushFn(ctx, versionRef); err != nil { | 576 | + | if err := ops.Push(ctx, versionRef); err != nil { |
| 577 | return fmt.Errorf("failed to push %s: %w", versionRef, err) | 577 | return fmt.Errorf("failed to push %s: %w", versionRef, err) | ||
| 578 | } | 578 | } | ||
| 579 | if version != "latest" { | 579 | if version != "latest" { | ||
| 580 | - | if err := dockerPushFn(ctx, latestRef); err != nil { | 580 | + | if err := ops.Push(ctx, latestRef); err != nil { |
| 581 | return fmt.Errorf("failed to push %s: %w", latestRef, err) | 581 | return fmt.Errorf("failed to push %s: %w", latestRef, err) | ||
| 582 | } | 582 | } | ||
| 583 | } | 583 | } | ||
| @@ -742,27 +742,6 @@ func getGitVersion(ctx context.Context) string { | |||||
| 742 | return strings.TrimSpace(string(out)) | 742 | return strings.TrimSpace(string(out)) | ||
| 743 | } | 743 | } | ||
| 744 | 744 | ||||
| 745 | - | func dockerTag(ctx context.Context, src, dst string) error { | |||
| 746 | - | cmd := exec.CommandContext(ctx, "docker", "tag", src, dst) //nolint:gosec // binary is constant; image refs validated by OCI ref parser | |||
| 747 | - | cmd.Stdout = os.Stdout | |||
| 748 | - | cmd.Stderr = os.Stderr | |||
| 749 | - | return cmd.Run() | |||
| 750 | - | } | |||
| 751 | - | ||||
| 752 | - | func dockerPush(ctx context.Context, ref string) error { | |||
| 753 | - | pusher := registrypush.New( | |||
| 754 | - | registrypush.WithProgress(os.Stderr), | |||
| 755 | - | ) | |||
| 756 | - | return pusher.Push(ctx, ref) | |||
| 757 | - | } | |||
| 758 | - | ||||
| 759 | - | func dockerImageExists(ctx context.Context, image string) bool { | |||
| 760 | - | cmd := exec.CommandContext(ctx, "docker", "image", "inspect", image) // #nosec G204 | |||
| 761 | - | cmd.Stdout = nil | |||
| 762 | - | cmd.Stderr = nil | |||
| 763 | - | return cmd.Run() == nil | |||
| 764 | - | } | |||
| 765 | - | ||||
| 766 | // buildArgPattern matches KEY=VALUE where KEY starts with a letter or underscore. | 745 | // buildArgPattern matches KEY=VALUE where KEY starts with a letter or underscore. | ||
| 767 | var buildArgPattern = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*=.*$`) | 746 | var buildArgPattern = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*=.*$`) | ||
| 768 | 747 | ||||
A internal/adapters/in/cli/push_image_ops.go +101 -0
| @@ -0,0 +1,101 @@ | |||
| 1 | + | package cli | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "fmt" | |
| 6 | + | "io" | |
| 7 | + | "os" | |
| 8 | + | "os/exec" | |
| 9 | + | ||
| 10 | + | cerrdefs "github.com/containerd/errdefs" | |
| 11 | + | "github.com/docker/docker/client" | |
| 12 | + | ||
| 13 | + | "github.com/bnema/gordon/internal/adapters/in/cli/remote" | |
| 14 | + | "github.com/bnema/gordon/pkg/registrypush" | |
| 15 | + | ) | |
| 16 | + | ||
| 17 | + | // pushImageOps abstracts container image operations needed by push commands. | |
| 18 | + | // The real implementation uses Docker SDK + registrypush; tests inject fakes. | |
| 19 | + | type pushImageOps interface { | |
| 20 | + | // Tag creates a new tag for an existing local image. | |
| 21 | + | Tag(ctx context.Context, sourceRef, targetRef string) error | |
| 22 | + | // Push uploads a local image to its registry using chunked blob uploads. | |
| 23 | + | Push(ctx context.Context, ref string) error | |
| 24 | + | // Exists checks whether a local image exists. | |
| 25 | + | Exists(ctx context.Context, imageRef string) (bool, error) | |
| 26 | + | // Build runs a container image build. Only Docker with buildx is supported; | |
| 27 | + | // other runtimes return an actionable error. | |
| 28 | + | Build(ctx context.Context, args []string) error | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | // dockerImageOps implements pushImageOps using the Docker SDK and registrypush. | |
| 32 | + | type dockerImageOps struct { | |
| 33 | + | cli client.APIClient | |
| 34 | + | insecureTLS bool | |
| 35 | + | progress io.Writer | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | // newDockerImageOps creates a dockerImageOps from the Docker environment. | |
| 39 | + | func newDockerImageOps(insecureTLS bool, progress io.Writer) (*dockerImageOps, error) { | |
| 40 | + | cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) | |
| 41 | + | if err != nil { | |
| 42 | + | return nil, fmt.Errorf("failed to create Docker client: %w", err) | |
| 43 | + | } | |
| 44 | + | return &dockerImageOps{ | |
| 45 | + | cli: cli, | |
| 46 | + | insecureTLS: insecureTLS, | |
| 47 | + | progress: progress, | |
| 48 | + | }, nil | |
| 49 | + | } | |
| 50 | + | ||
| 51 | + | // newImageOpsFromFlags resolves TLS settings from CLI flags/config and creates ops. | |
| 52 | + | func newImageOpsFromFlags() (pushImageOps, error) { | |
| 53 | + | _, _, insecureTLS, _ := remote.ResolveRemote(remoteFlag, tokenFlag, insecureTLSFlag) | |
| 54 | + | return newDockerImageOps(insecureTLS, os.Stderr) | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | func (d *dockerImageOps) Tag(ctx context.Context, sourceRef, targetRef string) error { | |
| 58 | + | if err := d.cli.ImageTag(ctx, sourceRef, targetRef); err != nil { | |
| 59 | + | return fmt.Errorf("failed to tag %s as %s: %w", sourceRef, targetRef, err) | |
| 60 | + | } | |
| 61 | + | return nil | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | func (d *dockerImageOps) Push(ctx context.Context, ref string) error { | |
| 65 | + | pusher := registrypush.New( | |
| 66 | + | registrypush.WithProgress(d.progress), | |
| 67 | + | registrypush.WithInsecureTLS(d.insecureTLS), | |
| 68 | + | ) | |
| 69 | + | return pusher.Push(ctx, ref) | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | func (d *dockerImageOps) Exists(ctx context.Context, imageRef string) (bool, error) { | |
| 73 | + | _, err := d.cli.ImageInspect(ctx, imageRef) | |
| 74 | + | if err != nil { | |
| 75 | + | if cerrdefs.IsNotFound(err) { | |
| 76 | + | return false, nil | |
| 77 | + | } | |
| 78 | + | return false, fmt.Errorf("failed to inspect image %s: %w", imageRef, err) | |
| 79 | + | } | |
| 80 | + | return true, nil | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | func (d *dockerImageOps) Build(ctx context.Context, args []string) error { | |
| 84 | + | if _, err := exec.LookPath("docker"); err != nil { | |
| 85 | + | return fmt.Errorf( | |
| 86 | + | "image build requires Docker with buildx\n\n" + | |
| 87 | + | "Podman and other runtimes are not supported for --build.\n" + | |
| 88 | + | "Build the image manually and push with:\n" + | |
| 89 | + | " podman build -t <image> .\n" + | |
| 90 | + | " gordon push <image>", | |
| 91 | + | ) | |
| 92 | + | } | |
| 93 | + | cmd := exec.CommandContext(ctx, "docker", args...) // #nosec G204 | |
| 94 | + | cmd.Env = os.Environ() | |
| 95 | + | cmd.Stdout = os.Stdout | |
| 96 | + | cmd.Stderr = os.Stderr | |
| 97 | + | if err := cmd.Run(); err != nil { | |
| 98 | + | return fmt.Errorf("docker buildx build failed: %w", err) | |
| 99 | + | } | |
| 100 | + | return nil | |
| 101 | + | } | |
| @@ -0,0 +1,101 @@ | |||||
| 1 | + | package cli | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "fmt" | |||
| 6 | + | "io" | |||
| 7 | + | "os" | |||
| 8 | + | "os/exec" | |||
| 9 | + | ||||
| 10 | + | cerrdefs "github.com/containerd/errdefs" | |||
| 11 | + | "github.com/docker/docker/client" | |||
| 12 | + | ||||
| 13 | + | "github.com/bnema/gordon/internal/adapters/in/cli/remote" | |||
| 14 | + | "github.com/bnema/gordon/pkg/registrypush" | |||
| 15 | + | ) | |||
| 16 | + | ||||
| 17 | + | // pushImageOps abstracts container image operations needed by push commands. | |||
| 18 | + | // The real implementation uses Docker SDK + registrypush; tests inject fakes. | |||
| 19 | + | type pushImageOps interface { | |||
| 20 | + | // Tag creates a new tag for an existing local image. | |||
| 21 | + | Tag(ctx context.Context, sourceRef, targetRef string) error | |||
| 22 | + | // Push uploads a local image to its registry using chunked blob uploads. | |||
| 23 | + | Push(ctx context.Context, ref string) error | |||
| 24 | + | // Exists checks whether a local image exists. | |||
| 25 | + | Exists(ctx context.Context, imageRef string) (bool, error) | |||
| 26 | + | // Build runs a container image build. Only Docker with buildx is supported; | |||
| 27 | + | // other runtimes return an actionable error. | |||
| 28 | + | Build(ctx context.Context, args []string) error | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | // dockerImageOps implements pushImageOps using the Docker SDK and registrypush. | |||
| 32 | + | type dockerImageOps struct { | |||
| 33 | + | cli client.APIClient | |||
| 34 | + | insecureTLS bool | |||
| 35 | + | progress io.Writer | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | // newDockerImageOps creates a dockerImageOps from the Docker environment. | |||
| 39 | + | func newDockerImageOps(insecureTLS bool, progress io.Writer) (*dockerImageOps, error) { | |||
| 40 | + | cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) | |||
| 41 | + | if err != nil { | |||
| 42 | + | return nil, fmt.Errorf("failed to create Docker client: %w", err) | |||
| 43 | + | } | |||
| 44 | + | return &dockerImageOps{ | |||
| 45 | + | cli: cli, | |||
| 46 | + | insecureTLS: insecureTLS, | |||
| 47 | + | progress: progress, | |||
| 48 | + | }, nil | |||
| 49 | + | } | |||
| 50 | + | ||||
| 51 | + | // newImageOpsFromFlags resolves TLS settings from CLI flags/config and creates ops. | |||
| 52 | + | func newImageOpsFromFlags() (pushImageOps, error) { | |||
| 53 | + | _, _, insecureTLS, _ := remote.ResolveRemote(remoteFlag, tokenFlag, insecureTLSFlag) | |||
| 54 | + | return newDockerImageOps(insecureTLS, os.Stderr) | |||
| 55 | + | } | |||
| 56 | + | ||||
| 57 | + | func (d *dockerImageOps) Tag(ctx context.Context, sourceRef, targetRef string) error { | |||
| 58 | + | if err := d.cli.ImageTag(ctx, sourceRef, targetRef); err != nil { | |||
| 59 | + | return fmt.Errorf("failed to tag %s as %s: %w", sourceRef, targetRef, err) | |||
| 60 | + | } | |||
| 61 | + | return nil | |||
| 62 | + | } | |||
| 63 | + | ||||
| 64 | + | func (d *dockerImageOps) Push(ctx context.Context, ref string) error { | |||
| 65 | + | pusher := registrypush.New( | |||
| 66 | + | registrypush.WithProgress(d.progress), | |||
| 67 | + | registrypush.WithInsecureTLS(d.insecureTLS), | |||
| 68 | + | ) | |||
| 69 | + | return pusher.Push(ctx, ref) | |||
| 70 | + | } | |||
| 71 | + | ||||
| 72 | + | func (d *dockerImageOps) Exists(ctx context.Context, imageRef string) (bool, error) { | |||
| 73 | + | _, err := d.cli.ImageInspect(ctx, imageRef) | |||
| 74 | + | if err != nil { | |||
| 75 | + | if cerrdefs.IsNotFound(err) { | |||
| 76 | + | return false, nil | |||
| 77 | + | } | |||
| 78 | + | return false, fmt.Errorf("failed to inspect image %s: %w", imageRef, err) | |||
| 79 | + | } | |||
| 80 | + | return true, nil | |||
| 81 | + | } | |||
| 82 | + | ||||
| 83 | + | func (d *dockerImageOps) Build(ctx context.Context, args []string) error { | |||
| 84 | + | if _, err := exec.LookPath("docker"); err != nil { | |||
| 85 | + | return fmt.Errorf( | |||
| 86 | + | "image build requires Docker with buildx\n\n" + | |||
| 87 | + | "Podman and other runtimes are not supported for --build.\n" + | |||
| 88 | + | "Build the image manually and push with:\n" + | |||
| 89 | + | " podman build -t <image> .\n" + | |||
| 90 | + | " gordon push <image>", | |||
| 91 | + | ) | |||
| 92 | + | } | |||
| 93 | + | cmd := exec.CommandContext(ctx, "docker", args...) // #nosec G204 | |||
| 94 | + | cmd.Env = os.Environ() | |||
| 95 | + | cmd.Stdout = os.Stdout | |||
| 96 | + | cmd.Stderr = os.Stderr | |||
| 97 | + | if err := cmd.Run(); err != nil { | |||
| 98 | + | return fmt.Errorf("docker buildx build failed: %w", err) | |||
| 99 | + | } | |||
| 100 | + | return nil | |||
| 101 | + | } | |||
M internal/adapters/in/cli/push_integration_test.go +8 -18
| @@ -8,10 +8,12 @@ import ( | |||
| 8 | 8 | "strings" | |
| 9 | 9 | "testing" | |
| 10 | 10 | ||
| 11 | + | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" | |
| 11 | 12 | "github.com/bnema/zerowrap" | |
| 12 | 13 | v1 "github.com/google/go-containerregistry/pkg/v1" | |
| 13 | 14 | "github.com/google/go-containerregistry/pkg/v1/random" | |
| 14 | 15 | "github.com/stretchr/testify/assert" | |
| 16 | + | "github.com/stretchr/testify/mock" | |
| 15 | 17 | "github.com/stretchr/testify/require" | |
| 16 | 18 | ||
| 17 | 19 | registryhttp "github.com/bnema/gordon/internal/adapters/in/http/registry" | |
| @@ -31,16 +33,10 @@ func TestIntegration_TagAndPush_NativeRegistry(t *testing.T) { | |||
| 31 | 33 | img, err := random.Image(512, 1) | |
| 32 | 34 | require.NoError(t, err) | |
| 33 | 35 | ||
| 34 | - | originalPush := dockerPushFn | |
| 35 | - | originalTag := dockerTagFn | |
| 36 | - | originalImageExists := dockerImageExistsFn | |
| 37 | - | t.Cleanup(func() { | |
| 38 | - | dockerPushFn = originalPush | |
| 39 | - | dockerTagFn = originalTag | |
| 40 | - | dockerImageExistsFn = originalImageExists | |
| 41 | - | }) | |
| 42 | - | ||
| 43 | - | dockerPushFn = func(ctx context.Context, ref string) error { | |
| 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 { | |
| 44 | 40 | pusher := registrypush.New( | |
| 45 | 41 | registrypush.WithChunkSize(256), | |
| 46 | 42 | registrypush.WithTransport(harness.server.Client().Transport), | |
| @@ -49,19 +45,13 @@ func TestIntegration_TagAndPush_NativeRegistry(t *testing.T) { | |||
| 49 | 45 | }), | |
| 50 | 46 | ) | |
| 51 | 47 | return pusher.Push(ctx, ref) | |
| 52 | - | } | |
| 53 | - | dockerTagFn = func(context.Context, string, string) error { | |
| 54 | - | return nil | |
| 55 | - | } | |
| 56 | - | dockerImageExistsFn = func(context.Context, string) bool { | |
| 57 | - | return true | |
| 58 | - | } | |
| 48 | + | }) | |
| 59 | 49 | ||
| 60 | 50 | registry := strings.TrimPrefix(harness.server.URL, "http://") | |
| 61 | 51 | versionRef := registry + "/testapp:v1.0.0" | |
| 62 | 52 | latestRef := registry + "/testapp:latest" | |
| 63 | 53 | ||
| 64 | - | err = tagAndPush(ctx, registry, "testapp", "v1.0.0", versionRef, latestRef) | |
| 54 | + | err = tagAndPush(ctx, ops, registry, "testapp", "v1.0.0", versionRef, latestRef) | |
| 65 | 55 | require.NoError(t, err) | |
| 66 | 56 | ||
| 67 | 57 | harness.RequireManifest(t, "v1.0.0") | |
| @@ -8,10 +8,12 @@ import ( | |||||
| 8 | "strings" | 8 | "strings" | ||
| 9 | "testing" | 9 | "testing" | ||
| 10 | 10 | ||||
| 11 | + | climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks" | |||
| 11 | "github.com/bnema/zerowrap" | 12 | "github.com/bnema/zerowrap" | ||
| 12 | v1 "github.com/google/go-containerregistry/pkg/v1" | 13 | v1 "github.com/google/go-containerregistry/pkg/v1" | ||
| 13 | "github.com/google/go-containerregistry/pkg/v1/random" | 14 | "github.com/google/go-containerregistry/pkg/v1/random" | ||
| 14 | "github.com/stretchr/testify/assert" | 15 | "github.com/stretchr/testify/assert" | ||
| 16 | + | "github.com/stretchr/testify/mock" | |||
| 15 | "github.com/stretchr/testify/require" | 17 | "github.com/stretchr/testify/require" | ||
| 16 | 18 | ||||
| 17 | registryhttp "github.com/bnema/gordon/internal/adapters/in/http/registry" | 19 | registryhttp "github.com/bnema/gordon/internal/adapters/in/http/registry" | ||
| @@ -31,16 +33,10 @@ func TestIntegration_TagAndPush_NativeRegistry(t *testing.T) { | |||||
| 31 | img, err := random.Image(512, 1) | 33 | img, err := random.Image(512, 1) | ||
| 32 | require.NoError(t, err) | 34 | require.NoError(t, err) | ||
| 33 | 35 | ||||
| 34 | - | originalPush := dockerPushFn | 36 | + | ops := climocks.NewMockpushImageOps(t) |
| 35 | - | originalTag := dockerTagFn | 37 | + | ops.On("Tag", mock.Anything, mock.Anything, mock.Anything).Return(nil) |
| 36 | - | originalImageExists := dockerImageExistsFn | 38 | + | ops.On("Exists", mock.Anything, mock.Anything).Return(true, nil) |
| 37 | - | t.Cleanup(func() { | 39 | + | ops.EXPECT().Push(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, ref string) error { |
| 38 | - | dockerPushFn = originalPush | |||
| 39 | - | dockerTagFn = originalTag | |||
| 40 | - | dockerImageExistsFn = originalImageExists | |||
| 41 | - | }) | |||
| 42 | - | ||||
| 43 | - | dockerPushFn = func(ctx context.Context, ref string) error { | |||
| 44 | pusher := registrypush.New( | 40 | pusher := registrypush.New( | ||
| 45 | registrypush.WithChunkSize(256), | 41 | registrypush.WithChunkSize(256), | ||
| 46 | registrypush.WithTransport(harness.server.Client().Transport), | 42 | registrypush.WithTransport(harness.server.Client().Transport), | ||
| @@ -49,19 +45,13 @@ func TestIntegration_TagAndPush_NativeRegistry(t *testing.T) { | |||||
| 49 | }), | 45 | }), | ||
| 50 | ) | 46 | ) | ||
| 51 | return pusher.Push(ctx, ref) | 47 | return pusher.Push(ctx, ref) | ||
| 52 | - | } | 48 | + | }) |
| 53 | - | dockerTagFn = func(context.Context, string, string) error { | |||
| 54 | - | return nil | |||
| 55 | - | } | |||
| 56 | - | dockerImageExistsFn = func(context.Context, string) bool { | |||
| 57 | - | return true | |||
| 58 | - | } | |||
| 59 | 49 | ||||
| 60 | registry := strings.TrimPrefix(harness.server.URL, "http://") | 50 | registry := strings.TrimPrefix(harness.server.URL, "http://") | ||
| 61 | versionRef := registry + "/testapp:v1.0.0" | 51 | versionRef := registry + "/testapp:v1.0.0" | ||
| 62 | latestRef := registry + "/testapp:latest" | 52 | latestRef := registry + "/testapp:latest" | ||
| 63 | 53 | ||||
| 64 | - | err = tagAndPush(ctx, registry, "testapp", "v1.0.0", versionRef, latestRef) | 54 | + | err = tagAndPush(ctx, ops, registry, "testapp", "v1.0.0", versionRef, latestRef) |
| 65 | require.NoError(t, err) | 55 | require.NoError(t, err) | ||
| 66 | 56 | ||||
| 67 | harness.RequireManifest(t, "v1.0.0") | 57 | harness.RequireManifest(t, "v1.0.0") | ||
M internal/boundaries/in/mocks/mock_config_service.go +30 -30
| @@ -158,38 +158,38 @@ func (_c *MockConfigService_AddRoute_Call) RunAndReturn(run func(ctx context.Con | |||
| 158 | 158 | return _c | |
| 159 | 159 | } | |
| 160 | 160 | ||
| 161 | - | // FindRoutesByImage provides a mock function for the type MockConfigService | |
| 162 | - | func (_mock *MockConfigService) FindRoutesByImage(ctx context.Context, imageName string) []domain.Route { | |
| 161 | + | // FindAttachmentTargetsByImage provides a mock function for the type MockConfigService | |
| 162 | + | func (_mock *MockConfigService) FindAttachmentTargetsByImage(ctx context.Context, imageName string) []string { | |
| 163 | 163 | ret := _mock.Called(ctx, imageName) | |
| 164 | 164 | ||
| 165 | 165 | if len(ret) == 0 { | |
| 166 | - | panic("no return value specified for FindRoutesByImage") | |
| 166 | + | panic("no return value specified for FindAttachmentTargetsByImage") | |
| 167 | 167 | } | |
| 168 | 168 | ||
| 169 | - | var r0 []domain.Route | |
| 170 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, string) []domain.Route); ok { | |
| 169 | + | var r0 []string | |
| 170 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) []string); ok { | |
| 171 | 171 | r0 = returnFunc(ctx, imageName) | |
| 172 | 172 | } else { | |
| 173 | 173 | if ret.Get(0) != nil { | |
| 174 | - | r0 = ret.Get(0).([]domain.Route) | |
| 174 | + | r0 = ret.Get(0).([]string) | |
| 175 | 175 | } | |
| 176 | 176 | } | |
| 177 | 177 | return r0 | |
| 178 | 178 | } | |
| 179 | 179 | ||
| 180 | - | // MockConfigService_FindRoutesByImage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindRoutesByImage' | |
| 181 | - | type MockConfigService_FindRoutesByImage_Call struct { | |
| 180 | + | // MockConfigService_FindAttachmentTargetsByImage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindAttachmentTargetsByImage' | |
| 181 | + | type MockConfigService_FindAttachmentTargetsByImage_Call struct { | |
| 182 | 182 | *mock.Call | |
| 183 | 183 | } | |
| 184 | 184 | ||
| 185 | - | // FindRoutesByImage is a helper method to define mock.On call | |
| 185 | + | // FindAttachmentTargetsByImage is a helper method to define mock.On call | |
| 186 | 186 | // - ctx context.Context | |
| 187 | 187 | // - imageName string | |
| 188 | - | func (_e *MockConfigService_Expecter) FindRoutesByImage(ctx interface{}, imageName interface{}) *MockConfigService_FindRoutesByImage_Call { | |
| 189 | - | return &MockConfigService_FindRoutesByImage_Call{Call: _e.mock.On("FindRoutesByImage", ctx, imageName)} | |
| 188 | + | func (_e *MockConfigService_Expecter) FindAttachmentTargetsByImage(ctx interface{}, imageName interface{}) *MockConfigService_FindAttachmentTargetsByImage_Call { | |
| 189 | + | return &MockConfigService_FindAttachmentTargetsByImage_Call{Call: _e.mock.On("FindAttachmentTargetsByImage", ctx, imageName)} | |
| 190 | 190 | } | |
| 191 | 191 | ||
| 192 | - | func (_c *MockConfigService_FindRoutesByImage_Call) Run(run func(ctx context.Context, imageName string)) *MockConfigService_FindRoutesByImage_Call { | |
| 192 | + | func (_c *MockConfigService_FindAttachmentTargetsByImage_Call) Run(run func(ctx context.Context, imageName string)) *MockConfigService_FindAttachmentTargetsByImage_Call { | |
| 193 | 193 | _c.Call.Run(func(args mock.Arguments) { | |
| 194 | 194 | var arg0 context.Context | |
| 195 | 195 | if args[0] != nil { | |
| @@ -207,48 +207,48 @@ func (_c *MockConfigService_FindRoutesByImage_Call) Run(run func(ctx context.Con | |||
| 207 | 207 | return _c | |
| 208 | 208 | } | |
| 209 | 209 | ||
| 210 | - | func (_c *MockConfigService_FindRoutesByImage_Call) Return(routes []domain.Route) *MockConfigService_FindRoutesByImage_Call { | |
| 211 | - | _c.Call.Return(routes) | |
| 210 | + | func (_c *MockConfigService_FindAttachmentTargetsByImage_Call) Return(strings []string) *MockConfigService_FindAttachmentTargetsByImage_Call { | |
| 211 | + | _c.Call.Return(strings) | |
| 212 | 212 | return _c | |
| 213 | 213 | } | |
| 214 | 214 | ||
| 215 | - | func (_c *MockConfigService_FindRoutesByImage_Call) RunAndReturn(run func(ctx context.Context, imageName string) []domain.Route) *MockConfigService_FindRoutesByImage_Call { | |
| 215 | + | func (_c *MockConfigService_FindAttachmentTargetsByImage_Call) RunAndReturn(run func(ctx context.Context, imageName string) []string) *MockConfigService_FindAttachmentTargetsByImage_Call { | |
| 216 | 216 | _c.Call.Return(run) | |
| 217 | 217 | return _c | |
| 218 | 218 | } | |
| 219 | 219 | ||
| 220 | - | // FindAttachmentTargetsByImage provides a mock function for the type MockConfigService | |
| 221 | - | func (_mock *MockConfigService) FindAttachmentTargetsByImage(ctx context.Context, imageName string) []string { | |
| 220 | + | // FindRoutesByImage provides a mock function for the type MockConfigService | |
| 221 | + | func (_mock *MockConfigService) FindRoutesByImage(ctx context.Context, imageName string) []domain.Route { | |
| 222 | 222 | ret := _mock.Called(ctx, imageName) | |
| 223 | 223 | ||
| 224 | 224 | if len(ret) == 0 { | |
| 225 | - | panic("no return value specified for FindAttachmentTargetsByImage") | |
| 225 | + | panic("no return value specified for FindRoutesByImage") | |
| 226 | 226 | } | |
| 227 | 227 | ||
| 228 | - | var r0 []string | |
| 229 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, string) []string); ok { | |
| 228 | + | var r0 []domain.Route | |
| 229 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) []domain.Route); ok { | |
| 230 | 230 | r0 = returnFunc(ctx, imageName) | |
| 231 | 231 | } else { | |
| 232 | 232 | if ret.Get(0) != nil { | |
| 233 | - | r0 = ret.Get(0).([]string) | |
| 233 | + | r0 = ret.Get(0).([]domain.Route) | |
| 234 | 234 | } | |
| 235 | 235 | } | |
| 236 | 236 | return r0 | |
| 237 | 237 | } | |
| 238 | 238 | ||
| 239 | - | // MockConfigService_FindAttachmentTargetsByImage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindAttachmentTargetsByImage' | |
| 240 | - | type MockConfigService_FindAttachmentTargetsByImage_Call struct { | |
| 239 | + | // MockConfigService_FindRoutesByImage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindRoutesByImage' | |
| 240 | + | type MockConfigService_FindRoutesByImage_Call struct { | |
| 241 | 241 | *mock.Call | |
| 242 | 242 | } | |
| 243 | 243 | ||
| 244 | - | // FindAttachmentTargetsByImage is a helper method to define mock.On call | |
| 244 | + | // FindRoutesByImage is a helper method to define mock.On call | |
| 245 | 245 | // - ctx context.Context | |
| 246 | 246 | // - imageName string | |
| 247 | - | func (_e *MockConfigService_Expecter) FindAttachmentTargetsByImage(ctx interface{}, imageName interface{}) *MockConfigService_FindAttachmentTargetsByImage_Call { | |
| 248 | - | return &MockConfigService_FindAttachmentTargetsByImage_Call{Call: _e.mock.On("FindAttachmentTargetsByImage", ctx, imageName)} | |
| 247 | + | func (_e *MockConfigService_Expecter) FindRoutesByImage(ctx interface{}, imageName interface{}) *MockConfigService_FindRoutesByImage_Call { | |
| 248 | + | return &MockConfigService_FindRoutesByImage_Call{Call: _e.mock.On("FindRoutesByImage", ctx, imageName)} | |
| 249 | 249 | } | |
| 250 | 250 | ||
| 251 | - | func (_c *MockConfigService_FindAttachmentTargetsByImage_Call) Run(run func(ctx context.Context, imageName string)) *MockConfigService_FindAttachmentTargetsByImage_Call { | |
| 251 | + | func (_c *MockConfigService_FindRoutesByImage_Call) Run(run func(ctx context.Context, imageName string)) *MockConfigService_FindRoutesByImage_Call { | |
| 252 | 252 | _c.Call.Run(func(args mock.Arguments) { | |
| 253 | 253 | var arg0 context.Context | |
| 254 | 254 | if args[0] != nil { | |
| @@ -266,12 +266,12 @@ func (_c *MockConfigService_FindAttachmentTargetsByImage_Call) Run(run func(ctx | |||
| 266 | 266 | return _c | |
| 267 | 267 | } | |
| 268 | 268 | ||
| 269 | - | func (_c *MockConfigService_FindAttachmentTargetsByImage_Call) Return(targets []string) *MockConfigService_FindAttachmentTargetsByImage_Call { | |
| 270 | - | _c.Call.Return(targets) | |
| 269 | + | func (_c *MockConfigService_FindRoutesByImage_Call) Return(routes []domain.Route) *MockConfigService_FindRoutesByImage_Call { | |
| 270 | + | _c.Call.Return(routes) | |
| 271 | 271 | return _c | |
| 272 | 272 | } | |
| 273 | 273 | ||
| 274 | - | func (_c *MockConfigService_FindAttachmentTargetsByImage_Call) RunAndReturn(run func(ctx context.Context, imageName string) []string) *MockConfigService_FindAttachmentTargetsByImage_Call { | |
| 274 | + | func (_c *MockConfigService_FindRoutesByImage_Call) RunAndReturn(run func(ctx context.Context, imageName string) []domain.Route) *MockConfigService_FindRoutesByImage_Call { | |
| 275 | 275 | _c.Call.Return(run) | |
| 276 | 276 | return _c | |
| 277 | 277 | } | |
| @@ -158,38 +158,38 @@ func (_c *MockConfigService_AddRoute_Call) RunAndReturn(run func(ctx context.Con | |||||
| 158 | return _c | 158 | return _c | ||
| 159 | } | 159 | } | ||
| 160 | 160 | ||||
| 161 | - | // FindRoutesByImage provides a mock function for the type MockConfigService | 161 | + | // FindAttachmentTargetsByImage provides a mock function for the type MockConfigService |
| 162 | - | func (_mock *MockConfigService) FindRoutesByImage(ctx context.Context, imageName string) []domain.Route { | 162 | + | func (_mock *MockConfigService) FindAttachmentTargetsByImage(ctx context.Context, imageName string) []string { |
| 163 | ret := _mock.Called(ctx, imageName) | 163 | ret := _mock.Called(ctx, imageName) | ||
| 164 | 164 | ||||
| 165 | if len(ret) == 0 { | 165 | if len(ret) == 0 { | ||
| 166 | - | panic("no return value specified for FindRoutesByImage") | 166 | + | panic("no return value specified for FindAttachmentTargetsByImage") |
| 167 | } | 167 | } | ||
| 168 | 168 | ||||
| 169 | - | var r0 []domain.Route | 169 | + | var r0 []string |
| 170 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, string) []domain.Route); ok { | 170 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) []string); ok { |
| 171 | r0 = returnFunc(ctx, imageName) | 171 | r0 = returnFunc(ctx, imageName) | ||
| 172 | } else { | 172 | } else { | ||
| 173 | if ret.Get(0) != nil { | 173 | if ret.Get(0) != nil { | ||
| 174 | - | r0 = ret.Get(0).([]domain.Route) | 174 | + | r0 = ret.Get(0).([]string) |
| 175 | } | 175 | } | ||
| 176 | } | 176 | } | ||
| 177 | return r0 | 177 | return r0 | ||
| 178 | } | 178 | } | ||
| 179 | 179 | ||||
| 180 | - | // MockConfigService_FindRoutesByImage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindRoutesByImage' | 180 | + | // MockConfigService_FindAttachmentTargetsByImage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindAttachmentTargetsByImage' |
| 181 | - | type MockConfigService_FindRoutesByImage_Call struct { | 181 | + | type MockConfigService_FindAttachmentTargetsByImage_Call struct { |
| 182 | *mock.Call | 182 | *mock.Call | ||
| 183 | } | 183 | } | ||
| 184 | 184 | ||||
| 185 | - | // FindRoutesByImage is a helper method to define mock.On call | 185 | + | // FindAttachmentTargetsByImage is a helper method to define mock.On call |
| 186 | // - ctx context.Context | 186 | // - ctx context.Context | ||
| 187 | // - imageName string | 187 | // - imageName string | ||
| 188 | - | func (_e *MockConfigService_Expecter) FindRoutesByImage(ctx interface{}, imageName interface{}) *MockConfigService_FindRoutesByImage_Call { | 188 | + | func (_e *MockConfigService_Expecter) FindAttachmentTargetsByImage(ctx interface{}, imageName interface{}) *MockConfigService_FindAttachmentTargetsByImage_Call { |
| 189 | - | return &MockConfigService_FindRoutesByImage_Call{Call: _e.mock.On("FindRoutesByImage", ctx, imageName)} | 189 | + | return &MockConfigService_FindAttachmentTargetsByImage_Call{Call: _e.mock.On("FindAttachmentTargetsByImage", ctx, imageName)} |
| 190 | } | 190 | } | ||
| 191 | 191 | ||||
| 192 | - | func (_c *MockConfigService_FindRoutesByImage_Call) Run(run func(ctx context.Context, imageName string)) *MockConfigService_FindRoutesByImage_Call { | 192 | + | func (_c *MockConfigService_FindAttachmentTargetsByImage_Call) Run(run func(ctx context.Context, imageName string)) *MockConfigService_FindAttachmentTargetsByImage_Call { |
| 193 | _c.Call.Run(func(args mock.Arguments) { | 193 | _c.Call.Run(func(args mock.Arguments) { | ||
| 194 | var arg0 context.Context | 194 | var arg0 context.Context | ||
| 195 | if args[0] != nil { | 195 | if args[0] != nil { | ||
| @@ -207,48 +207,48 @@ func (_c *MockConfigService_FindRoutesByImage_Call) Run(run func(ctx context.Con | |||||
| 207 | return _c | 207 | return _c | ||
| 208 | } | 208 | } | ||
| 209 | 209 | ||||
| 210 | - | func (_c *MockConfigService_FindRoutesByImage_Call) Return(routes []domain.Route) *MockConfigService_FindRoutesByImage_Call { | 210 | + | func (_c *MockConfigService_FindAttachmentTargetsByImage_Call) Return(strings []string) *MockConfigService_FindAttachmentTargetsByImage_Call { |
| 211 | - | _c.Call.Return(routes) | 211 | + | _c.Call.Return(strings) |
| 212 | return _c | 212 | return _c | ||
| 213 | } | 213 | } | ||
| 214 | 214 | ||||
| 215 | - | func (_c *MockConfigService_FindRoutesByImage_Call) RunAndReturn(run func(ctx context.Context, imageName string) []domain.Route) *MockConfigService_FindRoutesByImage_Call { | 215 | + | func (_c *MockConfigService_FindAttachmentTargetsByImage_Call) RunAndReturn(run func(ctx context.Context, imageName string) []string) *MockConfigService_FindAttachmentTargetsByImage_Call { |
| 216 | _c.Call.Return(run) | 216 | _c.Call.Return(run) | ||
| 217 | return _c | 217 | return _c | ||
| 218 | } | 218 | } | ||
| 219 | 219 | ||||
| 220 | - | // FindAttachmentTargetsByImage provides a mock function for the type MockConfigService | 220 | + | // FindRoutesByImage provides a mock function for the type MockConfigService |
| 221 | - | func (_mock *MockConfigService) FindAttachmentTargetsByImage(ctx context.Context, imageName string) []string { | 221 | + | func (_mock *MockConfigService) FindRoutesByImage(ctx context.Context, imageName string) []domain.Route { |
| 222 | ret := _mock.Called(ctx, imageName) | 222 | ret := _mock.Called(ctx, imageName) | ||
| 223 | 223 | ||||
| 224 | if len(ret) == 0 { | 224 | if len(ret) == 0 { | ||
| 225 | - | panic("no return value specified for FindAttachmentTargetsByImage") | 225 | + | panic("no return value specified for FindRoutesByImage") |
| 226 | } | 226 | } | ||
| 227 | 227 | ||||
| 228 | - | var r0 []string | 228 | + | var r0 []domain.Route |
| 229 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, string) []string); ok { | 229 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) []domain.Route); ok { |
| 230 | r0 = returnFunc(ctx, imageName) | 230 | r0 = returnFunc(ctx, imageName) | ||
| 231 | } else { | 231 | } else { | ||
| 232 | if ret.Get(0) != nil { | 232 | if ret.Get(0) != nil { | ||
| 233 | - | r0 = ret.Get(0).([]string) | 233 | + | r0 = ret.Get(0).([]domain.Route) |
| 234 | } | 234 | } | ||
| 235 | } | 235 | } | ||
| 236 | return r0 | 236 | return r0 | ||
| 237 | } | 237 | } | ||
| 238 | 238 | ||||
| 239 | - | // MockConfigService_FindAttachmentTargetsByImage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindAttachmentTargetsByImage' | 239 | + | // MockConfigService_FindRoutesByImage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindRoutesByImage' |
| 240 | - | type MockConfigService_FindAttachmentTargetsByImage_Call struct { | 240 | + | type MockConfigService_FindRoutesByImage_Call struct { |
| 241 | *mock.Call | 241 | *mock.Call | ||
| 242 | } | 242 | } | ||
| 243 | 243 | ||||
| 244 | - | // FindAttachmentTargetsByImage is a helper method to define mock.On call | 244 | + | // FindRoutesByImage is a helper method to define mock.On call |
| 245 | // - ctx context.Context | 245 | // - ctx context.Context | ||
| 246 | // - imageName string | 246 | // - imageName string | ||
| 247 | - | func (_e *MockConfigService_Expecter) FindAttachmentTargetsByImage(ctx interface{}, imageName interface{}) *MockConfigService_FindAttachmentTargetsByImage_Call { | 247 | + | func (_e *MockConfigService_Expecter) FindRoutesByImage(ctx interface{}, imageName interface{}) *MockConfigService_FindRoutesByImage_Call { |
| 248 | - | return &MockConfigService_FindAttachmentTargetsByImage_Call{Call: _e.mock.On("FindAttachmentTargetsByImage", ctx, imageName)} | 248 | + | return &MockConfigService_FindRoutesByImage_Call{Call: _e.mock.On("FindRoutesByImage", ctx, imageName)} |
| 249 | } | 249 | } | ||
| 250 | 250 | ||||
| 251 | - | func (_c *MockConfigService_FindAttachmentTargetsByImage_Call) Run(run func(ctx context.Context, imageName string)) *MockConfigService_FindAttachmentTargetsByImage_Call { | 251 | + | func (_c *MockConfigService_FindRoutesByImage_Call) Run(run func(ctx context.Context, imageName string)) *MockConfigService_FindRoutesByImage_Call { |
| 252 | _c.Call.Run(func(args mock.Arguments) { | 252 | _c.Call.Run(func(args mock.Arguments) { | ||
| 253 | var arg0 context.Context | 253 | var arg0 context.Context | ||
| 254 | if args[0] != nil { | 254 | if args[0] != nil { | ||
| @@ -266,12 +266,12 @@ func (_c *MockConfigService_FindAttachmentTargetsByImage_Call) Run(run func(ctx | |||||
| 266 | return _c | 266 | return _c | ||
| 267 | } | 267 | } | ||
| 268 | 268 | ||||
| 269 | - | func (_c *MockConfigService_FindAttachmentTargetsByImage_Call) Return(targets []string) *MockConfigService_FindAttachmentTargetsByImage_Call { | 269 | + | func (_c *MockConfigService_FindRoutesByImage_Call) Return(routes []domain.Route) *MockConfigService_FindRoutesByImage_Call { |
| 270 | - | _c.Call.Return(targets) | 270 | + | _c.Call.Return(routes) |
| 271 | return _c | 271 | return _c | ||
| 272 | } | 272 | } | ||
| 273 | 273 | ||||
| 274 | - | func (_c *MockConfigService_FindAttachmentTargetsByImage_Call) RunAndReturn(run func(ctx context.Context, imageName string) []string) *MockConfigService_FindAttachmentTargetsByImage_Call { | 274 | + | func (_c *MockConfigService_FindRoutesByImage_Call) RunAndReturn(run func(ctx context.Context, imageName string) []domain.Route) *MockConfigService_FindRoutesByImage_Call { |
| 275 | _c.Call.Return(run) | 275 | _c.Call.Return(run) | ||
| 276 | return _c | 276 | return _c | ||
| 277 | } | 277 | } | ||
M pkg/registrypush/push.go +28 -1
| @@ -4,6 +4,7 @@ import ( | |||
| 4 | 4 | "bytes" | |
| 5 | 5 | "context" | |
| 6 | 6 | "crypto/sha256" | |
| 7 | + | "crypto/tls" | |
| 7 | 8 | "encoding/base64" | |
| 8 | 9 | "errors" | |
| 9 | 10 | "fmt" | |
| @@ -33,6 +34,7 @@ type ImageSource func(ctx context.Context, ref string) (v1.Image, error) | |||
| 33 | 34 | type Pusher struct { | |
| 34 | 35 | chunkSize int64 | |
| 35 | 36 | transport http.RoundTripper | |
| 37 | + | insecureTLS bool | |
| 36 | 38 | imageSource ImageSource | |
| 37 | 39 | progress io.Writer | |
| 38 | 40 | } | |
| @@ -50,6 +52,12 @@ func WithTransport(t http.RoundTripper) Option { | |||
| 50 | 52 | return func(p *Pusher) { p.transport = t } | |
| 51 | 53 | } | |
| 52 | 54 | ||
| 55 | + | // WithInsecureTLS disables TLS certificate verification for registry requests. | |
| 56 | + | // This is ignored when a custom transport is provided via WithTransport. | |
| 57 | + | func WithInsecureTLS(insecure bool) Option { | |
| 58 | + | return func(p *Pusher) { p.insecureTLS = insecure } | |
| 59 | + | } | |
| 60 | + | ||
| 53 | 61 | // WithImageSource overrides the default daemon-based image reader. | |
| 54 | 62 | func WithImageSource(src ImageSource) Option { | |
| 55 | 63 | return func(p *Pusher) { p.imageSource = src } | |
| @@ -67,7 +75,11 @@ func New(opts ...Option) *Pusher { | |||
| 67 | 75 | o(p) | |
| 68 | 76 | } | |
| 69 | 77 | if p.transport == nil { | |
| 70 | - | p.transport = http.DefaultTransport | |
| 78 | + | if p.insecureTLS { | |
| 79 | + | p.transport = insecureTransport() | |
| 80 | + | } else { | |
| 81 | + | p.transport = http.DefaultTransport | |
| 82 | + | } | |
| 71 | 83 | } | |
| 72 | 84 | if p.imageSource == nil { | |
| 73 | 85 | p.imageSource = defaultImageSource | |
| @@ -500,6 +512,21 @@ func chunkCount(size, chunkSize int64) int64 { | |||
| 500 | 512 | return (size + chunkSize - 1) / chunkSize | |
| 501 | 513 | } | |
| 502 | 514 | ||
| 515 | + | func insecureTransport() http.RoundTripper { | |
| 516 | + | var transport *http.Transport | |
| 517 | + | if t, ok := http.DefaultTransport.(*http.Transport); ok { | |
| 518 | + | transport = t.Clone() | |
| 519 | + | } else { | |
| 520 | + | transport = &http.Transport{} | |
| 521 | + | } | |
| 522 | + | if transport.TLSClientConfig == nil { | |
| 523 | + | transport.TLSClientConfig = &tls.Config{MinVersion: tls.VersionTLS12} | |
| 524 | + | } | |
| 525 | + | //nolint:gosec // Explicit CLI opt-in via --insecure for self-signed/private cert deployments. | |
| 526 | + | transport.TLSClientConfig.InsecureSkipVerify = true | |
| 527 | + | return transport | |
| 528 | + | } | |
| 529 | + | ||
| 503 | 530 | func bytesReader(chunk []byte) io.Reader { | |
| 504 | 531 | return bytes.NewReader(chunk) | |
| 505 | 532 | } | |
| @@ -4,6 +4,7 @@ import ( | |||||
| 4 | "bytes" | 4 | "bytes" | ||
| 5 | "context" | 5 | "context" | ||
| 6 | "crypto/sha256" | 6 | "crypto/sha256" | ||
| 7 | + | "crypto/tls" | |||
| 7 | "encoding/base64" | 8 | "encoding/base64" | ||
| 8 | "errors" | 9 | "errors" | ||
| 9 | "fmt" | 10 | "fmt" | ||
| @@ -33,6 +34,7 @@ type ImageSource func(ctx context.Context, ref string) (v1.Image, error) | |||||
| 33 | type Pusher struct { | 34 | type Pusher struct { | ||
| 34 | chunkSize int64 | 35 | chunkSize int64 | ||
| 35 | transport http.RoundTripper | 36 | transport http.RoundTripper | ||
| 37 | + | insecureTLS bool | |||
| 36 | imageSource ImageSource | 38 | imageSource ImageSource | ||
| 37 | progress io.Writer | 39 | progress io.Writer | ||
| 38 | } | 40 | } | ||
| @@ -50,6 +52,12 @@ func WithTransport(t http.RoundTripper) Option { | |||||
| 50 | return func(p *Pusher) { p.transport = t } | 52 | return func(p *Pusher) { p.transport = t } | ||
| 51 | } | 53 | } | ||
| 52 | 54 | ||||
| 55 | + | // WithInsecureTLS disables TLS certificate verification for registry requests. | |||
| 56 | + | // This is ignored when a custom transport is provided via WithTransport. | |||
| 57 | + | func WithInsecureTLS(insecure bool) Option { | |||
| 58 | + | return func(p *Pusher) { p.insecureTLS = insecure } | |||
| 59 | + | } | |||
| 60 | + | ||||
| 53 | // WithImageSource overrides the default daemon-based image reader. | 61 | // WithImageSource overrides the default daemon-based image reader. | ||
| 54 | func WithImageSource(src ImageSource) Option { | 62 | func WithImageSource(src ImageSource) Option { | ||
| 55 | return func(p *Pusher) { p.imageSource = src } | 63 | return func(p *Pusher) { p.imageSource = src } | ||
| @@ -67,7 +75,11 @@ func New(opts ...Option) *Pusher { | |||||
| 67 | o(p) | 75 | o(p) | ||
| 68 | } | 76 | } | ||
| 69 | if p.transport == nil { | 77 | if p.transport == nil { | ||
| 70 | - | p.transport = http.DefaultTransport | 78 | + | if p.insecureTLS { |
| 79 | + | p.transport = insecureTransport() | |||
| 80 | + | } else { | |||
| 81 | + | p.transport = http.DefaultTransport | |||
| 82 | + | } | |||
| 71 | } | 83 | } | ||
| 72 | if p.imageSource == nil { | 84 | if p.imageSource == nil { | ||
| 73 | p.imageSource = defaultImageSource | 85 | p.imageSource = defaultImageSource | ||
| @@ -500,6 +512,21 @@ func chunkCount(size, chunkSize int64) int64 { | |||||
| 500 | return (size + chunkSize - 1) / chunkSize | 512 | return (size + chunkSize - 1) / chunkSize | ||
| 501 | } | 513 | } | ||
| 502 | 514 | ||||
| 515 | + | func insecureTransport() http.RoundTripper { | |||
| 516 | + | var transport *http.Transport | |||
| 517 | + | if t, ok := http.DefaultTransport.(*http.Transport); ok { | |||
| 518 | + | transport = t.Clone() | |||
| 519 | + | } else { | |||
| 520 | + | transport = &http.Transport{} | |||
| 521 | + | } | |||
| 522 | + | if transport.TLSClientConfig == nil { | |||
| 523 | + | transport.TLSClientConfig = &tls.Config{MinVersion: tls.VersionTLS12} | |||
| 524 | + | } | |||
| 525 | + | //nolint:gosec // Explicit CLI opt-in via --insecure for self-signed/private cert deployments. | |||
| 526 | + | transport.TLSClientConfig.InsecureSkipVerify = true | |||
| 527 | + | return transport | |||
| 528 | + | } | |||
| 529 | + | ||||
| 503 | func bytesReader(chunk []byte) io.Reader { | 530 | func bytesReader(chunk []byte) io.Reader { | ||
| 504 | return bytes.NewReader(chunk) | 531 | return bytes.NewReader(chunk) | ||
| 505 | } | 532 | } | ||
M pkg/registrypush/push_test.go +39 -0
| @@ -217,6 +217,45 @@ func TestPusher_Push(t *testing.T) { | |||
| 217 | 217 | } | |
| 218 | 218 | } | |
| 219 | 219 | ||
| 220 | + | func TestPusher_WithInsecureTLS_AllowsSelfSignedCertificate(t *testing.T) { | |
| 221 | + | t.Parallel() | |
| 222 | + | ||
| 223 | + | srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| 224 | + | if r.Method == http.MethodHead { | |
| 225 | + | w.WriteHeader(http.StatusNotFound) | |
| 226 | + | return | |
| 227 | + | } | |
| 228 | + | if r.Method == http.MethodPost { | |
| 229 | + | w.Header().Set("Location", "/v2/repo/blobs/uploads/test-uuid") | |
| 230 | + | w.WriteHeader(http.StatusAccepted) | |
| 231 | + | return | |
| 232 | + | } | |
| 233 | + | if r.Method == http.MethodPatch { | |
| 234 | + | w.Header().Set("Location", "/v2/repo/blobs/uploads/test-uuid") | |
| 235 | + | w.WriteHeader(http.StatusAccepted) | |
| 236 | + | return | |
| 237 | + | } | |
| 238 | + | if r.Method == http.MethodPut { | |
| 239 | + | w.WriteHeader(http.StatusCreated) | |
| 240 | + | return | |
| 241 | + | } | |
| 242 | + | })) | |
| 243 | + | defer srv.Close() | |
| 244 | + | ||
| 245 | + | ctx := context.Background() | |
| 246 | + | content := []byte("test-blob-data") | |
| 247 | + | digest := fmt.Sprintf("sha256:%x", sha256.Sum256(content)) | |
| 248 | + | ||
| 249 | + | pusherSecure := registrypush.New(registrypush.WithChunkSize(1024)) | |
| 250 | + | err := pusherSecure.UploadBlob(ctx, srv.URL, "repo", digest, int64(len(content)), bytes.NewReader(content), "") | |
| 251 | + | require.Error(t, err) | |
| 252 | + | assert.Contains(t, err.Error(), "certificate") | |
| 253 | + | ||
| 254 | + | pusherInsecure := registrypush.New(registrypush.WithChunkSize(1024), registrypush.WithInsecureTLS(true)) | |
| 255 | + | err = pusherInsecure.UploadBlob(ctx, srv.URL, "repo", digest, int64(len(content)), bytes.NewReader(content), "") | |
| 256 | + | require.NoError(t, err) | |
| 257 | + | } | |
| 258 | + | ||
| 220 | 259 | type fakeBlobUploadRegistry struct { | |
| 221 | 260 | t *testing.T | |
| 222 | 261 | expectedAuth string | |
| @@ -217,6 +217,45 @@ func TestPusher_Push(t *testing.T) { | |||||
| 217 | } | 217 | } | ||
| 218 | } | 218 | } | ||
| 219 | 219 | ||||
| 220 | + | func TestPusher_WithInsecureTLS_AllowsSelfSignedCertificate(t *testing.T) { | |||
| 221 | + | t.Parallel() | |||
| 222 | + | ||||
| 223 | + | srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||
| 224 | + | if r.Method == http.MethodHead { | |||
| 225 | + | w.WriteHeader(http.StatusNotFound) | |||
| 226 | + | return | |||
| 227 | + | } | |||
| 228 | + | if r.Method == http.MethodPost { | |||
| 229 | + | w.Header().Set("Location", "/v2/repo/blobs/uploads/test-uuid") | |||
| 230 | + | w.WriteHeader(http.StatusAccepted) | |||
| 231 | + | return | |||
| 232 | + | } | |||
| 233 | + | if r.Method == http.MethodPatch { | |||
| 234 | + | w.Header().Set("Location", "/v2/repo/blobs/uploads/test-uuid") | |||
| 235 | + | w.WriteHeader(http.StatusAccepted) | |||
| 236 | + | return | |||
| 237 | + | } | |||
| 238 | + | if r.Method == http.MethodPut { | |||
| 239 | + | w.WriteHeader(http.StatusCreated) | |||
| 240 | + | return | |||
| 241 | + | } | |||
| 242 | + | })) | |||
| 243 | + | defer srv.Close() | |||
| 244 | + | ||||
| 245 | + | ctx := context.Background() | |||
| 246 | + | content := []byte("test-blob-data") | |||
| 247 | + | digest := fmt.Sprintf("sha256:%x", sha256.Sum256(content)) | |||
| 248 | + | ||||
| 249 | + | pusherSecure := registrypush.New(registrypush.WithChunkSize(1024)) | |||
| 250 | + | err := pusherSecure.UploadBlob(ctx, srv.URL, "repo", digest, int64(len(content)), bytes.NewReader(content), "") | |||
| 251 | + | require.Error(t, err) | |||
| 252 | + | assert.Contains(t, err.Error(), "certificate") | |||
| 253 | + | ||||
| 254 | + | pusherInsecure := registrypush.New(registrypush.WithChunkSize(1024), registrypush.WithInsecureTLS(true)) | |||
| 255 | + | err = pusherInsecure.UploadBlob(ctx, srv.URL, "repo", digest, int64(len(content)), bytes.NewReader(content), "") | |||
| 256 | + | require.NoError(t, err) | |||
| 257 | + | } | |||
| 258 | + | ||||
| 220 | type fakeBlobUploadRegistry struct { | 259 | type fakeBlobUploadRegistry struct { | ||
| 221 | t *testing.T | 260 | t *testing.T | ||
| 222 | expectedAuth string | 261 | expectedAuth string | ||