From b2a4f340c92cf4be6fe696955be61b3cff599416 Mon Sep 17 00:00:00 2001 From: Smyslov Maxim Date: Tue, 19 May 2026 17:05:05 +0300 Subject: [PATCH 1/3] Implement LTS channel existence check in module discovery Signed-off-by: Smyslov Maxim --- internal/mirror/modules/modules.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/mirror/modules/modules.go b/internal/mirror/modules/modules.go index d08035e6..82d974f3 100644 --- a/internal/mirror/modules/modules.go +++ b/internal/mirror/modules/modules.go @@ -333,6 +333,16 @@ func (svc *Service) discoverChannelVersions(ctx context.Context, moduleName stri downloadList.ModuleReleaseChannels[svc.rootURL+"/modules/"+moduleName+"/release:"+channel] = nil } + ltsExistsErr := svc.modulesService.Module(moduleName).ReleaseChannels().CheckImageExists(ctx, internal.LTSChannel) + if ltsExistsErr != nil && !errors.Is(ltsExistsErr, client.ErrImageNotFound) { + return nil, fmt.Errorf("check LTS release channel: %w", ltsExistsErr) + } + + hasLTS := ltsExistsErr == nil + if hasLTS { + downloadList.ModuleReleaseChannels[svc.rootURL+"/modules/"+moduleName+"/release:"+internal.LTSChannel] = nil + } + if !svc.options.DryRun { config := puller.PullConfig{ Name: moduleName + " release channels", From 72b53ba1810cd590e7fc1dbcb284f29540c957ce Mon Sep 17 00:00:00 2001 From: Smyslov Maxim Date: Tue, 19 May 2026 18:35:57 +0300 Subject: [PATCH 2/3] add test Signed-off-by: Smyslov Maxim --- internal/mirror/modules/pull_modules_test.go | 47 +++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/internal/mirror/modules/pull_modules_test.go b/internal/mirror/modules/pull_modules_test.go index ca753c70..801b71c4 100644 --- a/internal/mirror/modules/pull_modules_test.go +++ b/internal/mirror/modules/pull_modules_test.go @@ -25,8 +25,8 @@ import ( "sync/atomic" "testing" - golayout "github.com/google/go-containerregistry/pkg/v1/layout" v1 "github.com/google/go-containerregistry/pkg/v1" + golayout "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -34,6 +34,7 @@ import ( dkpreg "github.com/deckhouse/deckhouse/pkg/registry" upfake "github.com/deckhouse/deckhouse/pkg/registry/fake" + "github.com/deckhouse/deckhouse-cli/internal" "github.com/deckhouse/deckhouse-cli/pkg" "github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log" pkgclient "github.com/deckhouse/deckhouse-cli/pkg/registry/client" @@ -296,6 +297,7 @@ func addModule(reg *upfake.Registry, name, channelVer string, versions []string) reg.MustAddImage("modules", name, versionImage(channelVer)) for _, v := range versions { reg.MustAddImage("modules/"+name, v, versionImage(v)) + reg.MustAddImage("modules/"+name+"/release", v, versionImage(v)) } for _, ch := range []string{"alpha", "beta", "early-access", "stable", "rock-solid"} { reg.MustAddImage("modules/"+name+"/release", ch, versionImage(channelVer)) @@ -309,6 +311,11 @@ func singleModuleRegistry(name, channelVer string, versions []string) *upfake.Re return reg } +// addLTSReleaseChannel adds the optional LTS release channel (CSE editions). +func addLTSReleaseChannel(reg *upfake.Registry, name, channelVer string) { + reg.MustAddImage("modules/"+name+"/release", internal.LTSChannel, versionImage(channelVer)) +} + // versionImage builds a v1.Image carrying only version.json. Missing // images_digests.json / extra_images.json is tolerated downstream. func versionImage(version string) v1.Image { @@ -328,6 +335,10 @@ func taggedModuleRef(moduleName, version string) string { return testHost + "/modules/" + moduleName + ":" + version } +func moduleReleaseChannelRef(moduleName, channel string) string { + return testHost + "/modules/" + moduleName + "/release:" + channel +} + func taggedModuleRefs(moduleName string, versions []string) []string { refs := make([]string, 0, len(versions)) for _, v := range versions { @@ -497,3 +508,37 @@ func TestImageLayouts_HasImages_WithImage(t *testing.T) { assert.True(t, layouts.HasImages(), "HasImages must return true after at least one image is appended") } + +// ============================================================================= +// Tests: LTS release channel +// ============================================================================= + +// CSE editions expose an optional LTS release channel in addition to the five +// default channels. discoverChannelVersions must detect it and include it in +// the pull without failing. +func TestPullModules_LTSChannel(t *testing.T) { + reg := singleModuleRegistry(testModuleName, channelVersion, defaultRegistryVersions) + addLTSReleaseChannel(reg, testModuleName, channelVersion) + + bundleDir := t.TempDir() + svc := newService(t, pkgclient.Adapt(upfake.NewClient(reg)), nil) + svc.options.BundleDir = bundleDir + + require.NoError(t, svc.PullModules(context.Background())) + + moduleDL := svc.modulesDownloadList.Module(testModuleName) + require.NotNil(t, moduleDL, "modulesDownloadList must have an entry for module %q", testModuleName) + + ltsRef := moduleReleaseChannelRef(testModuleName, internal.LTSChannel) + _, ok := moduleDL.ModuleReleaseChannels[ltsRef] + assert.True(t, ok, + "ModuleReleaseChannels should contain LTS ref %q; actual keys: %v", + ltsRef, moduleDL.ModuleReleaseChannels) + + assert.Contains(t, pulledModuleVersionRefs(t, svc, testModuleName), taggedModuleRef(testModuleName, channelVersion), + "LTS channel must contribute %s to the pulled module versions", channelVersion) + + entries, err := os.ReadDir(bundleDir) + require.NoError(t, err) + require.NotEmpty(t, entries, "bundle dir must contain a tar when LTS channel pull succeeds") +} From f72c0ff7ec68bb588b95043cf8334744c6932bb8 Mon Sep 17 00:00:00 2001 From: Smyslov Maxim Date: Tue, 19 May 2026 18:36:19 +0300 Subject: [PATCH 3/3] fix Signed-off-by: Smyslov Maxim --- internal/mirror/modules/modules.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/mirror/modules/modules.go b/internal/mirror/modules/modules.go index 82d974f3..9794f39b 100644 --- a/internal/mirror/modules/modules.go +++ b/internal/mirror/modules/modules.go @@ -333,13 +333,8 @@ func (svc *Service) discoverChannelVersions(ctx context.Context, moduleName stri downloadList.ModuleReleaseChannels[svc.rootURL+"/modules/"+moduleName+"/release:"+channel] = nil } - ltsExistsErr := svc.modulesService.Module(moduleName).ReleaseChannels().CheckImageExists(ctx, internal.LTSChannel) - if ltsExistsErr != nil && !errors.Is(ltsExistsErr, client.ErrImageNotFound) { - return nil, fmt.Errorf("check LTS release channel: %w", ltsExistsErr) - } - - hasLTS := ltsExistsErr == nil - if hasLTS { + // Add LTS channel if it exists + if err := svc.modulesService.Module(moduleName).ReleaseChannels().CheckImageExists(ctx, internal.LTSChannel); err == nil { downloadList.ModuleReleaseChannels[svc.rootURL+"/modules/"+moduleName+"/release:"+internal.LTSChannel] = nil } @@ -599,7 +594,14 @@ func (svc *Service) findVexImage(ctx context.Context, moduleName string, imageRe func (svc *Service) extractVersionsFromReleaseChannels(ctx context.Context, moduleName string) []string { versions := make([]string, 0) - for _, channel := range internal.GetAllDefaultReleaseChannels() { + channels := internal.GetAllDefaultReleaseChannels() + + // Add LTS channel if it exists + if err := svc.modulesService.Module(moduleName).ReleaseChannels().CheckImageExists(ctx, internal.LTSChannel); err == nil { + channels = append(channels, internal.LTSChannel) + } + + for _, channel := range channels { img, err := svc.modulesService.Module(moduleName).ReleaseChannels().GetImage(ctx, channel) if err != nil { svc.logger.Debug(fmt.Sprintf("Failed to get release channel image for %s/%s: %v", moduleName, channel, err))