Skip to content

Commit 17e3d88

Browse files
committed
feat: add static registry to talosctl
Fixes #11928 Fixes #11929 Signed-off-by: Mateusz Urbanek <[email protected]>
1 parent 0247324 commit 17e3d88

File tree

8 files changed

+605
-27
lines changed

8 files changed

+605
-27
lines changed

cmd/talosctl/cmd/talos/image.go

Lines changed: 233 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,31 @@ package talos
77
import (
88
"bufio"
99
"context"
10+
"crypto/tls"
1011
"errors"
1112
"fmt"
1213
"io"
14+
"log/slog"
15+
"net"
16+
"net/http"
1317
"os"
18+
"os/signal"
1419
"slices"
1520
"strings"
1621
"text/tabwriter"
1722
"time"
1823

1924
"github.com/blang/semver/v4"
2025
"github.com/dustin/go-humanize"
26+
"github.com/google/go-containerregistry/pkg/crane"
27+
"github.com/google/go-containerregistry/pkg/logs"
28+
"github.com/google/go-containerregistry/pkg/v1/remote"
29+
"github.com/olareg/olareg"
30+
"github.com/olareg/olareg/config"
31+
"github.com/siderolabs/go-pointer"
2132
"github.com/spf13/cobra"
2233
"github.com/spf13/pflag"
34+
"golang.org/x/sync/errgroup"
2335

2436
"github.com/siderolabs/talos/cmd/talosctl/pkg/talos/artifacts"
2537
"github.com/siderolabs/talos/cmd/talosctl/pkg/talos/helpers"
@@ -168,14 +180,24 @@ var imageDefaultCmd = &cobra.Command{
168180
},
169181
}
170182

171-
var minimumVersion = semver.MustParse("1.11.0-alpha.0")
183+
const (
184+
provisionerDocker = "docker"
185+
provisionerInstaller = "installer"
186+
provisionerAll = "all"
187+
)
188+
189+
var imageDefaultCmdFlags = struct {
190+
provisioner pflag.Value
191+
}{
192+
provisioner: helpers.StringChoice(provisionerInstaller, provisionerDocker, provisionerAll),
193+
}
172194

