Skip to content

Commit bb25803

Browse files
committed
feat: new push
Signed-off-by: Timur Tuktamyshev <[email protected]>
1 parent be434f1 commit bb25803

File tree

8 files changed

+720
-66
lines changed

8 files changed

+720
-66
lines changed

internal/mirror/cmd/pull/pull.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ import (
2323
"fmt"
2424
"log/slog"
2525
"os"
26+
"os/signal"
2627
"path"
2728
"path/filepath"
29+
"syscall"
2830
"time"
2931

3032
"github.com/Masterminds/semver/v3"
@@ -102,11 +104,19 @@ func NewCommand() *cobra.Command {
102104
}
103105

104106
func pull(cmd *cobra.Command, _ []string) error {
107+
// Set up graceful cancellation on Ctrl+C
108+
ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGINT, syscall.SIGTERM)
109+
defer cancel()
110+
105111
puller := NewPuller(cmd)
106112

107113
puller.logger.Infof("d8 version: %s", version.Version)
108114

109-
if err := puller.Execute(cmd.Context()); err != nil {
115+
if err := puller.Execute(ctx); err != nil {
116+
if errors.Is(err, context.Canceled) {
117+
puller.logger.WarnLn("\nOperation cancelled by user")
118+
return nil
119+
}
110120
return ErrPullFailed
111121
}
112122

internal/mirror/cmd/push/push.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ import (
2323
"io"
2424
"log/slog"
2525
"os"
26+
"os/signal"
2627
"path"
2728
"path/filepath"
2829
"strings"
30+
"syscall"
2931
"time"
3032

3133
"github.com/google/go-containerregistry/pkg/authn"
@@ -36,6 +38,7 @@ import (
3638
"github.com/deckhouse/deckhouse/pkg/registry"
3739
regclient "github.com/deckhouse/deckhouse/pkg/registry/client"
3840

41+
"github.com/deckhouse/deckhouse-cli/internal/mirror"
3942
"github.com/deckhouse/deckhouse-cli/internal/mirror/chunked"
4043
"github.com/deckhouse/deckhouse-cli/internal/mirror/operations"
4144
"github.com/deckhouse/deckhouse-cli/internal/version"
@@ -289,6 +292,11 @@ func (p *Pusher) Execute() error {
289292
return err
290293
}
291294

295+
// Use new push service when NEW_PULL env is set
296+
if os.Getenv("NEW_PULL") == "true" {
297+
return p.executeNewPush()
298+
}
299+
292300
if err := p.pushStaticPackages(); err != nil {
293301
return err
294302
}
@@ -300,6 +308,61 @@ func (p *Pusher) Execute() error {
300308
return nil
301309
}
302310

311+
// executeNewPush runs the push using the new service architecture
312+
func (p *Pusher) executeNewPush() error {
313+
// Set up graceful cancellation on Ctrl+C
314+
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
315+
defer cancel()
316+
317+
logger := dkplog.NewNop()
318+
319+
if log.DebugLogLevel() >= 3 {
320+
logger = dkplog.NewLogger(dkplog.WithLevel(slog.LevelDebug))
321+
}
322+
323+
// Create registry client
324+
clientOpts := &regclient.Options{
325+
Insecure: p.pushParams.Insecure,
326+
TLSSkipVerify: p.pushParams.SkipTLSVerification,
327+
Logger: logger,
328+
}
329+
330+
if p.pushParams.RegistryAuth != nil {
331+
clientOpts.Auth = p.pushParams.RegistryAuth
332+
}
333+
334+
var client registry.Client
335+
client = regclient.NewClientWithOptions(p.pushParams.RegistryHost, clientOpts)
336+
337+
// Scope to the registry path
338+
if p.pushParams.RegistryPath != "" {
339+
client = client.WithSegment(p.pushParams.RegistryPath)
340+
}
341+
342+
svc := mirror.NewPushService(
343+
client,
344+
&mirror.PushServiceOptions{
345+
BundleDir: p.pushParams.BundleDir,
346+
WorkingDir: p.pushParams.WorkingDir,
347+
ModulesPathSuffix: p.pushParams.ModulesPathSuffix,
348+
},
349+
logger.Named("push"),
350+
p.logger.(*log.SLogger),
351+
)
352+
353+
err := svc.Push(ctx)
354+
if err != nil {
355+
// Handle context cancellation gracefully
356+
if errors.Is(err, context.Canceled) {
357+
p.logger.WarnLn("Operation cancelled by user")
358+
return nil
359+
}
360+
return err
361+
}
362+
363+
return nil
364+
}
365+
303366
// validateRegistryAccess validates access to the registry
304367
func (p *Pusher) validateRegistryAccess() error {
305368
p.logger.InfoLn("Validating registry access")

internal/mirror/modules/push.go

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/*
2+
Copyright 2025 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package modules
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"log/slog"
23+
"os"
24+
"path"
25+
"path/filepath"
26+
"slices"
27+
"strings"
28+
29+
dkplog "github.com/deckhouse/deckhouse/pkg/log"
30+
"github.com/deckhouse/deckhouse/pkg/registry"
31+
32+
"github.com/deckhouse/deckhouse-cli/internal/mirror/pusher"
33+
"github.com/deckhouse/deckhouse-cli/pkg/libmirror/util/log"
34+
)
35+
36+
// PushOptions contains options for pushing module images
37+
type PushOptions struct {
38+
BundleDir string
39+
WorkingDir string
40+
}
41+
42+
// PushService handles pushing module images to registry
43+
type PushService struct {
44+
client registry.Client
45+
pusherService *pusher.Service
46+
options *PushOptions
47+
logger *dkplog.Logger
48+
userLogger *log.SLogger
49+
}
50+
51+
// NewPushService creates a new modules push service
52+
func NewPushService(
53+
client registry.Client,
54+
options *PushOptions,
55+
logger *dkplog.Logger,
56+
userLogger *log.SLogger,
57+
) *PushService {
58+
if options == nil {
59+
options = &PushOptions{}
60+
}
61+
62+
return &PushService{
63+
client: client,
64+
pusherService: pusher.NewService(logger, userLogger),
65+
options: options,
66+
logger: logger,
67+
userLogger: userLogger,
68+
}
69+
}
70+
71+
// Push pushes all module packages to the registry
72+
func (svc *PushService) Push(ctx context.Context) error {
73+
modulePackages, err := svc.findModulePackages()
74+
if err != nil {
75+
return fmt.Errorf("find module packages: %w", err)
76+
}
77+
78+
if len(modulePackages) == 0 {
79+
svc.userLogger.InfoLn("No module packages found, skipping")
80+
return nil
81+
}
82+
83+
pushed := make(map[string]struct{})
84+
for _, moduleName := range modulePackages {
85+
if _, ok := pushed[moduleName]; ok {
86+
continue
87+
}
88+
89+
if err := svc.pushModule(ctx, moduleName); err != nil {
90+
svc.userLogger.WarnLn(err)
91+
continue
92+
}
93+
pushed[moduleName] = struct{}{}
94+
}
95+
96+
if len(pushed) > 0 {
97+
names := make([]string, 0, len(pushed))
98+
for name := range pushed {
99+
names = append(names, name)
100+
}
101+
slices.Sort(names)
102+
svc.userLogger.Infof("Modules pushed: %s", strings.Join(names, ", "))
103+
}
104+
105+
return nil
106+
}
107+
108+
func (svc *PushService) findModulePackages() ([]string, error) {
109+
entries, err := os.ReadDir(svc.options.BundleDir)
110+
if err != nil {
111+
return nil, fmt.Errorf("list bundle directory: %w", err)
112+
}
113+
114+
modules := make([]string, 0, len(entries))
115+
for _, entry := range entries {
116+
name := entry.Name()
117+
118+
// Skip non-module files
119+
if !strings.HasPrefix(name, "module-") {
120+
continue
121+
}
122+
123+
// Only process .tar and .chunk files
124+
ext := filepath.Ext(name)
125+
if ext != ".tar" && ext != ".chunk" {
126+
continue
127+
}
128+
129+
// Extract module name: "module-foo.tar" -> "foo"
130+
// Handle chunked files: "module-foo.tar.chunk000" -> "foo"
131+
moduleName := strings.TrimPrefix(name, "module-")
132+
moduleName = strings.TrimSuffix(moduleName, ext)
133+
moduleName = strings.TrimSuffix(moduleName, ".tar")
134+
135+
modules = append(modules, moduleName)
136+
}
137+
138+
return modules, nil
139+
}
140+
141+
func (svc *PushService) pushModule(ctx context.Context, moduleName string) error {
142+
return svc.pusherService.PushPackage(ctx, pusher.PackagePushConfig{
143+
PackageName: "module-" + moduleName,
144+
ProcessName: "Push module: " + moduleName,
145+
WorkingDir: filepath.Join(svc.options.WorkingDir, "modules"),
146+
BundleDir: svc.options.BundleDir,
147+
Client: svc.client.WithSegment(moduleName),
148+
// New pull creates: module/, release/, extra/
149+
MandatoryLayoutsFunc: func(packageDir string) map[string]string {
150+
return map[string]string{
151+
"module root layout": filepath.Join(packageDir, "module"),
152+
"module release channels layout": filepath.Join(packageDir, "release"),
153+
}
154+
},
155+
// Dynamic layout discovery after unpacking
156+
LayoutsFunc: svc.buildModuleLayouts,
157+
})
158+
}
159+
160+
// buildModuleLayouts returns the list of layouts for a module, including dynamic extra discovery
161+
func (svc *PushService) buildModuleLayouts(packageDir string) []pusher.LayoutMapping {
162+
layouts := []pusher.LayoutMapping{
163+
{LayoutPath: "module", Segment: ""}, // Root module images
164+
{LayoutPath: "release", Segment: "release"}, // Release channels
165+
}
166+
167+
// Check if extra directory exists
168+
extraDir := filepath.Join(packageDir, "extra")
169+
if _, err := os.Stat(extraDir); os.IsNotExist(err) {
170+
return layouts
171+
}
172+
173+
// Add root extra layout
174+
layouts = append(layouts, pusher.LayoutMapping{
175+
LayoutPath: "extra",
176+
Segment: "extra",
177+
})
178+
179+
// Discover nested extra layouts
180+
entries, err := os.ReadDir(extraDir)
181+
if err != nil {
182+
svc.logger.Warn("Error reading extra dir", slog.Any("error", err))
183+
return layouts
184+
}
185+
186+
for _, entry := range entries {
187+
if entry.IsDir() {
188+
svc.logger.Debug("Found extra layout", slog.String("layout", entry.Name()))
189+
layouts = append(layouts, pusher.LayoutMapping{
190+
LayoutPath: path.Join("extra", entry.Name()),
191+
Segment: path.Join("extra", entry.Name()),
192+
})
193+
}
194+
}
195+
196+
return layouts
197+
}

internal/mirror/platform/platform.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,13 @@ func (svc *Service) getReleaseChannelVersionFromRegistry(ctx context.Context, re
288288

289289
svc.userLogger.Debugf("image reference: %s@%s", imageMeta, digest.String())
290290

291-
err = svc.layout.DeckhouseReleaseChannel.AddImage(image, imageMeta.GetTagReference())
291+
// Use just the channel name (e.g., "alpha") as the tag for the layout, not the full reference
292+
err = svc.layout.DeckhouseReleaseChannel.AddImage(image, releaseChannel)
292293
if err != nil {
293294
return nil, fmt.Errorf("append %s release channel image to layout: %w", releaseChannel, err)
294295
}
295296

297+
// But use full reference for internal tracking
296298
svc.downloadList.DeckhouseReleaseChannel[imageMeta.GetTagReference()] = puller.NewImageMeta(meta.Version, imageMeta.GetTagReference(), &digest)
297299

298300
return ver, nil

0 commit comments

Comments
 (0)