talos/internal/pkg/install/install.go
Andrew Rynhard d34e9f0984 fix: pass dev path to mkfs
This fixes a bug caused by a missing device argument to `mkfs.xfs`.
Without a device, `mkfs.xfs` will error out. Additionally, this ensures
that the installer container is started with the `kmsg` writer that
ensures logs are formatted correctly for `/dev/kmsg`. Without this we
lose a lot of the logs output by the container, one of them being any
error from `mkfs.xfs`

Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
2020-04-21 16:30:35 -07:00

149 lines
3.9 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 (
"context"
"fmt"
"log"
"os"
"strconv"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cio"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/oci"
"github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
"github.com/talos-systems/go-procfs/procfs"
"github.com/talos-systems/talos/internal/pkg/containers/image"
"github.com/talos-systems/talos/internal/pkg/kmsg"
"github.com/talos-systems/talos/internal/pkg/runtime"
"github.com/talos-systems/talos/pkg/constants"
)
// RunInstallerContainer performs an installation via the installer container.
//nolint: gocyclo
func RunInstallerContainer(r runtime.Runtime, opts ...Option) error {
options := DefaultInstallOptions()
for _, opt := range opts {
if err := opt(&options); err != nil {
return err
}
}
ctx := namespaces.WithNamespace(context.Background(), constants.SystemContainerdNamespace)
client, err := containerd.New(constants.SystemContainerdAddress)
if err != nil {
return err
}
log.Printf("pulling installer container image: %q", r.Config().Machine().Install().Image())
var img containerd.Image
if options.ImagePull {
img, err = image.Pull(ctx, r.Config().Machine().Registries(), client, r.Config().Machine().Install().Image())
if err != nil {
return err
}
} else {
img, err = client.GetImage(ctx, r.Config().Machine().Install().Image())
if err != nil {
return err
}
}
mounts := []specs.Mount{
{Type: "bind", Destination: "/dev", Source: "/dev", Options: []string{"rbind", "rshared", "rw"}},
}
// TODO(andrewrynhard): To handle cases when the newer version changes the
// platform name, this should be determined in the installer container.
var config *string
if config = procfs.ProcCmdline().Get(constants.KernelParamConfig).First(); config == nil {
return fmt.Errorf("no config option was found")
}
upgrade := "false"
if r.Sequence() == runtime.Upgrade {
upgrade = "true"
}
args := []string{
"/bin/installer",
"install",
"--disk=" + r.Config().Machine().Install().Disk(),
"--platform=" + r.Platform().Name(),
"--config=" + *config,
"--upgrade=" + upgrade,
"--force=" + strconv.FormatBool(!options.Preserve),
}
for _, arg := range r.Config().Machine().Install().ExtraKernelArgs() {
args = append(args, []string{"--extra-kernel-arg", arg}...)
}
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.WithPrivileged,
}
containerOpts := []containerd.NewContainerOpts{
containerd.WithImage(img),
containerd.WithNewSnapshot("upgrade", img),
containerd.WithNewSpec(specOpts...),
}
container, err := client.NewContainer(ctx, "upgrade", containerOpts...)
if err != nil {
return err
}
f, err := os.OpenFile("/dev/kmsg", os.O_RDWR|unix.O_CLOEXEC|unix.O_NONBLOCK|unix.O_NOCTTY, 0666)
if err != nil {
return fmt.Errorf("failed to open /dev/kmsg: %w", err)
}
// nolint: errcheck
defer f.Close()
w := &kmsg.Writer{KmsgWriter: f}
creator := cio.NewCreator(cio.WithStreams(nil, w, w))
t, err := container.NewTask(ctx, creator)
if err != nil {
return err
}
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
}