mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-07 13:41:20 +02:00
There's a cyclic dependency on siderolink library which imports talos machinery back. We will fix that after we get talos pushed under a new name. Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
284 lines
7.7 KiB
Go
284 lines
7.7 KiB
Go
// 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 install
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
|
|
"github.com/containerd/containerd"
|
|
"github.com/containerd/containerd/cio"
|
|
"github.com/containerd/containerd/errdefs"
|
|
"github.com/containerd/containerd/namespaces"
|
|
"github.com/containerd/containerd/oci"
|
|
"github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/siderolabs/go-kmsg"
|
|
"github.com/siderolabs/go-procfs/procfs"
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
|
|
containerdrunner "github.com/siderolabs/talos/internal/app/machined/pkg/system/runner/containerd"
|
|
"github.com/siderolabs/talos/internal/pkg/capability"
|
|
"github.com/siderolabs/talos/internal/pkg/containers/image"
|
|
"github.com/siderolabs/talos/internal/pkg/extensions"
|
|
machineapi "github.com/siderolabs/talos/pkg/machinery/api/machine"
|
|
"github.com/siderolabs/talos/pkg/machinery/config"
|
|
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
|
|
"github.com/siderolabs/talos/pkg/machinery/constants"
|
|
)
|
|
|
|
// RunInstallerContainer performs an installation via the installer container.
|
|
//
|
|
//nolint:gocyclo,cyclop
|
|
func RunInstallerContainer(disk, platform, ref string, cfg config.Provider, opts ...Option) error {
|
|
const containerID = "upgrade"
|
|
|
|
options := DefaultInstallOptions()
|
|
|
|
for _, opt := range opts {
|
|
if err := opt(&options); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var (
|
|
registriesConfig config.Registries
|
|
extensionsConfig []config.Extension
|
|
)
|
|
|
|
if cfg != nil {
|
|
registriesConfig = cfg.Machine().Registries()
|
|
extensionsConfig = cfg.Machine().Install().Extensions()
|
|
} else {
|
|
registriesConfig = &v1alpha1.RegistriesConfig{}
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
ctx = namespaces.WithNamespace(ctx, constants.SystemContainerdNamespace)
|
|
|
|
client, err := containerd.New(constants.SystemContainerdAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer client.Close() //nolint:errcheck
|
|
|
|
var done func(context.Context) error
|
|
|
|
ctx, done, err = client.WithLease(ctx)
|
|
defer done(ctx) //nolint:errcheck
|
|
|
|
var img containerd.Image
|
|
|
|
if !options.Pull {
|
|
img, err = client.GetImage(ctx, ref)
|
|
}
|
|
|
|
if img == nil || err != nil && errdefs.IsNotFound(err) {
|
|
log.Printf("pulling %q", ref)
|
|
|
|
img, err = image.Pull(ctx, registriesConfig, client, ref)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
puller, err := extensions.NewPuller(client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if extensionsConfig != nil {
|
|
if err = puller.PullAndMount(ctx, registriesConfig, extensionsConfig); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
defer func() {
|
|
if err = puller.Cleanup(ctx); err != nil {
|
|
log.Printf("error cleaning up pulled system extensions: %s", err)
|
|
}
|
|
}()
|
|
|
|
// See if there's previous container/snapshot to clean up
|
|
var oldcontainer containerd.Container
|
|
|
|
if oldcontainer, err = client.LoadContainer(ctx, containerID); err == nil {
|
|
if err = oldcontainer.Delete(ctx, containerd.WithSnapshotCleanup); err != nil {
|
|
return fmt.Errorf("error deleting old container instance: %w", err)
|
|
}
|
|
}
|
|
|
|
if err = client.SnapshotService("").Remove(ctx, containerID); err != nil && !errdefs.IsNotFound(err) {
|
|
return fmt.Errorf("error cleaning up stale snapshot: %w", err)
|
|
}
|
|
|
|
mounts := []specs.Mount{
|
|
{Type: "bind", Destination: "/dev", Source: "/dev", Options: []string{"rbind", "rshared", "rw"}},
|
|
{Type: "bind", Destination: constants.SystemExtensionsPath, Source: constants.SystemExtensionsPath, Options: []string{"rbind", "rshared", "ro"}},
|
|
}
|
|
|
|
// TODO(andrewrynhard): To handle cases when the newer version changes the
|
|
// platform name, this should be determined in the installer container.
|
|
config := constants.ConfigNone
|
|
if c := procfs.ProcCmdline().Get(constants.KernelParamConfig).First(); c != nil {
|
|
config = *c
|
|
}
|
|
|
|
upgrade := strconv.FormatBool(options.Upgrade)
|
|
force := strconv.FormatBool(options.Force)
|
|
zero := strconv.FormatBool(options.Zero)
|
|
|
|
args := []string{
|
|
"/bin/installer",
|
|
"install",
|
|
"--disk=" + disk,
|
|
"--platform=" + platform,
|
|
"--config=" + config,
|
|
"--upgrade=" + upgrade,
|
|
"--force=" + force,
|
|
"--zero=" + zero,
|
|
}
|
|
|
|
if c := procfs.ProcCmdline().Get(constants.KernelParamBoard).First(); c != nil {
|
|
args = append(args, "--board="+*c)
|
|
}
|
|
|
|
for _, arg := range options.ExtraKernelArgs {
|
|
args = append(args, "--extra-kernel-arg", arg)
|
|
}
|
|
|
|
if c := procfs.ProcCmdline().Get(constants.KernelParamSideroLink).First(); c != nil {
|
|
args = append(args, "--extra-kernel-arg", fmt.Sprintf("%s=%s", constants.KernelParamSideroLink, *c))
|
|
}
|
|
|
|
if c := procfs.ProcCmdline().Get(constants.KernelParamEventsSink).First(); c != nil {
|
|
args = append(args, "--extra-kernel-arg", fmt.Sprintf("%s=%s", constants.KernelParamEventsSink, *c))
|
|
}
|
|
|
|
if c := procfs.ProcCmdline().Get(constants.KernelParamLoggingKernel).First(); c != nil {
|
|
args = append(args, "--extra-kernel-arg", fmt.Sprintf("%s=%s", constants.KernelParamLoggingKernel, *c))
|
|
}
|
|
|
|
if c := procfs.ProcCmdline().Get(constants.KernelParamEquinixMetalEvents).First(); c != nil {
|
|
args = append(args, "--extra-kernel-arg", fmt.Sprintf("%s=%s", constants.KernelParamEquinixMetalEvents, *c))
|
|
}
|
|
|
|
specOpts := []oci.SpecOpts{
|
|
oci.WithImageConfig(img),
|
|
oci.WithProcessArgs(args...),
|
|
oci.WithHostNamespace(specs.NetworkNamespace),
|
|
oci.WithHostNamespace(specs.PIDNamespace),
|
|
oci.WithMounts(mounts),
|
|
oci.WithHostHostsFile,
|
|
oci.WithHostResolvconf,
|
|
oci.WithParentCgroupDevices,
|
|
oci.WithCapabilities(capability.AllGrantableCapabilities()),
|
|
oci.WithMaskedPaths(nil),
|
|
oci.WithReadonlyPaths(nil),
|
|
oci.WithWriteableSysfs,
|
|
oci.WithWriteableCgroupfs,
|
|
oci.WithSelinuxLabel(""),
|
|
oci.WithApparmorProfile(""),
|
|
oci.WithSeccompUnconfined,
|
|
oci.WithAllDevicesAllowed,
|
|
}
|
|
|
|
containerOpts := []containerd.NewContainerOpts{
|
|
containerd.WithImage(img),
|
|
containerd.WithNewSnapshot(containerID, img),
|
|
containerd.WithNewSpec(specOpts...),
|
|
}
|
|
|
|
container, err := client.NewContainer(ctx, containerID, containerOpts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer container.Delete(ctx, containerd.WithSnapshotCleanup) //nolint:errcheck
|
|
|
|
f, err := os.OpenFile("/dev/kmsg", os.O_RDWR|unix.O_CLOEXEC|unix.O_NONBLOCK|unix.O_NOCTTY, 0o666)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open /dev/kmsg: %w", err)
|
|
}
|
|
//nolint:errcheck
|
|
defer f.Close()
|
|
|
|
w := &kmsg.Writer{KmsgWriter: f}
|
|
|
|
var r interface {
|
|
io.Reader
|
|
WaitAndClose(context.Context, containerd.Task)
|
|
}
|
|
|
|
if cfg != nil {
|
|
var configBytes []byte
|
|
|
|
configBytes, err = cfg.Bytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
r = &containerdrunner.StdinCloser{
|
|
Stdin: bytes.NewReader(configBytes),
|
|
Closer: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
creator := cio.NewCreator(cio.WithStreams(r, w, w))
|
|
|
|
t, err := container.NewTask(ctx, creator)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if r != nil {
|
|
go r.WaitAndClose(ctx, t)
|
|
}
|
|
|
|
defer t.Delete(ctx) //nolint:errcheck
|
|
|
|
if err = t.Start(ctx); err != nil {
|
|
return fmt.Errorf("failed to start %q task: %w", "upgrade", err)
|
|
}
|
|
|
|
statusC, err := t.Wait(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed waiting for %q task: %w", "upgrade", err)
|
|
}
|
|
|
|
status := <-statusC
|
|
|
|
code := status.ExitCode()
|
|
if code != 0 {
|
|
return fmt.Errorf("task %q failed: exit code %d", "upgrade", code)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// OptionsFromUpgradeRequest builds installer options from upgrade request.
|
|
func OptionsFromUpgradeRequest(r runtime.Runtime, in *machineapi.UpgradeRequest) []Option {
|
|
opts := []Option{
|
|
WithPull(false),
|
|
WithUpgrade(true),
|
|
WithForce(!in.GetPreserve()),
|
|
}
|
|
|
|
if r.Config() != nil {
|
|
opts = append(opts, WithExtraKernelArgs(r.Config().Machine().Install().ExtraKernelArgs()))
|
|
}
|
|
|
|
return opts
|
|
}
|