diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b964f7ee..8f2705a1 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -100,6 +100,7 @@ jobs: runs-on: [self-hosted, regular] container: image: ubuntu:22.04 + name: Test steps: - name: Install dependency for linux-amd64 dist env: diff --git a/cmd/plugins/plugins.go b/cmd/plugins/plugins.go index bed54575..544142b5 100644 --- a/cmd/plugins/plugins.go +++ b/cmd/plugins/plugins.go @@ -34,10 +34,10 @@ import ( "sigs.k8s.io/yaml" dkplog "github.com/deckhouse/deckhouse/pkg/log" + client "github.com/deckhouse/deckhouse/pkg/registry" "github.com/deckhouse/deckhouse-cli/cmd/plugins/flags" "github.com/deckhouse/deckhouse-cli/internal" - client "github.com/deckhouse/deckhouse-cli/pkg/registry" "github.com/deckhouse/deckhouse-cli/pkg/registry/service" ) diff --git a/go.mod b/go.mod index f57bdfa2..7d907b97 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.13 require ( github.com/Masterminds/semver/v3 v3.3.1 github.com/deckhouse/deckhouse/pkg/log v0.2.0 - github.com/deckhouse/deckhouse/pkg/registry v0.0.0-20260326143935-b535ad6cb730 + github.com/deckhouse/deckhouse/pkg/registry v0.0.0-20260414112803-53a5662881d9 github.com/deckhouse/virtualization/src/cli v1.5.1 github.com/fatih/color v1.18.0 github.com/fluxcd/flagger v1.36.1 diff --git a/go.sum b/go.sum index 6663b739..a388bb87 100644 --- a/go.sum +++ b/go.sum @@ -420,8 +420,10 @@ github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80N github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/deckhouse/deckhouse/pkg/log v0.2.0 h1:6tmZQLwNb1o/hP1gzJQBjcwfA/bubbgObovXzxq+Exo= github.com/deckhouse/deckhouse/pkg/log v0.2.0/go.mod h1:pbAxTSDcPmwyl3wwKDcEB3qdxHnRxqTV+J0K+sha8bw= -github.com/deckhouse/deckhouse/pkg/registry v0.0.0-20260326143935-b535ad6cb730 h1:kJs7prT4Aq/Ek5j5LO0dmIytTSPuMqfxpYK5L3tDYR0= -github.com/deckhouse/deckhouse/pkg/registry v0.0.0-20260326143935-b535ad6cb730/go.mod h1:KDf44MqEif8jAKCehKJqOg0k4sJcnetKJKDGd0IFQjI= +github.com/deckhouse/deckhouse/pkg/registry v0.0.0-20260414071219-2e3c2ca0fbaf h1:TqeCxB/ys6yx+pPB94rBJf+DbvbuamQxUFcu5dNWiRc= +github.com/deckhouse/deckhouse/pkg/registry v0.0.0-20260414071219-2e3c2ca0fbaf/go.mod h1:KDf44MqEif8jAKCehKJqOg0k4sJcnetKJKDGd0IFQjI= +github.com/deckhouse/deckhouse/pkg/registry v0.0.0-20260414112803-53a5662881d9 h1:Il2d6wB6SdgjmD5ojC48qT9eQyITuANzIFjSd0DCdUI= +github.com/deckhouse/deckhouse/pkg/registry v0.0.0-20260414112803-53a5662881d9/go.mod h1:KDf44MqEif8jAKCehKJqOg0k4sJcnetKJKDGd0IFQjI= github.com/deckhouse/delivery-kit-sdk v1.0.1-0.20251022121655-0cbac8223333 h1:9YuKuSrRfTSdAaLIUwi0t4bkedoNgj3IMFXPcP/rkSo= github.com/deckhouse/delivery-kit-sdk v1.0.1-0.20251022121655-0cbac8223333/go.mod h1:iukQB9dt5DasWCI/XkjeyD5KtTJtHp5DMCGN8IS5PqA= github.com/deckhouse/delivery-kit/v2 v2.63.1-dk h1:1YDVwEJMoEfiNGdEuiuXIfyJ3Ux8evnCsmJYZrJBmHU= diff --git a/internal/mirror/cmd/pull/pull.go b/internal/mirror/cmd/pull/pull.go index fa058a58..f18a123d 100644 --- a/internal/mirror/cmd/pull/pull.go +++ b/internal/mirror/cmd/pull/pull.go @@ -46,11 +46,12 @@ import ( "github.com/deckhouse/deckhouse-cli/internal/mirror/modules" "github.com/deckhouse/deckhouse-cli/internal/mirror/validation" "github.com/deckhouse/deckhouse-cli/internal/version" + "github.com/deckhouse/deckhouse-cli/pkg" + "github.com/deckhouse/deckhouse-cli/pkg/fake" "github.com/deckhouse/deckhouse-cli/pkg/libmirror/operations/params" "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log" pkgclient "github.com/deckhouse/deckhouse-cli/pkg/registry/client" registryservice "github.com/deckhouse/deckhouse-cli/pkg/registry/service" - "github.com/deckhouse/deckhouse-cli/pkg/stub" ) var ErrPullFailed = errors.New("pull failed, see the log for details") @@ -223,6 +224,7 @@ func NewPuller(cmd *cobra.Command) *Puller { }, } } + func (p *Puller) Execute(ctx context.Context) error { if err := p.cleanupWorkingDirectory(); err != nil { return err @@ -256,7 +258,10 @@ func (p *Puller) Execute(ctx context.Context) error { c := pkgclient.NewFromOptions(repo, clientOpts...) if os.Getenv("STUB_REGISTRY_CLIENT") == "true" { - c = stub.NewRegistryClientStub() + c = fake.NewRegistryClientStub() + // The stub's root URL already includes the edition path segment, so we + // must not add it again via registryservice.NewService. + edition = pkg.NoEdition } // Scope to the registry path and modules suffix diff --git a/internal/mirror/installer/installer_dryrun_test.go b/internal/mirror/installer/installer_dryrun_test.go index b7cb3807..6449f5fa 100644 --- a/internal/mirror/installer/installer_dryrun_test.go +++ b/internal/mirror/installer/installer_dryrun_test.go @@ -31,7 +31,7 @@ import ( "github.com/deckhouse/deckhouse-cli/pkg" "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log" registryservice "github.com/deckhouse/deckhouse-cli/pkg/registry/service" - "github.com/deckhouse/deckhouse-cli/pkg/stub" + "github.com/deckhouse/deckhouse-cli/pkg/fake" ) // TestDryRun_NoBundleFilesWritten verifies that PullInstaller in dry-run mode @@ -40,7 +40,7 @@ func TestDryRun_NoBundleFilesWritten(t *testing.T) { workingDir := t.TempDir() bundleDir := t.TempDir() - stubClient := stub.NewRegistryClientStub() + stubClient := fake.NewRegistryClientStub() logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) userLogger := log.NewSLogger(slog.LevelWarn) @@ -73,7 +73,7 @@ func TestDryRun_WorkingDirHasLayouts(t *testing.T) { workingDir := t.TempDir() bundleDir := t.TempDir() - stubClient := stub.NewRegistryClientStub() + stubClient := fake.NewRegistryClientStub() logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) userLogger := log.NewSLogger(slog.LevelWarn) diff --git a/internal/mirror/modules/modules_dryrun_test.go b/internal/mirror/modules/modules_dryrun_test.go index df69bea0..5779a0e5 100644 --- a/internal/mirror/modules/modules_dryrun_test.go +++ b/internal/mirror/modules/modules_dryrun_test.go @@ -30,7 +30,7 @@ import ( "github.com/deckhouse/deckhouse-cli/pkg" "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log" registryservice "github.com/deckhouse/deckhouse-cli/pkg/registry/service" - "github.com/deckhouse/deckhouse-cli/pkg/stub" + "github.com/deckhouse/deckhouse-cli/pkg/fake" ) // TestDryRun_NoBundleFilesWritten verifies that PullModules in dry-run mode does @@ -39,7 +39,7 @@ func TestDryRun_NoBundleFilesWritten(t *testing.T) { workingDir := t.TempDir() bundleDir := t.TempDir() - stubClient := stub.NewRegistryClientStub() + stubClient := fake.NewRegistryClientStub() logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) userLogger := log.NewSLogger(slog.LevelWarn) @@ -71,7 +71,7 @@ func TestDryRun_WorkingDirHasLayouts(t *testing.T) { workingDir := t.TempDir() bundleDir := t.TempDir() - stubClient := stub.NewRegistryClientStub() + stubClient := fake.NewRegistryClientStub() logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) userLogger := log.NewSLogger(slog.LevelWarn) diff --git a/internal/mirror/modules/validate_access_test.go b/internal/mirror/modules/validate_access_test.go new file mode 100644 index 00000000..66460de6 --- /dev/null +++ b/internal/mirror/modules/validate_access_test.go @@ -0,0 +1,188 @@ +/* +Copyright 2026 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package modules + +import ( + "context" + "log/slog" + "testing" + + "github.com/stretchr/testify/require" + + dkplog "github.com/deckhouse/deckhouse/pkg/log" + + "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log" + registryservice "github.com/deckhouse/deckhouse-cli/pkg/registry/service" + upfake "github.com/deckhouse/deckhouse/pkg/registry/fake" + pkgclient "github.com/deckhouse/deckhouse-cli/pkg/registry/client" +) + +func TestService_validateModulesAccess(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + t.Run("modules present in registry returns no error", func(t *testing.T) { + // Build a stub where the "modules" repository has module names as tags. + reg := upfake.NewRegistry("registry.example.com") + placeholder := upfake.NewImageBuilder().MustBuild() + reg.MustAddImage("modules", "console", placeholder) + reg.MustAddImage("modules", "ingress-nginx", placeholder) + reg.MustAddImage("modules", "cert-manager", placeholder) + + stubClient := pkgclient.Adapt(upfake.NewClient(reg)) + // Scope client to "modules" so that ListTags returns module names. + modulesClient := stubClient.WithSegment("modules") + modulesService := registryservice.NewModulesService(modulesClient, logger) + + svc := &Service{ + modulesService: modulesService, + options: &Options{}, + logger: logger, + userLogger: userLogger, + } + + err := svc.validateModulesAccess(context.Background()) + require.NoError(t, err) + }) + + t.Run("modules repository absent in registry returns no error and emits warning", func(t *testing.T) { + // Empty registry – the "modules" repo does not exist, so ListTags returns + // ErrImageNotFound which validateModulesAccess treats as a graceful skip. + reg := upfake.NewRegistry("registry.example.com") + stubClient := pkgclient.Adapt(upfake.NewClient(reg)) + modulesClient := stubClient.WithSegment("modules") + modulesService := registryservice.NewModulesService(modulesClient, logger) + + svc := &Service{ + modulesService: modulesService, + options: &Options{}, + logger: logger, + userLogger: userLogger, + } + + err := svc.validateModulesAccess(context.Background()) + require.NoError(t, err) + }) +} + +func TestService_validateModulesAccess_WithFilter(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + reg := upfake.NewRegistry("registry.example.com") + placeholder := upfake.NewImageBuilder().MustBuild() + reg.MustAddImage("modules", "console", placeholder) + reg.MustAddImage("modules", "ingress-nginx", placeholder) + reg.MustAddImage("modules", "sds-replicated-volume", placeholder) + + stubClient := pkgclient.Adapt(upfake.NewClient(reg)) + modulesClient := stubClient.WithSegment("modules") + modulesService := registryservice.NewModulesService(modulesClient, logger) + + // validateModulesAccess does not use the filter; it only checks reachability. + // The filter is applied later in pullModules. We verify here that any valid + // filter configuration does not affect the access check result. + tests := []struct { + name string + filter *Filter + }{ + { + name: "whitelist subset of modules", + filter: func() *Filter { + f, _ := NewFilter([]string{"console", "ingress-nginx"}, FilterTypeWhitelist) + return f + }(), + }, + { + name: "blacklist single module", + filter: func() *Filter { + f, _ := NewFilter([]string{"sds-replicated-volume"}, FilterTypeBlacklist) + return f + }(), + }, + { + name: "empty blacklist accepts all", + filter: func() *Filter { + f, _ := NewFilter(nil, FilterTypeBlacklist) + return f + }(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + svc := &Service{ + modulesService: modulesService, + options: &Options{Filter: tt.filter}, + logger: logger, + userLogger: userLogger, + } + + err := svc.validateModulesAccess(context.Background()) + require.NoError(t, err) + }) + } +} + +func TestService_validateModulesAccess_Timeout(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + reg := upfake.NewRegistry("registry.example.com") + placeholder := upfake.NewImageBuilder().MustBuild() + reg.MustAddImage("modules", "console", placeholder) + + stubClient := pkgclient.Adapt(upfake.NewClient(reg)) + modulesClient := stubClient.WithSegment("modules") + modulesService := registryservice.NewModulesService(modulesClient, logger) + + // Even with a Timeout option set, validateModulesAccess should succeed + // (the timeout is applied at the network level, not in the stub). + svc := &Service{ + modulesService: modulesService, + options: &Options{}, + logger: logger, + userLogger: userLogger, + } + + err := svc.validateModulesAccess(context.Background()) + require.NoError(t, err) +} + +// TestModule_Versions verifies that Module.Versions() correctly parses semver +// release tags and ignores non-semver strings (such as channel names). +func TestModule_Versions(t *testing.T) { + _ = log.NewSLogger(slog.LevelDebug) // silence unused-import lint + + mod := &Module{ + Name: "console", + Releases: []string{ + "alpha", "beta", "early-access", "stable", "rock-solid", // non-semver + "v1.0.0", "v1.1.0", "v1.2.3", // semver + "notasemver", + }, + } + + versions := mod.Versions() + got := make([]string, 0, len(versions)) + for _, v := range versions { + got = append(got, "v"+v.String()) + } + + want := []string{"v1.0.0", "v1.1.0", "v1.2.3"} + require.ElementsMatch(t, want, got) +} diff --git a/internal/mirror/platform/platform_dryrun_test.go b/internal/mirror/platform/platform_dryrun_test.go index 40bc7a30..38eb9c94 100644 --- a/internal/mirror/platform/platform_dryrun_test.go +++ b/internal/mirror/platform/platform_dryrun_test.go @@ -28,33 +28,23 @@ import ( dkplog "github.com/deckhouse/deckhouse/pkg/log" - "github.com/deckhouse/deckhouse-cli/pkg" "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log" registryservice "github.com/deckhouse/deckhouse-cli/pkg/registry/service" - "github.com/deckhouse/deckhouse-cli/pkg/stub" + "github.com/deckhouse/deckhouse-cli/pkg/fake" ) // TestDryRun_NoBundleFilesWritten verifies that PullPlatform in dry-run mode does -// not write any files to the bundle directory. Temporary OCI layout data may only -// land under the working/tmp directory. +// not write any files to the bundle directory. func TestDryRun_NoBundleFilesWritten(t *testing.T) { - workingDir := t.TempDir() bundleDir := t.TempDir() // must stay empty after dry-run - stubClient := stub.NewRegistryClientStub() + stubClient := fake.NewRegistryClientStub() logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) userLogger := log.NewSLogger(slog.LevelWarn) - regSvc := registryservice.NewService(stubClient, pkg.FEEdition, logger) - - svc := NewService( - regSvc, - workingDir, - &Options{ - TargetTag: "v1.69.0", - BundleDir: bundleDir, - DryRun: true, - }, + svc := newTestPlatformService( + stubClient, + &Options{TargetTag: "v1.69.0", BundleDir: bundleDir, DryRun: true}, logger, userLogger, ) @@ -68,40 +58,34 @@ func TestDryRun_NoBundleFilesWritten(t *testing.T) { assert.Empty(t, entries, "dry-run must not write any files to the bundle directory; found: %v", entries) } -// TestDryRun_InstallerPulledToTmpDir verifies that in dry-run mode the installer -// image IS pulled into the working (tmp) directory so that images_digests.json can -// be read from it. This produces the complete list of images that would be -// downloaded in a real run. -func TestDryRun_InstallerPulledToTmpDir(t *testing.T) { +// TestDryRun_NoOCILayoutCreated verifies that in dry-run mode no OCI image layout +// directories are created under the working directory. images_digests.json is +// streamed directly from the remote registry without writing anything to disk. +func TestDryRun_NoOCILayoutCreated(t *testing.T) { workingDir := t.TempDir() bundleDir := t.TempDir() - stubClient := stub.NewRegistryClientStub() + stubClient := fake.NewRegistryClientStub() logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) userLogger := log.NewSLogger(slog.LevelWarn) - regSvc := registryservice.NewService(stubClient, pkg.FEEdition, logger) - - svc := NewService( - regSvc, - workingDir, - &Options{ - TargetTag: "v1.69.0", - BundleDir: bundleDir, - DryRun: true, - }, - logger, - userLogger, - ) + deckhouseSvc := registryservice.NewDeckhouseService(stubClient, logger) + svc := &Service{ + deckhouseService: deckhouseSvc, + downloadList: NewImageDownloadList(stubClient.GetRegistry()), + options: &Options{TargetTag: "v1.69.0", BundleDir: bundleDir, DryRun: true}, + logger: logger, + userLogger: userLogger, + } err := svc.PullPlatform(context.Background()) require.NoError(t, err) - // In optimized dry-run, no OCI layout is created - images_digests.json is - // streamed directly from the remote registry via ExtractFileFromImage. + // No OCI layout directories should be created. installerLayoutDir := filepath.Join(workingDir, "platform", "install") _, statErr := os.Stat(installerLayoutDir) - assert.ErrorIs(t, statErr, os.ErrNotExist, "installer OCI layout must NOT be created in dry-run; dir: %s", installerLayoutDir) + assert.ErrorIs(t, statErr, os.ErrNotExist, + "installer OCI layout must NOT be created in dry-run") // bundleDir must remain empty entries, err := os.ReadDir(bundleDir) diff --git a/internal/mirror/platform/platform_test.go b/internal/mirror/platform/platform_test.go index 162c073c..04bcb758 100644 --- a/internal/mirror/platform/platform_test.go +++ b/internal/mirror/platform/platform_test.go @@ -29,7 +29,7 @@ import ( "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log" registryservice "github.com/deckhouse/deckhouse-cli/pkg/registry/service" - "github.com/deckhouse/deckhouse-cli/pkg/stub" + "github.com/deckhouse/deckhouse-cli/pkg/fake" ) func TestService_versionsToMirror(t *testing.T) { @@ -141,7 +141,7 @@ func TestService_versionsToMirror(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create stub registry client - stubClient := stub.NewRegistryClientStub() + stubClient := fake.NewRegistryClientStub() // Create logger logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelInfo)) @@ -193,7 +193,7 @@ func TestService_versionsToMirror(t *testing.T) { func TestService_versionsToMirror_WithTargetTag(t *testing.T) { // Create stub registry client - stubClient := stub.NewRegistryClientStub() + stubClient := fake.NewRegistryClientStub() // Create logger logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelInfo)) @@ -234,7 +234,7 @@ func TestService_versionsToMirror_WithTargetTag(t *testing.T) { func TestService_versionsToMirror_CustomTagWithSemver(t *testing.T) { // Create stub registry client - stubClient := stub.NewRegistryClientStub() + stubClient := fake.NewRegistryClientStub() // Create logger logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelInfo)) @@ -272,7 +272,7 @@ func TestService_versionsToMirror_CustomTagWithSemver(t *testing.T) { func TestService_versionsToMirror_Deduplication(t *testing.T) { // Create stub registry client - stubClient := stub.NewRegistryClientStub() + stubClient := fake.NewRegistryClientStub() // Create logger logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelInfo)) diff --git a/internal/mirror/platform/pull_platform_test.go b/internal/mirror/platform/pull_platform_test.go new file mode 100644 index 00000000..a1940751 --- /dev/null +++ b/internal/mirror/platform/pull_platform_test.go @@ -0,0 +1,425 @@ +// Copyright 2025 Flant JSC +// SPDX-License-Identifier: Apache-2.0 + +package platform + +import ( +"context" +"fmt" +"log/slog" +"testing" + +"github.com/Masterminds/semver/v3" +"github.com/stretchr/testify/assert" +"github.com/stretchr/testify/require" + +dkplog "github.com/deckhouse/deckhouse/pkg/log" + +"github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log" +localreg "github.com/deckhouse/deckhouse/pkg/registry" +registryservice "github.com/deckhouse/deckhouse-cli/pkg/registry/service" +upfake "github.com/deckhouse/deckhouse/pkg/registry/fake" + localfake "github.com/deckhouse/deckhouse-cli/pkg/fake" + pkgclient "github.com/deckhouse/deckhouse-cli/pkg/registry/client" +) + +// stubRootURL is the host used by NewRegistryClientStub. +const stubRootURL = "registry.deckhouse.ru/deckhouse/fe" + +// newDryRunService builds a Service in DryRun mode backed by stubClient. +// layout and pullerService are intentionally nil — DryRun never uses them. +func newDryRunService( +stubClient localreg.Client, +options *Options, +logger *dkplog.Logger, +userLogger *log.SLogger, +) *Service { + if options == nil { + options = &Options{} + } + options.DryRun = true + return &Service{ + deckhouseService: registryservice.NewDeckhouseService(stubClient, logger), + downloadList: NewImageDownloadList(stubClient.GetRegistry()), + options: options, + logger: logger, + userLogger: userLogger, + } +} + +// suspendedStub returns a stub where the alpha release channel is suspended +// (version.json contains "suspend": true). +func suspendedStub() localreg.Client { + // reuse the full stub but replace the alpha release-channel image + reg := upfake.NewRegistry(stubRootURL) + + // Standard channels — alpha is suspended. + channels := map[string]struct { + version string + suspend bool + }{ + "alpha": {version: "v1.72.10", suspend: true}, + "beta": {version: "v1.71.0"}, + "early-access": {version: "v1.70.0"}, + "stable": {version: "v1.69.0"}, + "rock-solid": {version: "v1.68.0"}, + } + for ch, data := range channels { + content := fmt.Sprintf(`{"version":%q,"suspend":%v}`, data.version, data.suspend) + img := upfake.NewImageBuilder().WithFile("version.json", content).MustBuild() + reg.MustAddImage("release-channel", ch, img) + } + + // Root + installer repos (same as default stub). + versionTags := []string{"alpha", "beta", "early-access", "stable", "rock-solid", + "v1.72.10", "v1.71.0", "v1.70.0", "v1.69.0", "v1.68.0", "pr12345"} + for _, tag := range versionTags { + img := upfake.NewImageBuilder(). + WithFile("version.json", `{"version":"v1.72.10"}`). + WithFile("deckhouse/candi/images_digests.json", `{}`). + MustBuild() + reg.MustAddImage("", tag, img) + reg.MustAddImage("install", tag, img) + reg.MustAddImage("install-standalone", tag, img) + } + return pkgclient.Adapt(upfake.NewClient(reg)) +} + +// ---- validatePlatformAccess behaviour ---- + +func TestPullPlatform_ErrorWhenRegistryEmpty(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + svc := newDryRunService(emptyStub(), nil, logger, userLogger) + err := svc.PullPlatform(context.Background()) + + require.Error(t, err) + assert.Contains(t, err.Error(), "validate platform access") +} + +func TestPullPlatform_ErrorWhenSemverTagMissingInRegistry(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + svc := newDryRunService(localfake.NewRegistryClientStub(), &Options{TargetTag: "v9.99.0"}, logger, userLogger) + err := svc.PullPlatform(context.Background()) + + require.Error(t, err) + assert.Contains(t, err.Error(), "validate platform access") + assert.Contains(t, err.Error(), "v9.99.0") +} + +func TestPullPlatform_ErrorWhenCustomTagMissingInRegistry(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + svc := newDryRunService(localfake.NewRegistryClientStub(), &Options{TargetTag: "no-such-tag"}, logger, userLogger) + err := svc.PullPlatform(context.Background()) + + require.Error(t, err) + assert.Contains(t, err.Error(), "validate platform access") +} + +func TestPullPlatform_ErrorWhenSuspendedWithoutIgnore(t *testing.T) { + // Alpha channel is suspended; full discovery without IgnoreSuspend must fail. + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + svc := newDryRunService(suspendedStub(), nil, logger, userLogger) + err := svc.PullPlatform(context.Background()) + + require.Error(t, err) + assert.Contains(t, err.Error(), "suspended") +} + +// ---- IgnoreSuspend ---- + +func TestPullPlatform_IgnoreSuspend_SucceedsWithSuspendedChannel(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + svc := newDryRunService(suspendedStub(), &Options{IgnoreSuspend: true}, logger, userLogger) + err := svc.PullPlatform(context.Background()) + + require.NoError(t, err) + // All 5 channel versions + 5 channel aliases should be present. + assert.GreaterOrEqual(t, len(svc.downloadList.Deckhouse), 5) +} + +// ---- TargetTag: specific semver ---- + +func TestPullPlatform_DryRun_SemverTag_PopulatesDownloadList(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + svc := newDryRunService(localfake.NewRegistryClientStub(), &Options{TargetTag: "v1.69.0"}, logger, userLogger) + err := svc.PullPlatform(context.Background()) + require.NoError(t, err) + + rootURL := stubRootURL + // Version tag must appear in every image set. + assert.Contains(t, svc.downloadList.Deckhouse, rootURL+":v1.69.0", +"expected Deckhouse entry for requested version") + assert.Contains(t, svc.downloadList.DeckhouseInstall, rootURL+"/install:v1.69.0", +"expected installer entry for requested version") + assert.Contains(t, svc.downloadList.DeckhouseInstallStandalone, rootURL+"/install-standalone:v1.69.0", +"expected standalone installer entry for requested version") + // Channel alias must also appear because v1.69.0 == stable channel version. + assert.Contains(t, svc.downloadList.Deckhouse, rootURL+":stable", +"expected Deckhouse channel alias for matched channel") + // Other versions must NOT appear. + assert.NotContains(t, svc.downloadList.Deckhouse, rootURL+":v1.72.10") + assert.NotContains(t, svc.downloadList.Deckhouse, rootURL+":v1.68.0") +} + +func TestPullPlatform_DryRun_AlphaVersionTag_MatchesAlphaChannel(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + svc := newDryRunService(localfake.NewRegistryClientStub(), &Options{TargetTag: "v1.72.10"}, logger, userLogger) + err := svc.PullPlatform(context.Background()) + require.NoError(t, err) + + rootURL := stubRootURL + assert.Contains(t, svc.downloadList.Deckhouse, rootURL+":v1.72.10") + assert.Contains(t, svc.downloadList.Deckhouse, rootURL+":alpha", +"alpha channel alias expected because v1.72.10 is the alpha version") + // Stable and rock-solid must NOT appear. + assert.NotContains(t, svc.downloadList.Deckhouse, rootURL+":stable") + assert.NotContains(t, svc.downloadList.Deckhouse, rootURL+":rock-solid") +} + +// ---- TargetTag: channel name ---- + +func TestPullPlatform_DryRun_ChannelTag_PopulatesVersionAndAlias(t *testing.T) { + tests := []struct { + channel string + expectedSemver string // version that channel points to + }{ + {"alpha", "v1.72.10"}, + {"beta", "v1.71.0"}, + {"early-access", "v1.70.0"}, + {"stable", "v1.69.0"}, + {"rock-solid", "v1.68.0"}, + } + + for _, tt := range tests { + t.Run("channel="+tt.channel, func(t *testing.T) { +logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) +userLogger := log.NewSLogger(slog.LevelWarn) + +svc := newDryRunService(localfake.NewRegistryClientStub(), &Options{TargetTag: tt.channel}, logger, userLogger) + err := svc.PullPlatform(context.Background()) + require.NoError(t, err) + + rootURL := stubRootURL + assert.Contains(t, svc.downloadList.Deckhouse, rootURL+":"+tt.expectedSemver, +"semver entry expected for channel %s", tt.channel) + assert.Contains(t, svc.downloadList.Deckhouse, rootURL+":"+tt.channel, +"channel alias entry expected") + // Exactly 2 entries: one semver + one channel alias. + assert.Len(t, svc.downloadList.Deckhouse, 2, +"only the matched version and channel alias should be in Deckhouse") + }) + } +} + +// ---- TargetTag: custom non-semver tag ---- + +func TestPullPlatform_DryRun_CustomTag_OnlyThatTagInDownloadList(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + svc := newDryRunService(localfake.NewRegistryClientStub(), &Options{TargetTag: "pr12345"}, logger, userLogger) + err := svc.PullPlatform(context.Background()) + require.NoError(t, err) + + rootURL := stubRootURL + assert.Contains(t, svc.downloadList.Deckhouse, rootURL+":pr12345") + assert.Contains(t, svc.downloadList.DeckhouseInstall, rootURL+"/install:pr12345") + assert.Contains(t, svc.downloadList.DeckhouseInstallStandalone, rootURL+"/install-standalone:pr12345") + // No channels or semver versions should appear. + assert.Len(t, svc.downloadList.Deckhouse, 1, +"only the custom tag should appear in Deckhouse list") + assert.Len(t, svc.downloadList.DeckhouseInstall, 1) + assert.Len(t, svc.downloadList.DeckhouseInstallStandalone, 1) +} + +// ---- No TargetTag: full discovery ---- + +func TestPullPlatform_DryRun_FullDiscovery_AllVersionsAndChannels(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + svc := newDryRunService(localfake.NewRegistryClientStub(), nil, logger, userLogger) + err := svc.PullPlatform(context.Background()) + require.NoError(t, err) + + rootURL := stubRootURL + // Every semver tag from the stub must appear. + for _, ver := range []string{"v1.72.10", "v1.71.0", "v1.70.0", "v1.69.0", "v1.68.0"} { + assert.Contains(t, svc.downloadList.Deckhouse, rootURL+":"+ver, +"expected version %s in Deckhouse list", ver) + assert.Contains(t, svc.downloadList.DeckhouseInstall, rootURL+"/install:"+ver, +"expected installer entry for %s", ver) + assert.Contains(t, svc.downloadList.DeckhouseInstallStandalone, rootURL+"/install-standalone:"+ver, +"expected standalone installer entry for %s", ver) + } + // Every channel alias must appear in Deckhouse. + for _, ch := range []string{"alpha", "beta", "early-access", "stable", "rock-solid"} { + assert.Contains(t, svc.downloadList.Deckhouse, rootURL+":"+ch, +"expected channel alias %s in Deckhouse list", ch) + assert.Contains(t, svc.downloadList.DeckhouseInstall, rootURL+"/install:"+ch, +"expected channel installer entry for %s", ch) + } + // 5 versions + 5 channel aliases = 10 entries. + assert.Len(t, svc.downloadList.Deckhouse, 10) + assert.Len(t, svc.downloadList.DeckhouseInstall, 10) + // DeckhouseInstallStandalone receives only version tags (not channel aliases). + assert.Len(t, svc.downloadList.DeckhouseInstallStandalone, 5) +} + +// ---- SinceVersion filter ---- + +func TestPullPlatform_DryRun_SinceVersion_FiltersOlderChannels(t *testing.T) { + // SinceVersion=1.70.0 means stable(v1.69.0) and rock-solid(v1.68.0) are excluded. + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + since := semver.MustParse("1.70.0") + svc := newDryRunService(localfake.NewRegistryClientStub(), &Options{SinceVersion: since}, logger, userLogger) + err := svc.PullPlatform(context.Background()) + require.NoError(t, err) + + rootURL := stubRootURL + // Versions at or above 1.70.0 must be present. + for _, ver := range []string{"v1.72.10", "v1.71.0", "v1.70.0"} { + assert.Contains(t, svc.downloadList.Deckhouse, rootURL+":"+ver) + } + // Versions below 1.70.0 must be absent. + for _, ver := range []string{"v1.69.0", "v1.68.0"} { + assert.NotContains(t, svc.downloadList.Deckhouse, rootURL+":"+ver, +"version %s is below SinceVersion and must be excluded", ver) + } + // Channels below SinceVersion must be absent. + assert.NotContains(t, svc.downloadList.Deckhouse, rootURL+":stable") + assert.NotContains(t, svc.downloadList.Deckhouse, rootURL+":rock-solid") + // Channels at or above SinceVersion must be present. + for _, ch := range []string{"alpha", "beta", "early-access"} { + assert.Contains(t, svc.downloadList.Deckhouse, rootURL+":"+ch) + } +} + +func TestPullPlatform_DryRun_SinceVersion_EqualToAlpha_OnlyAlpha(t *testing.T) { + // SinceVersion=1.72.10 means only the alpha version (== newest) survives. + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + since := semver.MustParse("1.72.10") + svc := newDryRunService(localfake.NewRegistryClientStub(), &Options{SinceVersion: since}, logger, userLogger) + err := svc.PullPlatform(context.Background()) + require.NoError(t, err) + + rootURL := stubRootURL + assert.Contains(t, svc.downloadList.Deckhouse, rootURL+":v1.72.10") + assert.Contains(t, svc.downloadList.Deckhouse, rootURL+":alpha") + // All older channels must be gone. + for _, ch := range []string{"beta", "early-access", "stable", "rock-solid"} { + assert.NotContains(t, svc.downloadList.Deckhouse, rootURL+":"+ch) + } +} + +// ---- LTS channel fallback ---- + +func TestPullPlatform_DryRun_LTSRegistry_WithSemverTargetTag(t *testing.T) { + // ltsOnlyStub has only an LTS channel; a semver TargetTag should still work + // because access validation checks the tag directly (not via channels). + // However the stub's root repo has no tags so the check will fail. +// Use a custom stub that has the LTS channel AND a semver tag in the root repo. +reg := upfake.NewRegistry(stubRootURL) + +img := upfake.NewImageBuilder(). +WithFile("version.json", `{"version":"v1.68.0"}`). +WithFile("deckhouse/candi/images_digests.json", `{}`). +MustBuild() +reg.MustAddImage("release-channel", "lts", img) +reg.MustAddImage("", "v1.68.0", img) +reg.MustAddImage("install", "v1.68.0", img) +reg.MustAddImage("install-standalone", "v1.68.0", img) + +client := pkgclient.Adapt(upfake.NewClient(reg)) +logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) +userLogger := log.NewSLogger(slog.LevelWarn) + +svc := newDryRunService(client, &Options{TargetTag: "v1.68.0"}, logger, userLogger) +err := svc.PullPlatform(context.Background()) +require.NoError(t, err) + +assert.Contains(t, svc.downloadList.Deckhouse, stubRootURL+":v1.68.0") +} + +// ---- ReleaseChannel download list ---- + +func TestPullPlatform_DryRun_FullDiscovery_ReleaseChannelListPopulated(t *testing.T) { +logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) +userLogger := log.NewSLogger(slog.LevelWarn) + +svc := newDryRunService(localfake.NewRegistryClientStub(), nil, logger, userLogger) +err := svc.PullPlatform(context.Background()) +require.NoError(t, err) + +rootURL := stubRootURL +// Version-tagged release-channel entries from FillDeckhouseImages. +for _, ver := range []string{"v1.72.10", "v1.71.0", "v1.70.0", "v1.69.0", "v1.68.0"} { +assert.Contains(t, svc.downloadList.DeckhouseReleaseChannel, +rootURL+"/release-channel:"+ver, +"expected release-channel entry for version %s", ver) +} +// Channel-tagged entries from FillForChannels. +for _, ch := range []string{"alpha", "beta", "early-access", "stable", "rock-solid"} { +assert.Contains(t, svc.downloadList.DeckhouseReleaseChannel, +rootURL+"/release-channel:"+ch, +"expected release-channel entry for channel %s", ch) +} +} + +func TestPullPlatform_DryRun_SemverTag_ReleaseChannelListContainsVersionAndChannel(t *testing.T) { +logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) +userLogger := log.NewSLogger(slog.LevelWarn) + +svc := newDryRunService(localfake.NewRegistryClientStub(), &Options{TargetTag: "v1.69.0"}, logger, userLogger) +err := svc.PullPlatform(context.Background()) +require.NoError(t, err) + +rootURL := stubRootURL +assert.Contains(t, svc.downloadList.DeckhouseReleaseChannel, rootURL+"/release-channel:v1.69.0") +assert.Contains(t, svc.downloadList.DeckhouseReleaseChannel, rootURL+"/release-channel:stable") +} + +// ---- Multiple TargetTag: custom + semver combined ---- + +func TestPullPlatform_DryRun_AllSemverTags_HaveInstallerEntries(t *testing.T) { +// Confirm that every version pulled also appears in both installer lists. +logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) +userLogger := log.NewSLogger(slog.LevelWarn) + +svc := newDryRunService(localfake.NewRegistryClientStub(), nil, logger, userLogger) +err := svc.PullPlatform(context.Background()) +require.NoError(t, err) + +rootURL := stubRootURL +for deckhouseKey := range svc.downloadList.Deckhouse { +// Extract the tag part after the last ":" +tag := deckhouseKey[len(rootURL)+1:] +v, parseErr := semver.NewVersion(tag) +if parseErr != nil { +// Not a semver tag (e.g. channel alias) — skip installer-standalone check. +continue +} +vTag := "v" + v.String() +assert.Contains(t, svc.downloadList.DeckhouseInstallStandalone, +rootURL+"/install-standalone:"+vTag, +"standalone installer must have entry for semver version %s", vTag) +} +} diff --git a/internal/mirror/platform/validate_access_test.go b/internal/mirror/platform/validate_access_test.go new file mode 100644 index 00000000..70844ddb --- /dev/null +++ b/internal/mirror/platform/validate_access_test.go @@ -0,0 +1,276 @@ +/* +Copyright 2026 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package platform + +import ( + "context" + "log/slog" + "testing" + + "github.com/Masterminds/semver/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + dkplog "github.com/deckhouse/deckhouse/pkg/log" + + "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log" + localreg "github.com/deckhouse/deckhouse/pkg/registry" + registryservice "github.com/deckhouse/deckhouse-cli/pkg/registry/service" + upfake "github.com/deckhouse/deckhouse/pkg/registry/fake" + localfake "github.com/deckhouse/deckhouse-cli/pkg/fake" + pkgclient "github.com/deckhouse/deckhouse-cli/pkg/registry/client" +) + +// newTestPlatformService is a test helper that builds a Service with only the +// fields required by validatePlatformAccess and findTagsToMirror. +func newTestPlatformService( + stubClient localreg.Client, + options *Options, + logger *dkplog.Logger, + userLogger *log.SLogger, +) *Service { + return &Service{ + deckhouseService: registryservice.NewDeckhouseService(stubClient, logger), + downloadList: NewImageDownloadList(stubClient.GetRegistry()), + options: options, + logger: logger, + userLogger: userLogger, + } +} + +// ltsOnlyStub returns a stub registry that has only the LTS channel in +// release-channel/, simulating CSE-edition registries lacking standard channels. +func ltsOnlyStub() localreg.Client { + reg := upfake.NewRegistry("registry.deckhouse.ru/deckhouse/fe") + img := upfake.NewImageBuilder(). + WithFile("version.json", `{"version":"v1.68.0"}`). + MustBuild() + reg.MustAddImage("release-channel", "lts", img) + return pkgclient.Adapt(upfake.NewClient(reg)) +} + +// emptyStub returns a stub registry with no release channels at all. +func emptyStub() localreg.Client { + return pkgclient.Adapt(upfake.NewClient(upfake.NewRegistry("registry.deckhouse.ru/deckhouse/fe"))) +} + +func TestService_validatePlatformAccess(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + tests := []struct { + name string + makeClient func() localreg.Client + targetTag string + wantErr bool + errContains string + }{ + { + name: "no target tag defaults to stable channel which exists", + makeClient: localfake.NewRegistryClientStub, + targetTag: "", + wantErr: false, + }, + { + name: "explicit channel tag exists", + makeClient: localfake.NewRegistryClientStub, + targetTag: "alpha", + wantErr: false, + }, + { + name: "early-access channel exists", + makeClient: localfake.NewRegistryClientStub, + targetTag: "early-access", + wantErr: false, + }, + { + name: "rock-solid channel exists", + makeClient: localfake.NewRegistryClientStub, + targetTag: "rock-solid", + wantErr: false, + }, + { + name: "channel not found falls back to LTS successfully", + makeClient: ltsOnlyStub, + targetTag: "stable", + wantErr: false, + }, + { + name: "channel not found and LTS also missing returns error", + makeClient: emptyStub, + targetTag: "stable", + wantErr: true, + errContains: "release channel", + }, + { + name: "LTS channel missing with empty registry", + makeClient: emptyStub, + targetTag: "beta", + wantErr: true, + errContains: "lts", + }, + { + name: "semver tag exists in root repository", + makeClient: localfake.NewRegistryClientStub, + targetTag: "v1.72.10", + wantErr: false, + }, + { + name: "another semver tag exists", + makeClient: localfake.NewRegistryClientStub, + targetTag: "v1.69.0", + wantErr: false, + }, + { + name: "semver tag not present in registry returns error", + makeClient: localfake.NewRegistryClientStub, + targetTag: "v9.99.0", + wantErr: true, + errContains: "v9.99.0", + }, + { + name: "custom non-channel tag exists in root repository", + makeClient: localfake.NewRegistryClientStub, + targetTag: "pr12345", + wantErr: false, + }, + { + name: "custom non-channel tag not present in registry returns error", + makeClient: localfake.NewRegistryClientStub, + targetTag: "does-not-exist", + wantErr: true, + errContains: "does-not-exist", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := tt.makeClient() + svc := newTestPlatformService(client, &Options{TargetTag: tt.targetTag}, logger, userLogger) + + err := svc.validatePlatformAccess(context.Background()) + + if tt.wantErr { + require.Error(t, err) + if tt.errContains != "" { + assert.Contains(t, err.Error(), tt.errContains) + } + } else { + require.NoError(t, err) + } + }) + } +} + +func TestService_findTagsToMirror(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + tests := []struct { + name string + options *Options + wantVersions []string // expected in result[0] (both "vX.Y.Z" and custom tags) + wantChannels []string + wantErr bool + }{ + { + name: "empty TargetTag returns all channel versions", + options: &Options{}, + wantVersions: []string{ + "v1.72.10", + "v1.71.0", + "v1.70.0", + "v1.69.0", + "v1.68.0", + }, + wantChannels: []string{ + "alpha", "beta", "early-access", "stable", "rock-solid", + }, + wantErr: false, + }, + { + name: "TargetTag is specific semver returns only that version", + options: &Options{TargetTag: "v1.72.10"}, + wantVersions: []string{"v1.72.10"}, + wantChannels: []string{"alpha"}, + wantErr: false, + }, + { + name: "TargetTag is channel name returns that channel version", + options: &Options{TargetTag: "stable"}, + wantVersions: []string{"v1.69.0"}, + wantChannels: []string{"stable"}, + wantErr: false, + }, + { + name: "TargetTag is custom non-semver tag passes through as-is", + options: &Options{TargetTag: "pr12345"}, + wantVersions: []string{"pr12345"}, + wantChannels: []string{}, + wantErr: false, + }, + { + name: "TargetTag is semver with matching channel returns version and channel", + options: &Options{TargetTag: "v1.71.0"}, + // v1.71.0 is the beta channel version + wantVersions: []string{"v1.71.0"}, + wantChannels: []string{"beta"}, + wantErr: false, + }, + { + name: "empty TargetTag with SinceVersion filters old channels", + options: &Options{ + SinceVersion: mustParseSemver("1.70.0"), + }, + // rock-solid (v1.68.0) and stable (v1.69.0) are below SinceVersion 1.70.0 + wantVersions: []string{ + "v1.72.10", + "v1.71.0", + "v1.70.0", + }, + wantChannels: []string{"alpha", "beta", "early-access"}, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := localfake.NewRegistryClientStub() + svc := newTestPlatformService(client, tt.options, logger, userLogger) + + versions, channels, err := svc.findTagsToMirror(context.Background()) + + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + assert.ElementsMatch(t, tt.wantVersions, versions, "versions mismatch") + assert.ElementsMatch(t, tt.wantChannels, channels, "channels mismatch") + }) + } +} + +// mustParseSemver is a test helper that panics if version parsing fails. +func mustParseSemver(v string) *semver.Version { + ver, err := semver.NewVersion(v) + if err != nil { + panic("mustParseSemver: " + err.Error()) + } + return ver +} diff --git a/internal/mirror/pull_test.go b/internal/mirror/pull_test.go new file mode 100644 index 00000000..d8b626ad --- /dev/null +++ b/internal/mirror/pull_test.go @@ -0,0 +1,455 @@ +// Copyright 2026 Flant JSC +// SPDX-License-Identifier: Apache-2.0 + +package mirror + +import ( + "context" + "log/slog" + "testing" + + v1 "github.com/google/go-containerregistry/pkg/v1" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + dkplog "github.com/deckhouse/deckhouse/pkg/log" + + "github.com/deckhouse/deckhouse-cli/pkg" + "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log" + localreg "github.com/deckhouse/deckhouse/pkg/registry" + registryservice "github.com/deckhouse/deckhouse-cli/pkg/registry/service" + upfake "github.com/deckhouse/deckhouse/pkg/registry/fake" + localfake "github.com/deckhouse/deckhouse-cli/pkg/fake" + pkgclient "github.com/deckhouse/deckhouse-cli/pkg/registry/client" +) + +// pullStubRootURL matches the default host used by NewRegistryClientStub. +const pullStubRootURL = "registry.deckhouse.ru/deckhouse/fe" + +// newPullService builds a PullService backed by the given stub client using +// pkg.NoEdition (the stub's root URL already includes the edition). +func newPullService( + t *testing.T, + stubClient localreg.Client, + targetTag string, + options *PullServiceOptions, +) *PullService { + t.Helper() + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + regSvc := registryservice.NewService(stubClient, pkg.NoEdition, logger) + return NewPullService( + regSvc, + t.TempDir(), + targetTag, + options, + logger, + userLogger, + ) +} + +// fullStub returns a stub that has data in all four service areas: +// - platform (root, release-channel, install, install-standalone) +// - installer ("installer" repo at root, tag "latest") +// - security ("security/trivy-db" repo, tag "2") +// - modules ("modules" repo with two module names as tags) +func fullStub() localreg.Client { + reg := upfake.NewRegistry(pullStubRootURL) + + // ---- platform: release-channel ---- + channels := map[string]string{ + "alpha": "v1.72.10", + "beta": "v1.71.0", + "early-access": "v1.70.0", + "stable": "v1.69.0", + "rock-solid": "v1.68.0", + } + for ch, ver := range channels { + img := upfake.NewImageBuilder(). + WithFile("version.json", `{"version":"`+ver+`"}`). + MustBuild() + reg.MustAddImage("release-channel", ch, img) + // Version-tagged release-channel images are required by non-DryRun full-discovery pull. + reg.MustAddImage("release-channel", ver, img) + } + + // ---- platform: root + install + install-standalone ---- + platformImg := func(ver string) v1.Image { + return upfake.NewImageBuilder(). + WithFile("version.json", `{"version":"`+ver+`"}`). + WithFile("deckhouse/candi/images_digests.json", `{}`). + MustBuild() + } + for _, rt := range []struct{ tag, ver string }{ + {"alpha", "v1.72.10"}, {"beta", "v1.71.0"}, {"early-access", "v1.70.0"}, + {"stable", "v1.69.0"}, {"rock-solid", "v1.68.0"}, + {"v1.72.10", "v1.72.10"}, {"v1.71.0", "v1.71.0"}, {"v1.70.0", "v1.70.0"}, + {"v1.69.0", "v1.69.0"}, {"v1.68.0", "v1.68.0"}, + } { + img := platformImg(rt.ver) + reg.MustAddImage("", rt.tag, img) + reg.MustAddImage("install", rt.tag, img) + reg.MustAddImage("install-standalone", rt.tag, img) + } + + // ---- installer: "installer" repo (used by Service.InstallerService) ---- + installerImg := upfake.NewImageBuilder().MustBuild() + reg.MustAddImage("installer", "latest", installerImg) + reg.MustAddImage("installer", "v1.72.10", installerImg) + reg.MustAddImage("installer", "v1.69.0", installerImg) + + // ---- security: trivy-db tag "2" ---- + trivyImg := upfake.NewImageBuilder().MustBuild() + reg.MustAddImage("security/trivy-db", "2", trivyImg) + + // ---- modules: two module names as tags ---- + modImg := upfake.NewImageBuilder().MustBuild() + reg.MustAddImage("modules", "cert-manager", modImg) + reg.MustAddImage("modules", "ingress-nginx", modImg) + + return pkgclient.Adapt(upfake.NewClient(reg)) +} + +// --------------------------------------------------------------------------- +// Error path tests +// --------------------------------------------------------------------------- + +// TestPull_EmptyRegistry_ReturnsPlatformError verifies that Pull returns an +// error wrapping "pull platform" when the registry is empty and SkipPlatform +// is false. +func TestPull_EmptyRegistry_ReturnsPlatformError(t *testing.T) { + emptyStub := pkgclient.Adapt(upfake.NewClient(upfake.NewRegistry(pullStubRootURL))) + + svc := newPullService(t, emptyStub, "v1.69.0", &PullServiceOptions{ + SkipSecurity: true, + SkipModules: true, + SkipInstaller: true, + }) + + err := svc.Pull(context.Background()) + require.Error(t, err) + assert.Contains(t, err.Error(), "pull platform") +} + +// TestPull_MissingTargetTag_ReturnsPlatformError verifies that Pull wraps a +// platform error that names the missing tag. +func TestPull_MissingTargetTag_ReturnsPlatformError(t *testing.T) { + svc := newPullService(t, localfake.NewRegistryClientStub(), "v9.99.0", &PullServiceOptions{ + SkipSecurity: true, + SkipModules: true, + SkipInstaller: true, + }) + + err := svc.Pull(context.Background()) + require.Error(t, err) + assert.Contains(t, err.Error(), "pull platform") + assert.Contains(t, err.Error(), "v9.99.0") +} + +// TestPull_SuspendedChannel_ReturnsError verifies Pull returns an error +// wrapping "suspended" when a release channel is suspended and IgnoreSuspend +// is false. +func TestPull_SuspendedChannel_ReturnsError(t *testing.T) { + reg := upfake.NewRegistry(pullStubRootURL) + reg.MustAddImage("release-channel", "alpha", + upfake.NewImageBuilder(). + WithFile("version.json", `{"version":"v1.72.10","suspend":true}`). + MustBuild(), + ) + for _, ch := range []string{"beta", "early-access", "stable", "rock-solid"} { + reg.MustAddImage("release-channel", ch, + upfake.NewImageBuilder().WithFile("version.json", `{"version":"v1.69.0"}`).MustBuild(), + ) + } + img := upfake.NewImageBuilder(). + WithFile("version.json", `{"version":"v1.72.10"}`). + WithFile("deckhouse/candi/images_digests.json", `{}`). + MustBuild() + for _, tag := range []string{"alpha", "v1.72.10", "v1.69.0"} { + reg.MustAddImage("", tag, img) + reg.MustAddImage("install", tag, img) + reg.MustAddImage("install-standalone", tag, img) + } + + svc := newPullService(t, pkgclient.Adapt(upfake.NewClient(reg)), "", &PullServiceOptions{ + SkipSecurity: true, + SkipModules: true, + SkipInstaller: true, + IgnoreSuspend: false, + }) + + err := svc.Pull(context.Background()) + require.Error(t, err) + assert.Contains(t, err.Error(), "pull platform") + assert.Contains(t, err.Error(), "suspended") +} + +// --------------------------------------------------------------------------- +// Success path tests +// --------------------------------------------------------------------------- + +// TestPull_DefaultStub_Succeeds verifies Pull returns nil using the +// default stub with a specific semver tag and security/modules/installer +// skipped (those repos are absent from the default stub). +func TestPull_DefaultStub_Succeeds(t *testing.T) { + svc := newPullService(t, localfake.NewRegistryClientStub(), "v1.69.0", &PullServiceOptions{ + SkipSecurity: true, + SkipModules: true, + SkipInstaller: true, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// TestPull_AllChannelTags verifies Pull succeeds for each of the five +// standard release-channel tags as TargetTag. +func TestPull_AllChannelTags(t *testing.T) { + for _, ch := range []string{"alpha", "beta", "early-access", "stable", "rock-solid"} { + ch := ch + t.Run(ch, func(t *testing.T) { + svc := newPullService(t, localfake.NewRegistryClientStub(), ch, &PullServiceOptions{ + SkipSecurity: true, + SkipModules: true, + SkipInstaller: true, + }) + require.NoError(t, svc.Pull(context.Background())) + }) + } +} + +// TestPull_EmptyTargetTag_FullDiscovery verifies full-discovery mode +// (no TargetTag) returns nil. +func TestPull_EmptyTargetTag_FullDiscovery(t *testing.T) { + svc := newPullService(t, localfake.NewRegistryClientStub(), "", &PullServiceOptions{ + SkipSecurity: true, + SkipModules: true, + SkipInstaller: true, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// TestPull_IgnoreSuspend_AllowsSuspendedChannel verifies that +// IgnoreSuspend=true makes Pull succeed even when a channel is suspended. +func TestPull_IgnoreSuspend_AllowsSuspendedChannel(t *testing.T) { + reg := upfake.NewRegistry(pullStubRootURL) + channelVersions := map[string]struct { + ver string + suspend bool + }{ + "alpha": {"v1.72.10", true}, + "beta": {"v1.71.0", false}, + "early-access": {"v1.70.0", false}, + "stable": {"v1.69.0", false}, + "rock-solid": {"v1.68.0", false}, + } + for ch, data := range channelVersions { + content := `{"version":"` + data.ver + `"}` + if data.suspend { + content = `{"version":"` + data.ver + `","suspend":true}` + } + img := upfake.NewImageBuilder().WithFile("version.json", content).MustBuild() + reg.MustAddImage("release-channel", ch, img) + // Version-tagged release-channel images are required by non-DryRun full-discovery pull. + reg.MustAddImage("release-channel", data.ver, img) + } + img := upfake.NewImageBuilder(). + WithFile("version.json", `{"version":"v1.72.10"}`). + WithFile("deckhouse/candi/images_digests.json", `{}`). + MustBuild() + for _, tag := range []string{"alpha", "beta", "early-access", "stable", "rock-solid", + "v1.72.10", "v1.71.0", "v1.70.0", "v1.69.0", "v1.68.0"} { + reg.MustAddImage("", tag, img) + reg.MustAddImage("install", tag, img) + reg.MustAddImage("install-standalone", tag, img) + } + + svc := newPullService(t, pkgclient.Adapt(upfake.NewClient(reg)), "", &PullServiceOptions{ + SkipSecurity: true, + SkipModules: true, + SkipInstaller: true, + IgnoreSuspend: true, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// TestPull_SkipPlatform_AlwaysSucceeds verifies that SkipPlatform=true +// makes Pull succeed even with an empty registry. +func TestPull_SkipPlatform_AlwaysSucceeds(t *testing.T) { + emptyStub := pkgclient.Adapt(upfake.NewClient(upfake.NewRegistry(pullStubRootURL))) + + svc := newPullService(t, emptyStub, "v1.69.0", &PullServiceOptions{ + SkipPlatform: true, + SkipSecurity: true, + SkipModules: true, + SkipInstaller: true, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// TestPull_SkipAll_EmptyRegistry verifies Pull succeeds when every +// service is skipped, regardless of registry contents. +func TestPull_SkipAll_EmptyRegistry(t *testing.T) { + emptyStub := pkgclient.Adapt(upfake.NewClient(upfake.NewRegistry(pullStubRootURL))) + + svc := newPullService(t, emptyStub, "", &PullServiceOptions{ + SkipPlatform: true, + SkipSecurity: true, + SkipModules: true, + SkipInstaller: true, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// TestPull_InstallerGracefulSkip verifies that when the "installer" +// repo is absent Pull still succeeds (PullInstaller logs a warning and returns nil). +func TestPull_InstallerGracefulSkip(t *testing.T) { + // The default stub has no "installer" repo; installer access validation + // returns ErrImageNotFound which PullInstaller treats as a graceful skip. + svc := newPullService(t, localfake.NewRegistryClientStub(), "v1.69.0", &PullServiceOptions{ + SkipSecurity: true, + SkipModules: true, + SkipInstaller: false, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// TestPull_SecurityGracefulSkip verifies that when the security repo is +// absent Pull still succeeds (validateSecurityAccess gracefully skips on +// ErrImageNotFound). +func TestPull_SecurityGracefulSkip(t *testing.T) { + svc := newPullService(t, localfake.NewRegistryClientStub(), "v1.69.0", &PullServiceOptions{ + SkipPlatform: true, + SkipModules: true, + SkipInstaller: true, + SkipSecurity: false, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// TestPull_ModulesGracefulSkip verifies that when no modules exist in +// the registry Pull still succeeds (PullModules logs a warning and returns nil). +func TestPull_ModulesGracefulSkip(t *testing.T) { + svc := newPullService(t, localfake.NewRegistryClientStub(), "v1.69.0", &PullServiceOptions{ + SkipPlatform: true, + SkipSecurity: true, + SkipInstaller: true, + SkipModules: false, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// TestPull_SkipVexImages_DoesNotError verifies the SkipVexImages flag +// does not cause Pull to fail. +func TestPull_SkipVexImages_DoesNotError(t *testing.T) { + svc := newPullService(t, localfake.NewRegistryClientStub(), "v1.69.0", &PullServiceOptions{ + SkipVexImages: true, + SkipSecurity: true, + SkipModules: true, + SkipInstaller: true, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// --------------------------------------------------------------------------- +// Full-stub tests — all four services active simultaneously +// --------------------------------------------------------------------------- + +// TestPull_FullStub_AllServicesActive verifies Pull returns nil when +// all four services are enabled and the stub has data for each. +func TestPull_FullStub_AllServicesActive(t *testing.T) { + svc := newPullService(t, fullStub(), "v1.69.0", &PullServiceOptions{ + SkipVexImages: true, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// TestPull_FullStub_FullDiscovery verifies full-discovery mode (empty +// TargetTag) succeeds with all services enabled. +func TestPull_FullStub_FullDiscovery(t *testing.T) { + svc := newPullService(t, fullStub(), "", &PullServiceOptions{ + SkipVexImages: true, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// TestPull_FullStub_CustomTag verifies Pull succeeds with a custom +// (non-semver, non-channel) tag when that tag exists in the registry. +func TestPull_FullStub_CustomTag(t *testing.T) { + reg := upfake.NewRegistry(pullStubRootURL) + img := upfake.NewImageBuilder(). + WithFile("version.json", `{"version":"v1.72.10"}`). + WithFile("deckhouse/candi/images_digests.json", `{}`). + MustBuild() + reg.MustAddImage("", "pr12345", img) + reg.MustAddImage("install", "pr12345", img) + reg.MustAddImage("install-standalone", "pr12345", img) + + svc := newPullService(t, pkgclient.Adapt(upfake.NewClient(reg)), "pr12345", &PullServiceOptions{ + SkipSecurity: true, + SkipModules: true, + SkipInstaller: true, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// TestPull_FullStub_InstallerPresent verifies that when the "installer" +// repo exists Pull runs PullInstaller successfully. +func TestPull_FullStub_InstallerPresent(t *testing.T) { + svc := newPullService(t, fullStub(), "v1.69.0", &PullServiceOptions{ + InstallerTag: "v1.69.0", + SkipSecurity: true, + SkipModules: true, + SkipInstaller: false, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// TestPull_FullStub_SecurityPresent verifies that when the security +// repo exists Pull runs PullSecurity successfully. +func TestPull_FullStub_SecurityPresent(t *testing.T) { + svc := newPullService(t, fullStub(), "v1.69.0", &PullServiceOptions{ + SkipPlatform: true, + SkipModules: true, + SkipInstaller: true, + SkipSecurity: false, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// TestPull_FullStub_ModulesPresent verifies that when modules exist in +// the registry Pull runs PullModules successfully. +func TestPull_FullStub_ModulesPresent(t *testing.T) { + svc := newPullService(t, fullStub(), "", &PullServiceOptions{ + SkipPlatform: true, + SkipSecurity: true, + SkipInstaller: true, + SkipModules: false, + }) + + require.NoError(t, svc.Pull(context.Background())) +} + +// TestPull_FullStub_AllServices verifies Pull returns nil when all four +// services are enabled with installer tag set. +func TestPull_FullStub_AllServices(t *testing.T) { + svc := newPullService(t, fullStub(), "v1.69.0", &PullServiceOptions{ + InstallerTag: "v1.69.0", + SkipVexImages: true, + }) + + require.NoError(t, svc.Pull(context.Background())) +} diff --git a/internal/mirror/push.go b/internal/mirror/push.go index 2fbf6c0b..d2535010 100644 --- a/internal/mirror/push.go +++ b/internal/mirror/push.go @@ -31,13 +31,13 @@ import ( "github.com/google/go-containerregistry/pkg/v1/random" dkplog "github.com/deckhouse/deckhouse/pkg/log" + client "github.com/deckhouse/deckhouse/pkg/registry" "github.com/deckhouse/deckhouse-cli/internal" "github.com/deckhouse/deckhouse-cli/internal/mirror/chunked" "github.com/deckhouse/deckhouse-cli/internal/mirror/pusher" "github.com/deckhouse/deckhouse-cli/pkg/libmirror/bundle" "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log" - client "github.com/deckhouse/deckhouse-cli/pkg/registry" ) const ( diff --git a/internal/mirror/pusher/pusher.go b/internal/mirror/pusher/pusher.go index c065c9fa..7a0fbd2c 100644 --- a/internal/mirror/pusher/pusher.go +++ b/internal/mirror/pusher/pusher.go @@ -28,12 +28,12 @@ import ( "github.com/google/go-containerregistry/pkg/v1/layout" dkplog "github.com/deckhouse/deckhouse/pkg/log" + client "github.com/deckhouse/deckhouse/pkg/registry" "github.com/deckhouse/deckhouse-cli/internal/mirror/chunked" "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log" "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/retry" "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/retry/task" - client "github.com/deckhouse/deckhouse-cli/pkg/registry" ) const ( diff --git a/internal/mirror/pusher/types.go b/internal/mirror/pusher/types.go index d3c30205..10b99f8c 100644 --- a/internal/mirror/pusher/types.go +++ b/internal/mirror/pusher/types.go @@ -22,8 +22,9 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" + client "github.com/deckhouse/deckhouse/pkg/registry" + "github.com/deckhouse/deckhouse-cli/pkg" - client "github.com/deckhouse/deckhouse-cli/pkg/registry" regimage "github.com/deckhouse/deckhouse-cli/pkg/registry/image" ) diff --git a/internal/mirror/security/security_dryrun_test.go b/internal/mirror/security/security_dryrun_test.go index 0057acd2..43743dc4 100644 --- a/internal/mirror/security/security_dryrun_test.go +++ b/internal/mirror/security/security_dryrun_test.go @@ -30,7 +30,7 @@ import ( "github.com/deckhouse/deckhouse-cli/pkg" "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log" registryservice "github.com/deckhouse/deckhouse-cli/pkg/registry/service" - "github.com/deckhouse/deckhouse-cli/pkg/stub" + "github.com/deckhouse/deckhouse-cli/pkg/fake" ) // TestDryRun_NoBundleFilesWritten verifies that PullSecurity in dry-run mode does @@ -39,7 +39,7 @@ func TestDryRun_NoBundleFilesWritten(t *testing.T) { workingDir := t.TempDir() bundleDir := t.TempDir() - stubClient := stub.NewRegistryClientStub() + stubClient := fake.NewRegistryClientStub() logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) userLogger := log.NewSLogger(slog.LevelWarn) @@ -71,7 +71,7 @@ func TestDryRun_WorkingDirHasLayouts(t *testing.T) { workingDir := t.TempDir() bundleDir := t.TempDir() - stubClient := stub.NewRegistryClientStub() + stubClient := fake.NewRegistryClientStub() logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) userLogger := log.NewSLogger(slog.LevelWarn) diff --git a/internal/mirror/security/security_test.go b/internal/mirror/security/security_test.go new file mode 100644 index 00000000..7071f686 --- /dev/null +++ b/internal/mirror/security/security_test.go @@ -0,0 +1,129 @@ +// Copyright 2026 Flant JSC +// SPDX-License-Identifier: Apache-2.0 + +package security + +import ( + "context" + "log/slog" + "testing" + + "github.com/stretchr/testify/require" + + dkplog "github.com/deckhouse/deckhouse/pkg/log" + + "github.com/deckhouse/deckhouse-cli/internal" + "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log" + registryservice "github.com/deckhouse/deckhouse-cli/pkg/registry/service" + upfake "github.com/deckhouse/deckhouse/pkg/registry/fake" + pkgclient "github.com/deckhouse/deckhouse-cli/pkg/registry/client" +) + +func newTestSecurityService( + securityService *registryservice.SecurityServices, + logger *dkplog.Logger, + userLogger *log.SLogger, +) *Service { + return &Service{ + securityService: securityService, + options: &Options{}, + logger: logger, + userLogger: userLogger, + } +} + +func TestService_validateSecurityAccess(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + t.Run("trivy-db tag 2 exists – no error", func(t *testing.T) { + reg := upfake.NewRegistry("registry.example.com") + trivyImg := upfake.NewImageBuilder().MustBuild() + reg.MustAddImage("security/trivy-db", "2", trivyImg) + + stubClient := pkgclient.Adapt(upfake.NewClient(reg)) + securityClient := stubClient.WithSegment("security") + securityService := registryservice.NewSecurityServices("security", securityClient, logger) + + svc := newTestSecurityService(securityService, logger, userLogger) + err := svc.validateSecurityAccess(context.Background()) + require.NoError(t, err) + }) + + t.Run("trivy-db tag 2 absent – no error (graceful skip)", func(t *testing.T) { + reg := upfake.NewRegistry("registry.example.com") + stubClient := pkgclient.Adapt(upfake.NewClient(reg)) + securityClient := stubClient.WithSegment("security") + securityService := registryservice.NewSecurityServices("security", securityClient, logger) + + svc := newTestSecurityService(securityService, logger, userLogger) + err := svc.validateSecurityAccess(context.Background()) + require.NoError(t, err) + }) +} + +// TestService_validateSecurityAccess_MultipleDatabases verifies that when all +// security databases are present the service reports no error. +func TestService_validateSecurityAccess_MultipleDatabases(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + reg := upfake.NewRegistry("registry.example.com") + trivyImg := upfake.NewImageBuilder().MustBuild() + + for _, dbSegment := range []string{ + internal.SecurityTrivyDBSegment, + internal.SecurityTrivyBDUSegment, + internal.SecurityTrivyJavaDBSegment, + internal.SecurityTrivyChecksSegment, + } { + reg.MustAddImage("security/"+dbSegment, "2", trivyImg) + } + + stubClient := pkgclient.Adapt(upfake.NewClient(reg)) + securityClient := stubClient.WithSegment("security") + securityService := registryservice.NewSecurityServices("security", securityClient, logger) + + svc := newTestSecurityService(securityService, logger, userLogger) + err := svc.validateSecurityAccess(context.Background()) + require.NoError(t, err) +} + +// TestService_validateSecurityAccess_PerDatabase exercises validateSecurityAccess +// for each known security database variant. Only trivy-db is checked during +// access validation; the others are handled with AllowMissingTags in the puller. +func TestService_validateSecurityAccess_PerDatabase(t *testing.T) { + logger := dkplog.NewLogger(dkplog.WithLevel(slog.LevelWarn)) + userLogger := log.NewSLogger(slog.LevelWarn) + + databases := []struct { + segment string + wantErr bool + }{ + {internal.SecurityTrivyDBSegment, false}, + {internal.SecurityTrivyBDUSegment, false}, + {internal.SecurityTrivyJavaDBSegment, false}, + {internal.SecurityTrivyChecksSegment, false}, + } + + for _, db := range databases { + t.Run("database "+db.segment+" with tag 2 present", func(t *testing.T) { + reg := upfake.NewRegistry("registry.example.com") + dbImg := upfake.NewImageBuilder().MustBuild() + reg.MustAddImage("security/"+db.segment, "2", dbImg) + + stubClient := pkgclient.Adapt(upfake.NewClient(reg)) + securityClient := stubClient.WithSegment("security") + securityService := registryservice.NewSecurityServices("security", securityClient, logger) + + svc := newTestSecurityService(securityService, logger, userLogger) + err := svc.validateSecurityAccess(context.Background()) + + if db.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/pkg/fake/client_test.go b/pkg/fake/client_test.go new file mode 100644 index 00000000..0ea3c3a8 --- /dev/null +++ b/pkg/fake/client_test.go @@ -0,0 +1,336 @@ +// Copyright 2026 Flant JSC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fake_test + +import ( +"context" +"errors" +"strings" +"testing" + +dkpclient "github.com/deckhouse/deckhouse/pkg/registry/client" +"github.com/stretchr/testify/assert" +"github.com/stretchr/testify/require" + +upfake "github.com/deckhouse/deckhouse/pkg/registry/fake" +) + +// ----- helpers ----- + +func newFilledRegistry(host string) *upfake.Registry { + reg := upfake.NewRegistry(host) + reg.MustAddImage("deckhouse/ee", "v1.65.0", +upfake.NewImageBuilder(). + WithFile("version.json", `{"version":"v1.65.0"}`). + WithLabel("org.opencontainers.image.version", "v1.65.0"). + MustBuild(), + ) + reg.MustAddImage("deckhouse/ee", "v1.64.0", +upfake.NewImageBuilder(). + WithFile("version.json", `{"version":"v1.64.0"}`). + MustBuild(), + ) + reg.MustAddImage("deckhouse/ee/release-channel", "stable", +upfake.NewImageBuilder(). + WithFile("version.json", `{"version":"v1.64.0"}`). + MustBuild(), + ) + return reg +} + +// ----- WithSegment / GetRegistry ----- + +func TestClient_WithSegment_ChainedPaths(t *testing.T) { + reg := upfake.NewRegistry("gcr.io") + client := upfake.NewClient(reg) + + scoped := client.WithSegment("org").WithSegment("repo") + assert.Equal(t, "gcr.io", scoped.GetRegistry()) +} + +func TestClient_WithSegment_MultiSegments(t *testing.T) { + reg := upfake.NewRegistry("gcr.io") + client := upfake.NewClient(reg) + + scoped := client.WithSegment("org", "repo", "sub") + assert.Equal(t, "gcr.io", scoped.GetRegistry()) +} + +func TestClient_GetRegistry_DefaultHost(t *testing.T) { + reg := upfake.NewRegistry("reg.example.com") + client := upfake.NewClient(reg) + + assert.Equal(t, "reg.example.com", client.GetRegistry()) +} + +func TestClient_WithSegment_ScopeListTags(t *testing.T) { + reg := upfake.NewRegistry("gcr.io") + img := upfake.NewImageBuilder().WithFile("f.txt", "x").MustBuild() + reg.MustAddImage("org/repo", "v1", img) + client := upfake.NewClient(reg) + + scoped := client.WithSegment("org").WithSegment("repo") + tags, err := scoped.ListTags(context.Background()) + require.NoError(t, err) + assert.Contains(t, tags, "v1") +} + +// ----- GetDigest ----- + +func TestClient_GetDigest_ExistingTag(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + hash, err := client.GetDigest(context.Background(), "v1.65.0") + require.NoError(t, err) + assert.True(t, strings.HasPrefix(hash.String(), "sha256:")) +} + +func TestClient_GetDigest_MissingTag(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + _, err := client.GetDigest(context.Background(), "does-not-exist") + require.Error(t, err) + assert.True(t, errors.Is(err, dkpclient.ErrImageNotFound)) +} + +// ----- GetManifest ----- + +func TestClient_GetManifest_ExistingTag(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + manifest, err := client.GetManifest(context.Background(), "v1.65.0") + require.NoError(t, err) + require.NotNil(t, manifest) +} + +func TestClient_GetManifest_MissingTag(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + _, err := client.GetManifest(context.Background(), "missing") + require.Error(t, err) +} + +// ----- GetImageConfig ----- + +func TestClient_GetImageConfig_ExistingTag(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + cfg, err := client.GetImageConfig(context.Background(), "v1.65.0") + require.NoError(t, err) + require.NotNil(t, cfg) + assert.Equal(t, "v1.65.0", cfg.Config.Labels["org.opencontainers.image.version"]) +} + +// ----- CheckImageExists ----- + +func TestClient_CheckImageExists_Present(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + err := client.CheckImageExists(context.Background(), "v1.65.0") + assert.NoError(t, err) +} + +func TestClient_CheckImageExists_Absent(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + err := client.CheckImageExists(context.Background(), "v2.0.0") + require.Error(t, err) + assert.True(t, errors.Is(err, dkpclient.ErrImageNotFound)) +} + +// ----- GetImage ----- + +func TestClient_GetImage_ByTag(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + img, err := client.GetImage(context.Background(), "v1.65.0") + require.NoError(t, err) + require.NotNil(t, img) +} + +func TestClient_GetImage_ByDigest(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + hash, err := client.GetDigest(context.Background(), "v1.65.0") + require.NoError(t, err) + + img, err := client.GetImage(context.Background(), "@"+hash.String()) + require.NoError(t, err) + require.NotNil(t, img) +} + +func TestClient_GetImage_MissingTag(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + _, err := client.GetImage(context.Background(), "missing-tag") + require.Error(t, err) + assert.True(t, errors.Is(err, dkpclient.ErrImageNotFound)) +} + +// ----- ListTags ----- + +func TestClient_ListTags(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + tags, err := client.ListTags(context.Background()) + require.NoError(t, err) + + assert.ElementsMatch(t, []string{"v1.65.0", "v1.64.0"}, tags) +} + +func TestClient_ListTags_EmptyRepo(t *testing.T) { + reg := upfake.NewRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("no-such-repo") + + tags, err := client.ListTags(context.Background()) + require.NoError(t, err) + assert.Empty(t, tags) +} + +// ----- ListRepositories ----- + +func TestClient_ListRepositories_AllUnderHost(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg) + + repos, err := client.ListRepositories(context.Background()) + require.NoError(t, err) + + assert.ElementsMatch(t, []string{ +"deckhouse/ee", +"deckhouse/ee/release-channel", +}, repos) +} + +func TestClient_ListRepositories_Scoped(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse") + + repos, err := client.ListRepositories(context.Background()) + require.NoError(t, err) + + assert.ElementsMatch(t, []string{ +"deckhouse/ee", +"deckhouse/ee/release-channel", +}, repos) +} + +// ----- DeleteTag ----- + +func TestClient_DeleteTag_Existing(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + require.NoError(t, client.DeleteTag(context.Background(), "v1.65.0")) + + err := client.CheckImageExists(context.Background(), "v1.65.0") + require.Error(t, err) +} + +func TestClient_DeleteTag_Missing(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + err := client.DeleteTag(context.Background(), "does-not-exist") + require.Error(t, err) + assert.True(t, errors.Is(err, dkpclient.ErrImageNotFound)) +} + +// ----- TagImage ----- + +func TestClient_TagImage(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + require.NoError(t, client.TagImage(context.Background(), "v1.65.0", "stable")) + + origDigest, err := client.GetDigest(context.Background(), "v1.65.0") + require.NoError(t, err) + newDigest, err := client.GetDigest(context.Background(), "stable") + require.NoError(t, err) + + assert.Equal(t, origDigest.String(), newDigest.String()) +} + +func TestClient_TagImage_SourceMissing(t *testing.T) { + reg := newFilledRegistry("gcr.io") + client := upfake.NewClient(reg).WithSegment("deckhouse", "ee") + + err := client.TagImage(context.Background(), "no-such-tag", "dest") + require.Error(t, err) +} + +// ----- PushImage ----- + +func TestClient_PushImage_NewTag(t *testing.T) { + reg := upfake.NewRegistry("push.io") + client := upfake.NewClient(reg).WithSegment("org", "app") + + img := upfake.NewImageBuilder().WithFile("app.txt", "app v1").MustBuild() + + require.NoError(t, client.PushImage(context.Background(), "v2", img)) + + tags, err := client.ListTags(context.Background()) + require.NoError(t, err) + assert.Contains(t, tags, "v2") +} + +func TestClient_PushImage_AutoCreatesRegistry(t *testing.T) { + reg := upfake.NewRegistry("known.io") + client := upfake.NewClient(reg) + + img := upfake.NewImageBuilder().MustBuild() + + scopedToUnknown := client.WithSegment("unknown.io", "repo") + require.NoError(t, scopedToUnknown.PushImage(context.Background(), "v1", img)) +} + +// ----- Cross-registry routing ----- + +func TestClient_CrossRegistryRouting(t *testing.T) { + regSrc := upfake.NewRegistry("src.io") + regDst := upfake.NewRegistry("dst.io") + + imgSrc := upfake.NewImageBuilder().WithFile("src.txt", "source").MustBuild() + imgDst := upfake.NewImageBuilder().WithFile("dst.txt", "dest").MustBuild() + + regSrc.MustAddImage("lib", "v1", imgSrc) + regDst.MustAddImage("lib", "v1", imgDst) + + clientSrc := upfake.NewClient(regSrc) + clientDst := upfake.NewClient(regDst) + + assert.Equal(t, "src.io", clientSrc.GetRegistry()) + assert.Equal(t, "dst.io", clientDst.GetRegistry()) + + tagsFromSrc, err := clientSrc.WithSegment("lib").ListTags(context.Background()) + require.NoError(t, err) + assert.Contains(t, tagsFromSrc, "v1") + + tagsFromDst, err := clientDst.WithSegment("lib").ListTags(context.Background()) + require.NoError(t, err) + assert.Contains(t, tagsFromDst, "v1") +} diff --git a/pkg/fake/deckhouse_stub.go b/pkg/fake/deckhouse_stub.go new file mode 100644 index 00000000..5dbb3902 --- /dev/null +++ b/pkg/fake/deckhouse_stub.go @@ -0,0 +1,125 @@ +/* +Copyright 2026 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fake + +import ( + "fmt" + + v1 "github.com/google/go-containerregistry/pkg/v1" + + localreg "github.com/deckhouse/deckhouse/pkg/registry" + upfake "github.com/deckhouse/deckhouse/pkg/registry/fake" + + pkgclient "github.com/deckhouse/deckhouse-cli/pkg/registry/client" +) + +// defaultSource is the registry root used by NewRegistryClientStub. +const defaultSource = "registry.deckhouse.ru/deckhouse/fe" + +// releaseChannelData maps a release-channel tag to the version its image +// carries in version.json. +var releaseChannelData = map[string]string{ + "alpha": "v1.72.10", + "beta": "v1.71.0", + "early-access": "v1.70.0", + "stable": "v1.69.0", + "rock-solid": "v1.68.0", +} + +// changelogYAML is the sample changelog file embedded in every stub image. +const changelogYAML = `candi: + fixes: + - summary: "Fix deckhouse containerd start after installing new containerd-deckhouse package." + pull_request: "https://github.com/deckhouse/deckhouse/pull/6329" +` + +// imagesDigestsJSON is the sample images-tags file embedded in stub version images. +const imagesDigestsJSON = `{}` + +// NewRegistryClientStub creates a [localreg.Client] pre-populated with +// Deckhouse-shaped registry data that mirrors the structure expected by the +// platform test suite. +// +// The stub exposes a registry at [defaultSource] +// ("registry.deckhouse.ru/deckhouse/fe") with the following structure: +// +// - root repository (empty path): tags alpha, beta, early-access, stable, +// rock-solid, v1.72.10, v1.71.0, v1.70.0, v1.69.0, v1.68.0, pr12345. +// +// - "release-channel" repository: tags alpha, beta, early-access, stable, +// rock-solid. Each image carries version.json with the channel's current +// version (e.g. alpha → v1.72.10). +// +// - "install" and "install-standalone" repositories: same tags as root. +func NewRegistryClientStub() localreg.Client { + reg := upfake.NewRegistry(defaultSource) + + // ---- release-channel repository ---- + for channel, version := range releaseChannelData { + img := releaseChannelImage(version) + reg.MustAddImage("release-channel", channel, img) + // Version-tagged release-channel images are required by non-DryRun full-discovery pull. + reg.MustAddImage("release-channel", version, img) + } + + // ---- root-level and installer repositories ---- + rootTags := []struct { + tag string + version string + }{ + {"alpha", "v1.72.10"}, + {"beta", "v1.71.0"}, + {"early-access", "v1.70.0"}, + {"stable", "v1.69.0"}, + {"rock-solid", "v1.68.0"}, + {"v1.72.10", "v1.72.10"}, + {"v1.71.0", "v1.71.0"}, + {"v1.70.0", "v1.70.0"}, + {"v1.69.0", "v1.69.0"}, + {"v1.68.0", "v1.68.0"}, + {"pr12345", "v1.72.10"}, // custom non-semver tag + } + + for _, rt := range rootTags { + img := platformImage(rt.version) + reg.MustAddImage("", rt.tag, img) + reg.MustAddImage("install", rt.tag, img) + reg.MustAddImage("install-standalone", rt.tag, img) + } + + return pkgclient.Adapt(upfake.NewClient(reg)) +} + +// platformImage creates a stub v1.Image for the root (edition) repository +// containing the files that the deckhouse platform service reads during +// version discovery. +func platformImage(version string) v1.Image { + return upfake.NewImageBuilder(). + WithFile("version.json", fmt.Sprintf(`{"version":%q}`, version)). + WithFile("changelog.yaml", changelogYAML). + WithFile("deckhouse/candi/images_digests.json", imagesDigestsJSON). + WithLabel("org.opencontainers.image.version", version). + MustBuild() +} + +// releaseChannelImage creates a stub v1.Image for the release-channel +// repository containing version.json that DeckhouseReleaseService reads. +func releaseChannelImage(version string) v1.Image { + return upfake.NewImageBuilder(). + WithFile("version.json", fmt.Sprintf(`{"version":%q}`, version)). + MustBuild() +} diff --git a/pkg/fake/deckhouse_stub_test.go b/pkg/fake/deckhouse_stub_test.go new file mode 100644 index 00000000..10f741ee --- /dev/null +++ b/pkg/fake/deckhouse_stub_test.go @@ -0,0 +1,167 @@ +/* +Copyright 2026 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fake_test + +import ( + "archive/tar" + "context" + "encoding/json" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/deckhouse/deckhouse-cli/pkg/fake" +) + +// TestNewRegistryClientStub_ReleaseChannelTags verifies that the Deckhouse +// stub exposes all five release channels and their version tags under the +// "release-channel" repo. +func TestNewRegistryClientStub_ReleaseChannelTags(t *testing.T) { + client := fake.NewRegistryClientStub() + rcClient := client.WithSegment("release-channel") + + tags, err := rcClient.ListTags(context.Background()) + require.NoError(t, err) + + assert.ElementsMatch(t, []string{ + "alpha", "beta", "early-access", "stable", "rock-solid", + "v1.72.10", "v1.71.0", "v1.70.0", "v1.69.0", "v1.68.0", + }, tags) +} + +// TestNewRegistryClientStub_ReleaseChannelVersionJSON verifies that the +// version.json file inside each release-channel image contains a valid version. +func TestNewRegistryClientStub_ReleaseChannelVersionJSON(t *testing.T) { + wantVersions := map[string]string{ + "alpha": "v1.72.10", + "beta": "v1.71.0", + "early-access": "v1.70.0", + "stable": "v1.69.0", + "rock-solid": "v1.68.0", + } + + client := fake.NewRegistryClientStub() + + for channel, wantVersion := range wantVersions { + t.Run(channel, func(t *testing.T) { + rcClient := client.WithSegment("release-channel") + img, err := rcClient.GetImage(context.Background(), channel) + require.NoError(t, err) + + // Extract() returns the flattened tar stream of the image filesystem. + rc := img.Extract() + defer rc.Close() + + tr := tar.NewReader(rc) + var versionJSON string + + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + if hdr.Name != "version.json" { + continue + } + data, err := io.ReadAll(tr) + require.NoError(t, err) + versionJSON = string(data) + break + } + + require.NotEmpty(t, versionJSON, "version.json not found in image for channel %q", channel) + + type vj struct { + Version string `json:"version"` + } + var parsed vj + require.NoError(t, json.Unmarshal([]byte(versionJSON), &parsed)) + assert.Equal(t, wantVersion, parsed.Version, "channel %q", channel) + }) + } +} + +// TestNewRegistryClientStub_RootTags verifies that root-level tags are present. +func TestNewRegistryClientStub_RootTags(t *testing.T) { + client := fake.NewRegistryClientStub() + + tags, err := client.ListTags(context.Background()) + require.NoError(t, err) + + // All root-level tags must be present. + wantTags := []string{ + "alpha", "beta", "early-access", "stable", "rock-solid", + "v1.72.10", "v1.71.0", "v1.70.0", "v1.69.0", "v1.68.0", + "pr12345", + } + assert.ElementsMatch(t, wantTags, tags) +} + +// TestNewRegistryClientStub_InstallTags verifies the "install" repository. +func TestNewRegistryClientStub_InstallTags(t *testing.T) { + client := fake.NewRegistryClientStub() + installClient := client.WithSegment("install") + + tags, err := installClient.ListTags(context.Background()) + require.NoError(t, err) + + assert.Contains(t, tags, "v1.72.10") + assert.Contains(t, tags, "stable") +} + +// TestNewRegistryClientStub_InstallStandaloneTags verifies the +// "install-standalone" repository. +func TestNewRegistryClientStub_InstallStandaloneTags(t *testing.T) { + client := fake.NewRegistryClientStub() + saClient := client.WithSegment("install-standalone") + + tags, err := saClient.ListTags(context.Background()) + require.NoError(t, err) + + assert.Contains(t, tags, "v1.71.0") +} + +// TestNewRegistryClientStub_PlatformImageHasVersionJSON verifies that root +// images carry a version.json file. +func TestNewRegistryClientStub_PlatformImageHasVersionJSON(t *testing.T) { + client := fake.NewRegistryClientStub() + + cfg, err := client.GetImageConfig(context.Background(), "v1.72.10") + require.NoError(t, err) + require.NotNil(t, cfg) + + assert.Equal(t, "v1.72.10", cfg.Config.Labels["org.opencontainers.image.version"]) +} + +// TestNewRegistryClientStub_CustomTag verifies that the non-semver "pr12345" tag exists. +func TestNewRegistryClientStub_CustomTag(t *testing.T) { + client := fake.NewRegistryClientStub() + + err := client.CheckImageExists(context.Background(), "pr12345") + assert.NoError(t, err) +} + +// TestNewRegistryClientStub_GetRegistry verifies the default registry path. +func TestNewRegistryClientStub_GetRegistry(t *testing.T) { + client := fake.NewRegistryClientStub() + // Default host set in deckhouse_stub.go: + // registry.deckhouse.ru/deckhouse/fe + assert.Equal(t, "registry.deckhouse.ru/deckhouse/fe", client.GetRegistry()) +} diff --git a/pkg/mock/generate.go b/pkg/mock/generate.go index 3b6e0bec..73f82256 100644 --- a/pkg/mock/generate.go +++ b/pkg/mock/generate.go @@ -1,3 +1,3 @@ -//go:generate minimock -i github.com/deckhouse/deckhouse-cli/pkg/registry.Client -o registry_client_mock.go -n RegistryClientMock -p mock +//go:generate minimock -i github.com/deckhouse/deckhouse/pkg/registry.Client -o registry_client_mock.go -n RegistryClientMock -p mock package mock diff --git a/pkg/mock/registry_client_mock.go b/pkg/mock/registry_client_mock.go index 430576d5..9ab7039b 100644 --- a/pkg/mock/registry_client_mock.go +++ b/pkg/mock/registry_client_mock.go @@ -2,7 +2,7 @@ package mock -//go:generate minimock -i github.com/deckhouse/deckhouse-cli/pkg/registry.Client -o registry_client_mock.go -n RegistryClientMock -p mock +//go:generate minimock -i github.com/deckhouse/deckhouse/pkg/registry.Client -o registry_client_mock.go -n RegistryClientMock -p mock import ( "context" @@ -10,13 +10,12 @@ import ( mm_atomic "sync/atomic" mm_time "time" - mm_client "github.com/deckhouse/deckhouse-cli/pkg/registry" - "github.com/deckhouse/deckhouse/pkg/registry" + mm_registry "github.com/deckhouse/deckhouse/pkg/registry" "github.com/gojuno/minimock/v3" v1 "github.com/google/go-containerregistry/pkg/v1" ) -// RegistryClientMock implements mm_client.Client +// RegistryClientMock implements mm_registry.Client type RegistryClientMock struct { t minimock.Tester finishOnce sync.Once @@ -28,6 +27,20 @@ type RegistryClientMock struct { beforeCheckImageExistsCounter uint64 CheckImageExistsMock mRegistryClientMockCheckImageExists + funcCopyImage func(ctx context.Context, srcTag string, dest mm_registry.Client, destTag string) (err error) + funcCopyImageOrigin string + inspectFuncCopyImage func(ctx context.Context, srcTag string, dest mm_registry.Client, destTag string) + afterCopyImageCounter uint64 + beforeCopyImageCounter uint64 + CopyImageMock mRegistryClientMockCopyImage + + funcDeleteByDigest func(ctx context.Context, digest v1.Hash) (err error) + funcDeleteByDigestOrigin string + inspectFuncDeleteByDigest func(ctx context.Context, digest v1.Hash) + afterDeleteByDigestCounter uint64 + beforeDeleteByDigestCounter uint64 + DeleteByDigestMock mRegistryClientMockDeleteByDigest + funcDeleteTag func(ctx context.Context, tag string) (err error) funcDeleteTagOrigin string inspectFuncDeleteTag func(ctx context.Context, tag string) @@ -42,9 +55,9 @@ type RegistryClientMock struct { beforeGetDigestCounter uint64 GetDigestMock mRegistryClientMockGetDigest - funcGetImage func(ctx context.Context, tag string, opts ...registry.ImageGetOption) (i1 registry.Image, err error) + funcGetImage func(ctx context.Context, tag string, opts ...mm_registry.ImageGetOption) (i1 mm_registry.Image, err error) funcGetImageOrigin string - inspectFuncGetImage func(ctx context.Context, tag string, opts ...registry.ImageGetOption) + inspectFuncGetImage func(ctx context.Context, tag string, opts ...mm_registry.ImageGetOption) afterGetImageCounter uint64 beforeGetImageCounter uint64 GetImageMock mRegistryClientMockGetImage @@ -56,7 +69,7 @@ type RegistryClientMock struct { beforeGetImageConfigCounter uint64 GetImageConfigMock mRegistryClientMockGetImageConfig - funcGetManifest func(ctx context.Context, tag string) (m1 registry.ManifestResult, err error) + funcGetManifest func(ctx context.Context, tag string) (m1 mm_registry.ManifestResult, err error) funcGetManifestOrigin string inspectFuncGetManifest func(ctx context.Context, tag string) afterGetManifestCounter uint64 @@ -70,27 +83,34 @@ type RegistryClientMock struct { beforeGetRegistryCounter uint64 GetRegistryMock mRegistryClientMockGetRegistry - funcListRepositories func(ctx context.Context, opts ...registry.ListRepositoriesOption) (sa1 []string, err error) + funcListRepositories func(ctx context.Context, opts ...mm_registry.ListRepositoriesOption) (sa1 []string, err error) funcListRepositoriesOrigin string - inspectFuncListRepositories func(ctx context.Context, opts ...registry.ListRepositoriesOption) + inspectFuncListRepositories func(ctx context.Context, opts ...mm_registry.ListRepositoriesOption) afterListRepositoriesCounter uint64 beforeListRepositoriesCounter uint64 ListRepositoriesMock mRegistryClientMockListRepositories - funcListTags func(ctx context.Context, opts ...registry.ListTagsOption) (sa1 []string, err error) + funcListTags func(ctx context.Context, opts ...mm_registry.ListTagsOption) (sa1 []string, err error) funcListTagsOrigin string - inspectFuncListTags func(ctx context.Context, opts ...registry.ListTagsOption) + inspectFuncListTags func(ctx context.Context, opts ...mm_registry.ListTagsOption) afterListTagsCounter uint64 beforeListTagsCounter uint64 ListTagsMock mRegistryClientMockListTags - funcPushImage func(ctx context.Context, tag string, img v1.Image, opts ...registry.ImagePushOption) (err error) + funcPushImage func(ctx context.Context, tag string, img v1.Image, opts ...mm_registry.ImagePushOption) (err error) funcPushImageOrigin string - inspectFuncPushImage func(ctx context.Context, tag string, img v1.Image, opts ...registry.ImagePushOption) + inspectFuncPushImage func(ctx context.Context, tag string, img v1.Image, opts ...mm_registry.ImagePushOption) afterPushImageCounter uint64 beforePushImageCounter uint64 PushImageMock mRegistryClientMockPushImage + funcPushIndex func(ctx context.Context, tag string, idx v1.ImageIndex, opts ...mm_registry.ImagePushOption) (err error) + funcPushIndexOrigin string + inspectFuncPushIndex func(ctx context.Context, tag string, idx v1.ImageIndex, opts ...mm_registry.ImagePushOption) + afterPushIndexCounter uint64 + beforePushIndexCounter uint64 + PushIndexMock mRegistryClientMockPushIndex + funcTagImage func(ctx context.Context, sourceTag string, destTag string) (err error) funcTagImageOrigin string inspectFuncTagImage func(ctx context.Context, sourceTag string, destTag string) @@ -98,7 +118,7 @@ type RegistryClientMock struct { beforeTagImageCounter uint64 TagImageMock mRegistryClientMockTagImage - funcWithSegment func(segments ...string) (c1 mm_client.Client) + funcWithSegment func(segments ...string) (c1 mm_registry.Client) funcWithSegmentOrigin string inspectFuncWithSegment func(segments ...string) afterWithSegmentCounter uint64 @@ -106,7 +126,7 @@ type RegistryClientMock struct { WithSegmentMock mRegistryClientMockWithSegment } -// NewRegistryClientMock returns a mock for mm_client.Client +// NewRegistryClientMock returns a mock for mm_registry.Client func NewRegistryClientMock(t minimock.Tester) *RegistryClientMock { m := &RegistryClientMock{t: t} @@ -117,6 +137,12 @@ func NewRegistryClientMock(t minimock.Tester) *RegistryClientMock { m.CheckImageExistsMock = mRegistryClientMockCheckImageExists{mock: m} m.CheckImageExistsMock.callArgs = []*RegistryClientMockCheckImageExistsParams{} + m.CopyImageMock = mRegistryClientMockCopyImage{mock: m} + m.CopyImageMock.callArgs = []*RegistryClientMockCopyImageParams{} + + m.DeleteByDigestMock = mRegistryClientMockDeleteByDigest{mock: m} + m.DeleteByDigestMock.callArgs = []*RegistryClientMockDeleteByDigestParams{} + m.DeleteTagMock = mRegistryClientMockDeleteTag{mock: m} m.DeleteTagMock.callArgs = []*RegistryClientMockDeleteTagParams{} @@ -143,6 +169,9 @@ func NewRegistryClientMock(t minimock.Tester) *RegistryClientMock { m.PushImageMock = mRegistryClientMockPushImage{mock: m} m.PushImageMock.callArgs = []*RegistryClientMockPushImageParams{} + m.PushIndexMock = mRegistryClientMockPushIndex{mock: m} + m.PushIndexMock.callArgs = []*RegistryClientMockPushIndexParams{} + m.TagImageMock = mRegistryClientMockTagImage{mock: m} m.TagImageMock.callArgs = []*RegistryClientMockTagImageParams{} @@ -366,7 +395,7 @@ func (mmCheckImageExists *mRegistryClientMockCheckImageExists) invocationsDone() return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) } -// CheckImageExists implements mm_client.Client +// CheckImageExists implements mm_registry.Client func (mmCheckImageExists *RegistryClientMock) CheckImageExists(ctx context.Context, tag string) (err error) { mm_atomic.AddUint64(&mmCheckImageExists.beforeCheckImageExistsCounter, 1) defer mm_atomic.AddUint64(&mmCheckImageExists.afterCheckImageExistsCounter, 1) @@ -384,115 +413,861 @@ func (mmCheckImageExists *RegistryClientMock) CheckImageExists(ctx context.Conte mmCheckImageExists.CheckImageExistsMock.callArgs = append(mmCheckImageExists.CheckImageExistsMock.callArgs, &mm_params) mmCheckImageExists.CheckImageExistsMock.mutex.Unlock() - for _, e := range mmCheckImageExists.CheckImageExistsMock.expectations { + for _, e := range mmCheckImageExists.CheckImageExistsMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.err + } + } + + if mmCheckImageExists.CheckImageExistsMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmCheckImageExists.CheckImageExistsMock.defaultExpectation.Counter, 1) + mm_want := mmCheckImageExists.CheckImageExistsMock.defaultExpectation.params + mm_want_ptrs := mmCheckImageExists.CheckImageExistsMock.defaultExpectation.paramPtrs + + mm_got := RegistryClientMockCheckImageExistsParams{ctx, tag} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmCheckImageExists.t.Errorf("RegistryClientMock.CheckImageExists got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmCheckImageExists.CheckImageExistsMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.tag != nil && !minimock.Equal(*mm_want_ptrs.tag, mm_got.tag) { + mmCheckImageExists.t.Errorf("RegistryClientMock.CheckImageExists got unexpected parameter tag, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmCheckImageExists.CheckImageExistsMock.defaultExpectation.expectationOrigins.originTag, *mm_want_ptrs.tag, mm_got.tag, minimock.Diff(*mm_want_ptrs.tag, mm_got.tag)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmCheckImageExists.t.Errorf("RegistryClientMock.CheckImageExists got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmCheckImageExists.CheckImageExistsMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmCheckImageExists.CheckImageExistsMock.defaultExpectation.results + if mm_results == nil { + mmCheckImageExists.t.Fatal("No results are set for the RegistryClientMock.CheckImageExists") + } + return (*mm_results).err + } + if mmCheckImageExists.funcCheckImageExists != nil { + return mmCheckImageExists.funcCheckImageExists(ctx, tag) + } + mmCheckImageExists.t.Fatalf("Unexpected call to RegistryClientMock.CheckImageExists. %v %v", ctx, tag) + return +} + +// CheckImageExistsAfterCounter returns a count of finished RegistryClientMock.CheckImageExists invocations +func (mmCheckImageExists *RegistryClientMock) CheckImageExistsAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmCheckImageExists.afterCheckImageExistsCounter) +} + +// CheckImageExistsBeforeCounter returns a count of RegistryClientMock.CheckImageExists invocations +func (mmCheckImageExists *RegistryClientMock) CheckImageExistsBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmCheckImageExists.beforeCheckImageExistsCounter) +} + +// Calls returns a list of arguments used in each call to RegistryClientMock.CheckImageExists. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmCheckImageExists *mRegistryClientMockCheckImageExists) Calls() []*RegistryClientMockCheckImageExistsParams { + mmCheckImageExists.mutex.RLock() + + argCopy := make([]*RegistryClientMockCheckImageExistsParams, len(mmCheckImageExists.callArgs)) + copy(argCopy, mmCheckImageExists.callArgs) + + mmCheckImageExists.mutex.RUnlock() + + return argCopy +} + +// MinimockCheckImageExistsDone returns true if the count of the CheckImageExists invocations corresponds +// the number of defined expectations +func (m *RegistryClientMock) MinimockCheckImageExistsDone() bool { + if m.CheckImageExistsMock.optional { + // Optional methods provide '0 or more' call count restriction. + return true + } + + for _, e := range m.CheckImageExistsMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.CheckImageExistsMock.invocationsDone() +} + +// MinimockCheckImageExistsInspect logs each unmet expectation +func (m *RegistryClientMock) MinimockCheckImageExistsInspect() { + for _, e := range m.CheckImageExistsMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to RegistryClientMock.CheckImageExists at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + } + } + + afterCheckImageExistsCounter := mm_atomic.LoadUint64(&m.afterCheckImageExistsCounter) + // if default expectation was set then invocations count should be greater than zero + if m.CheckImageExistsMock.defaultExpectation != nil && afterCheckImageExistsCounter < 1 { + if m.CheckImageExistsMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to RegistryClientMock.CheckImageExists at\n%s", m.CheckImageExistsMock.defaultExpectation.returnOrigin) + } else { + m.t.Errorf("Expected call to RegistryClientMock.CheckImageExists at\n%s with params: %#v", m.CheckImageExistsMock.defaultExpectation.expectationOrigins.origin, *m.CheckImageExistsMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcCheckImageExists != nil && afterCheckImageExistsCounter < 1 { + m.t.Errorf("Expected call to RegistryClientMock.CheckImageExists at\n%s", m.funcCheckImageExistsOrigin) + } + + if !m.CheckImageExistsMock.invocationsDone() && afterCheckImageExistsCounter > 0 { + m.t.Errorf("Expected %d calls to RegistryClientMock.CheckImageExists at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.CheckImageExistsMock.expectedInvocations), m.CheckImageExistsMock.expectedInvocationsOrigin, afterCheckImageExistsCounter) + } +} + +type mRegistryClientMockCopyImage struct { + optional bool + mock *RegistryClientMock + defaultExpectation *RegistryClientMockCopyImageExpectation + expectations []*RegistryClientMockCopyImageExpectation + + callArgs []*RegistryClientMockCopyImageParams + mutex sync.RWMutex + + expectedInvocations uint64 + expectedInvocationsOrigin string +} + +// RegistryClientMockCopyImageExpectation specifies expectation struct of the Client.CopyImage +type RegistryClientMockCopyImageExpectation struct { + mock *RegistryClientMock + params *RegistryClientMockCopyImageParams + paramPtrs *RegistryClientMockCopyImageParamPtrs + expectationOrigins RegistryClientMockCopyImageExpectationOrigins + results *RegistryClientMockCopyImageResults + returnOrigin string + Counter uint64 +} + +// RegistryClientMockCopyImageParams contains parameters of the Client.CopyImage +type RegistryClientMockCopyImageParams struct { + ctx context.Context + srcTag string + dest mm_registry.Client + destTag string +} + +// RegistryClientMockCopyImageParamPtrs contains pointers to parameters of the Client.CopyImage +type RegistryClientMockCopyImageParamPtrs struct { + ctx *context.Context + srcTag *string + dest *mm_registry.Client + destTag *string +} + +// RegistryClientMockCopyImageResults contains results of the Client.CopyImage +type RegistryClientMockCopyImageResults struct { + err error +} + +// RegistryClientMockCopyImageOrigins contains origins of expectations of the Client.CopyImage +type RegistryClientMockCopyImageExpectationOrigins struct { + origin string + originCtx string + originSrcTag string + originDest string + originDestTag string +} + +// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning +// the test will fail minimock's automatic final call check if the mocked method was not called at least once. +// Optional() makes method check to work in '0 or more' mode. +// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to +// catch the problems when the expected method call is totally skipped during test run. +func (mmCopyImage *mRegistryClientMockCopyImage) Optional() *mRegistryClientMockCopyImage { + mmCopyImage.optional = true + return mmCopyImage +} + +// Expect sets up expected params for Client.CopyImage +func (mmCopyImage *mRegistryClientMockCopyImage) Expect(ctx context.Context, srcTag string, dest mm_registry.Client, destTag string) *mRegistryClientMockCopyImage { + if mmCopyImage.mock.funcCopyImage != nil { + mmCopyImage.mock.t.Fatalf("RegistryClientMock.CopyImage mock is already set by Set") + } + + if mmCopyImage.defaultExpectation == nil { + mmCopyImage.defaultExpectation = &RegistryClientMockCopyImageExpectation{} + } + + if mmCopyImage.defaultExpectation.paramPtrs != nil { + mmCopyImage.mock.t.Fatalf("RegistryClientMock.CopyImage mock is already set by ExpectParams functions") + } + + mmCopyImage.defaultExpectation.params = &RegistryClientMockCopyImageParams{ctx, srcTag, dest, destTag} + mmCopyImage.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1) + for _, e := range mmCopyImage.expectations { + if minimock.Equal(e.params, mmCopyImage.defaultExpectation.params) { + mmCopyImage.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmCopyImage.defaultExpectation.params) + } + } + + return mmCopyImage +} + +// ExpectCtxParam1 sets up expected param ctx for Client.CopyImage +func (mmCopyImage *mRegistryClientMockCopyImage) ExpectCtxParam1(ctx context.Context) *mRegistryClientMockCopyImage { + if mmCopyImage.mock.funcCopyImage != nil { + mmCopyImage.mock.t.Fatalf("RegistryClientMock.CopyImage mock is already set by Set") + } + + if mmCopyImage.defaultExpectation == nil { + mmCopyImage.defaultExpectation = &RegistryClientMockCopyImageExpectation{} + } + + if mmCopyImage.defaultExpectation.params != nil { + mmCopyImage.mock.t.Fatalf("RegistryClientMock.CopyImage mock is already set by Expect") + } + + if mmCopyImage.defaultExpectation.paramPtrs == nil { + mmCopyImage.defaultExpectation.paramPtrs = &RegistryClientMockCopyImageParamPtrs{} + } + mmCopyImage.defaultExpectation.paramPtrs.ctx = &ctx + mmCopyImage.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1) + + return mmCopyImage +} + +// ExpectSrcTagParam2 sets up expected param srcTag for Client.CopyImage +func (mmCopyImage *mRegistryClientMockCopyImage) ExpectSrcTagParam2(srcTag string) *mRegistryClientMockCopyImage { + if mmCopyImage.mock.funcCopyImage != nil { + mmCopyImage.mock.t.Fatalf("RegistryClientMock.CopyImage mock is already set by Set") + } + + if mmCopyImage.defaultExpectation == nil { + mmCopyImage.defaultExpectation = &RegistryClientMockCopyImageExpectation{} + } + + if mmCopyImage.defaultExpectation.params != nil { + mmCopyImage.mock.t.Fatalf("RegistryClientMock.CopyImage mock is already set by Expect") + } + + if mmCopyImage.defaultExpectation.paramPtrs == nil { + mmCopyImage.defaultExpectation.paramPtrs = &RegistryClientMockCopyImageParamPtrs{} + } + mmCopyImage.defaultExpectation.paramPtrs.srcTag = &srcTag + mmCopyImage.defaultExpectation.expectationOrigins.originSrcTag = minimock.CallerInfo(1) + + return mmCopyImage +} + +// ExpectDestParam3 sets up expected param dest for Client.CopyImage +func (mmCopyImage *mRegistryClientMockCopyImage) ExpectDestParam3(dest mm_registry.Client) *mRegistryClientMockCopyImage { + if mmCopyImage.mock.funcCopyImage != nil { + mmCopyImage.mock.t.Fatalf("RegistryClientMock.CopyImage mock is already set by Set") + } + + if mmCopyImage.defaultExpectation == nil { + mmCopyImage.defaultExpectation = &RegistryClientMockCopyImageExpectation{} + } + + if mmCopyImage.defaultExpectation.params != nil { + mmCopyImage.mock.t.Fatalf("RegistryClientMock.CopyImage mock is already set by Expect") + } + + if mmCopyImage.defaultExpectation.paramPtrs == nil { + mmCopyImage.defaultExpectation.paramPtrs = &RegistryClientMockCopyImageParamPtrs{} + } + mmCopyImage.defaultExpectation.paramPtrs.dest = &dest + mmCopyImage.defaultExpectation.expectationOrigins.originDest = minimock.CallerInfo(1) + + return mmCopyImage +} + +// ExpectDestTagParam4 sets up expected param destTag for Client.CopyImage +func (mmCopyImage *mRegistryClientMockCopyImage) ExpectDestTagParam4(destTag string) *mRegistryClientMockCopyImage { + if mmCopyImage.mock.funcCopyImage != nil { + mmCopyImage.mock.t.Fatalf("RegistryClientMock.CopyImage mock is already set by Set") + } + + if mmCopyImage.defaultExpectation == nil { + mmCopyImage.defaultExpectation = &RegistryClientMockCopyImageExpectation{} + } + + if mmCopyImage.defaultExpectation.params != nil { + mmCopyImage.mock.t.Fatalf("RegistryClientMock.CopyImage mock is already set by Expect") + } + + if mmCopyImage.defaultExpectation.paramPtrs == nil { + mmCopyImage.defaultExpectation.paramPtrs = &RegistryClientMockCopyImageParamPtrs{} + } + mmCopyImage.defaultExpectation.paramPtrs.destTag = &destTag + mmCopyImage.defaultExpectation.expectationOrigins.originDestTag = minimock.CallerInfo(1) + + return mmCopyImage +} + +// Inspect accepts an inspector function that has same arguments as the Client.CopyImage +func (mmCopyImage *mRegistryClientMockCopyImage) Inspect(f func(ctx context.Context, srcTag string, dest mm_registry.Client, destTag string)) *mRegistryClientMockCopyImage { + if mmCopyImage.mock.inspectFuncCopyImage != nil { + mmCopyImage.mock.t.Fatalf("Inspect function is already set for RegistryClientMock.CopyImage") + } + + mmCopyImage.mock.inspectFuncCopyImage = f + + return mmCopyImage +} + +// Return sets up results that will be returned by Client.CopyImage +func (mmCopyImage *mRegistryClientMockCopyImage) Return(err error) *RegistryClientMock { + if mmCopyImage.mock.funcCopyImage != nil { + mmCopyImage.mock.t.Fatalf("RegistryClientMock.CopyImage mock is already set by Set") + } + + if mmCopyImage.defaultExpectation == nil { + mmCopyImage.defaultExpectation = &RegistryClientMockCopyImageExpectation{mock: mmCopyImage.mock} + } + mmCopyImage.defaultExpectation.results = &RegistryClientMockCopyImageResults{err} + mmCopyImage.defaultExpectation.returnOrigin = minimock.CallerInfo(1) + return mmCopyImage.mock +} + +// Set uses given function f to mock the Client.CopyImage method +func (mmCopyImage *mRegistryClientMockCopyImage) Set(f func(ctx context.Context, srcTag string, dest mm_registry.Client, destTag string) (err error)) *RegistryClientMock { + if mmCopyImage.defaultExpectation != nil { + mmCopyImage.mock.t.Fatalf("Default expectation is already set for the Client.CopyImage method") + } + + if len(mmCopyImage.expectations) > 0 { + mmCopyImage.mock.t.Fatalf("Some expectations are already set for the Client.CopyImage method") + } + + mmCopyImage.mock.funcCopyImage = f + mmCopyImage.mock.funcCopyImageOrigin = minimock.CallerInfo(1) + return mmCopyImage.mock +} + +// When sets expectation for the Client.CopyImage which will trigger the result defined by the following +// Then helper +func (mmCopyImage *mRegistryClientMockCopyImage) When(ctx context.Context, srcTag string, dest mm_registry.Client, destTag string) *RegistryClientMockCopyImageExpectation { + if mmCopyImage.mock.funcCopyImage != nil { + mmCopyImage.mock.t.Fatalf("RegistryClientMock.CopyImage mock is already set by Set") + } + + expectation := &RegistryClientMockCopyImageExpectation{ + mock: mmCopyImage.mock, + params: &RegistryClientMockCopyImageParams{ctx, srcTag, dest, destTag}, + expectationOrigins: RegistryClientMockCopyImageExpectationOrigins{origin: minimock.CallerInfo(1)}, + } + mmCopyImage.expectations = append(mmCopyImage.expectations, expectation) + return expectation +} + +// Then sets up Client.CopyImage return parameters for the expectation previously defined by the When method +func (e *RegistryClientMockCopyImageExpectation) Then(err error) *RegistryClientMock { + e.results = &RegistryClientMockCopyImageResults{err} + return e.mock +} + +// Times sets number of times Client.CopyImage should be invoked +func (mmCopyImage *mRegistryClientMockCopyImage) Times(n uint64) *mRegistryClientMockCopyImage { + if n == 0 { + mmCopyImage.mock.t.Fatalf("Times of RegistryClientMock.CopyImage mock can not be zero") + } + mm_atomic.StoreUint64(&mmCopyImage.expectedInvocations, n) + mmCopyImage.expectedInvocationsOrigin = minimock.CallerInfo(1) + return mmCopyImage +} + +func (mmCopyImage *mRegistryClientMockCopyImage) invocationsDone() bool { + if len(mmCopyImage.expectations) == 0 && mmCopyImage.defaultExpectation == nil && mmCopyImage.mock.funcCopyImage == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmCopyImage.mock.afterCopyImageCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmCopyImage.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// CopyImage implements mm_registry.Client +func (mmCopyImage *RegistryClientMock) CopyImage(ctx context.Context, srcTag string, dest mm_registry.Client, destTag string) (err error) { + mm_atomic.AddUint64(&mmCopyImage.beforeCopyImageCounter, 1) + defer mm_atomic.AddUint64(&mmCopyImage.afterCopyImageCounter, 1) + + mmCopyImage.t.Helper() + + if mmCopyImage.inspectFuncCopyImage != nil { + mmCopyImage.inspectFuncCopyImage(ctx, srcTag, dest, destTag) + } + + mm_params := RegistryClientMockCopyImageParams{ctx, srcTag, dest, destTag} + + // Record call args + mmCopyImage.CopyImageMock.mutex.Lock() + mmCopyImage.CopyImageMock.callArgs = append(mmCopyImage.CopyImageMock.callArgs, &mm_params) + mmCopyImage.CopyImageMock.mutex.Unlock() + + for _, e := range mmCopyImage.CopyImageMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.err + } + } + + if mmCopyImage.CopyImageMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmCopyImage.CopyImageMock.defaultExpectation.Counter, 1) + mm_want := mmCopyImage.CopyImageMock.defaultExpectation.params + mm_want_ptrs := mmCopyImage.CopyImageMock.defaultExpectation.paramPtrs + + mm_got := RegistryClientMockCopyImageParams{ctx, srcTag, dest, destTag} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmCopyImage.t.Errorf("RegistryClientMock.CopyImage got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmCopyImage.CopyImageMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.srcTag != nil && !minimock.Equal(*mm_want_ptrs.srcTag, mm_got.srcTag) { + mmCopyImage.t.Errorf("RegistryClientMock.CopyImage got unexpected parameter srcTag, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmCopyImage.CopyImageMock.defaultExpectation.expectationOrigins.originSrcTag, *mm_want_ptrs.srcTag, mm_got.srcTag, minimock.Diff(*mm_want_ptrs.srcTag, mm_got.srcTag)) + } + + if mm_want_ptrs.dest != nil && !minimock.Equal(*mm_want_ptrs.dest, mm_got.dest) { + mmCopyImage.t.Errorf("RegistryClientMock.CopyImage got unexpected parameter dest, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmCopyImage.CopyImageMock.defaultExpectation.expectationOrigins.originDest, *mm_want_ptrs.dest, mm_got.dest, minimock.Diff(*mm_want_ptrs.dest, mm_got.dest)) + } + + if mm_want_ptrs.destTag != nil && !minimock.Equal(*mm_want_ptrs.destTag, mm_got.destTag) { + mmCopyImage.t.Errorf("RegistryClientMock.CopyImage got unexpected parameter destTag, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmCopyImage.CopyImageMock.defaultExpectation.expectationOrigins.originDestTag, *mm_want_ptrs.destTag, mm_got.destTag, minimock.Diff(*mm_want_ptrs.destTag, mm_got.destTag)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmCopyImage.t.Errorf("RegistryClientMock.CopyImage got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmCopyImage.CopyImageMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmCopyImage.CopyImageMock.defaultExpectation.results + if mm_results == nil { + mmCopyImage.t.Fatal("No results are set for the RegistryClientMock.CopyImage") + } + return (*mm_results).err + } + if mmCopyImage.funcCopyImage != nil { + return mmCopyImage.funcCopyImage(ctx, srcTag, dest, destTag) + } + mmCopyImage.t.Fatalf("Unexpected call to RegistryClientMock.CopyImage. %v %v %v %v", ctx, srcTag, dest, destTag) + return +} + +// CopyImageAfterCounter returns a count of finished RegistryClientMock.CopyImage invocations +func (mmCopyImage *RegistryClientMock) CopyImageAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmCopyImage.afterCopyImageCounter) +} + +// CopyImageBeforeCounter returns a count of RegistryClientMock.CopyImage invocations +func (mmCopyImage *RegistryClientMock) CopyImageBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmCopyImage.beforeCopyImageCounter) +} + +// Calls returns a list of arguments used in each call to RegistryClientMock.CopyImage. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmCopyImage *mRegistryClientMockCopyImage) Calls() []*RegistryClientMockCopyImageParams { + mmCopyImage.mutex.RLock() + + argCopy := make([]*RegistryClientMockCopyImageParams, len(mmCopyImage.callArgs)) + copy(argCopy, mmCopyImage.callArgs) + + mmCopyImage.mutex.RUnlock() + + return argCopy +} + +// MinimockCopyImageDone returns true if the count of the CopyImage invocations corresponds +// the number of defined expectations +func (m *RegistryClientMock) MinimockCopyImageDone() bool { + if m.CopyImageMock.optional { + // Optional methods provide '0 or more' call count restriction. + return true + } + + for _, e := range m.CopyImageMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.CopyImageMock.invocationsDone() +} + +// MinimockCopyImageInspect logs each unmet expectation +func (m *RegistryClientMock) MinimockCopyImageInspect() { + for _, e := range m.CopyImageMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to RegistryClientMock.CopyImage at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + } + } + + afterCopyImageCounter := mm_atomic.LoadUint64(&m.afterCopyImageCounter) + // if default expectation was set then invocations count should be greater than zero + if m.CopyImageMock.defaultExpectation != nil && afterCopyImageCounter < 1 { + if m.CopyImageMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to RegistryClientMock.CopyImage at\n%s", m.CopyImageMock.defaultExpectation.returnOrigin) + } else { + m.t.Errorf("Expected call to RegistryClientMock.CopyImage at\n%s with params: %#v", m.CopyImageMock.defaultExpectation.expectationOrigins.origin, *m.CopyImageMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcCopyImage != nil && afterCopyImageCounter < 1 { + m.t.Errorf("Expected call to RegistryClientMock.CopyImage at\n%s", m.funcCopyImageOrigin) + } + + if !m.CopyImageMock.invocationsDone() && afterCopyImageCounter > 0 { + m.t.Errorf("Expected %d calls to RegistryClientMock.CopyImage at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.CopyImageMock.expectedInvocations), m.CopyImageMock.expectedInvocationsOrigin, afterCopyImageCounter) + } +} + +type mRegistryClientMockDeleteByDigest struct { + optional bool + mock *RegistryClientMock + defaultExpectation *RegistryClientMockDeleteByDigestExpectation + expectations []*RegistryClientMockDeleteByDigestExpectation + + callArgs []*RegistryClientMockDeleteByDigestParams + mutex sync.RWMutex + + expectedInvocations uint64 + expectedInvocationsOrigin string +} + +// RegistryClientMockDeleteByDigestExpectation specifies expectation struct of the Client.DeleteByDigest +type RegistryClientMockDeleteByDigestExpectation struct { + mock *RegistryClientMock + params *RegistryClientMockDeleteByDigestParams + paramPtrs *RegistryClientMockDeleteByDigestParamPtrs + expectationOrigins RegistryClientMockDeleteByDigestExpectationOrigins + results *RegistryClientMockDeleteByDigestResults + returnOrigin string + Counter uint64 +} + +// RegistryClientMockDeleteByDigestParams contains parameters of the Client.DeleteByDigest +type RegistryClientMockDeleteByDigestParams struct { + ctx context.Context + digest v1.Hash +} + +// RegistryClientMockDeleteByDigestParamPtrs contains pointers to parameters of the Client.DeleteByDigest +type RegistryClientMockDeleteByDigestParamPtrs struct { + ctx *context.Context + digest *v1.Hash +} + +// RegistryClientMockDeleteByDigestResults contains results of the Client.DeleteByDigest +type RegistryClientMockDeleteByDigestResults struct { + err error +} + +// RegistryClientMockDeleteByDigestOrigins contains origins of expectations of the Client.DeleteByDigest +type RegistryClientMockDeleteByDigestExpectationOrigins struct { + origin string + originCtx string + originDigest string +} + +// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning +// the test will fail minimock's automatic final call check if the mocked method was not called at least once. +// Optional() makes method check to work in '0 or more' mode. +// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to +// catch the problems when the expected method call is totally skipped during test run. +func (mmDeleteByDigest *mRegistryClientMockDeleteByDigest) Optional() *mRegistryClientMockDeleteByDigest { + mmDeleteByDigest.optional = true + return mmDeleteByDigest +} + +// Expect sets up expected params for Client.DeleteByDigest +func (mmDeleteByDigest *mRegistryClientMockDeleteByDigest) Expect(ctx context.Context, digest v1.Hash) *mRegistryClientMockDeleteByDigest { + if mmDeleteByDigest.mock.funcDeleteByDigest != nil { + mmDeleteByDigest.mock.t.Fatalf("RegistryClientMock.DeleteByDigest mock is already set by Set") + } + + if mmDeleteByDigest.defaultExpectation == nil { + mmDeleteByDigest.defaultExpectation = &RegistryClientMockDeleteByDigestExpectation{} + } + + if mmDeleteByDigest.defaultExpectation.paramPtrs != nil { + mmDeleteByDigest.mock.t.Fatalf("RegistryClientMock.DeleteByDigest mock is already set by ExpectParams functions") + } + + mmDeleteByDigest.defaultExpectation.params = &RegistryClientMockDeleteByDigestParams{ctx, digest} + mmDeleteByDigest.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1) + for _, e := range mmDeleteByDigest.expectations { + if minimock.Equal(e.params, mmDeleteByDigest.defaultExpectation.params) { + mmDeleteByDigest.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmDeleteByDigest.defaultExpectation.params) + } + } + + return mmDeleteByDigest +} + +// ExpectCtxParam1 sets up expected param ctx for Client.DeleteByDigest +func (mmDeleteByDigest *mRegistryClientMockDeleteByDigest) ExpectCtxParam1(ctx context.Context) *mRegistryClientMockDeleteByDigest { + if mmDeleteByDigest.mock.funcDeleteByDigest != nil { + mmDeleteByDigest.mock.t.Fatalf("RegistryClientMock.DeleteByDigest mock is already set by Set") + } + + if mmDeleteByDigest.defaultExpectation == nil { + mmDeleteByDigest.defaultExpectation = &RegistryClientMockDeleteByDigestExpectation{} + } + + if mmDeleteByDigest.defaultExpectation.params != nil { + mmDeleteByDigest.mock.t.Fatalf("RegistryClientMock.DeleteByDigest mock is already set by Expect") + } + + if mmDeleteByDigest.defaultExpectation.paramPtrs == nil { + mmDeleteByDigest.defaultExpectation.paramPtrs = &RegistryClientMockDeleteByDigestParamPtrs{} + } + mmDeleteByDigest.defaultExpectation.paramPtrs.ctx = &ctx + mmDeleteByDigest.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1) + + return mmDeleteByDigest +} + +// ExpectDigestParam2 sets up expected param digest for Client.DeleteByDigest +func (mmDeleteByDigest *mRegistryClientMockDeleteByDigest) ExpectDigestParam2(digest v1.Hash) *mRegistryClientMockDeleteByDigest { + if mmDeleteByDigest.mock.funcDeleteByDigest != nil { + mmDeleteByDigest.mock.t.Fatalf("RegistryClientMock.DeleteByDigest mock is already set by Set") + } + + if mmDeleteByDigest.defaultExpectation == nil { + mmDeleteByDigest.defaultExpectation = &RegistryClientMockDeleteByDigestExpectation{} + } + + if mmDeleteByDigest.defaultExpectation.params != nil { + mmDeleteByDigest.mock.t.Fatalf("RegistryClientMock.DeleteByDigest mock is already set by Expect") + } + + if mmDeleteByDigest.defaultExpectation.paramPtrs == nil { + mmDeleteByDigest.defaultExpectation.paramPtrs = &RegistryClientMockDeleteByDigestParamPtrs{} + } + mmDeleteByDigest.defaultExpectation.paramPtrs.digest = &digest + mmDeleteByDigest.defaultExpectation.expectationOrigins.originDigest = minimock.CallerInfo(1) + + return mmDeleteByDigest +} + +// Inspect accepts an inspector function that has same arguments as the Client.DeleteByDigest +func (mmDeleteByDigest *mRegistryClientMockDeleteByDigest) Inspect(f func(ctx context.Context, digest v1.Hash)) *mRegistryClientMockDeleteByDigest { + if mmDeleteByDigest.mock.inspectFuncDeleteByDigest != nil { + mmDeleteByDigest.mock.t.Fatalf("Inspect function is already set for RegistryClientMock.DeleteByDigest") + } + + mmDeleteByDigest.mock.inspectFuncDeleteByDigest = f + + return mmDeleteByDigest +} + +// Return sets up results that will be returned by Client.DeleteByDigest +func (mmDeleteByDigest *mRegistryClientMockDeleteByDigest) Return(err error) *RegistryClientMock { + if mmDeleteByDigest.mock.funcDeleteByDigest != nil { + mmDeleteByDigest.mock.t.Fatalf("RegistryClientMock.DeleteByDigest mock is already set by Set") + } + + if mmDeleteByDigest.defaultExpectation == nil { + mmDeleteByDigest.defaultExpectation = &RegistryClientMockDeleteByDigestExpectation{mock: mmDeleteByDigest.mock} + } + mmDeleteByDigest.defaultExpectation.results = &RegistryClientMockDeleteByDigestResults{err} + mmDeleteByDigest.defaultExpectation.returnOrigin = minimock.CallerInfo(1) + return mmDeleteByDigest.mock +} + +// Set uses given function f to mock the Client.DeleteByDigest method +func (mmDeleteByDigest *mRegistryClientMockDeleteByDigest) Set(f func(ctx context.Context, digest v1.Hash) (err error)) *RegistryClientMock { + if mmDeleteByDigest.defaultExpectation != nil { + mmDeleteByDigest.mock.t.Fatalf("Default expectation is already set for the Client.DeleteByDigest method") + } + + if len(mmDeleteByDigest.expectations) > 0 { + mmDeleteByDigest.mock.t.Fatalf("Some expectations are already set for the Client.DeleteByDigest method") + } + + mmDeleteByDigest.mock.funcDeleteByDigest = f + mmDeleteByDigest.mock.funcDeleteByDigestOrigin = minimock.CallerInfo(1) + return mmDeleteByDigest.mock +} + +// When sets expectation for the Client.DeleteByDigest which will trigger the result defined by the following +// Then helper +func (mmDeleteByDigest *mRegistryClientMockDeleteByDigest) When(ctx context.Context, digest v1.Hash) *RegistryClientMockDeleteByDigestExpectation { + if mmDeleteByDigest.mock.funcDeleteByDigest != nil { + mmDeleteByDigest.mock.t.Fatalf("RegistryClientMock.DeleteByDigest mock is already set by Set") + } + + expectation := &RegistryClientMockDeleteByDigestExpectation{ + mock: mmDeleteByDigest.mock, + params: &RegistryClientMockDeleteByDigestParams{ctx, digest}, + expectationOrigins: RegistryClientMockDeleteByDigestExpectationOrigins{origin: minimock.CallerInfo(1)}, + } + mmDeleteByDigest.expectations = append(mmDeleteByDigest.expectations, expectation) + return expectation +} + +// Then sets up Client.DeleteByDigest return parameters for the expectation previously defined by the When method +func (e *RegistryClientMockDeleteByDigestExpectation) Then(err error) *RegistryClientMock { + e.results = &RegistryClientMockDeleteByDigestResults{err} + return e.mock +} + +// Times sets number of times Client.DeleteByDigest should be invoked +func (mmDeleteByDigest *mRegistryClientMockDeleteByDigest) Times(n uint64) *mRegistryClientMockDeleteByDigest { + if n == 0 { + mmDeleteByDigest.mock.t.Fatalf("Times of RegistryClientMock.DeleteByDigest mock can not be zero") + } + mm_atomic.StoreUint64(&mmDeleteByDigest.expectedInvocations, n) + mmDeleteByDigest.expectedInvocationsOrigin = minimock.CallerInfo(1) + return mmDeleteByDigest +} + +func (mmDeleteByDigest *mRegistryClientMockDeleteByDigest) invocationsDone() bool { + if len(mmDeleteByDigest.expectations) == 0 && mmDeleteByDigest.defaultExpectation == nil && mmDeleteByDigest.mock.funcDeleteByDigest == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmDeleteByDigest.mock.afterDeleteByDigestCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmDeleteByDigest.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// DeleteByDigest implements mm_registry.Client +func (mmDeleteByDigest *RegistryClientMock) DeleteByDigest(ctx context.Context, digest v1.Hash) (err error) { + mm_atomic.AddUint64(&mmDeleteByDigest.beforeDeleteByDigestCounter, 1) + defer mm_atomic.AddUint64(&mmDeleteByDigest.afterDeleteByDigestCounter, 1) + + mmDeleteByDigest.t.Helper() + + if mmDeleteByDigest.inspectFuncDeleteByDigest != nil { + mmDeleteByDigest.inspectFuncDeleteByDigest(ctx, digest) + } + + mm_params := RegistryClientMockDeleteByDigestParams{ctx, digest} + + // Record call args + mmDeleteByDigest.DeleteByDigestMock.mutex.Lock() + mmDeleteByDigest.DeleteByDigestMock.callArgs = append(mmDeleteByDigest.DeleteByDigestMock.callArgs, &mm_params) + mmDeleteByDigest.DeleteByDigestMock.mutex.Unlock() + + for _, e := range mmDeleteByDigest.DeleteByDigestMock.expectations { if minimock.Equal(*e.params, mm_params) { mm_atomic.AddUint64(&e.Counter, 1) return e.results.err } } - if mmCheckImageExists.CheckImageExistsMock.defaultExpectation != nil { - mm_atomic.AddUint64(&mmCheckImageExists.CheckImageExistsMock.defaultExpectation.Counter, 1) - mm_want := mmCheckImageExists.CheckImageExistsMock.defaultExpectation.params - mm_want_ptrs := mmCheckImageExists.CheckImageExistsMock.defaultExpectation.paramPtrs + if mmDeleteByDigest.DeleteByDigestMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmDeleteByDigest.DeleteByDigestMock.defaultExpectation.Counter, 1) + mm_want := mmDeleteByDigest.DeleteByDigestMock.defaultExpectation.params + mm_want_ptrs := mmDeleteByDigest.DeleteByDigestMock.defaultExpectation.paramPtrs - mm_got := RegistryClientMockCheckImageExistsParams{ctx, tag} + mm_got := RegistryClientMockDeleteByDigestParams{ctx, digest} if mm_want_ptrs != nil { if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { - mmCheckImageExists.t.Errorf("RegistryClientMock.CheckImageExists got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", - mmCheckImageExists.CheckImageExistsMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + mmDeleteByDigest.t.Errorf("RegistryClientMock.DeleteByDigest got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmDeleteByDigest.DeleteByDigestMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) } - if mm_want_ptrs.tag != nil && !minimock.Equal(*mm_want_ptrs.tag, mm_got.tag) { - mmCheckImageExists.t.Errorf("RegistryClientMock.CheckImageExists got unexpected parameter tag, expected at\n%s:\nwant: %#v\n got: %#v%s\n", - mmCheckImageExists.CheckImageExistsMock.defaultExpectation.expectationOrigins.originTag, *mm_want_ptrs.tag, mm_got.tag, minimock.Diff(*mm_want_ptrs.tag, mm_got.tag)) + if mm_want_ptrs.digest != nil && !minimock.Equal(*mm_want_ptrs.digest, mm_got.digest) { + mmDeleteByDigest.t.Errorf("RegistryClientMock.DeleteByDigest got unexpected parameter digest, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmDeleteByDigest.DeleteByDigestMock.defaultExpectation.expectationOrigins.originDigest, *mm_want_ptrs.digest, mm_got.digest, minimock.Diff(*mm_want_ptrs.digest, mm_got.digest)) } } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { - mmCheckImageExists.t.Errorf("RegistryClientMock.CheckImageExists got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", - mmCheckImageExists.CheckImageExistsMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + mmDeleteByDigest.t.Errorf("RegistryClientMock.DeleteByDigest got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmDeleteByDigest.DeleteByDigestMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) } - mm_results := mmCheckImageExists.CheckImageExistsMock.defaultExpectation.results + mm_results := mmDeleteByDigest.DeleteByDigestMock.defaultExpectation.results if mm_results == nil { - mmCheckImageExists.t.Fatal("No results are set for the RegistryClientMock.CheckImageExists") + mmDeleteByDigest.t.Fatal("No results are set for the RegistryClientMock.DeleteByDigest") } return (*mm_results).err } - if mmCheckImageExists.funcCheckImageExists != nil { - return mmCheckImageExists.funcCheckImageExists(ctx, tag) + if mmDeleteByDigest.funcDeleteByDigest != nil { + return mmDeleteByDigest.funcDeleteByDigest(ctx, digest) } - mmCheckImageExists.t.Fatalf("Unexpected call to RegistryClientMock.CheckImageExists. %v %v", ctx, tag) + mmDeleteByDigest.t.Fatalf("Unexpected call to RegistryClientMock.DeleteByDigest. %v %v", ctx, digest) return } -// CheckImageExistsAfterCounter returns a count of finished RegistryClientMock.CheckImageExists invocations -func (mmCheckImageExists *RegistryClientMock) CheckImageExistsAfterCounter() uint64 { - return mm_atomic.LoadUint64(&mmCheckImageExists.afterCheckImageExistsCounter) +// DeleteByDigestAfterCounter returns a count of finished RegistryClientMock.DeleteByDigest invocations +func (mmDeleteByDigest *RegistryClientMock) DeleteByDigestAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmDeleteByDigest.afterDeleteByDigestCounter) } -// CheckImageExistsBeforeCounter returns a count of RegistryClientMock.CheckImageExists invocations -func (mmCheckImageExists *RegistryClientMock) CheckImageExistsBeforeCounter() uint64 { - return mm_atomic.LoadUint64(&mmCheckImageExists.beforeCheckImageExistsCounter) +// DeleteByDigestBeforeCounter returns a count of RegistryClientMock.DeleteByDigest invocations +func (mmDeleteByDigest *RegistryClientMock) DeleteByDigestBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmDeleteByDigest.beforeDeleteByDigestCounter) } -// Calls returns a list of arguments used in each call to RegistryClientMock.CheckImageExists. +// Calls returns a list of arguments used in each call to RegistryClientMock.DeleteByDigest. // The list is in the same order as the calls were made (i.e. recent calls have a higher index) -func (mmCheckImageExists *mRegistryClientMockCheckImageExists) Calls() []*RegistryClientMockCheckImageExistsParams { - mmCheckImageExists.mutex.RLock() +func (mmDeleteByDigest *mRegistryClientMockDeleteByDigest) Calls() []*RegistryClientMockDeleteByDigestParams { + mmDeleteByDigest.mutex.RLock() - argCopy := make([]*RegistryClientMockCheckImageExistsParams, len(mmCheckImageExists.callArgs)) - copy(argCopy, mmCheckImageExists.callArgs) + argCopy := make([]*RegistryClientMockDeleteByDigestParams, len(mmDeleteByDigest.callArgs)) + copy(argCopy, mmDeleteByDigest.callArgs) - mmCheckImageExists.mutex.RUnlock() + mmDeleteByDigest.mutex.RUnlock() return argCopy } -// MinimockCheckImageExistsDone returns true if the count of the CheckImageExists invocations corresponds +// MinimockDeleteByDigestDone returns true if the count of the DeleteByDigest invocations corresponds // the number of defined expectations -func (m *RegistryClientMock) MinimockCheckImageExistsDone() bool { - if m.CheckImageExistsMock.optional { +func (m *RegistryClientMock) MinimockDeleteByDigestDone() bool { + if m.DeleteByDigestMock.optional { // Optional methods provide '0 or more' call count restriction. return true } - for _, e := range m.CheckImageExistsMock.expectations { + for _, e := range m.DeleteByDigestMock.expectations { if mm_atomic.LoadUint64(&e.Counter) < 1 { return false } } - return m.CheckImageExistsMock.invocationsDone() + return m.DeleteByDigestMock.invocationsDone() } -// MinimockCheckImageExistsInspect logs each unmet expectation -func (m *RegistryClientMock) MinimockCheckImageExistsInspect() { - for _, e := range m.CheckImageExistsMock.expectations { +// MinimockDeleteByDigestInspect logs each unmet expectation +func (m *RegistryClientMock) MinimockDeleteByDigestInspect() { + for _, e := range m.DeleteByDigestMock.expectations { if mm_atomic.LoadUint64(&e.Counter) < 1 { - m.t.Errorf("Expected call to RegistryClientMock.CheckImageExists at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + m.t.Errorf("Expected call to RegistryClientMock.DeleteByDigest at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) } } - afterCheckImageExistsCounter := mm_atomic.LoadUint64(&m.afterCheckImageExistsCounter) + afterDeleteByDigestCounter := mm_atomic.LoadUint64(&m.afterDeleteByDigestCounter) // if default expectation was set then invocations count should be greater than zero - if m.CheckImageExistsMock.defaultExpectation != nil && afterCheckImageExistsCounter < 1 { - if m.CheckImageExistsMock.defaultExpectation.params == nil { - m.t.Errorf("Expected call to RegistryClientMock.CheckImageExists at\n%s", m.CheckImageExistsMock.defaultExpectation.returnOrigin) + if m.DeleteByDigestMock.defaultExpectation != nil && afterDeleteByDigestCounter < 1 { + if m.DeleteByDigestMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to RegistryClientMock.DeleteByDigest at\n%s", m.DeleteByDigestMock.defaultExpectation.returnOrigin) } else { - m.t.Errorf("Expected call to RegistryClientMock.CheckImageExists at\n%s with params: %#v", m.CheckImageExistsMock.defaultExpectation.expectationOrigins.origin, *m.CheckImageExistsMock.defaultExpectation.params) + m.t.Errorf("Expected call to RegistryClientMock.DeleteByDigest at\n%s with params: %#v", m.DeleteByDigestMock.defaultExpectation.expectationOrigins.origin, *m.DeleteByDigestMock.defaultExpectation.params) } } // if func was set then invocations count should be greater than zero - if m.funcCheckImageExists != nil && afterCheckImageExistsCounter < 1 { - m.t.Errorf("Expected call to RegistryClientMock.CheckImageExists at\n%s", m.funcCheckImageExistsOrigin) + if m.funcDeleteByDigest != nil && afterDeleteByDigestCounter < 1 { + m.t.Errorf("Expected call to RegistryClientMock.DeleteByDigest at\n%s", m.funcDeleteByDigestOrigin) } - if !m.CheckImageExistsMock.invocationsDone() && afterCheckImageExistsCounter > 0 { - m.t.Errorf("Expected %d calls to RegistryClientMock.CheckImageExists at\n%s but found %d calls", - mm_atomic.LoadUint64(&m.CheckImageExistsMock.expectedInvocations), m.CheckImageExistsMock.expectedInvocationsOrigin, afterCheckImageExistsCounter) + if !m.DeleteByDigestMock.invocationsDone() && afterDeleteByDigestCounter > 0 { + m.t.Errorf("Expected %d calls to RegistryClientMock.DeleteByDigest at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.DeleteByDigestMock.expectedInvocations), m.DeleteByDigestMock.expectedInvocationsOrigin, afterDeleteByDigestCounter) } } @@ -708,7 +1483,7 @@ func (mmDeleteTag *mRegistryClientMockDeleteTag) invocationsDone() bool { return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) } -// DeleteTag implements mm_client.Client +// DeleteTag implements mm_registry.Client func (mmDeleteTag *RegistryClientMock) DeleteTag(ctx context.Context, tag string) (err error) { mm_atomic.AddUint64(&mmDeleteTag.beforeDeleteTagCounter, 1) defer mm_atomic.AddUint64(&mmDeleteTag.afterDeleteTagCounter, 1) @@ -1051,7 +1826,7 @@ func (mmGetDigest *mRegistryClientMockGetDigest) invocationsDone() bool { return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) } -// GetDigest implements mm_client.Client +// GetDigest implements mm_registry.Client func (mmGetDigest *RegistryClientMock) GetDigest(ctx context.Context, tag string) (hp1 *v1.Hash, err error) { mm_atomic.AddUint64(&mmGetDigest.beforeGetDigestCounter, 1) defer mm_atomic.AddUint64(&mmGetDigest.afterGetDigestCounter, 1) @@ -1209,19 +1984,19 @@ type RegistryClientMockGetImageExpectation struct { type RegistryClientMockGetImageParams struct { ctx context.Context tag string - opts []registry.ImageGetOption + opts []mm_registry.ImageGetOption } // RegistryClientMockGetImageParamPtrs contains pointers to parameters of the Client.GetImage type RegistryClientMockGetImageParamPtrs struct { ctx *context.Context tag *string - opts *[]registry.ImageGetOption + opts *[]mm_registry.ImageGetOption } // RegistryClientMockGetImageResults contains results of the Client.GetImage type RegistryClientMockGetImageResults struct { - i1 registry.Image + i1 mm_registry.Image err error } @@ -1244,7 +2019,7 @@ func (mmGetImage *mRegistryClientMockGetImage) Optional() *mRegistryClientMockGe } // Expect sets up expected params for Client.GetImage -func (mmGetImage *mRegistryClientMockGetImage) Expect(ctx context.Context, tag string, opts ...registry.ImageGetOption) *mRegistryClientMockGetImage { +func (mmGetImage *mRegistryClientMockGetImage) Expect(ctx context.Context, tag string, opts ...mm_registry.ImageGetOption) *mRegistryClientMockGetImage { if mmGetImage.mock.funcGetImage != nil { mmGetImage.mock.t.Fatalf("RegistryClientMock.GetImage mock is already set by Set") } @@ -1315,7 +2090,7 @@ func (mmGetImage *mRegistryClientMockGetImage) ExpectTagParam2(tag string) *mReg } // ExpectOptsParam3 sets up expected param opts for Client.GetImage -func (mmGetImage *mRegistryClientMockGetImage) ExpectOptsParam3(opts ...registry.ImageGetOption) *mRegistryClientMockGetImage { +func (mmGetImage *mRegistryClientMockGetImage) ExpectOptsParam3(opts ...mm_registry.ImageGetOption) *mRegistryClientMockGetImage { if mmGetImage.mock.funcGetImage != nil { mmGetImage.mock.t.Fatalf("RegistryClientMock.GetImage mock is already set by Set") } @@ -1338,7 +2113,7 @@ func (mmGetImage *mRegistryClientMockGetImage) ExpectOptsParam3(opts ...registry } // Inspect accepts an inspector function that has same arguments as the Client.GetImage -func (mmGetImage *mRegistryClientMockGetImage) Inspect(f func(ctx context.Context, tag string, opts ...registry.ImageGetOption)) *mRegistryClientMockGetImage { +func (mmGetImage *mRegistryClientMockGetImage) Inspect(f func(ctx context.Context, tag string, opts ...mm_registry.ImageGetOption)) *mRegistryClientMockGetImage { if mmGetImage.mock.inspectFuncGetImage != nil { mmGetImage.mock.t.Fatalf("Inspect function is already set for RegistryClientMock.GetImage") } @@ -1349,7 +2124,7 @@ func (mmGetImage *mRegistryClientMockGetImage) Inspect(f func(ctx context.Contex } // Return sets up results that will be returned by Client.GetImage -func (mmGetImage *mRegistryClientMockGetImage) Return(i1 registry.Image, err error) *RegistryClientMock { +func (mmGetImage *mRegistryClientMockGetImage) Return(i1 mm_registry.Image, err error) *RegistryClientMock { if mmGetImage.mock.funcGetImage != nil { mmGetImage.mock.t.Fatalf("RegistryClientMock.GetImage mock is already set by Set") } @@ -1363,7 +2138,7 @@ func (mmGetImage *mRegistryClientMockGetImage) Return(i1 registry.Image, err err } // Set uses given function f to mock the Client.GetImage method -func (mmGetImage *mRegistryClientMockGetImage) Set(f func(ctx context.Context, tag string, opts ...registry.ImageGetOption) (i1 registry.Image, err error)) *RegistryClientMock { +func (mmGetImage *mRegistryClientMockGetImage) Set(f func(ctx context.Context, tag string, opts ...mm_registry.ImageGetOption) (i1 mm_registry.Image, err error)) *RegistryClientMock { if mmGetImage.defaultExpectation != nil { mmGetImage.mock.t.Fatalf("Default expectation is already set for the Client.GetImage method") } @@ -1379,7 +2154,7 @@ func (mmGetImage *mRegistryClientMockGetImage) Set(f func(ctx context.Context, t // When sets expectation for the Client.GetImage which will trigger the result defined by the following // Then helper -func (mmGetImage *mRegistryClientMockGetImage) When(ctx context.Context, tag string, opts ...registry.ImageGetOption) *RegistryClientMockGetImageExpectation { +func (mmGetImage *mRegistryClientMockGetImage) When(ctx context.Context, tag string, opts ...mm_registry.ImageGetOption) *RegistryClientMockGetImageExpectation { if mmGetImage.mock.funcGetImage != nil { mmGetImage.mock.t.Fatalf("RegistryClientMock.GetImage mock is already set by Set") } @@ -1394,7 +2169,7 @@ func (mmGetImage *mRegistryClientMockGetImage) When(ctx context.Context, tag str } // Then sets up Client.GetImage return parameters for the expectation previously defined by the When method -func (e *RegistryClientMockGetImageExpectation) Then(i1 registry.Image, err error) *RegistryClientMock { +func (e *RegistryClientMockGetImageExpectation) Then(i1 mm_registry.Image, err error) *RegistryClientMock { e.results = &RegistryClientMockGetImageResults{i1, err} return e.mock } @@ -1420,8 +2195,8 @@ func (mmGetImage *mRegistryClientMockGetImage) invocationsDone() bool { return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) } -// GetImage implements mm_client.Client -func (mmGetImage *RegistryClientMock) GetImage(ctx context.Context, tag string, opts ...registry.ImageGetOption) (i1 registry.Image, err error) { +// GetImage implements mm_registry.Client +func (mmGetImage *RegistryClientMock) GetImage(ctx context.Context, tag string, opts ...mm_registry.ImageGetOption) (i1 mm_registry.Image, err error) { mm_atomic.AddUint64(&mmGetImage.beforeGetImageCounter, 1) defer mm_atomic.AddUint64(&mmGetImage.afterGetImageCounter, 1) @@ -1768,7 +2543,7 @@ func (mmGetImageConfig *mRegistryClientMockGetImageConfig) invocationsDone() boo return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) } -// GetImageConfig implements mm_client.Client +// GetImageConfig implements mm_registry.Client func (mmGetImageConfig *RegistryClientMock) GetImageConfig(ctx context.Context, tag string) (cp1 *v1.ConfigFile, err error) { mm_atomic.AddUint64(&mmGetImageConfig.beforeGetImageConfigCounter, 1) defer mm_atomic.AddUint64(&mmGetImageConfig.afterGetImageConfigCounter, 1) @@ -1936,7 +2711,7 @@ type RegistryClientMockGetManifestParamPtrs struct { // RegistryClientMockGetManifestResults contains results of the Client.GetManifest type RegistryClientMockGetManifestResults struct { - m1 registry.ManifestResult + m1 mm_registry.ManifestResult err error } @@ -2040,7 +2815,7 @@ func (mmGetManifest *mRegistryClientMockGetManifest) Inspect(f func(ctx context. } // Return sets up results that will be returned by Client.GetManifest -func (mmGetManifest *mRegistryClientMockGetManifest) Return(m1 registry.ManifestResult, err error) *RegistryClientMock { +func (mmGetManifest *mRegistryClientMockGetManifest) Return(m1 mm_registry.ManifestResult, err error) *RegistryClientMock { if mmGetManifest.mock.funcGetManifest != nil { mmGetManifest.mock.t.Fatalf("RegistryClientMock.GetManifest mock is already set by Set") } @@ -2054,7 +2829,7 @@ func (mmGetManifest *mRegistryClientMockGetManifest) Return(m1 registry.Manifest } // Set uses given function f to mock the Client.GetManifest method -func (mmGetManifest *mRegistryClientMockGetManifest) Set(f func(ctx context.Context, tag string) (m1 registry.ManifestResult, err error)) *RegistryClientMock { +func (mmGetManifest *mRegistryClientMockGetManifest) Set(f func(ctx context.Context, tag string) (m1 mm_registry.ManifestResult, err error)) *RegistryClientMock { if mmGetManifest.defaultExpectation != nil { mmGetManifest.mock.t.Fatalf("Default expectation is already set for the Client.GetManifest method") } @@ -2085,7 +2860,7 @@ func (mmGetManifest *mRegistryClientMockGetManifest) When(ctx context.Context, t } // Then sets up Client.GetManifest return parameters for the expectation previously defined by the When method -func (e *RegistryClientMockGetManifestExpectation) Then(m1 registry.ManifestResult, err error) *RegistryClientMock { +func (e *RegistryClientMockGetManifestExpectation) Then(m1 mm_registry.ManifestResult, err error) *RegistryClientMock { e.results = &RegistryClientMockGetManifestResults{m1, err} return e.mock } @@ -2111,8 +2886,8 @@ func (mmGetManifest *mRegistryClientMockGetManifest) invocationsDone() bool { return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) } -// GetManifest implements mm_client.Client -func (mmGetManifest *RegistryClientMock) GetManifest(ctx context.Context, tag string) (m1 registry.ManifestResult, err error) { +// GetManifest implements mm_registry.Client +func (mmGetManifest *RegistryClientMock) GetManifest(ctx context.Context, tag string) (m1 mm_registry.ManifestResult, err error) { mm_atomic.AddUint64(&mmGetManifest.beforeGetManifestCounter, 1) defer mm_atomic.AddUint64(&mmGetManifest.afterGetManifestCounter, 1) @@ -2349,7 +3124,7 @@ func (mmGetRegistry *mRegistryClientMockGetRegistry) invocationsDone() bool { return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) } -// GetRegistry implements mm_client.Client +// GetRegistry implements mm_registry.Client func (mmGetRegistry *RegistryClientMock) GetRegistry() (s1 string) { mm_atomic.AddUint64(&mmGetRegistry.beforeGetRegistryCounter, 1) defer mm_atomic.AddUint64(&mmGetRegistry.afterGetRegistryCounter, 1) @@ -2454,13 +3229,13 @@ type RegistryClientMockListRepositoriesExpectation struct { // RegistryClientMockListRepositoriesParams contains parameters of the Client.ListRepositories type RegistryClientMockListRepositoriesParams struct { ctx context.Context - opts []registry.ListRepositoriesOption + opts []mm_registry.ListRepositoriesOption } // RegistryClientMockListRepositoriesParamPtrs contains pointers to parameters of the Client.ListRepositories type RegistryClientMockListRepositoriesParamPtrs struct { ctx *context.Context - opts *[]registry.ListRepositoriesOption + opts *[]mm_registry.ListRepositoriesOption } // RegistryClientMockListRepositoriesResults contains results of the Client.ListRepositories @@ -2487,7 +3262,7 @@ func (mmListRepositories *mRegistryClientMockListRepositories) Optional() *mRegi } // Expect sets up expected params for Client.ListRepositories -func (mmListRepositories *mRegistryClientMockListRepositories) Expect(ctx context.Context, opts ...registry.ListRepositoriesOption) *mRegistryClientMockListRepositories { +func (mmListRepositories *mRegistryClientMockListRepositories) Expect(ctx context.Context, opts ...mm_registry.ListRepositoriesOption) *mRegistryClientMockListRepositories { if mmListRepositories.mock.funcListRepositories != nil { mmListRepositories.mock.t.Fatalf("RegistryClientMock.ListRepositories mock is already set by Set") } @@ -2535,7 +3310,7 @@ func (mmListRepositories *mRegistryClientMockListRepositories) ExpectCtxParam1(c } // ExpectOptsParam2 sets up expected param opts for Client.ListRepositories -func (mmListRepositories *mRegistryClientMockListRepositories) ExpectOptsParam2(opts ...registry.ListRepositoriesOption) *mRegistryClientMockListRepositories { +func (mmListRepositories *mRegistryClientMockListRepositories) ExpectOptsParam2(opts ...mm_registry.ListRepositoriesOption) *mRegistryClientMockListRepositories { if mmListRepositories.mock.funcListRepositories != nil { mmListRepositories.mock.t.Fatalf("RegistryClientMock.ListRepositories mock is already set by Set") } @@ -2558,7 +3333,7 @@ func (mmListRepositories *mRegistryClientMockListRepositories) ExpectOptsParam2( } // Inspect accepts an inspector function that has same arguments as the Client.ListRepositories -func (mmListRepositories *mRegistryClientMockListRepositories) Inspect(f func(ctx context.Context, opts ...registry.ListRepositoriesOption)) *mRegistryClientMockListRepositories { +func (mmListRepositories *mRegistryClientMockListRepositories) Inspect(f func(ctx context.Context, opts ...mm_registry.ListRepositoriesOption)) *mRegistryClientMockListRepositories { if mmListRepositories.mock.inspectFuncListRepositories != nil { mmListRepositories.mock.t.Fatalf("Inspect function is already set for RegistryClientMock.ListRepositories") } @@ -2583,7 +3358,7 @@ func (mmListRepositories *mRegistryClientMockListRepositories) Return(sa1 []stri } // Set uses given function f to mock the Client.ListRepositories method -func (mmListRepositories *mRegistryClientMockListRepositories) Set(f func(ctx context.Context, opts ...registry.ListRepositoriesOption) (sa1 []string, err error)) *RegistryClientMock { +func (mmListRepositories *mRegistryClientMockListRepositories) Set(f func(ctx context.Context, opts ...mm_registry.ListRepositoriesOption) (sa1 []string, err error)) *RegistryClientMock { if mmListRepositories.defaultExpectation != nil { mmListRepositories.mock.t.Fatalf("Default expectation is already set for the Client.ListRepositories method") } @@ -2599,7 +3374,7 @@ func (mmListRepositories *mRegistryClientMockListRepositories) Set(f func(ctx co // When sets expectation for the Client.ListRepositories which will trigger the result defined by the following // Then helper -func (mmListRepositories *mRegistryClientMockListRepositories) When(ctx context.Context, opts ...registry.ListRepositoriesOption) *RegistryClientMockListRepositoriesExpectation { +func (mmListRepositories *mRegistryClientMockListRepositories) When(ctx context.Context, opts ...mm_registry.ListRepositoriesOption) *RegistryClientMockListRepositoriesExpectation { if mmListRepositories.mock.funcListRepositories != nil { mmListRepositories.mock.t.Fatalf("RegistryClientMock.ListRepositories mock is already set by Set") } @@ -2640,8 +3415,8 @@ func (mmListRepositories *mRegistryClientMockListRepositories) invocationsDone() return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) } -// ListRepositories implements mm_client.Client -func (mmListRepositories *RegistryClientMock) ListRepositories(ctx context.Context, opts ...registry.ListRepositoriesOption) (sa1 []string, err error) { +// ListRepositories implements mm_registry.Client +func (mmListRepositories *RegistryClientMock) ListRepositories(ctx context.Context, opts ...mm_registry.ListRepositoriesOption) (sa1 []string, err error) { mm_atomic.AddUint64(&mmListRepositories.beforeListRepositoriesCounter, 1) defer mm_atomic.AddUint64(&mmListRepositories.afterListRepositoriesCounter, 1) @@ -2797,13 +3572,13 @@ type RegistryClientMockListTagsExpectation struct { // RegistryClientMockListTagsParams contains parameters of the Client.ListTags type RegistryClientMockListTagsParams struct { ctx context.Context - opts []registry.ListTagsOption + opts []mm_registry.ListTagsOption } // RegistryClientMockListTagsParamPtrs contains pointers to parameters of the Client.ListTags type RegistryClientMockListTagsParamPtrs struct { ctx *context.Context - opts *[]registry.ListTagsOption + opts *[]mm_registry.ListTagsOption } // RegistryClientMockListTagsResults contains results of the Client.ListTags @@ -2830,7 +3605,7 @@ func (mmListTags *mRegistryClientMockListTags) Optional() *mRegistryClientMockLi } // Expect sets up expected params for Client.ListTags -func (mmListTags *mRegistryClientMockListTags) Expect(ctx context.Context, opts ...registry.ListTagsOption) *mRegistryClientMockListTags { +func (mmListTags *mRegistryClientMockListTags) Expect(ctx context.Context, opts ...mm_registry.ListTagsOption) *mRegistryClientMockListTags { if mmListTags.mock.funcListTags != nil { mmListTags.mock.t.Fatalf("RegistryClientMock.ListTags mock is already set by Set") } @@ -2878,7 +3653,7 @@ func (mmListTags *mRegistryClientMockListTags) ExpectCtxParam1(ctx context.Conte } // ExpectOptsParam2 sets up expected param opts for Client.ListTags -func (mmListTags *mRegistryClientMockListTags) ExpectOptsParam2(opts ...registry.ListTagsOption) *mRegistryClientMockListTags { +func (mmListTags *mRegistryClientMockListTags) ExpectOptsParam2(opts ...mm_registry.ListTagsOption) *mRegistryClientMockListTags { if mmListTags.mock.funcListTags != nil { mmListTags.mock.t.Fatalf("RegistryClientMock.ListTags mock is already set by Set") } @@ -2901,7 +3676,7 @@ func (mmListTags *mRegistryClientMockListTags) ExpectOptsParam2(opts ...registry } // Inspect accepts an inspector function that has same arguments as the Client.ListTags -func (mmListTags *mRegistryClientMockListTags) Inspect(f func(ctx context.Context, opts ...registry.ListTagsOption)) *mRegistryClientMockListTags { +func (mmListTags *mRegistryClientMockListTags) Inspect(f func(ctx context.Context, opts ...mm_registry.ListTagsOption)) *mRegistryClientMockListTags { if mmListTags.mock.inspectFuncListTags != nil { mmListTags.mock.t.Fatalf("Inspect function is already set for RegistryClientMock.ListTags") } @@ -2926,7 +3701,7 @@ func (mmListTags *mRegistryClientMockListTags) Return(sa1 []string, err error) * } // Set uses given function f to mock the Client.ListTags method -func (mmListTags *mRegistryClientMockListTags) Set(f func(ctx context.Context, opts ...registry.ListTagsOption) (sa1 []string, err error)) *RegistryClientMock { +func (mmListTags *mRegistryClientMockListTags) Set(f func(ctx context.Context, opts ...mm_registry.ListTagsOption) (sa1 []string, err error)) *RegistryClientMock { if mmListTags.defaultExpectation != nil { mmListTags.mock.t.Fatalf("Default expectation is already set for the Client.ListTags method") } @@ -2942,7 +3717,7 @@ func (mmListTags *mRegistryClientMockListTags) Set(f func(ctx context.Context, o // When sets expectation for the Client.ListTags which will trigger the result defined by the following // Then helper -func (mmListTags *mRegistryClientMockListTags) When(ctx context.Context, opts ...registry.ListTagsOption) *RegistryClientMockListTagsExpectation { +func (mmListTags *mRegistryClientMockListTags) When(ctx context.Context, opts ...mm_registry.ListTagsOption) *RegistryClientMockListTagsExpectation { if mmListTags.mock.funcListTags != nil { mmListTags.mock.t.Fatalf("RegistryClientMock.ListTags mock is already set by Set") } @@ -2983,8 +3758,8 @@ func (mmListTags *mRegistryClientMockListTags) invocationsDone() bool { return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) } -// ListTags implements mm_client.Client -func (mmListTags *RegistryClientMock) ListTags(ctx context.Context, opts ...registry.ListTagsOption) (sa1 []string, err error) { +// ListTags implements mm_registry.Client +func (mmListTags *RegistryClientMock) ListTags(ctx context.Context, opts ...mm_registry.ListTagsOption) (sa1 []string, err error) { mm_atomic.AddUint64(&mmListTags.beforeListTagsCounter, 1) defer mm_atomic.AddUint64(&mmListTags.afterListTagsCounter, 1) @@ -3142,7 +3917,7 @@ type RegistryClientMockPushImageParams struct { ctx context.Context tag string img v1.Image - opts []registry.ImagePushOption + opts []mm_registry.ImagePushOption } // RegistryClientMockPushImageParamPtrs contains pointers to parameters of the Client.PushImage @@ -3150,7 +3925,7 @@ type RegistryClientMockPushImageParamPtrs struct { ctx *context.Context tag *string img *v1.Image - opts *[]registry.ImagePushOption + opts *[]mm_registry.ImagePushOption } // RegistryClientMockPushImageResults contains results of the Client.PushImage @@ -3178,7 +3953,7 @@ func (mmPushImage *mRegistryClientMockPushImage) Optional() *mRegistryClientMock } // Expect sets up expected params for Client.PushImage -func (mmPushImage *mRegistryClientMockPushImage) Expect(ctx context.Context, tag string, img v1.Image, opts ...registry.ImagePushOption) *mRegistryClientMockPushImage { +func (mmPushImage *mRegistryClientMockPushImage) Expect(ctx context.Context, tag string, img v1.Image, opts ...mm_registry.ImagePushOption) *mRegistryClientMockPushImage { if mmPushImage.mock.funcPushImage != nil { mmPushImage.mock.t.Fatalf("RegistryClientMock.PushImage mock is already set by Set") } @@ -3272,7 +4047,7 @@ func (mmPushImage *mRegistryClientMockPushImage) ExpectImgParam3(img v1.Image) * } // ExpectOptsParam4 sets up expected param opts for Client.PushImage -func (mmPushImage *mRegistryClientMockPushImage) ExpectOptsParam4(opts ...registry.ImagePushOption) *mRegistryClientMockPushImage { +func (mmPushImage *mRegistryClientMockPushImage) ExpectOptsParam4(opts ...mm_registry.ImagePushOption) *mRegistryClientMockPushImage { if mmPushImage.mock.funcPushImage != nil { mmPushImage.mock.t.Fatalf("RegistryClientMock.PushImage mock is already set by Set") } @@ -3295,7 +4070,7 @@ func (mmPushImage *mRegistryClientMockPushImage) ExpectOptsParam4(opts ...regist } // Inspect accepts an inspector function that has same arguments as the Client.PushImage -func (mmPushImage *mRegistryClientMockPushImage) Inspect(f func(ctx context.Context, tag string, img v1.Image, opts ...registry.ImagePushOption)) *mRegistryClientMockPushImage { +func (mmPushImage *mRegistryClientMockPushImage) Inspect(f func(ctx context.Context, tag string, img v1.Image, opts ...mm_registry.ImagePushOption)) *mRegistryClientMockPushImage { if mmPushImage.mock.inspectFuncPushImage != nil { mmPushImage.mock.t.Fatalf("Inspect function is already set for RegistryClientMock.PushImage") } @@ -3320,7 +4095,7 @@ func (mmPushImage *mRegistryClientMockPushImage) Return(err error) *RegistryClie } // Set uses given function f to mock the Client.PushImage method -func (mmPushImage *mRegistryClientMockPushImage) Set(f func(ctx context.Context, tag string, img v1.Image, opts ...registry.ImagePushOption) (err error)) *RegistryClientMock { +func (mmPushImage *mRegistryClientMockPushImage) Set(f func(ctx context.Context, tag string, img v1.Image, opts ...mm_registry.ImagePushOption) (err error)) *RegistryClientMock { if mmPushImage.defaultExpectation != nil { mmPushImage.mock.t.Fatalf("Default expectation is already set for the Client.PushImage method") } @@ -3336,7 +4111,7 @@ func (mmPushImage *mRegistryClientMockPushImage) Set(f func(ctx context.Context, // When sets expectation for the Client.PushImage which will trigger the result defined by the following // Then helper -func (mmPushImage *mRegistryClientMockPushImage) When(ctx context.Context, tag string, img v1.Image, opts ...registry.ImagePushOption) *RegistryClientMockPushImageExpectation { +func (mmPushImage *mRegistryClientMockPushImage) When(ctx context.Context, tag string, img v1.Image, opts ...mm_registry.ImagePushOption) *RegistryClientMockPushImageExpectation { if mmPushImage.mock.funcPushImage != nil { mmPushImage.mock.t.Fatalf("RegistryClientMock.PushImage mock is already set by Set") } @@ -3377,8 +4152,8 @@ func (mmPushImage *mRegistryClientMockPushImage) invocationsDone() bool { return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) } -// PushImage implements mm_client.Client -func (mmPushImage *RegistryClientMock) PushImage(ctx context.Context, tag string, img v1.Image, opts ...registry.ImagePushOption) (err error) { +// PushImage implements mm_registry.Client +func (mmPushImage *RegistryClientMock) PushImage(ctx context.Context, tag string, img v1.Image, opts ...mm_registry.ImagePushOption) (err error) { mm_atomic.AddUint64(&mmPushImage.beforePushImageCounter, 1) defer mm_atomic.AddUint64(&mmPushImage.afterPushImageCounter, 1) @@ -3517,6 +4292,410 @@ func (m *RegistryClientMock) MinimockPushImageInspect() { } } +type mRegistryClientMockPushIndex struct { + optional bool + mock *RegistryClientMock + defaultExpectation *RegistryClientMockPushIndexExpectation + expectations []*RegistryClientMockPushIndexExpectation + + callArgs []*RegistryClientMockPushIndexParams + mutex sync.RWMutex + + expectedInvocations uint64 + expectedInvocationsOrigin string +} + +// RegistryClientMockPushIndexExpectation specifies expectation struct of the Client.PushIndex +type RegistryClientMockPushIndexExpectation struct { + mock *RegistryClientMock + params *RegistryClientMockPushIndexParams + paramPtrs *RegistryClientMockPushIndexParamPtrs + expectationOrigins RegistryClientMockPushIndexExpectationOrigins + results *RegistryClientMockPushIndexResults + returnOrigin string + Counter uint64 +} + +// RegistryClientMockPushIndexParams contains parameters of the Client.PushIndex +type RegistryClientMockPushIndexParams struct { + ctx context.Context + tag string + idx v1.ImageIndex + opts []mm_registry.ImagePushOption +} + +// RegistryClientMockPushIndexParamPtrs contains pointers to parameters of the Client.PushIndex +type RegistryClientMockPushIndexParamPtrs struct { + ctx *context.Context + tag *string + idx *v1.ImageIndex + opts *[]mm_registry.ImagePushOption +} + +// RegistryClientMockPushIndexResults contains results of the Client.PushIndex +type RegistryClientMockPushIndexResults struct { + err error +} + +// RegistryClientMockPushIndexOrigins contains origins of expectations of the Client.PushIndex +type RegistryClientMockPushIndexExpectationOrigins struct { + origin string + originCtx string + originTag string + originIdx string + originOpts string +} + +// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning +// the test will fail minimock's automatic final call check if the mocked method was not called at least once. +// Optional() makes method check to work in '0 or more' mode. +// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to +// catch the problems when the expected method call is totally skipped during test run. +func (mmPushIndex *mRegistryClientMockPushIndex) Optional() *mRegistryClientMockPushIndex { + mmPushIndex.optional = true + return mmPushIndex +} + +// Expect sets up expected params for Client.PushIndex +func (mmPushIndex *mRegistryClientMockPushIndex) Expect(ctx context.Context, tag string, idx v1.ImageIndex, opts ...mm_registry.ImagePushOption) *mRegistryClientMockPushIndex { + if mmPushIndex.mock.funcPushIndex != nil { + mmPushIndex.mock.t.Fatalf("RegistryClientMock.PushIndex mock is already set by Set") + } + + if mmPushIndex.defaultExpectation == nil { + mmPushIndex.defaultExpectation = &RegistryClientMockPushIndexExpectation{} + } + + if mmPushIndex.defaultExpectation.paramPtrs != nil { + mmPushIndex.mock.t.Fatalf("RegistryClientMock.PushIndex mock is already set by ExpectParams functions") + } + + mmPushIndex.defaultExpectation.params = &RegistryClientMockPushIndexParams{ctx, tag, idx, opts} + mmPushIndex.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1) + for _, e := range mmPushIndex.expectations { + if minimock.Equal(e.params, mmPushIndex.defaultExpectation.params) { + mmPushIndex.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmPushIndex.defaultExpectation.params) + } + } + + return mmPushIndex +} + +// ExpectCtxParam1 sets up expected param ctx for Client.PushIndex +func (mmPushIndex *mRegistryClientMockPushIndex) ExpectCtxParam1(ctx context.Context) *mRegistryClientMockPushIndex { + if mmPushIndex.mock.funcPushIndex != nil { + mmPushIndex.mock.t.Fatalf("RegistryClientMock.PushIndex mock is already set by Set") + } + + if mmPushIndex.defaultExpectation == nil { + mmPushIndex.defaultExpectation = &RegistryClientMockPushIndexExpectation{} + } + + if mmPushIndex.defaultExpectation.params != nil { + mmPushIndex.mock.t.Fatalf("RegistryClientMock.PushIndex mock is already set by Expect") + } + + if mmPushIndex.defaultExpectation.paramPtrs == nil { + mmPushIndex.defaultExpectation.paramPtrs = &RegistryClientMockPushIndexParamPtrs{} + } + mmPushIndex.defaultExpectation.paramPtrs.ctx = &ctx + mmPushIndex.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1) + + return mmPushIndex +} + +// ExpectTagParam2 sets up expected param tag for Client.PushIndex +func (mmPushIndex *mRegistryClientMockPushIndex) ExpectTagParam2(tag string) *mRegistryClientMockPushIndex { + if mmPushIndex.mock.funcPushIndex != nil { + mmPushIndex.mock.t.Fatalf("RegistryClientMock.PushIndex mock is already set by Set") + } + + if mmPushIndex.defaultExpectation == nil { + mmPushIndex.defaultExpectation = &RegistryClientMockPushIndexExpectation{} + } + + if mmPushIndex.defaultExpectation.params != nil { + mmPushIndex.mock.t.Fatalf("RegistryClientMock.PushIndex mock is already set by Expect") + } + + if mmPushIndex.defaultExpectation.paramPtrs == nil { + mmPushIndex.defaultExpectation.paramPtrs = &RegistryClientMockPushIndexParamPtrs{} + } + mmPushIndex.defaultExpectation.paramPtrs.tag = &tag + mmPushIndex.defaultExpectation.expectationOrigins.originTag = minimock.CallerInfo(1) + + return mmPushIndex +} + +// ExpectIdxParam3 sets up expected param idx for Client.PushIndex +func (mmPushIndex *mRegistryClientMockPushIndex) ExpectIdxParam3(idx v1.ImageIndex) *mRegistryClientMockPushIndex { + if mmPushIndex.mock.funcPushIndex != nil { + mmPushIndex.mock.t.Fatalf("RegistryClientMock.PushIndex mock is already set by Set") + } + + if mmPushIndex.defaultExpectation == nil { + mmPushIndex.defaultExpectation = &RegistryClientMockPushIndexExpectation{} + } + + if mmPushIndex.defaultExpectation.params != nil { + mmPushIndex.mock.t.Fatalf("RegistryClientMock.PushIndex mock is already set by Expect") + } + + if mmPushIndex.defaultExpectation.paramPtrs == nil { + mmPushIndex.defaultExpectation.paramPtrs = &RegistryClientMockPushIndexParamPtrs{} + } + mmPushIndex.defaultExpectation.paramPtrs.idx = &idx + mmPushIndex.defaultExpectation.expectationOrigins.originIdx = minimock.CallerInfo(1) + + return mmPushIndex +} + +// ExpectOptsParam4 sets up expected param opts for Client.PushIndex +func (mmPushIndex *mRegistryClientMockPushIndex) ExpectOptsParam4(opts ...mm_registry.ImagePushOption) *mRegistryClientMockPushIndex { + if mmPushIndex.mock.funcPushIndex != nil { + mmPushIndex.mock.t.Fatalf("RegistryClientMock.PushIndex mock is already set by Set") + } + + if mmPushIndex.defaultExpectation == nil { + mmPushIndex.defaultExpectation = &RegistryClientMockPushIndexExpectation{} + } + + if mmPushIndex.defaultExpectation.params != nil { + mmPushIndex.mock.t.Fatalf("RegistryClientMock.PushIndex mock is already set by Expect") + } + + if mmPushIndex.defaultExpectation.paramPtrs == nil { + mmPushIndex.defaultExpectation.paramPtrs = &RegistryClientMockPushIndexParamPtrs{} + } + mmPushIndex.defaultExpectation.paramPtrs.opts = &opts + mmPushIndex.defaultExpectation.expectationOrigins.originOpts = minimock.CallerInfo(1) + + return mmPushIndex +} + +// Inspect accepts an inspector function that has same arguments as the Client.PushIndex +func (mmPushIndex *mRegistryClientMockPushIndex) Inspect(f func(ctx context.Context, tag string, idx v1.ImageIndex, opts ...mm_registry.ImagePushOption)) *mRegistryClientMockPushIndex { + if mmPushIndex.mock.inspectFuncPushIndex != nil { + mmPushIndex.mock.t.Fatalf("Inspect function is already set for RegistryClientMock.PushIndex") + } + + mmPushIndex.mock.inspectFuncPushIndex = f + + return mmPushIndex +} + +// Return sets up results that will be returned by Client.PushIndex +func (mmPushIndex *mRegistryClientMockPushIndex) Return(err error) *RegistryClientMock { + if mmPushIndex.mock.funcPushIndex != nil { + mmPushIndex.mock.t.Fatalf("RegistryClientMock.PushIndex mock is already set by Set") + } + + if mmPushIndex.defaultExpectation == nil { + mmPushIndex.defaultExpectation = &RegistryClientMockPushIndexExpectation{mock: mmPushIndex.mock} + } + mmPushIndex.defaultExpectation.results = &RegistryClientMockPushIndexResults{err} + mmPushIndex.defaultExpectation.returnOrigin = minimock.CallerInfo(1) + return mmPushIndex.mock +} + +// Set uses given function f to mock the Client.PushIndex method +func (mmPushIndex *mRegistryClientMockPushIndex) Set(f func(ctx context.Context, tag string, idx v1.ImageIndex, opts ...mm_registry.ImagePushOption) (err error)) *RegistryClientMock { + if mmPushIndex.defaultExpectation != nil { + mmPushIndex.mock.t.Fatalf("Default expectation is already set for the Client.PushIndex method") + } + + if len(mmPushIndex.expectations) > 0 { + mmPushIndex.mock.t.Fatalf("Some expectations are already set for the Client.PushIndex method") + } + + mmPushIndex.mock.funcPushIndex = f + mmPushIndex.mock.funcPushIndexOrigin = minimock.CallerInfo(1) + return mmPushIndex.mock +} + +// When sets expectation for the Client.PushIndex which will trigger the result defined by the following +// Then helper +func (mmPushIndex *mRegistryClientMockPushIndex) When(ctx context.Context, tag string, idx v1.ImageIndex, opts ...mm_registry.ImagePushOption) *RegistryClientMockPushIndexExpectation { + if mmPushIndex.mock.funcPushIndex != nil { + mmPushIndex.mock.t.Fatalf("RegistryClientMock.PushIndex mock is already set by Set") + } + + expectation := &RegistryClientMockPushIndexExpectation{ + mock: mmPushIndex.mock, + params: &RegistryClientMockPushIndexParams{ctx, tag, idx, opts}, + expectationOrigins: RegistryClientMockPushIndexExpectationOrigins{origin: minimock.CallerInfo(1)}, + } + mmPushIndex.expectations = append(mmPushIndex.expectations, expectation) + return expectation +} + +// Then sets up Client.PushIndex return parameters for the expectation previously defined by the When method +func (e *RegistryClientMockPushIndexExpectation) Then(err error) *RegistryClientMock { + e.results = &RegistryClientMockPushIndexResults{err} + return e.mock +} + +// Times sets number of times Client.PushIndex should be invoked +func (mmPushIndex *mRegistryClientMockPushIndex) Times(n uint64) *mRegistryClientMockPushIndex { + if n == 0 { + mmPushIndex.mock.t.Fatalf("Times of RegistryClientMock.PushIndex mock can not be zero") + } + mm_atomic.StoreUint64(&mmPushIndex.expectedInvocations, n) + mmPushIndex.expectedInvocationsOrigin = minimock.CallerInfo(1) + return mmPushIndex +} + +func (mmPushIndex *mRegistryClientMockPushIndex) invocationsDone() bool { + if len(mmPushIndex.expectations) == 0 && mmPushIndex.defaultExpectation == nil && mmPushIndex.mock.funcPushIndex == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmPushIndex.mock.afterPushIndexCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmPushIndex.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// PushIndex implements mm_registry.Client +func (mmPushIndex *RegistryClientMock) PushIndex(ctx context.Context, tag string, idx v1.ImageIndex, opts ...mm_registry.ImagePushOption) (err error) { + mm_atomic.AddUint64(&mmPushIndex.beforePushIndexCounter, 1) + defer mm_atomic.AddUint64(&mmPushIndex.afterPushIndexCounter, 1) + + mmPushIndex.t.Helper() + + if mmPushIndex.inspectFuncPushIndex != nil { + mmPushIndex.inspectFuncPushIndex(ctx, tag, idx, opts...) + } + + mm_params := RegistryClientMockPushIndexParams{ctx, tag, idx, opts} + + // Record call args + mmPushIndex.PushIndexMock.mutex.Lock() + mmPushIndex.PushIndexMock.callArgs = append(mmPushIndex.PushIndexMock.callArgs, &mm_params) + mmPushIndex.PushIndexMock.mutex.Unlock() + + for _, e := range mmPushIndex.PushIndexMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.err + } + } + + if mmPushIndex.PushIndexMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmPushIndex.PushIndexMock.defaultExpectation.Counter, 1) + mm_want := mmPushIndex.PushIndexMock.defaultExpectation.params + mm_want_ptrs := mmPushIndex.PushIndexMock.defaultExpectation.paramPtrs + + mm_got := RegistryClientMockPushIndexParams{ctx, tag, idx, opts} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmPushIndex.t.Errorf("RegistryClientMock.PushIndex got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmPushIndex.PushIndexMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.tag != nil && !minimock.Equal(*mm_want_ptrs.tag, mm_got.tag) { + mmPushIndex.t.Errorf("RegistryClientMock.PushIndex got unexpected parameter tag, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmPushIndex.PushIndexMock.defaultExpectation.expectationOrigins.originTag, *mm_want_ptrs.tag, mm_got.tag, minimock.Diff(*mm_want_ptrs.tag, mm_got.tag)) + } + + if mm_want_ptrs.idx != nil && !minimock.Equal(*mm_want_ptrs.idx, mm_got.idx) { + mmPushIndex.t.Errorf("RegistryClientMock.PushIndex got unexpected parameter idx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmPushIndex.PushIndexMock.defaultExpectation.expectationOrigins.originIdx, *mm_want_ptrs.idx, mm_got.idx, minimock.Diff(*mm_want_ptrs.idx, mm_got.idx)) + } + + if mm_want_ptrs.opts != nil && !minimock.Equal(*mm_want_ptrs.opts, mm_got.opts) { + mmPushIndex.t.Errorf("RegistryClientMock.PushIndex got unexpected parameter opts, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmPushIndex.PushIndexMock.defaultExpectation.expectationOrigins.originOpts, *mm_want_ptrs.opts, mm_got.opts, minimock.Diff(*mm_want_ptrs.opts, mm_got.opts)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmPushIndex.t.Errorf("RegistryClientMock.PushIndex got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmPushIndex.PushIndexMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmPushIndex.PushIndexMock.defaultExpectation.results + if mm_results == nil { + mmPushIndex.t.Fatal("No results are set for the RegistryClientMock.PushIndex") + } + return (*mm_results).err + } + if mmPushIndex.funcPushIndex != nil { + return mmPushIndex.funcPushIndex(ctx, tag, idx, opts...) + } + mmPushIndex.t.Fatalf("Unexpected call to RegistryClientMock.PushIndex. %v %v %v %v", ctx, tag, idx, opts) + return +} + +// PushIndexAfterCounter returns a count of finished RegistryClientMock.PushIndex invocations +func (mmPushIndex *RegistryClientMock) PushIndexAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmPushIndex.afterPushIndexCounter) +} + +// PushIndexBeforeCounter returns a count of RegistryClientMock.PushIndex invocations +func (mmPushIndex *RegistryClientMock) PushIndexBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmPushIndex.beforePushIndexCounter) +} + +// Calls returns a list of arguments used in each call to RegistryClientMock.PushIndex. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmPushIndex *mRegistryClientMockPushIndex) Calls() []*RegistryClientMockPushIndexParams { + mmPushIndex.mutex.RLock() + + argCopy := make([]*RegistryClientMockPushIndexParams, len(mmPushIndex.callArgs)) + copy(argCopy, mmPushIndex.callArgs) + + mmPushIndex.mutex.RUnlock() + + return argCopy +} + +// MinimockPushIndexDone returns true if the count of the PushIndex invocations corresponds +// the number of defined expectations +func (m *RegistryClientMock) MinimockPushIndexDone() bool { + if m.PushIndexMock.optional { + // Optional methods provide '0 or more' call count restriction. + return true + } + + for _, e := range m.PushIndexMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.PushIndexMock.invocationsDone() +} + +// MinimockPushIndexInspect logs each unmet expectation +func (m *RegistryClientMock) MinimockPushIndexInspect() { + for _, e := range m.PushIndexMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to RegistryClientMock.PushIndex at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + } + } + + afterPushIndexCounter := mm_atomic.LoadUint64(&m.afterPushIndexCounter) + // if default expectation was set then invocations count should be greater than zero + if m.PushIndexMock.defaultExpectation != nil && afterPushIndexCounter < 1 { + if m.PushIndexMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to RegistryClientMock.PushIndex at\n%s", m.PushIndexMock.defaultExpectation.returnOrigin) + } else { + m.t.Errorf("Expected call to RegistryClientMock.PushIndex at\n%s with params: %#v", m.PushIndexMock.defaultExpectation.expectationOrigins.origin, *m.PushIndexMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcPushIndex != nil && afterPushIndexCounter < 1 { + m.t.Errorf("Expected call to RegistryClientMock.PushIndex at\n%s", m.funcPushIndexOrigin) + } + + if !m.PushIndexMock.invocationsDone() && afterPushIndexCounter > 0 { + m.t.Errorf("Expected %d calls to RegistryClientMock.PushIndex at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.PushIndexMock.expectedInvocations), m.PushIndexMock.expectedInvocationsOrigin, afterPushIndexCounter) + } +} + type mRegistryClientMockTagImage struct { optional bool mock *RegistryClientMock @@ -3755,7 +4934,7 @@ func (mmTagImage *mRegistryClientMockTagImage) invocationsDone() bool { return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) } -// TagImage implements mm_client.Client +// TagImage implements mm_registry.Client func (mmTagImage *RegistryClientMock) TagImage(ctx context.Context, sourceTag string, destTag string) (err error) { mm_atomic.AddUint64(&mmTagImage.beforeTagImageCounter, 1) defer mm_atomic.AddUint64(&mmTagImage.afterTagImageCounter, 1) @@ -3926,7 +5105,7 @@ type RegistryClientMockWithSegmentParamPtrs struct { // RegistryClientMockWithSegmentResults contains results of the Client.WithSegment type RegistryClientMockWithSegmentResults struct { - c1 mm_client.Client + c1 mm_registry.Client } // RegistryClientMockWithSegmentOrigins contains origins of expectations of the Client.WithSegment @@ -4005,7 +5184,7 @@ func (mmWithSegment *mRegistryClientMockWithSegment) Inspect(f func(segments ... } // Return sets up results that will be returned by Client.WithSegment -func (mmWithSegment *mRegistryClientMockWithSegment) Return(c1 mm_client.Client) *RegistryClientMock { +func (mmWithSegment *mRegistryClientMockWithSegment) Return(c1 mm_registry.Client) *RegistryClientMock { if mmWithSegment.mock.funcWithSegment != nil { mmWithSegment.mock.t.Fatalf("RegistryClientMock.WithSegment mock is already set by Set") } @@ -4019,7 +5198,7 @@ func (mmWithSegment *mRegistryClientMockWithSegment) Return(c1 mm_client.Client) } // Set uses given function f to mock the Client.WithSegment method -func (mmWithSegment *mRegistryClientMockWithSegment) Set(f func(segments ...string) (c1 mm_client.Client)) *RegistryClientMock { +func (mmWithSegment *mRegistryClientMockWithSegment) Set(f func(segments ...string) (c1 mm_registry.Client)) *RegistryClientMock { if mmWithSegment.defaultExpectation != nil { mmWithSegment.mock.t.Fatalf("Default expectation is already set for the Client.WithSegment method") } @@ -4050,7 +5229,7 @@ func (mmWithSegment *mRegistryClientMockWithSegment) When(segments ...string) *R } // Then sets up Client.WithSegment return parameters for the expectation previously defined by the When method -func (e *RegistryClientMockWithSegmentExpectation) Then(c1 mm_client.Client) *RegistryClientMock { +func (e *RegistryClientMockWithSegmentExpectation) Then(c1 mm_registry.Client) *RegistryClientMock { e.results = &RegistryClientMockWithSegmentResults{c1} return e.mock } @@ -4076,8 +5255,8 @@ func (mmWithSegment *mRegistryClientMockWithSegment) invocationsDone() bool { return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) } -// WithSegment implements mm_client.Client -func (mmWithSegment *RegistryClientMock) WithSegment(segments ...string) (c1 mm_client.Client) { +// WithSegment implements mm_registry.Client +func (mmWithSegment *RegistryClientMock) WithSegment(segments ...string) (c1 mm_registry.Client) { mm_atomic.AddUint64(&mmWithSegment.beforeWithSegmentCounter, 1) defer mm_atomic.AddUint64(&mmWithSegment.afterWithSegmentCounter, 1) @@ -4207,6 +5386,10 @@ func (m *RegistryClientMock) MinimockFinish() { if !m.minimockDone() { m.MinimockCheckImageExistsInspect() + m.MinimockCopyImageInspect() + + m.MinimockDeleteByDigestInspect() + m.MinimockDeleteTagInspect() m.MinimockGetDigestInspect() @@ -4225,6 +5408,8 @@ func (m *RegistryClientMock) MinimockFinish() { m.MinimockPushImageInspect() + m.MinimockPushIndexInspect() + m.MinimockTagImageInspect() m.MinimockWithSegmentInspect() @@ -4252,6 +5437,8 @@ func (m *RegistryClientMock) minimockDone() bool { done := true return done && m.MinimockCheckImageExistsDone() && + m.MinimockCopyImageDone() && + m.MinimockDeleteByDigestDone() && m.MinimockDeleteTagDone() && m.MinimockGetDigestDone() && m.MinimockGetImageDone() && @@ -4261,6 +5448,7 @@ func (m *RegistryClientMock) minimockDone() bool { m.MinimockListRepositoriesDone() && m.MinimockListTagsDone() && m.MinimockPushImageDone() && + m.MinimockPushIndexDone() && m.MinimockTagImageDone() && m.MinimockWithSegmentDone() } diff --git a/pkg/registry/client.go b/pkg/registry/client.go deleted file mode 100644 index c6f8b33b..00000000 --- a/pkg/registry/client.go +++ /dev/null @@ -1,65 +0,0 @@ -package client - -import ( - "context" - - v1 "github.com/google/go-containerregistry/pkg/v1" - - "github.com/deckhouse/deckhouse/pkg/registry" -) - -// Client defines the contract for interacting with container registries -type Client interface { - // WithSegment creates a new client with an additional scope path segment - // This method can be chained to build complex paths - WithSegment(segments ...string) Client - - // GetRegistry returns the full registry path (host + scope) - GetRegistry() string - - // GetDigest retrieves the digest for a specific image tag - // The repository is determined by the chained WithSegment() calls - GetDigest(ctx context.Context, tag string) (*v1.Hash, error) - - // GetManifest retrieves the manifest for a specific image tag - // The repository is determined by the chained WithSegment() calls - GetManifest(ctx context.Context, tag string) (registry.ManifestResult, error) - - // GetImageConfig retrieves the image config file containing labels and metadata - // The repository is determined by the chained WithSegment() calls - GetImageConfig(ctx context.Context, tag string) (*v1.ConfigFile, error) - - // CheckImageExists checks if a specific image exists in the registry - // If image not found, return an error - // The repository is determined by the chained WithSegment() calls - CheckImageExists(ctx context.Context, tag string) error - - // GetImage retrieves an remote image for a specific reference - // Do not return remote image to avoid drop connection with context cancelation. - // It will be in use while passed context will be alive. - // The repository is determined by the chained WithSegment() calls - GetImage(ctx context.Context, tag string, opts ...registry.ImageGetOption) (registry.Image, error) - - // PushImage pushes an image to the registry at the specified tag - // The repository is determined by the chained WithSegment() calls - PushImage(ctx context.Context, tag string, img v1.Image, opts ...registry.ImagePushOption) error - - // ListTags retrieves tags for the current scope with pagination - // The repository is determined by the chained WithSegment() calls - ListTags(ctx context.Context, opts ...registry.ListTagsOption) ([]string, error) - - // ListRepositories retrieves sub-repositories under the current scope with pagination - // The scope is determined by the chained WithSegment() calls - ListRepositories(ctx context.Context, opts ...registry.ListRepositoriesOption) ([]string, error) - - // DeleteTag deletes a specific tag from the repository. - // Returns ErrImageNotFound if the tag does not exist. - // The repository is determined by the chained WithSegment() calls. - DeleteTag(ctx context.Context, tag string) error - - // TagImage adds a new tag pointing to the same manifest as sourceTag without - // re-uploading any layers (single manifest PUT). - // Standard promotion pattern: e.g. :latest → :v1.2.3. - // The repository is determined by the chained WithSegment() calls. - TagImage(ctx context.Context, sourceTag, destTag string) error -} diff --git a/pkg/registry/client/client.go b/pkg/registry/client/client.go index 75881818..0518a1fb 100644 --- a/pkg/registry/client/client.go +++ b/pkg/registry/client/client.go @@ -1,28 +1,34 @@ package client import ( + dkpreg "github.com/deckhouse/deckhouse/pkg/registry" regclient "github.com/deckhouse/deckhouse/pkg/registry/client" - - localreg "github.com/deckhouse/deckhouse-cli/pkg/registry" ) -// adapter wraps *regclient.Client from the upstream package and makes it satisfy +// adapter wraps the upstream dkpreg.Client interface and makes it satisfy // the local Client interface by overriding WithSegment to return the local type. -// All other methods are promoted from the embedded *regclient.Client. +// All other methods are promoted from the embedded dkpreg.Client. type adapter struct { - *regclient.Client + dkpreg.Client } // WithSegment overrides the upstream method so that it returns the local Client -// interface instead of the concrete *regclient.Client, satisfying the interface +// interface instead of the upstream dkpreg.Client, satisfying the interface // covariance requirement that Go does not allow otherwise. -func (a *adapter) WithSegment(segments ...string) localreg.Client { +func (a *adapter) WithSegment(segments ...string) dkpreg.Client { return &adapter{a.Client.WithSegment(segments...)} } +// Adapt wraps an upstream dkpreg.Client so that it satisfies the local +// Client interface. This is useful for fake/stub clients that return the +// upstream interface type. +func Adapt(c dkpreg.Client) dkpreg.Client { + return &adapter{c} +} + // NewFromOptions wraps regclient.New (the functional-options constructor) // and returns a value that fully satisfies the local Client interface. // Use this when building options via Option functions (e.g. regclient.WithAuth). -func NewFromOptions(host string, opts ...regclient.Option) localreg.Client { +func NewFromOptions(host string, opts ...regclient.Option) dkpreg.Client { return &adapter{regclient.New(host, opts...)} } diff --git a/pkg/registry/service/basic_service.go b/pkg/registry/service/basic_service.go index 73994160..d03b1ce0 100644 --- a/pkg/registry/service/basic_service.go +++ b/pkg/registry/service/basic_service.go @@ -28,19 +28,18 @@ import ( "github.com/deckhouse/deckhouse/pkg/registry" "github.com/deckhouse/deckhouse-cli/pkg" - client "github.com/deckhouse/deckhouse-cli/pkg/registry" "github.com/deckhouse/deckhouse-cli/pkg/registry/image" ) // BasicService provides common registry operations with standardized logging type BasicService struct { name string - client client.Client + client registry.Client logger *log.Logger } // NewBasicService creates a new basic service -func NewBasicService(name string, client client.Client, logger *log.Logger) *BasicService { +func NewBasicService(name string, client registry.Client, logger *log.Logger) *BasicService { return &BasicService{ name: name, client: client, diff --git a/pkg/registry/service/deckhouse_service.go b/pkg/registry/service/deckhouse_service.go index df5bc0e3..7bf65b1c 100644 --- a/pkg/registry/service/deckhouse_service.go +++ b/pkg/registry/service/deckhouse_service.go @@ -25,8 +25,7 @@ import ( "log/slog" "github.com/deckhouse/deckhouse/pkg/log" - - client "github.com/deckhouse/deckhouse-cli/pkg/registry" + client "github.com/deckhouse/deckhouse/pkg/registry" ) const ( diff --git a/pkg/registry/service/installer_service.go b/pkg/registry/service/installer_service.go index 9ca2ee0a..94526835 100644 --- a/pkg/registry/service/installer_service.go +++ b/pkg/registry/service/installer_service.go @@ -2,8 +2,7 @@ package service import ( "github.com/deckhouse/deckhouse/pkg/log" - - client "github.com/deckhouse/deckhouse-cli/pkg/registry" + client "github.com/deckhouse/deckhouse/pkg/registry" ) type InstallerServices struct { diff --git a/pkg/registry/service/module_service.go b/pkg/registry/service/module_service.go index 48c50c45..d6e87a08 100644 --- a/pkg/registry/service/module_service.go +++ b/pkg/registry/service/module_service.go @@ -18,8 +18,7 @@ package service import ( "github.com/deckhouse/deckhouse/pkg/log" - - client "github.com/deckhouse/deckhouse-cli/pkg/registry" + client "github.com/deckhouse/deckhouse/pkg/registry" ) const ( diff --git a/pkg/registry/service/plugin_service.go b/pkg/registry/service/plugin_service.go index e7b60209..086e6441 100644 --- a/pkg/registry/service/plugin_service.go +++ b/pkg/registry/service/plugin_service.go @@ -34,7 +34,6 @@ import ( regclient "github.com/deckhouse/deckhouse/pkg/registry/client" "github.com/deckhouse/deckhouse-cli/internal" - localclient "github.com/deckhouse/deckhouse-cli/pkg/registry" ) const ( @@ -43,12 +42,12 @@ const ( // PluginService provides high-level operations for plugin management type PluginService struct { - client localclient.Client + client registry.Client log *log.Logger } // NewPluginService creates a new plugin service -func NewPluginService(client localclient.Client, logger *log.Logger) *PluginService { +func NewPluginService(client registry.Client, logger *log.Logger) *PluginService { return &PluginService{ client: client, log: logger, diff --git a/pkg/registry/service/security_service.go b/pkg/registry/service/security_service.go index 850f48b8..d8e01021 100644 --- a/pkg/registry/service/security_service.go +++ b/pkg/registry/service/security_service.go @@ -2,8 +2,7 @@ package service import ( "github.com/deckhouse/deckhouse/pkg/log" - - client "github.com/deckhouse/deckhouse-cli/pkg/registry" + client "github.com/deckhouse/deckhouse/pkg/registry" ) type SecurityServices struct { diff --git a/pkg/registry/service/service.go b/pkg/registry/service/service.go index 07a16e49..f9d34be4 100644 --- a/pkg/registry/service/service.go +++ b/pkg/registry/service/service.go @@ -20,9 +20,9 @@ import ( "strings" "github.com/deckhouse/deckhouse/pkg/log" + client "github.com/deckhouse/deckhouse/pkg/registry" "github.com/deckhouse/deckhouse-cli/pkg" - client "github.com/deckhouse/deckhouse-cli/pkg/registry" ) const ( @@ -47,19 +47,28 @@ type Service struct { } // NewService creates a new registry service with the given client and logger -func NewService(client client.Client, edition pkg.Edition, logger *log.Logger) *Service { +func NewService(c client.Client, edition pkg.Edition, logger *log.Logger) *Service { s := &Service{ - client: client, + client: c, logger: logger, } - s.modulesService = NewModulesService(client.WithSegment(edition.String(), moduleSegment), logger.Named("modules")) - s.deckhouseService = NewDeckhouseService(client.WithSegment(edition.String()), logger.Named("deckhouse")) - s.security = NewSecurityServices(securityServiceName, client.WithSegment(edition.String(), securitySegment), logger.Named("security")) + // base is scoped to the edition sub-path when an edition is specified. + // When edition is NoEdition the client already points at the correct root. + var base client.Client + if edition == pkg.NoEdition { + base = c + } else { + base = c.WithSegment(edition.String()) + } + + s.modulesService = NewModulesService(base.WithSegment(moduleSegment), logger.Named("modules")) + s.deckhouseService = NewDeckhouseService(base, logger.Named("deckhouse")) + s.security = NewSecurityServices(securityServiceName, base.WithSegment(securitySegment), logger.Named("security")) // services that are not scoped by edition - s.pluginService = NewPluginService(client.WithSegment(pluginSegment), logger.Named("plugins")) - s.installer = NewInstallerServices(installerServiceName, client.WithSegment("installer"), logger.Named("installer")) + s.pluginService = NewPluginService(c.WithSegment(pluginSegment), logger.Named("plugins")) + s.installer = NewInstallerServices(installerServiceName, c.WithSegment("installer"), logger.Named("installer")) return s } diff --git a/pkg/stub/registry_client.go b/pkg/stub/registry_client.go deleted file mode 100644 index 76c9154b..00000000 --- a/pkg/stub/registry_client.go +++ /dev/null @@ -1,932 +0,0 @@ -/* -Copyright 2025 Flant JSC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package stub - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "context" - "crypto/sha256" - "fmt" - "io" - "os" - "strings" - "time" - - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/types" - - "github.com/deckhouse/deckhouse/pkg/registry" - "github.com/deckhouse/deckhouse/pkg/registry/client" - - "github.com/deckhouse/deckhouse-cli/pkg" - localreg "github.com/deckhouse/deckhouse-cli/pkg/registry" - "github.com/deckhouse/deckhouse-cli/pkg/registry/image" -) - -// RegistryClientStub provides a stub implementation of RegistryClient for testing -type RegistryClientStub struct { - registries map[string]*RegistryData - currentRegistry string - // defaultRegistry holds the deterministic 'source' registry added during initialization - defaultRegistry string -} - -// RegistryData holds data for a specific registry -type RegistryData struct { - repositories map[string]*RepositoryData -} - -// RepositoryData holds data for a specific repository -type RepositoryData struct { - tags []string - images map[string]*ImageData - digests map[string]*v1.Hash -} - -// ImageData holds data for a specific image -type ImageData struct { - manifest []byte - config *v1.ConfigFile - image pkg.RegistryImage - digest *v1.Hash - exists bool -} - -// RegistryImageStub provides a simple stub implementation of RegistryImage -type RegistryImageStub struct { - manifest *v1.Manifest - config *v1.ConfigFile - digest v1.Hash - tag string - version string -} - -// StubLayer provides a simple stub implementation of v1.Layer -type StubLayer struct { - digest v1.Hash - size int64 - data []byte // gzipped tar data -} - -// Digest implements v1.Layer -func (l *StubLayer) Digest() (v1.Hash, error) { - return l.digest, nil -} - -// DiffID implements v1.Layer -func (l *StubLayer) DiffID() (v1.Hash, error) { - return l.digest, nil -} - -// Compressed implements v1.Layer -func (l *StubLayer) Compressed() (io.ReadCloser, error) { - return io.NopCloser(bytes.NewReader(l.data)), nil -} - -// Uncompressed implements v1.Layer -func (l *StubLayer) Uncompressed() (io.ReadCloser, error) { - // Decompress the gzipped data - gzipReader, err := gzip.NewReader(bytes.NewReader(l.data)) - if err != nil { - return nil, err - } - return gzipReader, nil -} - -// Size implements v1.Layer -func (l *StubLayer) Size() (int64, error) { - return l.size, nil -} - -// MediaType implements v1.Layer -func (l *StubLayer) MediaType() (types.MediaType, error) { - return types.DockerLayer, nil -} - -// Digest implements v1.Image -func (r *RegistryImageStub) Digest() (v1.Hash, error) { - return r.digest, nil -} - -// Manifest implements v1.Image -func (r *RegistryImageStub) Manifest() (*v1.Manifest, error) { - return r.manifest, nil -} - -// ConfigFile implements v1.Image -func (r *RegistryImageStub) ConfigFile() (*v1.ConfigFile, error) { - return r.config, nil -} - -// ConfigName implements v1.Image -func (r *RegistryImageStub) ConfigName() (v1.Hash, error) { - return r.digest, nil -} - -// RawManifest implements v1.Image -func (r *RegistryImageStub) RawManifest() ([]byte, error) { - return []byte(`{"schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json"}`), nil -} - -// RawConfigFile implements v1.Image -func (r *RegistryImageStub) RawConfigFile() ([]byte, error) { - return []byte(`{"config":{"Image":"test"},"rootfs":{"type":"layers","diff_ids":["sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"]}}`), nil -} - -// MediaType implements v1.Image -func (r *RegistryImageStub) MediaType() (types.MediaType, error) { - return types.DockerManifestSchema2, nil -} - -// Size implements v1.Image -func (r *RegistryImageStub) Size() (int64, error) { - return 1024, nil -} - -// LayerByDigest implements v1.Image -func (r *RegistryImageStub) LayerByDigest(_ v1.Hash) (v1.Layer, error) { - return nil, fmt.Errorf("LayerByDigest not implemented in stub") -} - -// LayerByDiffID implements v1.Image -func (r *RegistryImageStub) LayerByDiffID(_ v1.Hash) (v1.Layer, error) { - return nil, fmt.Errorf("LayerByDiffID not implemented in stub") -} - -// Layers implements v1.Image -func (r *RegistryImageStub) Layers() ([]v1.Layer, error) { - // Determine the version based on the tag - version := r.version - if version == "" { - // Fallback: parse the tag to determine version - parts := strings.Split(r.tag, ":") - if len(parts) == 2 { - tag := parts[1] - switch { - case strings.Contains(r.tag, "/release-channel:"): - switch tag { - case "alpha": - version = "v1.72.10" - case "beta": - version = "v1.71.0" - case "early-access": - version = "v1.70.0" - case "stable": - version = "v1.69.0" - case "rock-solid": - version = "v1.68.0" - default: - if strings.HasPrefix(tag, "v") { - version = tag - } else { - version = "v1.72.10" - } - } - case strings.HasPrefix(tag, "v"): - version = tag - default: - version = "v1.72.10" - } - } else { - version = "v1.72.10" - } - } - - // Create uncompressed tar - var tarBuf bytes.Buffer - tarWriter := tar.NewWriter(&tarBuf) - - // Add directory headers - if err := tarWriter.WriteHeader(&tar.Header{ - Name: "deckhouse/", - Typeflag: tar.TypeDir, - Mode: 0755, - ModTime: time.Now(), - }); err != nil { - return nil, err - } - if err := tarWriter.WriteHeader(&tar.Header{ - Name: "deckhouse/candi/", - Typeflag: tar.TypeDir, - Mode: 0755, - ModTime: time.Now(), - }); err != nil { - return nil, err - } - - // Add changelog.yaml file - changelogData := `candi: - fixes: - - summary: "Fix deckhouse containerd start after installing new containerd-deckhouse package." - pull_request: "https://github.com/deckhouse/deckhouse/pull/6329" -` - changelogHeader := &tar.Header{ - Name: "changelog.yaml", - Mode: 0644, - Size: int64(len(changelogData)), - ModTime: time.Now(), - Typeflag: tar.TypeReg, - } - if err := tarWriter.WriteHeader(changelogHeader); err != nil { - return nil, err - } - if _, err := tarWriter.Write([]byte(changelogData)); err != nil { - return nil, err - } - - // Add version.json file - versionData := fmt.Sprintf(`{"version":"%s"}`, version) - versionHeader := &tar.Header{ - Name: "version.json", - Mode: 0644, - Size: int64(len(versionData)), - ModTime: time.Now(), - Typeflag: tar.TypeReg, - } - if err := tarWriter.WriteHeader(versionHeader); err != nil { - return nil, err - } - if _, err := tarWriter.Write([]byte(versionData)); err != nil { - return nil, err - } - - // Add deckhouse/candi/images_tags.json file - imagesTagsData := `{}` - imagesTagsHeader := &tar.Header{ - Name: "deckhouse/candi/images_tags.json", - Mode: 0644, - Size: int64(len(imagesTagsData)), - ModTime: time.Now(), - Typeflag: tar.TypeReg, - } - if err := tarWriter.WriteHeader(imagesTagsHeader); err != nil { - return nil, err - } - if _, err := tarWriter.Write([]byte(imagesTagsData)); err != nil { - return nil, err - } - - tarWriter.Close() - - // Now gzip the tar - var gzipBuf bytes.Buffer - gzipWriter := gzip.NewWriter(&gzipBuf) - if _, err := gzipWriter.Write(tarBuf.Bytes()); err != nil { - return nil, err - } - gzipWriter.Close() - - digest, _ := v1.NewHash("sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f") - - return []v1.Layer{ - &StubLayer{ - digest: digest, - size: int64(gzipBuf.Len()), - data: gzipBuf.Bytes(), // gzipped tar - }, - }, nil -} - -// Extract implements RegistryImage -func (r *RegistryImageStub) Extract() io.ReadCloser { - // Determine the version based on the tag - version := r.version - if version == "" { - // Fallback: parse the tag to determine version - parts := strings.Split(r.tag, ":") - if len(parts) == 2 { - tag := parts[1] - switch { - case strings.Contains(r.tag, "/release-channel:"): - switch tag { - case "alpha": - version = "v1.72.10" - case "beta": - version = "v1.71.0" - case "early-access": - version = "v1.70.0" - case "stable": - version = "v1.69.0" - case "rock-solid": - version = "v1.68.0" - default: - if strings.HasPrefix(tag, "v") { - version = tag - } else { - version = "v1.72.10" - } - } - case strings.HasPrefix(tag, "v"): - version = tag - default: - version = "v1.72.10" - } - } else { - version = "v1.72.10" - } - } - - // Create uncompressed tar - var tarBuf bytes.Buffer - tarWriter := tar.NewWriter(&tarBuf) - - // Add directory headers - if err := tarWriter.WriteHeader(&tar.Header{ - Name: "deckhouse/", - Typeflag: tar.TypeDir, - Mode: 0755, - ModTime: time.Now(), - }); err != nil { - return io.NopCloser(strings.NewReader("")) - } - if err := tarWriter.WriteHeader(&tar.Header{ - Name: "deckhouse/candi/", - Typeflag: tar.TypeDir, - Mode: 0755, - ModTime: time.Now(), - }); err != nil { - return io.NopCloser(strings.NewReader("")) - } - - // Add changelog.yaml file - changelogData := `candi: - fixes: - - summary: "Fix deckhouse containerd start after installing new containerd-deckhouse package." - pull_request: "https://github.com/deckhouse/deckhouse/pull/6329" -` - changelogHeader := &tar.Header{ - Name: "changelog.yaml", - Mode: 0644, - Size: int64(len(changelogData)), - ModTime: time.Now(), - Typeflag: tar.TypeReg, - } - if err := tarWriter.WriteHeader(changelogHeader); err != nil { - return io.NopCloser(strings.NewReader("")) - } - if _, err := tarWriter.Write([]byte(changelogData)); err != nil { - return io.NopCloser(strings.NewReader("")) - } - - // Add version.json file - versionData := fmt.Sprintf(`{"version":"%s"}`, version) - versionHeader := &tar.Header{ - Name: "version.json", - Mode: 0644, - Size: int64(len(versionData)), - ModTime: time.Now(), - Typeflag: tar.TypeReg, - } - if err := tarWriter.WriteHeader(versionHeader); err != nil { - return io.NopCloser(strings.NewReader("")) - } - if _, err := tarWriter.Write([]byte(versionData)); err != nil { - return io.NopCloser(strings.NewReader("")) - } - - // Add deckhouse/candi/images_tags.json file - imagesTagsData := `{}` - imagesTagsHeader := &tar.Header{ - Name: "deckhouse/candi/images_tags.json", - Mode: 0644, - Size: int64(len(imagesTagsData)), - ModTime: time.Now(), - Typeflag: tar.TypeReg, - } - if err := tarWriter.WriteHeader(imagesTagsHeader); err != nil { - return io.NopCloser(strings.NewReader("")) - } - if _, err := tarWriter.Write([]byte(imagesTagsData)); err != nil { - return io.NopCloser(strings.NewReader("")) - } - - tarWriter.Close() - - return io.NopCloser(bytes.NewBuffer(tarBuf.Bytes())) -} - -// GetMetadata implements RegistryImage -func (r *RegistryImageStub) GetMetadata() (pkg.ImageMeta, error) { - return image.NewImageMeta(r.tag, fmt.Sprintf("sha256:%s", r.digest.String()), &r.digest), nil -} - -func (r *RegistryImageStub) SetMetadata(_ pkg.ImageMeta) { - // No-op for mock -} - -// ImageMetaStub provides a simple stub implementation of ImageMeta -type ImageMetaStub struct { - tagRef string - digestRef string - digest *v1.Hash -} - -func (i *ImageMetaStub) GetTagReference() string { - return i.tagRef -} - -func (i *ImageMetaStub) GetDigestReference() string { - return i.digestRef -} - -func (i *ImageMetaStub) GetDigest() *v1.Hash { - return i.digest -} - -// NewRegistryClientStub creates a new stub registry client -func NewRegistryClientStub() localreg.Client { - stub := &RegistryClientStub{ - registries: make(map[string]*RegistryData), - } - stub.initializeRegistries() - return stub -} - -// getSourceFromArgs parses the command line arguments to find the --source flag value -func getSourceFromArgs() string { - args := os.Args - for i, arg := range args { - if arg == "--source" && i+1 < len(args) { - return args[i+1] - } - if strings.HasPrefix(arg, "--source=") { - return strings.TrimPrefix(arg, "--source=") - } - } - - return "registry.deckhouse.ru/deckhouse/fe" // default -} - -// initializeRegistries sets up mock data for multiple registries -func (s *RegistryClientStub) initializeRegistries() { - source := getSourceFromArgs() - - // record deterministic source registry so stub resolves segments consistently - s.defaultRegistry = source - - // Registry 1: dynamic source - s.addRegistry(source, map[string][]string{ - "": {"alpha", "beta", "early-access", "stable", "rock-solid", "v1.72.10", "v1.71.0", "v1.70.0", "v1.69.0", "v1.68.0", "pr12345"}, // including custom tag - "release-channel": {"alpha", "beta", "early-access", "stable", "rock-solid"}, // channel names, not versions - "install": {"alpha", "beta", "early-access", "stable", "rock-solid", "v1.72.10", "v1.71.0", "v1.70.0", "v1.69.0", "v1.68.0", "pr12345"}, - "install-standalone": {"alpha", "beta", "early-access", "stable", "rock-solid", "v1.72.10", "v1.71.0", "v1.70.0", "v1.69.0", "v1.68.0", "pr12345"}, - }) - - // Registry 2: gcr.io - s.addRegistry("gcr.io/google-containers", map[string][]string{ - "": {"alpha", "beta", "early-access", "stable", "rock-solid", "v1.72.10", "v1.71.0", "v1.70.0", "v1.69.0", "v1.68.0"}, - "pause": {"alpha", "beta", "early-access", "stable", "rock-solid", "3.9", "latest"}, - "kube-apiserver": {"alpha", "beta", "early-access", "stable", "rock-solid", "v1.28.0", "v1.29.0", "latest"}, - "release-channel": {"alpha", "beta", "early-access", "stable", "rock-solid"}, - "install": {"alpha", "beta", "early-access", "stable", "rock-solid", "v1.72.10", "v1.71.0", "v1.70.0", "v1.69.0", "v1.68.0"}, - "install-standalone": {"alpha", "beta", "early-access", "stable", "rock-solid", "v1.72.10", "v1.71.0", "v1.70.0", "v1.69.0", "v1.68.0"}, - }) - - // Registry 3: quay.io - s.addRegistry("quay.io/prometheus", map[string][]string{ - "prometheus": {"alpha", "beta", "early-access", "stable", "rock-solid", "v2.45.0", "v2.46.0", "latest"}, - "alertmanager": {"alpha", "beta", "early-access", "stable", "rock-solid", "v0.26.0", "v0.27.0", "latest"}, - }) -} - -// addRegistry adds a registry with repositories and tags -func (s *RegistryClientStub) addRegistry(registryPath string, repos map[string][]string) { - if s.registries[registryPath] == nil { - s.registries[registryPath] = &RegistryData{ - repositories: make(map[string]*RepositoryData), - } - } - - regData := s.registries[registryPath] - for repo, tags := range repos { - if regData.repositories[repo] == nil { - regData.repositories[repo] = &RepositoryData{ - tags: tags, - images: make(map[string]*ImageData), - digests: make(map[string]*v1.Hash), - } - } - - repoData := regData.repositories[repo] - for _, tag := range tags { - // Create mock image data for each tag - imageData := s.createMockImageData(registryPath, repo, tag) - repoData.images[tag] = imageData - - // Set digest - repoData.digests[tag] = imageData.digest - } - } -} - -// generateUniqueDigest creates a unique SHA256 digest based on the image reference -func generateUniqueDigest(registry, repo, tag string) v1.Hash { - data := fmt.Sprintf("%s/%s:%s", registry, repo, tag) - hash := sha256.Sum256([]byte(data)) - digestStr := fmt.Sprintf("sha256:%x", hash) - digest, _ := v1.NewHash(digestStr) - return digest -} - -// findRegistryAndRepo finds the registry and repo from currentRegistry -func (s *RegistryClientStub) findRegistryAndRepo() (string, string) { - var registry, repo string - if s.currentRegistry == "" { - return "", "" - } - - // Find the longest registry key that is a prefix of currentRegistry - for reg := range s.registries { - if strings.HasPrefix(s.currentRegistry, reg+"/") || s.currentRegistry == reg { - if len(reg) > len(registry) { - registry = reg - } - } - } - - if registry == "" { - return s.currentRegistry, "" - } - - if s.currentRegistry == registry { - return registry, "" - } - - repo = strings.TrimPrefix(s.currentRegistry, registry+"/") - return registry, repo -} - -// createMockImageData creates mock image data with manifest, config, and registry image -func (s *RegistryClientStub) createMockImageData(reg, repo, tag string) *ImageData { - // Create a unique digest based on the image reference - digest := generateUniqueDigest(reg, repo, tag) - - // Create mock manifest - manifest := []byte(`{ - "schemaVersion": 2, - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "config": { - "mediaType": "application/vnd.docker.container.image.v1+json", - "size": 1469, - "digest": "sha256:b5d2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" - }, - "layers": [ - { - "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "size": 32654, - "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" - } - ] - }`) - - // Create mock config - config := &v1.ConfigFile{ - Config: v1.Config{ - Image: fmt.Sprintf("%s/%s:%s", reg, repo, tag), - Labels: map[string]string{ - "org.opencontainers.image.created": "2024-01-01T00:00:00Z", - "org.opencontainers.image.version": tag, - "org.opencontainers.image.source": fmt.Sprintf("https://%s", reg), - }, - }, - RootFS: v1.RootFS{ - Type: "layers", - DiffIDs: []v1.Hash{ - func() v1.Hash { - h, _ := v1.NewHash("sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890") - return h - }(), - }, - }, - History: []v1.History{ - { - Created: v1.Time{Time: v1.Time{}.Time}, - Comment: fmt.Sprintf("Build for %s/%s:%s", reg, repo, tag), - }, - }, - } - - // Determine the version to put in version.json based on the tag - version := "v1.72.10" // default fallback - if repo == "release-channel" { - // For release channels, map to semver versions - switch tag { - case "alpha": - // version = "v1.72.10" // same as default - case "beta": - version = "v1.71.0" - case "early-access": - version = "v1.70.0" - case "stable": - version = "v1.69.0" - case "rock-solid": - version = "v1.68.0" - default: - // If it's already a semver version, use it - if strings.HasPrefix(tag, "v") { - version = tag - } // else keep default - } - } else if strings.HasPrefix(tag, "v") { - version = tag - } // else keep default - - // Create mock registry image (v1.Image implementation) - imageStub := &RegistryImageStub{ - manifest: &v1.Manifest{ - SchemaVersion: 2, - MediaType: types.DockerManifestSchema2, - Config: v1.Descriptor{ - MediaType: types.DockerConfigJSON, - Size: 1469, - Digest: func() v1.Hash { - h, _ := v1.NewHash("sha256:b5d2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7") - return h - }(), - }, - Layers: []v1.Descriptor{ - { - MediaType: types.DockerLayer, - Size: 32654, - Digest: func() v1.Hash { - h, _ := v1.NewHash("sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f") - return h - }(), - }, - }, - }, - config: config, - digest: digest, - tag: fmt.Sprintf("%s/%s:%s", reg, repo, tag), - version: version, // Store the version for the Extract method - } - - // Create real registry.Image wrapping the mock v1.Image - tagReference := fmt.Sprintf("%s/%s:%s", reg, repo, tag) - digestReference := fmt.Sprintf("%s/%s@%s", reg, repo, digest.String()) - _ = image.NewImageMeta(tagReference, digestReference, &digest) - - // Always use the stub directly to ensure Extract method works - var registryImage pkg.RegistryImage = imageStub - - return &ImageData{ - manifest: manifest, - config: config, - image: registryImage, - digest: &digest, - exists: true, - } -} - -// WithSegment creates a new client with an additional scope path segment -func (s *RegistryClientStub) WithSegment(segments ...string) localreg.Client { - // If no current registry is set, use the stub's deterministic default registry as base - base := s.currentRegistry - if base == "" { - base = s.defaultRegistry - } - - var newRegistry string - if len(segments) == 0 { - newRegistry = base - } else { - segPath := strings.Join(segments, "/") - if base == "" { - // fall back to segment-only (legacy behavior) - newRegistry = segPath - } else { - newRegistry = base + "/" + segPath - } - } - - return &RegistryClientStub{ - registries: s.registries, - currentRegistry: newRegistry, - } -} - -// GetRegistry returns the full registry path -func (s *RegistryClientStub) GetRegistry() string { - if s.currentRegistry == "" { - if s.defaultRegistry != "" { - return s.defaultRegistry - } - - for registry := range s.registries { - return registry - } - return "" - } - - registry, _ := s.findRegistryAndRepo() - return registry -} - -// GetDigest retrieves the digest for a specific image tag -func (s *RegistryClientStub) GetDigest(_ context.Context, tag string) (*v1.Hash, error) { - registry, repo := s.findRegistryAndRepo() - - if regData, exists := s.registries[registry]; exists { - if repoData, exists := regData.repositories[repo]; exists { - if digest, exists := repoData.digests[tag]; exists { - return digest, nil - } - } - } - - // Fall back to all registries - for _, regData := range s.registries { - for _, repoData := range regData.repositories { - if digest, exists := repoData.digests[tag]; exists { - return digest, nil - } - } - } - return nil, fmt.Errorf("digest not found for tag: %s", tag) -} - -// GetManifest retrieves the manifest for a specific image tag -func (s *RegistryClientStub) GetManifest(_ context.Context, tag string) (registry.ManifestResult, error) { - registry, repo := s.findRegistryAndRepo() - - if regData, exists := s.registries[registry]; exists { - if repoData, exists := regData.repositories[repo]; exists { - if imageData, exists := repoData.images[tag]; exists { - return client.NewManifestResultFromBytes(imageData.manifest), nil - } - } - } - - // Fall back to all registries - for _, regData := range s.registries { - for _, repoData := range regData.repositories { - if imageData, exists := repoData.images[tag]; exists { - return client.NewManifestResultFromBytes(imageData.manifest), nil - } - } - } - return nil, fmt.Errorf("manifest not found for tag: %s", tag) -} - -// GetImageConfig retrieves the image config file -func (s *RegistryClientStub) GetImageConfig(_ context.Context, tag string) (*v1.ConfigFile, error) { - registry, repo := s.findRegistryAndRepo() - - if regData, exists := s.registries[registry]; exists { - if repoData, exists := regData.repositories[repo]; exists { - if imageData, exists := repoData.images[tag]; exists { - return imageData.config, nil - } - } - } - - // Fall back to all registries - for _, regData := range s.registries { - for _, repoData := range regData.repositories { - if imageData, exists := repoData.images[tag]; exists { - return imageData.config, nil - } - } - } - return nil, fmt.Errorf("config not found for tag: %s", tag) -} - -// CheckImageExists checks if a specific image exists -func (s *RegistryClientStub) CheckImageExists(_ context.Context, tag string) error { - registry, repo := s.findRegistryAndRepo() - - if regData, exists := s.registries[registry]; exists { - if repoData, exists := regData.repositories[repo]; exists { - if _, exists := repoData.images[tag]; exists { - return nil - } - } - } - - // Fall back to all registries - for _, regData := range s.registries { - for _, repoData := range regData.repositories { - if _, exists := repoData.images[tag]; exists { - return nil - } - } - } - return fmt.Errorf("%w: %s", client.ErrImageNotFound, tag) -} - -// GetImage retrieves an image for a specific reference -func (s *RegistryClientStub) GetImage(_ context.Context, tag string, _ ...registry.ImageGetOption) (registry.Image, error) { - // Handle digest references (start with @) - if strings.HasPrefix(tag, "@") { - digestStr := strings.TrimPrefix(tag, "@") - digest, err := v1.NewHash(digestStr) - if err != nil { - return nil, fmt.Errorf("invalid digest: %s", digestStr) - } - - // Find image by digest - for _, regData := range s.registries { - for _, repoData := range regData.repositories { - for _, imageData := range repoData.images { - if imageData.digest != nil && imageData.digest.String() == digest.String() { - return imageData.image.(pkg.ClientImage), nil - } - } - } - } - return nil, fmt.Errorf("%w for digest: %s", client.ErrImageNotFound, digestStr) - } - - registry, repo := s.findRegistryAndRepo() - - if regData, exists := s.registries[registry]; exists { - if repoData, exists := regData.repositories[repo]; exists { - if imageData, exists := repoData.images[tag]; exists { - return imageData.image.(pkg.ClientImage), nil - } - } - } - - // Fall back to searching in the specific registry first, then all registries - if regData, exists := s.registries[registry]; exists { - for _, repoData := range regData.repositories { - if imageData, exists := repoData.images[tag]; exists { - return imageData.image.(pkg.ClientImage), nil - } - } - } - - // Last resort: search all registries - for _, regData := range s.registries { - for _, repoData := range regData.repositories { - if imageData, exists := repoData.images[tag]; exists { - return imageData.image.(pkg.ClientImage), nil - } - } - } - return nil, fmt.Errorf("%w for tag: %s", client.ErrImageNotFound, tag) -} - -// PushImage pushes an image to the registry -func (s *RegistryClientStub) PushImage(_ context.Context, _ string, _ v1.Image, _ ...registry.ImagePushOption) error { - // Stub implementation - always succeeds - return nil -} - -// ListTags retrieves all available tags -func (s *RegistryClientStub) ListTags(_ context.Context, _ ...registry.ListTagsOption) ([]string, error) { - total := 0 - for _, regData := range s.registries { - for _, repoData := range regData.repositories { - total += len(repoData.tags) - } - } - allTags := make([]string, 0, total) - for _, regData := range s.registries { - for _, repoData := range regData.repositories { - allTags = append(allTags, repoData.tags...) - } - } - return allTags, nil -} - -// ListRepositories retrieves all sub-repositories -func (s *RegistryClientStub) ListRepositories(_ context.Context, _ ...registry.ListRepositoriesOption) ([]string, error) { - total := 0 - for _, regData := range s.registries { - total += len(regData.repositories) - } - allRepos := make([]string, 0, total) - for _, regData := range s.registries { - for repo := range regData.repositories { - allRepos = append(allRepos, repo) - } - } - return allRepos, nil -} - -// DeleteTag removes a specific tag from the repository. -func (s *RegistryClientStub) DeleteTag(_ context.Context, _ string) error { - return nil -} - -// TagImage adds a new tag pointing to the same manifest as sourceTag. -func (s *RegistryClientStub) TagImage(_ context.Context, _, _ string) error { - return nil -}