173195
// imageSourceBundleCmd represents the image source-bundle command.
174196
var imageSourceBundleCmd = &cobra.Command{
175197
Use: "source-bundle <talos-version>",
176198
Short: "List the source images used for building Talos",
177199
Long: ``,
178-
Args: helpers.ChainCobraPositionalArgs(
200+
Args: cobra.MatchAll(
179201
cobra.ExactArgs(1),
180202
func(cmd *cobra.Command, args []string) error {
181203
maximumVersion, err := semver.ParseTolerant(version.Tag)
@@ -245,6 +267,8 @@ var imageSourceBundleCmd = &cobra.Command{
245267
},
246268
}
247269

270+
var minimumVersion = semver.MustParse("1.11.0-alpha.0")
271+
248272
// imageIntegrationCmd represents the integration image command.
249273
var imageIntegrationCmd = &cobra.Command{
250274
Use: "integration",
@@ -394,16 +418,202 @@ var imageCacheCreateCmdFlags struct {
394418
force bool
395419
}
396420

397-
const (
398-
provisionerDocker = "docker"
399-
provisionerInstaller = "installer"
400-
provisionerAll = "all"
401-
)
421+
var imageRegistryCommand = &cobra.Command{
422+
Use: "registry",
423+
Short: "Commands for working with a local image registry",
424+
Long: ``,
425+
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
426+
logs.Progress.SetOutput(os.Stderr)
402427

403-
var imageDefaultCmdFlags = struct {
404-
provisioner pflag.Value
405-
}{
406-
provisioner: helpers.StringChoice(provisionerInstaller, provisionerDocker, provisionerAll),
428+
transport := remote.DefaultTransport.(*http.Transport).Clone()
429+
transport.TLSClientConfig = &tls.Config{
430+
InsecureSkipVerify: true, //nolint: gosec
431+
}
432+
433+
options = append(options,
434+
crane.WithJobs(0),
435+
crane.Insecure,
436+
crane.WithNondistributable(),
437+
crane.WithTransport(transport),
438+
)
439+
},
440+
}
441+
442+
var options []crane.Option
443+
444+
var imageRegistryCreateCommand = &cobra.Command{
445+
Use: "create <path>",
446+
Short: "Create a local OCI from a list of images",
447+
Long: `Create a local OCI from a list of images`,
448+
Example: fmt.Sprintf(
449+
`talosctl images registry create --images=ghcr.io/siderolabs/kubelet:v%s /tmp/registry
450+
451+
Alternatively, stdin can be piped to the command:
452+
talosctl images source-bundle | talosctl images registry create /tmp/registry --images=-
453+
`,
454+
constants.DefaultKubernetesVersion,
455+
),
456+
Args: cobra.ExactArgs(1),
457+
RunE: func(cmd *cobra.Command, args []string) error {
458+
var err error
459+
460+
storagePath := args[0]
461+
462+
lis, err := net.Listen("tcp", "127.0.0.1:0")
463+
if err != nil {
464+
return fmt.Errorf("error finding free port: %w", err)
465+
}
466+
467+
addr := lis.Addr().String()
468+
469+
lis.Close() //nolint:errcheck
470+
471+
log := slog.New(slog.DiscardHandler)
472+
473+
if imageRegistryCreateCmdFlags.debug {
474+
logs.Debug.SetOutput(os.Stderr)
475+
log = slog.Default()
476+
}
477+
478+
reg := olareg.New(config.Config{
479+
HTTP: config.ConfigHTTP{
480+
Addr: addr,
481+
},
482+
API: config.ConfigAPI{
483+
PushEnabled: pointer.To(true),
484+
DeleteEnabled: pointer.To(false),
485+
Blob: config.ConfigAPIBlob{
486+
DeleteEnabled: pointer.To(false),
487+
},
488+
},
489+
Storage: config.ConfigStorage{
490+
StoreType: config.StoreDir,
491+
RootDir: storagePath,
492+
ReadOnly: pointer.To(false),
493+
GC: config.ConfigGC{
494+
Frequency: -1, // disabled
495+
},
496+
},
497+
Log: log,
498+
})
499+
500+
ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt)
501+
defer cancel()
502+
503+
eg, ctx := errgroup.WithContext(ctx)
504+
505+
eg.Go(func() error {
506+
slog.Info("starting registry", "path", storagePath)
507+
508+
return reg.Run(ctx)
509+
})
510+
511+
eg.Go(func() error {
512+
<-ctx.Done()
513+
514+
ctx2, cancel := context.WithTimeout(context.Background(), 5*time.Second)
515+
defer cancel()
516+
517+
return reg.Shutdown(ctx2)
518+
})
519+
520+
eg.Go(func() error {
521+
if imageRegistryCreateCmdFlags.images[0] == "-" {
522+
var imagesListData strings.Builder
523+
524+
if _, err := io.Copy(&imagesListData, os.Stdin); err != nil {
525+
return fmt.Errorf("error reading from stdin: %w", err)
526+
}
527+
528+
imageRegistryCreateCmdFlags.images = strings.Split(strings.Trim(imagesListData.String(), "\n"), "\n")
529+
}
530+
531+
slog.Info("starting image mirror", "images", len(imageRegistryCreateCmdFlags.images))
532+
533+
if err := artifacts.Mirror(ctx, options, imageRegistryCreateCmdFlags.images, addr); err != nil {
534+
return fmt.Errorf("error copying images: %w", err)
535+
}
536+
537+
cancel()
538+
539+
return nil
540+
})
541+
542+
if err := eg.Wait(); err != nil {
543+
return err
544+
}
545+
546+
return nil
547+
},
548+
}
549+
550+
var imageRegistryCreateCmdFlags struct {
551+
debug bool
552+
images []string
553+
}
554+
555+
var imageRegistryServeCommand = &cobra.Command{
556+
Use: "serve <path>",
557+
Short: "Serve images from a local storage",
558+
Long: ``,
559+
Args: cobra.ExactArgs(1),
560+
RunE: func(cmd *cobra.Command, args []string) error {
561+
storePath := args[0]
562+
563+
reg := olareg.New(config.Config{
564+
HTTP: config.ConfigHTTP{
565+
Addr: imageRegistryServeCmdFlags.address,
566+
CertFile: imageRegistryServeCmdFlags.tlsCertFile,
567+
KeyFile: imageRegistryServeCmdFlags.tlsKeyFile,
568+
},
569+
API: config.ConfigAPI{
570+
PushEnabled: pointer.To(false),
571+
DeleteEnabled: pointer.To(false),
572+
Blob: config.ConfigAPIBlob{
573+
DeleteEnabled: pointer.To(false),
574+
},
575+
},
576+
Storage: config.ConfigStorage{
577+
StoreType: config.StoreDir,
578+
RootDir: storePath,
579+
ReadOnly: pointer.To(true),
580+
GC: config.ConfigGC{
581+
Frequency: -1, // disabled
582+
},
583+
},
584+
Log: slog.Default(),
585+
})
586+
587+
ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt)
588+
defer cancel()
589+
590+
eg, ctx := errgroup.WithContext(ctx)
591+
592+
eg.Go(func() error {
593+
slog.Info("Starting registry", "addr", imageRegistryServeCmdFlags.address, "path", storePath)
594+
595+
return reg.Run(ctx)
596+
})
597+
598+
eg.Go(func() error {
599+
<-ctx.Done()
600+
601+
slog.Info("Shutting down")
602+
603+
ctx2, cancel := context.WithTimeout(context.Background(), 5*time.Second)
604+
defer cancel()
605+
606+
return reg.Shutdown(ctx2)
607+
})
608+
609+
return eg.Wait()
610+
},
611+
}
612+
613+
var imageRegistryServeCmdFlags struct {
614+
address string
615+
tlsCertFile string
616+
tlsKeyFile string
407617
}
408618

409619
func init() {
@@ -415,10 +625,9 @@ func init() {
415625

416626
imageCmd.AddCommand(imageListCmd)
417627
imageCmd.AddCommand(imagePullCmd)
418-
imageCmd.AddCommand(imageCacheCreateCmd)
419-
imageCmd.AddCommand(imageIntegrationCmd)
420628
imageCmd.AddCommand(imageSourceBundleCmd)
421629

630+
imageCmd.AddCommand(imageCacheCreateCmd)
422631
imageCacheCreateCmd.PersistentFlags().StringVar(&imageCacheCreateCmdFlags.imageCachePath, "image-cache-path", "", "directory to save the image cache in OCI format")
423632
imageCacheCreateCmd.MarkPersistentFlagRequired("image-cache-path") //nolint:errcheck
424633
imageCacheCreateCmd.PersistentFlags().StringVar(&imageCacheCreateCmdFlags.imageLayerCachePath, "image-layer-cache-path", "", "directory to save the image layer cache")
@@ -428,8 +637,19 @@ func init() {
428637
imageCacheCreateCmd.PersistentFlags().BoolVar(&imageCacheCreateCmdFlags.insecure, "insecure", false, "allow insecure registries")
429638
imageCacheCreateCmd.PersistentFlags().BoolVar(&imageCacheCreateCmdFlags.force, "force", false, "force overwrite of existing image cache")
430639

640+
imageCmd.AddCommand(imageIntegrationCmd)
431641
imageIntegrationCmd.PersistentFlags().StringVar(&imageIntegrationCmdFlags.installerTag, "installer-tag", "", "tag of the installer image to use")
432642
imageIntegrationCmd.MarkPersistentFlagRequired("installer-tag") //nolint:errcheck
433643
imageIntegrationCmd.PersistentFlags().StringVar(&imageIntegrationCmdFlags.registryAndUser, "registry-and-user", "", "registry and user to use for the images")
434644
imageIntegrationCmd.MarkPersistentFlagRequired("registry-and-user") //nolint:errcheck
645+
646+
imageCmd.AddCommand(imageRegistryCommand)
647+
imageRegistryCommand.AddCommand(imageRegistryCreateCommand)
648+
imageRegistryCreateCommand.PersistentFlags().BoolVar(&imageRegistryCreateCmdFlags.debug, "debug", false, "enable debug logging")
649+
imageRegistryCreateCommand.PersistentFlags().StringSliceVar(&imageRegistryCreateCmdFlags.images, "images", nil, "images to cache")
650+
imageRegistryCreateCommand.MarkPersistentFlagRequired("images") //nolint:errcheck
651+
imageRegistryCommand.AddCommand(imageRegistryServeCommand)
652+
imageRegistryServeCommand.PersistentFlags().StringVar(&imageRegistryServeCmdFlags.address, "addr", ":5000", "address to serve the registry on")
653+
imageRegistryServeCommand.PersistentFlags().StringVar(&imageRegistryServeCmdFlags.tlsCertFile, "tls-cert-file", "", "path to TLS certificate file")
654+
imageRegistryServeCommand.PersistentFlags().StringVar(&imageRegistryServeCmdFlags.tlsKeyFile, "tls-key-file", "", "path to TLS key file")
435655
}

0 commit comments

Comments
 (0)