// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package extensions import ( "context" "fmt" "log" "os" "path/filepath" "strings" "github.com/containerd/containerd" "github.com/containerd/containerd/mount" "github.com/opencontainers/image-spec/identity" "github.com/siderolabs/talos/internal/pkg/containers/image" "github.com/siderolabs/talos/pkg/machinery/config" "github.com/siderolabs/talos/pkg/machinery/constants" ) // Puller pulls, unpacks and mounts extensions images. type Puller struct { client *containerd.Client snapshots []string mounts []string } // NewPuller creates a new instance of system extensions puller helper. func NewPuller(client *containerd.Client) (*Puller, error) { // prepare by ensuring empty extension directory if _, err := os.Stat(constants.SystemExtensionsPath); err == nil { if err = os.RemoveAll(constants.SystemExtensionsPath); err != nil { return nil, err } } if err := os.MkdirAll(constants.SystemExtensionsPath, 0o700); err != nil { return nil, err } return &Puller{ client: client, }, nil } // PullAndMount pulls the system extension images, unpacks them and mounts under well known path (constants.SystemExtensionsPath). func (puller *Puller) PullAndMount(ctx context.Context, registryConfig config.Registries, extensions []config.Extension) error { snapshotService := puller.client.SnapshotService(containerd.DefaultSnapshotter) for i, ext := range extensions { extensionImage := ext.Image() // use numeric prefix to keep extensions sorted in a proper way path := fmt.Sprintf("%03d.%s", i, strings.ReplaceAll(strings.ReplaceAll(extensionImage, ":", "-"), "/", "-")) log.Printf("pulling extension %q", extensionImage) var extImg containerd.Image extImg, err := image.Pull(ctx, registryConfig, puller.client, extensionImage, image.WithSkipIfAlreadyPulled()) if err != nil { return err } diffs, err := extImg.RootFS(ctx) if err != nil { return err } chainID := identity.ChainID(diffs) _, err = snapshotService.Stat(ctx, chainID.String()) if err != nil { return err } mounts, err := snapshotService.Prepare(ctx, path, chainID.String()) if err != nil { return err } puller.snapshots = append(puller.snapshots, path) mountTarget := filepath.Join(constants.SystemExtensionsPath, path) if err = os.Mkdir(mountTarget, 0o700); err != nil { return err } if err = mount.All(mounts, mountTarget); err != nil { return err } puller.mounts = append(puller.mounts, mountTarget) } return nil } // Cleanup the temporary stuff created by the puller process. func (puller *Puller) Cleanup(ctx context.Context) error { for _, target := range puller.mounts { if err := mount.UnmountAll(target, 0); err != nil { return err } } snapshotService := puller.client.SnapshotService(containerd.DefaultSnapshotter) for _, key := range puller.snapshots { if err := snapshotService.Remove(ctx, key); err != nil { return err } } return os.RemoveAll(constants.SystemExtensionsPath) }