mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-19 05:31:14 +02:00
refactor: improve installation reliability
This change aims to make installations more unified and reliable. It introduces the concept of a mountpoint manager that is capable of mounting, unmounting, and moving a set of mountpoints in the correct order. Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
This commit is contained in:
parent
9c63f4ed0a
commit
ca35b85300
@ -12,7 +12,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
// "github.com/talos-systems/talos/cmd/osctl/internal/userdata"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/internal/pkg/install"
|
||||
"github.com/talos-systems/talos/internal/pkg/installer"
|
||||
"github.com/talos-systems/talos/internal/pkg/kernel"
|
||||
"github.com/talos-systems/talos/internal/pkg/version"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
@ -55,14 +55,6 @@ var installCmd = &cobra.Command{
|
||||
}
|
||||
}
|
||||
|
||||
if err = install.Prepare(data); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err = install.Mount(data); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cmdline := kernel.NewDefaultCmdline()
|
||||
cmdline.Append("initrd", filepath.Join("/", "default", "initramfs.xz"))
|
||||
cmdline.Append(constants.KernelParamPlatform, platform)
|
||||
@ -71,7 +63,8 @@ var installCmd = &cobra.Command{
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err = install.Install(cmdline.String(), data); err != nil {
|
||||
i := installer.NewInstaller(cmdline, data)
|
||||
if err = i.Install(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -1,199 +0,0 @@
|
||||
/* 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 mount
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
"golang.org/x/sys/unix"
|
||||
"gopkg.in/freddierice/go-losetup.v1"
|
||||
)
|
||||
|
||||
// Initializer represents the early boot initialization control.
|
||||
type Initializer struct {
|
||||
prefix string
|
||||
|
||||
special *mount.Points
|
||||
}
|
||||
|
||||
// NewInitializer initializes and returns an Initializer struct.
|
||||
func NewInitializer(prefix string) (initializer *Initializer, err error) {
|
||||
special := mount.NewMountPoints()
|
||||
special.Set("dev", mount.NewMountPoint("devtmpfs", "/dev", "devtmpfs", unix.MS_NOSUID, "mode=0755"))
|
||||
special.Set("proc", mount.NewMountPoint("proc", "/proc", "proc", unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV, ""))
|
||||
special.Set("sys", mount.NewMountPoint("sysfs", "/sys", "sysfs", 0, ""))
|
||||
special.Set("run", mount.NewMountPoint("tmpfs", "/run", "tmpfs", 0, ""))
|
||||
special.Set("tmp", mount.NewMountPoint("tmpfs", "/tmp", "tmpfs", 0, ""))
|
||||
|
||||
initializer = &Initializer{
|
||||
prefix: prefix,
|
||||
special: special,
|
||||
}
|
||||
|
||||
return initializer, nil
|
||||
}
|
||||
|
||||
// Special returns the special devices.
|
||||
func (i *Initializer) Special() *mount.Points {
|
||||
return i.special
|
||||
}
|
||||
|
||||
// InitSpecial initializes and mounts the special devices in the early boot
|
||||
// tasks.
|
||||
func (i *Initializer) InitSpecial() (err error) {
|
||||
iter := i.special.Iter()
|
||||
for iter.Next() {
|
||||
if err = mount.WithRetry(iter.Value()); err != nil {
|
||||
return errors.Errorf("error initializing special device at %s: %v", iter.Value().Target(), err)
|
||||
}
|
||||
}
|
||||
if iter.Err() != nil {
|
||||
return iter.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rootfs initializes and mounts the OS owned block devices in the early boot
|
||||
// tasks.
|
||||
func (i *Initializer) Rootfs() (err error) {
|
||||
var dev losetup.Device
|
||||
dev, err = losetup.Attach("/"+constants.RootfsAsset, 0, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m := mount.NewMountPoint(dev.Path(), "/", "squashfs", unix.MS_RDONLY, "")
|
||||
if err = mount.WithRetry(m, mount.WithPrefix(i.prefix), mount.WithReadOnly(true), mount.WithShared(true)); err != nil {
|
||||
return errors.Wrap(err, "failed to mount squashfs")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveSpecial moves the special device mount points to the new root.
|
||||
func (i *Initializer) MoveSpecial() (err error) {
|
||||
iter := i.special.Iter()
|
||||
for iter.Next() {
|
||||
mountpoint := mount.NewMountPoint(iter.Value().Target(), iter.Value().Target(), "", unix.MS_MOVE, "")
|
||||
if err = mount.WithRetry(mountpoint, mount.WithPrefix(i.prefix)); err != nil {
|
||||
return errors.Errorf("error moving mount point %s: %v", iter.Value().Target(), err)
|
||||
}
|
||||
}
|
||||
if iter.Err() != nil {
|
||||
return iter.Err()
|
||||
}
|
||||
|
||||
if err = mount.WithRetry(mount.NewMountPoint("tmpfs", "/dev/shm", "tmpfs", unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_RELATIME, ""), mount.WithPrefix(i.prefix)); err != nil {
|
||||
return errors.Errorf("error mounting mount point %s: %v", "/dev/shm", err)
|
||||
}
|
||||
|
||||
if err = mount.WithRetry(mount.NewMountPoint("devpts", "/dev/pts", "devpts", unix.MS_NOSUID|unix.MS_NOEXEC, "ptmxmode=000,mode=620,gid=5"), mount.WithPrefix(i.prefix)); err != nil {
|
||||
return errors.Errorf("error mounting mount point %s: %v", "/dev/pts", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Switch moves the root to a specified directory. See
|
||||
// https://github.com/karelzak/util-linux/blob/master/sys-utils/switch_root.c.
|
||||
// nolint: gocyclo
|
||||
func (i *Initializer) Switch() (err error) {
|
||||
if err = i.MoveSpecial(); err != nil {
|
||||
return errors.Wrap(err, "error moving special devices")
|
||||
}
|
||||
|
||||
if err = unix.Chdir(i.prefix); err != nil {
|
||||
return errors.Wrapf(err, "error changing working directory to %s", i.prefix)
|
||||
}
|
||||
|
||||
var old *os.File
|
||||
if old, err = os.Open("/"); err != nil {
|
||||
return errors.Wrap(err, "error opening /")
|
||||
}
|
||||
// nolint: errcheck
|
||||
defer old.Close()
|
||||
|
||||
if err = unix.Mount(i.prefix, "/", "", unix.MS_MOVE, ""); err != nil {
|
||||
return errors.Wrap(err, "error moving /")
|
||||
}
|
||||
|
||||
if err = unix.Chroot("."); err != nil {
|
||||
return errors.Wrap(err, "error chroot")
|
||||
}
|
||||
|
||||
if err = recursiveDelete(int(old.Fd())); err != nil {
|
||||
return errors.Wrap(err, "error deleting initramfs")
|
||||
}
|
||||
|
||||
// Note that /sbin/init is machined. We call it init since this is the
|
||||
// convention.
|
||||
if err = unix.Exec("/sbin/init", []string{"/sbin/init"}, []string{}); err != nil {
|
||||
return errors.Wrap(err, "error executing /sbin/init")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func recursiveDelete(fd int) error {
|
||||
parentDev, err := getDev(fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dir := os.NewFile(uintptr(fd), "__ignored__")
|
||||
// nolint: errcheck
|
||||
defer dir.Close()
|
||||
names, err := dir.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
if err := recusiveDeleteInner(fd, parentDev, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func recusiveDeleteInner(parentFd int, parentDev uint64, childName string) error {
|
||||
childFd, err := unix.Openat(parentFd, childName, unix.O_DIRECTORY|unix.O_NOFOLLOW, unix.O_RDWR)
|
||||
if err != nil {
|
||||
if err := unix.Unlinkat(parentFd, childName, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// nolint: errcheck
|
||||
defer unix.Close(childFd)
|
||||
|
||||
if childFdDev, err := getDev(childFd); err != nil {
|
||||
return err
|
||||
} else if childFdDev != parentDev {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := recursiveDelete(childFd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := unix.Unlinkat(parentFd, childName, unix.AT_REMOVEDIR); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDev(fd int) (dev uint64, err error) {
|
||||
var stat unix.Stat_t
|
||||
|
||||
if err := unix.Fstat(fd, &stat); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return stat.Dev, nil
|
||||
}
|
@ -6,22 +6,27 @@ package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/app/init/internal/mount"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/internal/pkg/kmsg"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount/manager"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount/manager/squashfs"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount/manager/virtual"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount/switchroot"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// nolint: gocyclo
|
||||
func initram() (err error) {
|
||||
var initializer *mount.Initializer
|
||||
if initializer, err = mount.NewInitializer(constants.NewRoot); err != nil {
|
||||
func run() (err error) {
|
||||
// Mount the virtual devices.
|
||||
mountpoints, err := virtual.MountPoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mount the special devices.
|
||||
if err = initializer.InitSpecial(); err != nil {
|
||||
virtual := manager.NewManager(mountpoints)
|
||||
if err = virtual.MountAll(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -31,23 +36,43 @@ func initram() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Perform the equivalent of switch_root.
|
||||
// Mount the rootfs.
|
||||
log.Println("mounting the rootfs")
|
||||
if err = initializer.Rootfs(); err != nil {
|
||||
mountpoints, err = squashfs.MountPoints(constants.NewRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
squashfs := manager.NewManager(mountpoints)
|
||||
if err = squashfs.MountAll(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Perform the equivalent of switch_root.
|
||||
// Switch into the new rootfs.
|
||||
log.Println("entering the rootfs")
|
||||
if err = initializer.Switch(); err != nil {
|
||||
if err = switchroot.Switch(constants.NewRoot, virtual); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func recovery() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("recovered from: %+v\n", r)
|
||||
for i := 10; i >= 0; i-- {
|
||||
log.Printf("rebooting in %d seconds\n", i)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
unix.Reboot(unix.LINUX_REBOOT_CMD_RESTART)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := initram(); err != nil {
|
||||
defer recovery()
|
||||
|
||||
if err := run(); err != nil {
|
||||
panic(errors.Wrap(err, "early boot failed"))
|
||||
}
|
||||
|
||||
|
@ -1,67 +0,0 @@
|
||||
/* 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 cgroups
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
memoryCgroup = "memory"
|
||||
memoryUseHierarchy = "memory.use_hierarchy"
|
||||
memoryUseHierarchyPermissions = os.FileMode(400)
|
||||
)
|
||||
|
||||
var (
|
||||
memoryUseHierarchyContents = []byte(strconv.Itoa(1))
|
||||
)
|
||||
|
||||
// Mount creates the cgroup mount points.
|
||||
func Mount() error {
|
||||
target := "/sys/fs/cgroup"
|
||||
if err := os.MkdirAll(target, os.ModeDir); err != nil {
|
||||
return errors.Errorf("failed to create %s: %+v", target, err)
|
||||
}
|
||||
if err := unix.Mount("tmpfs", target, "tmpfs", unix.MS_NOSUID|unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_RELATIME, "mode=755"); err != nil {
|
||||
return errors.Errorf("failed to mount %s: %+v", target, err)
|
||||
}
|
||||
|
||||
cgroups := []string{
|
||||
"blkio",
|
||||
"cpu",
|
||||
"cpuacct",
|
||||
"cpuset",
|
||||
"devices",
|
||||
"freezer",
|
||||
"hugetlb",
|
||||
"memory",
|
||||
"net_cls",
|
||||
"net_prio",
|
||||
"perf_event",
|
||||
"pids",
|
||||
}
|
||||
for _, c := range cgroups {
|
||||
p := path.Join("/sys/fs/cgroup", c)
|
||||
if err := os.MkdirAll(p, os.ModeDir); err != nil {
|
||||
return errors.Errorf("failed to create %s: %+v", p, err)
|
||||
}
|
||||
if err := unix.Mount(c, p, "cgroup", unix.MS_NOSUID|unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_RELATIME, c); err != nil {
|
||||
return errors.Errorf("failed to mount %s: %+v", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
// See https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt
|
||||
target = path.Join("/sys/fs/cgroup", memoryCgroup, memoryUseHierarchy)
|
||||
err := ioutil.WriteFile(target, memoryUseHierarchyContents, memoryUseHierarchyPermissions)
|
||||
|
||||
return err
|
||||
}
|
@ -1,198 +0,0 @@
|
||||
/* 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 mount
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/filesystem/xfs"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
||||
gptpartition "github.com/talos-systems/talos/internal/pkg/blockdevice/table/gpt/partition"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/util"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Initializer represents the early boot initialization control.
|
||||
type Initializer struct {
|
||||
prefix string
|
||||
|
||||
owned *mount.Points
|
||||
}
|
||||
|
||||
// NewInitializer initializes and returns an Initializer struct.
|
||||
func NewInitializer(prefix string) (initializer *Initializer, err error) {
|
||||
initializer = &Initializer{
|
||||
prefix: prefix,
|
||||
}
|
||||
|
||||
return initializer, nil
|
||||
}
|
||||
|
||||
// Owned returns the OS owned block devices.
|
||||
func (i *Initializer) Owned() *mount.Points {
|
||||
return i.owned
|
||||
}
|
||||
|
||||
// InitOwned initializes and mounts the OS owned block devices in the early boot
|
||||
// tasks.
|
||||
func (i *Initializer) InitOwned() (err error) {
|
||||
var owned *mount.Points
|
||||
if owned, err = mountpoints(); err != nil {
|
||||
return errors.Errorf("error initializing owned block devices: %v", err)
|
||||
}
|
||||
i.owned = owned
|
||||
if mountpoint, ok := i.owned.Get(constants.DataPartitionLabel); ok {
|
||||
if err = repair(mountpoint); err != nil {
|
||||
return errors.Errorf("error fixing data partition: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
iter := i.owned.Iter()
|
||||
for iter.Next() {
|
||||
if err = mount.WithRetry(iter.Value(), mount.WithPrefix(i.prefix)); err != nil {
|
||||
return errors.Errorf("error mounting partitions: %v", err)
|
||||
}
|
||||
}
|
||||
if iter.Err() != nil {
|
||||
return iter.Err()
|
||||
}
|
||||
|
||||
if mountpoint, ok := i.owned.Get(constants.DataPartitionLabel); ok {
|
||||
// NB: The XFS partition MUST be mounted, or this will fail.
|
||||
log.Printf("growing the %s partition", constants.DataPartitionLabel)
|
||||
if err = xfs.GrowFS(path.Join(i.prefix, mountpoint.Target())); err != nil {
|
||||
return errors.Errorf("error growing data partition file system: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtraDevices mounts the extra devices.
|
||||
func ExtraDevices(data *userdata.UserData) (err error) {
|
||||
if data.Install == nil || data.Install.ExtraDevices == nil {
|
||||
return nil
|
||||
}
|
||||
for _, extra := range data.Install.ExtraDevices {
|
||||
for i, part := range extra.Partitions {
|
||||
devname := fmt.Sprintf("%s%d", extra.Device, i+1)
|
||||
mountpoint := mount.NewMountPoint(devname, part.MountPoint, "xfs", unix.MS_NOATIME, "")
|
||||
if err = mount.WithRetry(mountpoint); err != nil {
|
||||
return errors.Errorf("failed to mount %s at %s: %v", devname, part.MountPoint, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MountOwned mounts the OS owned block devices.
|
||||
func (i *Initializer) MountOwned() (err error) {
|
||||
iter := i.owned.Iter()
|
||||
for iter.Next() {
|
||||
if err = mount.WithRetry(iter.Value(), mount.WithPrefix(i.prefix)); err != nil {
|
||||
return errors.Errorf("error mounting partitions: %v", err)
|
||||
}
|
||||
}
|
||||
if iter.Err() != nil {
|
||||
return iter.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmountOwned unmounts the OS owned block devices.
|
||||
func (i *Initializer) UnmountOwned() (err error) {
|
||||
iter := i.owned.IterRev()
|
||||
for iter.Next() {
|
||||
if err = mount.UnWithRetry(iter.Value(), mount.WithPrefix(i.prefix)); err != nil {
|
||||
return errors.Errorf("error mounting partitions: %v", err)
|
||||
}
|
||||
}
|
||||
if iter.Err() != nil {
|
||||
return iter.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// nolint: dupl
|
||||
func mountpoints() (mountpoints *mount.Points, err error) {
|
||||
mountpoints = mount.NewMountPoints()
|
||||
for _, name := range []string{constants.DataPartitionLabel, constants.BootPartitionLabel} {
|
||||
var target string
|
||||
switch name {
|
||||
case constants.DataPartitionLabel:
|
||||
target = constants.DataMountPoint
|
||||
case constants.BootPartitionLabel:
|
||||
target = constants.BootMountPoint
|
||||
}
|
||||
|
||||
var dev *probe.ProbedBlockDevice
|
||||
if dev, err = probe.GetDevWithFileSystemLabel(name); err != nil {
|
||||
if name == constants.BootPartitionLabel {
|
||||
// A bootloader is not always required.
|
||||
log.Println("WARNING: no ESP partition was found")
|
||||
continue
|
||||
}
|
||||
return nil, errors.Errorf("failed to find device with label %s: %v", name, err)
|
||||
}
|
||||
|
||||
mountpoint := mount.NewMountPoint(dev.Path, target, dev.SuperBlock.Type(), unix.MS_NOATIME, "")
|
||||
|
||||
mountpoints.Set(name, mountpoint)
|
||||
}
|
||||
|
||||
return mountpoints, nil
|
||||
}
|
||||
|
||||
func repair(mountpoint *mount.Point) (err error) {
|
||||
var devname string
|
||||
if devname, err = util.DevnameFromPartname(mountpoint.Source()); err != nil {
|
||||
return err
|
||||
}
|
||||
bd, err := blockdevice.Open("/dev/" + devname)
|
||||
if err != nil {
|
||||
return errors.Errorf("error opening block device %q: %v", devname, err)
|
||||
}
|
||||
// nolint: errcheck
|
||||
defer bd.Close()
|
||||
|
||||
pt, err := bd.PartitionTable(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := pt.Repair(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, partition := range pt.Partitions() {
|
||||
if partition.(*gptpartition.Partition).Name == constants.DataPartitionLabel {
|
||||
if err := pt.Resize(partition); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := pt.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rereading the partition table requires that all partitions be unmounted
|
||||
// or it will fail with EBUSY.
|
||||
if err := bd.RereadPartitionTable(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
/* 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 (
|
||||
"log"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/mount"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
// Install represents the Install task.
|
||||
type Install struct{}
|
||||
|
||||
// NewInstallTask initializes and returns an Install task.
|
||||
func NewInstallTask() phase.Task {
|
||||
return &Install{}
|
||||
}
|
||||
|
||||
// RuntimeFunc returns the runtime function.
|
||||
func (task *Install) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
||||
switch mode {
|
||||
case runtime.Standard:
|
||||
return task.runtime
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (task *Install) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||
// Perform any tasks required by a particular platform.
|
||||
log.Printf("performing platform specific tasks")
|
||||
if err = platform.Prepare(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var initializer *mount.Initializer
|
||||
if initializer, err = mount.NewInitializer(""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mount the owned partitions.
|
||||
log.Printf("mounting the owned partitions")
|
||||
if err = initializer.InitOwned(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Install handles additional system setup
|
||||
if err = platform.Install(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
33
internal/app/machined/internal/phase/platform/platform.go
Normal file
33
internal/app/machined/internal/phase/platform/platform.go
Normal file
@ -0,0 +1,33 @@
|
||||
/* 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 platform
|
||||
|
||||
import (
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
// Platform represents the Platform task.
|
||||
type Platform struct{}
|
||||
|
||||
// NewPlatformTask initializes and returns an Platform task.
|
||||
func NewPlatformTask() phase.Task {
|
||||
return &Platform{}
|
||||
}
|
||||
|
||||
// RuntimeFunc returns the runtime function.
|
||||
func (task *Platform) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
||||
return task.runtime
|
||||
}
|
||||
|
||||
func (task *Platform) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||
if err = platform.Initialize(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
* 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_test
|
||||
package platform_test
|
||||
|
||||
import "testing"
|
||||
|
@ -42,7 +42,7 @@ BUG_REPORT_URL="https://github.com/talos-systems/talos/issues"
|
||||
// Hosts renders a valid /etc/hosts file and writes it to disk.
|
||||
func Hosts() (err error) {
|
||||
var h *string
|
||||
if h = kernel.Cmdline().Get(constants.KernelParamHostname).First(); h != nil {
|
||||
if h = kernel.ProcCmdline().Get(constants.KernelParamHostname).First(); h != nil {
|
||||
if err = unix.Sethostname([]byte(*h)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -5,14 +5,32 @@
|
||||
package rootfs
|
||||
|
||||
import (
|
||||
// "github.com/pkg/errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/mount/cgroups"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount/manager"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount/manager/cgroups"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
const (
|
||||
memoryCgroup = "memory"
|
||||
memoryUseHierarchy = "memory.use_hierarchy"
|
||||
memoryUseHierarchyPermissions = os.FileMode(400)
|
||||
)
|
||||
|
||||
var (
|
||||
memoryUseHierarchyContents = []byte(strconv.Itoa(1))
|
||||
)
|
||||
|
||||
// MountCgroups represents the MountCgroups task.
|
||||
type MountCgroups struct{}
|
||||
|
||||
@ -32,8 +50,21 @@ func (task *MountCgroups) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
||||
}
|
||||
|
||||
func (task *MountCgroups) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||
if err = cgroups.Mount(); err != nil {
|
||||
return errors.Wrap(err, "error mounting cgroups")
|
||||
var mountpoints *mount.Points
|
||||
mountpoints, err = cgroups.MountPoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m := manager.NewManager(mountpoints)
|
||||
if err = m.MountAll(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// See https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt
|
||||
target := path.Join("/sys/fs/cgroup", memoryCgroup, memoryUseHierarchy)
|
||||
if err = ioutil.WriteFile(target, memoryUseHierarchyContents, memoryUseHierarchyPermissions); err != nil {
|
||||
return errors.Wrap(err, "failed to enable memory hierarchy support")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -0,0 +1,50 @@
|
||||
/* 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 rootfs
|
||||
|
||||
import (
|
||||
// "github.com/pkg/errors"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount/manager"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount/manager/virtual"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
// MountSubDevices represents the MountSubDevices task.
|
||||
type MountSubDevices struct{}
|
||||
|
||||
// NewMountSubDevicesTask initializes and returns an MountSubDevices task.
|
||||
func NewMountSubDevicesTask() phase.Task {
|
||||
return &MountSubDevices{}
|
||||
}
|
||||
|
||||
// RuntimeFunc returns the runtime function.
|
||||
func (task *MountSubDevices) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
||||
switch mode {
|
||||
case runtime.Standard:
|
||||
return task.runtime
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (task *MountSubDevices) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||
var mountpoints *mount.Points
|
||||
mountpoints, err = virtual.SubMountPoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m := manager.NewManager(mountpoints)
|
||||
if err = m.MountAll(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -8,7 +8,7 @@ import (
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||
"github.com/talos-systems/talos/internal/pkg/proc"
|
||||
"github.com/talos-systems/talos/internal/pkg/kernel/sysctl"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
@ -26,5 +26,5 @@ func (task *Sysctls) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
||||
}
|
||||
|
||||
func (task *Sysctls) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||
return proc.WriteSystemProperty(&proc.SystemProperty{Key: "net.ipv4.ip_forward", Value: "1"})
|
||||
return sysctl.WriteSystemProperty(&sysctl.SystemProperty{Key: "net.ipv4.ip_forward", Value: "1"})
|
||||
}
|
||||
|
@ -5,11 +5,15 @@
|
||||
package userdata
|
||||
|
||||
import (
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/mount"
|
||||
"fmt"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount/manager"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// ExtraDevices represents the ExtraDevices task.
|
||||
@ -26,8 +30,20 @@ func (task *ExtraDevices) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
||||
}
|
||||
|
||||
func (task *ExtraDevices) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||
// Mount the extra devices.
|
||||
if err = mount.ExtraDevices(data); err != nil {
|
||||
if data.Install == nil || data.Install.ExtraDevices == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mountpoints := mount.NewMountPoints()
|
||||
for _, extra := range data.Install.ExtraDevices {
|
||||
for i, part := range extra.Partitions {
|
||||
devname := fmt.Sprintf("%s%d", extra.Device, i+1)
|
||||
mountpoints.Set(devname, mount.NewMountPoint(devname, part.MountPoint, "xfs", unix.MS_NOATIME, ""))
|
||||
}
|
||||
}
|
||||
|
||||
extras := manager.NewManager(mountpoints)
|
||||
if err = extras.MountAll(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/internal/pkg/install"
|
||||
"github.com/talos-systems/talos/internal/pkg/installer"
|
||||
"github.com/talos-systems/talos/internal/pkg/kernel"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
|
||||
@ -37,7 +37,7 @@ func (b *BareMetal) Name() string {
|
||||
// UserData implements the platform.Platform interface.
|
||||
func (b *BareMetal) UserData() (data *userdata.UserData, err error) {
|
||||
var option *string
|
||||
if option = kernel.Cmdline().Get(constants.KernelParamUserData).First(); option == nil {
|
||||
if option = kernel.ProcCmdline().Get(constants.KernelParamUserData).First(); option == nil {
|
||||
return data, errors.Errorf("no user data option was found")
|
||||
}
|
||||
|
||||
@ -71,17 +71,12 @@ func (b *BareMetal) UserData() (data *userdata.UserData, err error) {
|
||||
return userdata.Download(*option)
|
||||
}
|
||||
|
||||
// Prepare implements the platform.Platform interface.
|
||||
func (b *BareMetal) Prepare(data *userdata.UserData) (err error) {
|
||||
return install.Prepare(data)
|
||||
}
|
||||
|
||||
// Install provides the functionality to install talos by downloading the
|
||||
// Initialize provides the functionality to install talos by downloading the
|
||||
// required artifacts and writing them to a target device.
|
||||
// nolint: dupl
|
||||
func (b *BareMetal) Install(data *userdata.UserData) (err error) {
|
||||
func (b *BareMetal) Initialize(data *userdata.UserData) (err error) {
|
||||
var endpoint *string
|
||||
if endpoint = kernel.Cmdline().Get(constants.KernelParamUserData).First(); endpoint == nil {
|
||||
if endpoint = kernel.ProcCmdline().Get(constants.KernelParamUserData).First(); endpoint == nil {
|
||||
return errors.Errorf("failed to find %s in kernel parameters", constants.KernelParamUserData)
|
||||
}
|
||||
cmdline := kernel.NewDefaultCmdline()
|
||||
@ -93,7 +88,8 @@ func (b *BareMetal) Install(data *userdata.UserData) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = install.Install(cmdline.String(), data); err != nil {
|
||||
i := installer.NewInstaller(cmdline, data)
|
||||
if err = i.Install(); err != nil {
|
||||
return errors.Wrap(err, "failed to install")
|
||||
}
|
||||
|
||||
|
@ -121,9 +121,9 @@ func (a *AWS) UserData() (*userdata.UserData, error) {
|
||||
return userdata.Download(AWSUserDataEndpoint)
|
||||
}
|
||||
|
||||
// Prepare implements the platform.Platform interface and handles initial host preparation.
|
||||
func (a *AWS) Prepare(data *userdata.UserData) (err error) {
|
||||
return nil
|
||||
// Initialize implements the platform.Platform interface and handles additional system setup.
|
||||
func (a *AWS) Initialize(data *userdata.UserData) (err error) {
|
||||
return hostname()
|
||||
}
|
||||
|
||||
func hostname() (err error) {
|
||||
@ -149,8 +149,3 @@ func hostname() (err error) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Install implements the platform.Platform interface and handles additional system setup.
|
||||
func (a *AWS) Install(data *userdata.UserData) (err error) {
|
||||
return hostname()
|
||||
}
|
||||
|
@ -37,11 +37,6 @@ func (a *Azure) UserData() (*userdata.UserData, error) {
|
||||
return userdata.Download(AzureUserDataEndpoint, userdata.WithHeaders(map[string]string{"Metadata": "true"}), userdata.WithFormat("base64"))
|
||||
}
|
||||
|
||||
// Prepare implements the platform.Platform interface and handles initial host preparation.
|
||||
func (a *Azure) Prepare(data *userdata.UserData) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func hostname() (err error) {
|
||||
|
||||
// TODO get this sorted; assuming we need to set appropriate headers
|
||||
@ -72,7 +67,7 @@ func hostname() (err error) {
|
||||
*/
|
||||
}
|
||||
|
||||
// Install implements the platform.Platform interface and handles additional system setup.
|
||||
func (a *Azure) Install(data *userdata.UserData) (err error) {
|
||||
// Initialize implements the platform.Platform interface and handles additional system setup.
|
||||
func (a *Azure) Initialize(data *userdata.UserData) (err error) {
|
||||
return hostname()
|
||||
}
|
||||
|
@ -46,12 +46,7 @@ func (gc *GoogleCloud) UserData() (data *userdata.UserData, err error) {
|
||||
return ud, nil
|
||||
}
|
||||
|
||||
// Prepare implements the platform.Platform interface and handles initial host preparation.
|
||||
func (gc *GoogleCloud) Prepare(data *userdata.UserData) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Install implements the platform.Platform interface and handles additional system setup.
|
||||
func (gc *GoogleCloud) Install(data *userdata.UserData) (err error) {
|
||||
// Initialize implements the platform.Platform interface and handles additional system setup.
|
||||
func (gc *GoogleCloud) Initialize(data *userdata.UserData) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/internal/pkg/install"
|
||||
"github.com/talos-systems/talos/internal/pkg/installer"
|
||||
"github.com/talos-systems/talos/internal/pkg/kernel"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
@ -32,19 +32,14 @@ func (p *Packet) UserData() (data *userdata.UserData, err error) {
|
||||
return userdata.Download(PacketUserDataEndpoint)
|
||||
}
|
||||
|
||||
// Prepare implements the platform.Platform interface.
|
||||
func (p *Packet) Prepare(data *userdata.UserData) (err error) {
|
||||
return install.Prepare(data)
|
||||
}
|
||||
|
||||
// Install provides the functionality to install talos by downloading the
|
||||
// required artifacts and writing them to a target device.
|
||||
// Initialize implements the platform.Platform interface.
|
||||
// nolint: dupl
|
||||
func (p *Packet) Install(data *userdata.UserData) (err error) {
|
||||
func (p *Packet) Initialize(data *userdata.UserData) (err error) {
|
||||
var endpoint *string
|
||||
if endpoint = kernel.Cmdline().Get(constants.KernelParamUserData).First(); endpoint == nil {
|
||||
if endpoint = kernel.ProcCmdline().Get(constants.KernelParamUserData).First(); endpoint == nil {
|
||||
return errors.Errorf("failed to find %s in kernel parameters", constants.KernelParamUserData)
|
||||
}
|
||||
|
||||
cmdline := kernel.NewDefaultCmdline()
|
||||
cmdline.Append("initrd", filepath.Join("/", "default", "initramfs.xz"))
|
||||
cmdline.Append(constants.KernelParamPlatform, "packet")
|
||||
@ -54,7 +49,8 @@ func (p *Packet) Install(data *userdata.UserData) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = install.Install(cmdline.String(), data); err != nil {
|
||||
i := installer.NewInstaller(cmdline, data)
|
||||
if err = i.Install(); err != nil {
|
||||
return errors.Wrap(err, "failed to install")
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ func (vmw *VMware) Name() string {
|
||||
// UserData implements the platform.Platform interface.
|
||||
func (vmw *VMware) UserData() (data *userdata.UserData, err error) {
|
||||
var option *string
|
||||
if option = kernel.Cmdline().Get(constants.KernelParamUserData).First(); option == nil {
|
||||
if option = kernel.ProcCmdline().Get(constants.KernelParamUserData).First(); option == nil {
|
||||
return data, fmt.Errorf("no user data option was found")
|
||||
}
|
||||
|
||||
@ -65,12 +65,7 @@ func (vmw *VMware) UserData() (data *userdata.UserData, err error) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Prepare implements the platform.Platform interface and handles initial host preparation.
|
||||
func (vmw *VMware) Prepare(data *userdata.UserData) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Install implements the platform.Platform interface and handles additional system setup.
|
||||
func (vmw *VMware) Install(data *userdata.UserData) (err error) {
|
||||
// Initialize implements the platform.Platform interface and handles additional system setup.
|
||||
func (vmw *VMware) Initialize(data *userdata.UserData) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
@ -40,12 +40,7 @@ func (c *Container) UserData() (data *userdata.UserData, err error) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Prepare implements the platform.Platform interface.
|
||||
func (c *Container) Prepare(data *userdata.UserData) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Install implements the platform.Platform interface.
|
||||
func (c *Container) Install(data *userdata.UserData) error {
|
||||
// Initialize implements the platform.Platform interface.
|
||||
func (c *Container) Initialize(data *userdata.UserData) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -14,9 +14,8 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/internal/pkg/install"
|
||||
"github.com/talos-systems/talos/internal/pkg/installer"
|
||||
"github.com/talos-systems/talos/internal/pkg/kernel"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
"github.com/talos-systems/talos/pkg/crypto/x509"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"golang.org/x/sys/unix"
|
||||
@ -62,21 +61,21 @@ func (i *ISO) UserData() (data *userdata.UserData, err error) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Prepare implements the platform.Platform interface.
|
||||
func (i *ISO) Prepare(data *userdata.UserData) (err error) {
|
||||
// Initialize implements the platform.Platform interface.
|
||||
func (i *ISO) Initialize(data *userdata.UserData) (err error) {
|
||||
var dev *probe.ProbedBlockDevice
|
||||
dev, err = probe.GetDevWithFileSystemLabel(constants.ISOFilesystemLabel)
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to find %s iso: %v", constants.ISOFilesystemLabel, err)
|
||||
}
|
||||
|
||||
mountpoint := mount.NewMountPoint(dev.Path, "/tmp", dev.SuperBlock.Type(), unix.MS_RDONLY, "")
|
||||
if err = mount.WithRetry(mountpoint); err != nil {
|
||||
if err = unix.Mount(dev.Path, "/tmp", dev.SuperBlock.Type(), unix.MS_RDONLY, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range []string{"/tmp/usr/install/vmlinuz", "/tmp/usr/install/initramfs.xz"} {
|
||||
source, err := ioutil.ReadFile(f)
|
||||
var source []byte
|
||||
source, err = ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -85,11 +84,6 @@ func (i *ISO) Prepare(data *userdata.UserData) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
return install.Prepare(data)
|
||||
}
|
||||
|
||||
// Install implements the platform.Platform interface.
|
||||
func (i *ISO) Install(data *userdata.UserData) error {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("Talos configuration URL: ")
|
||||
endpoint, err := reader.ReadString('\n')
|
||||
@ -102,7 +96,8 @@ func (i *ISO) Install(data *userdata.UserData) error {
|
||||
cmdline.Append(constants.KernelParamPlatform, "bare-metal")
|
||||
cmdline.Append(constants.KernelParamUserData, endpoint)
|
||||
|
||||
if err = install.Install(cmdline.String(), data); err != nil {
|
||||
inst := installer.NewInstaller(cmdline, data)
|
||||
if err = inst.Install(); err != nil {
|
||||
return errors.Wrap(err, "failed to install")
|
||||
}
|
||||
|
||||
|
@ -25,8 +25,7 @@ import (
|
||||
type Platform interface {
|
||||
Name() string
|
||||
UserData() (*userdata.UserData, error)
|
||||
Prepare(*userdata.UserData) error
|
||||
Install(*userdata.UserData) error
|
||||
Initialize(*userdata.UserData) error
|
||||
}
|
||||
|
||||
// NewPlatform is a helper func for discovering the current platform.
|
||||
@ -34,7 +33,7 @@ type Platform interface {
|
||||
// nolint: gocyclo
|
||||
func NewPlatform() (p Platform, err error) {
|
||||
var platform string
|
||||
if p := kernel.Cmdline().Get(constants.KernelParamPlatform).First(); p != nil {
|
||||
if p := kernel.ProcCmdline().Get(constants.KernelParamPlatform).First(); p != nil {
|
||||
platform = *p
|
||||
}
|
||||
|
||||
|
@ -11,9 +11,9 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||
apiptask "github.com/talos-systems/talos/internal/app/machined/internal/phase/api"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase/install"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase/api"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase/network"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase/platform"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase/rootfs"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase/security"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase/services"
|
||||
@ -45,6 +45,7 @@ func run() (err error) {
|
||||
security.NewSecurityTask(),
|
||||
rootfs.NewSystemDirectoryTask(),
|
||||
rootfs.NewMountCgroupsTask(),
|
||||
rootfs.NewMountSubDevicesTask(),
|
||||
sysctls.NewSysctlsTask(),
|
||||
),
|
||||
phase.NewPhase(
|
||||
@ -68,11 +69,11 @@ func run() (err error) {
|
||||
userdatatask.NewExtraFilesTask(),
|
||||
),
|
||||
phase.NewPhase(
|
||||
"installation",
|
||||
install.NewInstallTask(),
|
||||
"platform tasks",
|
||||
platform.NewPlatformTask(),
|
||||
),
|
||||
phase.NewPhase(
|
||||
"overlay",
|
||||
"overlay mounts",
|
||||
rootfs.NewMountOverlayTask(),
|
||||
),
|
||||
phase.NewPhase(
|
||||
@ -85,7 +86,7 @@ func run() (err error) {
|
||||
),
|
||||
phase.NewPhase(
|
||||
"service setup",
|
||||
apiptask.NewAPITask(),
|
||||
api.NewAPITask(),
|
||||
services.NewServicesTask(),
|
||||
),
|
||||
)
|
||||
|
@ -1,52 +0,0 @@
|
||||
/* 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 (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
)
|
||||
|
||||
// Exists checks if Talos has already been installed to a block device.
|
||||
// It works by searching for a filesystem with a well known label, and then,
|
||||
// if the filesystem exists, checking for the existence of a file.
|
||||
func Exists(devpath string) (bool, error) {
|
||||
var (
|
||||
err error
|
||||
dev *probe.ProbedBlockDevice
|
||||
)
|
||||
|
||||
if dev, err = probe.DevForFileSystemLabel(devpath, constants.BootPartitionLabel); err == nil {
|
||||
// nolint: errcheck
|
||||
defer dev.Close()
|
||||
if dev.SuperBlock != nil {
|
||||
mountpoint := mount.NewMountPoint(dev.Path, "/tmp", dev.SuperBlock.Type(), 0, "")
|
||||
if err = mount.WithRetry(mountpoint); err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
if err = mount.UnWithRetry(mountpoint); err != nil {
|
||||
log.Printf("WARNING: failed to unmount %s from /tmp", dev.Path)
|
||||
}
|
||||
}()
|
||||
_, err = os.Stat(filepath.Join("tmp", "installed"))
|
||||
switch {
|
||||
case err == nil:
|
||||
return true, nil
|
||||
case os.IsNotExist(err):
|
||||
return false, nil
|
||||
default:
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
@ -1,205 +0,0 @@
|
||||
/* 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 (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/bootloader/syslinux"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
// Install fetches the necessary data locations and copies or extracts
|
||||
// to the target locations
|
||||
// nolint: gocyclo
|
||||
func Install(args string, data *userdata.UserData) (err error) {
|
||||
if data.Install == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var exists bool
|
||||
if exists, err = Exists(data.Install.Boot.InstallDevice.Device); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exists {
|
||||
log.Println("found existing installation, skipping install step")
|
||||
return nil
|
||||
}
|
||||
|
||||
manifest := NewManifest(data)
|
||||
for _, targets := range manifest.Targets {
|
||||
for _, target := range targets {
|
||||
// Handle any pre-setup work that's required
|
||||
switch target.Label {
|
||||
case constants.BootPartitionLabel:
|
||||
// Install/Update the bootloader.
|
||||
if err = syslinux.Prepare(target.Device); err != nil {
|
||||
return err
|
||||
}
|
||||
case constants.DataPartitionLabel:
|
||||
// Do nothing
|
||||
continue
|
||||
}
|
||||
|
||||
// Handles the download and extraction of assets
|
||||
if err = target.Install(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if data.Install.Boot == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
syslinuxcfg := &syslinux.Cfg{
|
||||
Default: "default",
|
||||
Labels: []*syslinux.Label{
|
||||
{
|
||||
Root: "default",
|
||||
Kernel: filepath.Join("/", "default", filepath.Base(data.Install.Boot.Kernel)),
|
||||
Initrd: filepath.Join("/", "default", filepath.Base(data.Install.Boot.Initramfs)),
|
||||
Append: args,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err = syslinux.Install(filepath.Join(constants.NewRoot, constants.BootMountPoint), syslinuxcfg); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ioutil.WriteFile(filepath.Join(constants.NewRoot, constants.BootMountPoint, "installed"), []byte{}, 0400); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Simple extract function
|
||||
// nolint: gocyclo, dupl
|
||||
func untar(tarball *os.File, dst string) error {
|
||||
|
||||
var input io.Reader
|
||||
var err error
|
||||
|
||||
if strings.HasSuffix(tarball.Name(), ".tar.gz") {
|
||||
input, err = gzip.NewReader(tarball)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
defer input.(*gzip.Reader).Close()
|
||||
} else {
|
||||
input = tarball
|
||||
}
|
||||
|
||||
tr := tar.NewReader(input)
|
||||
|
||||
for {
|
||||
var header *tar.Header
|
||||
|
||||
header, err = tr.Next()
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
return err
|
||||
case err != nil:
|
||||
return err
|
||||
case header == nil:
|
||||
continue
|
||||
}
|
||||
|
||||
// the target location where the dir/file should be created
|
||||
target := filepath.Join(dst, header.Name)
|
||||
|
||||
// May need to add in support for these
|
||||
/*
|
||||
// Type '1' to '6' are header-only flags and may not have a data body.
|
||||
TypeLink = '1' // Hard link
|
||||
TypeSymlink = '2' // Symbolic link
|
||||
TypeChar = '3' // Character device node
|
||||
TypeBlock = '4' // Block device node
|
||||
TypeDir = '5' // Directory
|
||||
TypeFifo = '6' // FIFO node
|
||||
*/
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err = os.MkdirAll(target, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeReg:
|
||||
var downloadedFileput *os.File
|
||||
|
||||
downloadedFileput, err = os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = io.Copy(downloadedFileput, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = downloadedFileput.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeSymlink:
|
||||
dest := filepath.Join(dst, header.Name)
|
||||
source := header.Linkname
|
||||
if err := os.Symlink(source, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func download(artifact, dest string) (*os.File, error) {
|
||||
if err := os.MkdirAll(filepath.Dir(dest), 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
downloadedFile, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the data
|
||||
resp, err := http.Get(artifact)
|
||||
if err != nil {
|
||||
return downloadedFile, err
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
// nolint: errcheck
|
||||
downloadedFile.Close()
|
||||
return nil, errors.Errorf("failed to download %s, got %d", artifact, resp.StatusCode)
|
||||
}
|
||||
|
||||
// Write the body to file
|
||||
_, err = io.Copy(downloadedFile, resp.Body)
|
||||
if err != nil {
|
||||
return downloadedFile, err
|
||||
}
|
||||
|
||||
// Reset downloadedFile file position to 0 so we can immediately read from it
|
||||
_, err = downloadedFile.Seek(0, 0)
|
||||
|
||||
// TODO add support for checksum validation of downloaded file
|
||||
|
||||
return downloadedFile, err
|
||||
}
|
252
internal/pkg/installer/installer.go
Normal file
252
internal/pkg/installer/installer.go
Normal file
@ -0,0 +1,252 @@
|
||||
/* 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 installer
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/internal/pkg/installer/bootloader/syslinux"
|
||||
"github.com/talos-systems/talos/internal/pkg/installer/manifest"
|
||||
"github.com/talos-systems/talos/internal/pkg/kernel"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount/manager"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount/manager/owned"
|
||||
"github.com/talos-systems/talos/internal/pkg/version"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultSizeBootDevice is the default size of the boot partition.
|
||||
// TODO(andrewrynhard): We should inspect the sizes of the artifacts and dynamically set the boot partition's size.
|
||||
DefaultSizeBootDevice = 512 * 1000 * 1000
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultURLBase is the base URL for all default artifacts.
|
||||
// TODO(andrewrynhard): We need to setup infrastructure for publishing artifacts and not depend on GitHub.
|
||||
DefaultURLBase = "https://github.com/talos-systems/talos/releases/download/" + version.Tag
|
||||
|
||||
// DefaultKernelURL is the URL to the kernel.
|
||||
DefaultKernelURL = DefaultURLBase + "/vmlinuz"
|
||||
|
||||
// DefaultInitramfsURL is the URL to the initramfs.
|
||||
DefaultInitramfsURL = DefaultURLBase + "/initramfs.xz"
|
||||
)
|
||||
|
||||
// Installer represents the installer logic. It serves as the entrypoint to all
|
||||
// installation methods.
|
||||
type Installer struct {
|
||||
cmdline *kernel.Cmdline
|
||||
data *userdata.UserData
|
||||
manifest *manifest.Manifest
|
||||
}
|
||||
|
||||
// NewInstaller initializes and returns an Installer.
|
||||
func NewInstaller(cmdline *kernel.Cmdline, data *userdata.UserData) *Installer {
|
||||
i := &Installer{
|
||||
cmdline: cmdline,
|
||||
data: data,
|
||||
}
|
||||
|
||||
i.manifest = manifest.NewManifest(data)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// Install fetches the necessary data locations and copies or extracts
|
||||
// to the target locations.
|
||||
// nolint: gocyclo
|
||||
func (i *Installer) Install() (err error) {
|
||||
if i.data.Install == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.data.Install.Boot != nil {
|
||||
var ok bool
|
||||
if ok, err = exists(i.data.Install.Boot.InstallDevice.Device); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ok {
|
||||
log.Println("found existing installation")
|
||||
var mountpoints *mount.Points
|
||||
mountpoints, err = owned.MountPointsFromLabels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m := manager.NewManager(mountpoints)
|
||||
if err = m.MountAll(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = VerifyBootDevice(i.data); err != nil {
|
||||
return errors.Wrap(err, "failed to prepare boot device")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the target device(s) can satisify the requested options.
|
||||
|
||||
if err = VerifyDataDevice(i.data); err != nil {
|
||||
return errors.Wrap(err, "failed to prepare data device")
|
||||
}
|
||||
|
||||
if i.data.Install.Wipe {
|
||||
if err = wipe(i.manifest); err != nil {
|
||||
return errors.Wrap(err, "failed to wipe device(s)")
|
||||
}
|
||||
}
|
||||
|
||||
// Partition and format the block device(s).
|
||||
|
||||
if err = i.manifest.ExecuteManifest(i.data, i.manifest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mount the partitions.
|
||||
|
||||
var mountpoints *mount.Points
|
||||
if i.data.Install.Boot != nil {
|
||||
mountpoints, err = owned.MountPointsForDevice(i.data.Install.Boot.InstallDevice.Device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
mountpoints, err = owned.MountPointsFromLabels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
m := manager.NewManager(mountpoints)
|
||||
if err = m.MountAll(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Install the assets.
|
||||
|
||||
for _, targets := range i.manifest.Targets {
|
||||
for _, target := range targets {
|
||||
switch target.Label {
|
||||
case constants.BootPartitionLabel:
|
||||
// Install the bootloader.
|
||||
if err = syslinux.Prepare(target.Device); err != nil {
|
||||
return err
|
||||
}
|
||||
case constants.DataPartitionLabel:
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle the download and extraction of assets.
|
||||
if err = target.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Install the bootloader.
|
||||
|
||||
if i.data.Install.Boot == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
syslinuxcfg := &syslinux.Cfg{
|
||||
Default: "default",
|
||||
Labels: []*syslinux.Label{
|
||||
{
|
||||
Root: "default",
|
||||
Kernel: filepath.Join("/", "default", filepath.Base(i.data.Install.Boot.Kernel)),
|
||||
Initrd: filepath.Join("/", "default", filepath.Base(i.data.Install.Boot.Initramfs)),
|
||||
Append: i.cmdline.String(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err = syslinux.Install(filepath.Join(constants.BootMountPoint), syslinuxcfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(filepath.Join(constants.BootMountPoint, "installed"), []byte{}, 0400); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func wipe(manifest *manifest.Manifest) (err error) {
|
||||
var zero *os.File
|
||||
if zero, err = os.Open("/dev/zero"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for dev := range manifest.Targets {
|
||||
var f *os.File
|
||||
if f, err = os.OpenFile(dev, os.O_RDWR, os.ModeDevice); err != nil {
|
||||
return err
|
||||
}
|
||||
var size uint64
|
||||
if _, _, ret := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); ret != 0 {
|
||||
return errors.Errorf("failed to got block device size: %v", ret)
|
||||
}
|
||||
if _, err = io.CopyN(f, zero, int64(size)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = zero.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func exists(devpath string) (bool, error) {
|
||||
var (
|
||||
err error
|
||||
dev *probe.ProbedBlockDevice
|
||||
)
|
||||
|
||||
if dev, err = probe.DevForFileSystemLabel(devpath, constants.BootPartitionLabel); err == nil {
|
||||
// nolint: errcheck
|
||||
defer dev.Close()
|
||||
if dev.SuperBlock != nil {
|
||||
mountpoint := mount.NewMountPoint(dev.Path, "/tmp", dev.SuperBlock.Type(), 0, "")
|
||||
if err = mountpoint.Mount(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
if err = mountpoint.Unmount(); err != nil {
|
||||
log.Printf("WARNING: failed to unmount %s from /tmp", dev.Path)
|
||||
}
|
||||
}()
|
||||
_, err = os.Stat(filepath.Join("tmp", "installed"))
|
||||
switch {
|
||||
case err == nil:
|
||||
return true, nil
|
||||
case os.IsNotExist(err):
|
||||
return false, nil
|
||||
default:
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
107
internal/pkg/installer/installer_test.go
Normal file
107
internal/pkg/installer/installer_test.go
Normal file
@ -0,0 +1,107 @@
|
||||
/* 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 installer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type validateSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestValidateSuite(t *testing.T) {
|
||||
suite.Run(t, new(validateSuite))
|
||||
}
|
||||
|
||||
func (suite *validateSuite) TestVerifyDevice() {
|
||||
// Start off with success and then remove bits
|
||||
data := &userdata.UserData{}
|
||||
err := yaml.Unmarshal([]byte(testConfig), data)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Require().NoError(VerifyBootDevice(data))
|
||||
suite.Require().NoError(VerifyDataDevice(data))
|
||||
|
||||
// No impact because we can infer all data from the data device and
|
||||
// defaults.
|
||||
data.Install.Boot = nil
|
||||
suite.Require().NoError(VerifyBootDevice(data))
|
||||
data.Install.Data = &userdata.InstallDevice{
|
||||
Device: "/dev/sda",
|
||||
}
|
||||
suite.Require().NoError(VerifyDataDevice(data))
|
||||
}
|
||||
|
||||
// TODO we should move this to a well defined location
|
||||
// Copied from userdata_test.go
|
||||
const testConfig = `version: "1"
|
||||
security:
|
||||
os:
|
||||
ca:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
identity:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
kubernetes:
|
||||
ca:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
sa:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
frontproxy:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
etcd:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
networking:
|
||||
os: {}
|
||||
kubernetes: {}
|
||||
services:
|
||||
init:
|
||||
cni: flannel
|
||||
kubeadm:
|
||||
certificateKey: 'test'
|
||||
configuration: |
|
||||
apiVersion: kubeadm.k8s.io/v1beta1
|
||||
kind: InitConfiguration
|
||||
localAPIEndpoint:
|
||||
bindPort: 6443
|
||||
bootstrapTokens:
|
||||
- token: '1qbsj9.3oz5hsk6grdfp98b'
|
||||
ttl: 0s
|
||||
---
|
||||
apiVersion: kubeadm.k8s.io/v1beta1
|
||||
kind: ClusterConfiguration
|
||||
clusterName: test
|
||||
kubernetesVersion: v1.14.1
|
||||
---
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||
kind: KubeProxyConfiguration
|
||||
mode: ipvs
|
||||
ipvs:
|
||||
scheduler: lc
|
||||
trustd:
|
||||
username: 'test'
|
||||
password: 'test'
|
||||
endpoints: [ "1.2.3.4" ]
|
||||
certSANs: []
|
||||
install:
|
||||
wipe: true
|
||||
force: true
|
||||
boot:
|
||||
device: /dev/sda
|
||||
size: 1024000000
|
||||
data:
|
||||
device: /dev/sda
|
||||
size: 1024000000
|
||||
`
|
@ -2,230 +2,55 @@
|
||||
* 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
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/filesystem/vfat"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/filesystem/xfs"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/table"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/table/gpt/partition"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/internal/pkg/version"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultSizeRootDevice is the default size of the root partition.
|
||||
// TODO(andrewrynhard): We should inspect the tarball's uncompressed size and dynamically set the root partition's size.
|
||||
DefaultSizeRootDevice = 2048 * 1000 * 1000
|
||||
|
||||
// DefaultSizeBootDevice is the default size of the boot partition.
|
||||
// TODO(andrewrynhard): We should inspect the sizes of the artifacts and dynamically set the boot partition's size.
|
||||
DefaultSizeBootDevice = 512 * 1000 * 1000
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultURLBase is the base URL for all default artifacts.
|
||||
// TODO(andrewrynhard): We need to setup infrastructure for publishing artifacts and not depend on GitHub.
|
||||
DefaultURLBase = "https://github.com/talos-systems/talos/releases/download/" + version.Tag
|
||||
|
||||
// DefaultKernelURL is the URL to the kernel.
|
||||
DefaultKernelURL = DefaultURLBase + "/vmlinuz"
|
||||
|
||||
// DefaultInitramfsURL is the URL to the initramfs.
|
||||
DefaultInitramfsURL = DefaultURLBase + "/initramfs.xz"
|
||||
)
|
||||
|
||||
// Prepare handles setting/consolidating/defaulting userdata pieces specific to
|
||||
// installation
|
||||
// TODO: See if this would be more appropriate in userdata
|
||||
// nolint: dupl, gocyclo
|
||||
func Prepare(data *userdata.UserData) (err error) {
|
||||
if data.Install == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var exists bool
|
||||
if exists, err = Exists(data.Install.Boot.InstallDevice.Device); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exists {
|
||||
log.Println("found existing installation, skipping prepare step")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify that the target device(s) can satisify the requested options.
|
||||
|
||||
if err = VerifyDataDevice(data); err != nil {
|
||||
return errors.Wrap(err, "failed to prepare data device")
|
||||
}
|
||||
if err = VerifyBootDevice(data); err != nil {
|
||||
return errors.Wrap(err, "failed to prepare boot device")
|
||||
}
|
||||
|
||||
manifest := NewManifest(data)
|
||||
|
||||
if data.Install.Wipe {
|
||||
if err = WipeDevices(manifest); err != nil {
|
||||
return errors.Wrap(err, "failed to wipe device(s)")
|
||||
}
|
||||
}
|
||||
|
||||
// Create and format all partitions.
|
||||
|
||||
if err = ExecuteManifest(data, manifest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
// Manifest represents the instructions for preparing all block devices
|
||||
// for an installation.
|
||||
type Manifest struct {
|
||||
Targets map[string][]*Target
|
||||
}
|
||||
|
||||
// ExecuteManifest partitions and formats all disks in a manifest.
|
||||
func ExecuteManifest(data *userdata.UserData, manifest *Manifest) (err error) {
|
||||
for dev, targets := range manifest.Targets {
|
||||
var bd *blockdevice.BlockDevice
|
||||
if bd, err = blockdevice.Open(dev, blockdevice.WithNewGPT(data.Install.Force)); err != nil {
|
||||
return err
|
||||
}
|
||||
// nolint: errcheck
|
||||
defer bd.Close()
|
||||
|
||||
for _, target := range targets {
|
||||
if err = target.Partition(bd); err != nil {
|
||||
return errors.Wrap(err, "failed to partition device")
|
||||
}
|
||||
}
|
||||
|
||||
if err = bd.RereadPartitionTable(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
if err = target.Format(); err != nil {
|
||||
return errors.Wrap(err, "failed to format device")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
// Target represents an installation partition.
|
||||
type Target struct {
|
||||
Label string
|
||||
MountPoint string
|
||||
Device string
|
||||
FileSystemType string
|
||||
PartitionName string
|
||||
Size uint
|
||||
Force bool
|
||||
Test bool
|
||||
Assets []*Asset
|
||||
BlockDevice *blockdevice.BlockDevice
|
||||
}
|
||||
|
||||
// VerifyDataDevice verifies the supplied data device options.
|
||||
func VerifyDataDevice(data *userdata.UserData) (err error) {
|
||||
// Set data device to root device if not specified
|
||||
if data.Install.Data == nil {
|
||||
data.Install.Data = &userdata.InstallDevice{}
|
||||
}
|
||||
|
||||
if data.Install.Data.Device == "" {
|
||||
return errors.New("a data device is required")
|
||||
}
|
||||
|
||||
if !data.Install.Force {
|
||||
if err = VerifyDiskAvailability(constants.DataPartitionLabel); err != nil {
|
||||
return errors.Wrap(err, "failed to verify disk availability")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyBootDevice verifies the supplied boot device options.
|
||||
func VerifyBootDevice(data *userdata.UserData) (err error) {
|
||||
if data.Install.Boot == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if data.Install.Boot.Device == "" {
|
||||
// We can safely assume data device is defined at this point
|
||||
// because VerifyDataDevice should have been called first in
|
||||
// in the chain
|
||||
data.Install.Boot.Device = data.Install.Data.Device
|
||||
}
|
||||
|
||||
if data.Install.Boot.Size == 0 {
|
||||
data.Install.Boot.Size = DefaultSizeBootDevice
|
||||
}
|
||||
|
||||
if data.Install.Boot.Kernel == "" {
|
||||
data.Install.Boot.Kernel = DefaultKernelURL
|
||||
}
|
||||
|
||||
if data.Install.Boot.Initramfs == "" {
|
||||
data.Install.Boot.Initramfs = DefaultInitramfsURL
|
||||
}
|
||||
|
||||
if !data.Install.Force {
|
||||
if err = VerifyDiskAvailability(constants.BootPartitionLabel); err != nil {
|
||||
return errors.Wrap(err, "failed to verify disk availability")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyDiskAvailability verifies that no filesystems currently exist with
|
||||
// the labels used by the OS.
|
||||
func VerifyDiskAvailability(label string) (err error) {
|
||||
var dev *probe.ProbedBlockDevice
|
||||
if dev, err = probe.GetDevWithFileSystemLabel(label); err != nil {
|
||||
// We return here because we only care if we can discover the
|
||||
// device successfully and confirm that the disk is not in use.
|
||||
// TODO(andrewrynhard): We should return a custom error type here
|
||||
// that we can use to confirm the device was not found.
|
||||
return nil
|
||||
}
|
||||
if dev.SuperBlock != nil {
|
||||
return errors.Errorf("target install device %s is not empty, found existing %s file system", label, dev.SuperBlock.Type())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WipeDevices writes zeros to each block device in the preparation manifest.
|
||||
func WipeDevices(manifest *Manifest) (err error) {
|
||||
var zero *os.File
|
||||
if zero, err = os.Open("/dev/zero"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for dev := range manifest.Targets {
|
||||
var f *os.File
|
||||
if f, err = os.OpenFile(dev, os.O_RDWR, os.ModeDevice); err != nil {
|
||||
return err
|
||||
}
|
||||
var size uint64
|
||||
if _, _, ret := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); ret != 0 {
|
||||
return errors.Errorf("failed to got block device size: %v", ret)
|
||||
}
|
||||
if _, err = io.CopyN(f, zero, int64(size)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = zero.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
// Asset represents a file required by a target.
|
||||
type Asset struct {
|
||||
Source string
|
||||
Destination string
|
||||
}
|
||||
|
||||
// NewManifest initializes and returns a Manifest.
|
||||
@ -259,7 +84,7 @@ func NewManifest(data *userdata.UserData) (manifest *Manifest) {
|
||||
Destination: filepath.Join("/", "default", filepath.Base(data.Install.Boot.Initramfs)),
|
||||
},
|
||||
},
|
||||
MountPoint: filepath.Join(constants.NewRoot, constants.BootMountPoint),
|
||||
MountPoint: constants.BootMountPoint,
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,7 +94,7 @@ func NewManifest(data *userdata.UserData) (manifest *Manifest) {
|
||||
Size: data.Install.Data.Size,
|
||||
Force: data.Install.Force,
|
||||
Test: false,
|
||||
MountPoint: filepath.Join(constants.NewRoot, constants.DataMountPoint),
|
||||
MountPoint: constants.DataMountPoint,
|
||||
}
|
||||
|
||||
for _, target := range []*Target{bootTarget, dataTarget} {
|
||||
@ -299,33 +124,37 @@ func NewManifest(data *userdata.UserData) (manifest *Manifest) {
|
||||
return manifest
|
||||
}
|
||||
|
||||
// Manifest represents the instructions for preparing all block devices
|
||||
// for an installation.
|
||||
type Manifest struct {
|
||||
Targets map[string][]*Target
|
||||
// ExecuteManifest partitions and formats all disks in a manifest.
|
||||
func (m *Manifest) ExecuteManifest(data *userdata.UserData, manifest *Manifest) (err error) {
|
||||
for dev, targets := range manifest.Targets {
|
||||
var bd *blockdevice.BlockDevice
|
||||
if bd, err = blockdevice.Open(dev, blockdevice.WithNewGPT(data.Install.Force)); err != nil {
|
||||
return err
|
||||
}
|
||||
// nolint: errcheck
|
||||
defer bd.Close()
|
||||
|
||||
for _, target := range targets {
|
||||
if err = target.Partition(bd); err != nil {
|
||||
return errors.Wrap(err, "failed to partition device")
|
||||
}
|
||||
}
|
||||
|
||||
if err = bd.RereadPartitionTable(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
if err = target.Format(); err != nil {
|
||||
return errors.Wrap(err, "failed to format device")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Target represents an installation partition.
|
||||
type Target struct {
|
||||
Label string
|
||||
MountPoint string
|
||||
Device string
|
||||
FileSystemType string
|
||||
PartitionName string
|
||||
Size uint
|
||||
Force bool
|
||||
Test bool
|
||||
Assets []*Asset
|
||||
BlockDevice *blockdevice.BlockDevice
|
||||
}
|
||||
|
||||
// Asset represents a file required by a target.
|
||||
type Asset struct {
|
||||
Source string
|
||||
Destination string
|
||||
}
|
||||
|
||||
// Partition creates a new partition on the specified device
|
||||
// Partition creates a new partition on the specified device.
|
||||
// nolint: dupl, gocyclo
|
||||
func (t *Target) Partition(bd *blockdevice.BlockDevice) (err error) {
|
||||
log.Printf("partitioning %s - %s\n", t.Device, t.Label)
|
||||
@ -374,7 +203,7 @@ func (t *Target) Partition(bd *blockdevice.BlockDevice) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format creates a xfs filesystem on the device/partition
|
||||
// Format creates a filesystem on the device/partition.
|
||||
func (t *Target) Format() error {
|
||||
log.Printf("formatting partition %s - %s\n", t.PartitionName, t.Label)
|
||||
if t.Label == constants.BootPartitionLabel {
|
||||
@ -387,10 +216,10 @@ func (t *Target) Format() error {
|
||||
return xfs.MakeFS(t.PartitionName, opts...)
|
||||
}
|
||||
|
||||
// Install handles downloading the necessary assets and extracting them to
|
||||
// the appropriate locations
|
||||
// Save handles downloading the necessary assets and extracting them to
|
||||
// the appropriate location.
|
||||
// nolint: gocyclo
|
||||
func (t *Target) Install() error {
|
||||
func (t *Target) Save() error {
|
||||
// Download and extract all artifacts.
|
||||
var sourceFile *os.File
|
||||
var destFile *os.File
|
||||
@ -425,7 +254,6 @@ func (t *Target) Install() error {
|
||||
source := u.Path
|
||||
dest := filepath.Join(t.MountPoint, asset.Destination)
|
||||
|
||||
log.Printf("copying %s to %s\n", asset.Source, dest)
|
||||
sourceFile, err = os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -492,3 +320,120 @@ func (t *Target) Install() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// nolint: gocyclo, dupl
|
||||
func untar(tarball *os.File, dst string) error {
|
||||
|
||||
var input io.Reader
|
||||
var err error
|
||||
|
||||
if strings.HasSuffix(tarball.Name(), ".tar.gz") {
|
||||
input, err = gzip.NewReader(tarball)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
defer input.(*gzip.Reader).Close()
|
||||
} else {
|
||||
input = tarball
|
||||
}
|
||||
|
||||
tr := tar.NewReader(input)
|
||||
|
||||
for {
|
||||
var header *tar.Header
|
||||
|
||||
header, err = tr.Next()
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
return err
|
||||
case err != nil:
|
||||
return err
|
||||
case header == nil:
|
||||
continue
|
||||
}
|
||||
|
||||
// the target location where the dir/file should be created
|
||||
target := filepath.Join(dst, header.Name)
|
||||
|
||||
// May need to add in support for these
|
||||
/*
|
||||
// Type '1' to '6' are header-only flags and may not have a data body.
|
||||
TypeLink = '1' // Hard link
|
||||
TypeSymlink = '2' // Symbolic link
|
||||
TypeChar = '3' // Character device node
|
||||
TypeBlock = '4' // Block device node
|
||||
TypeDir = '5' // Directory
|
||||
TypeFifo = '6' // FIFO node
|
||||
*/
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err = os.MkdirAll(target, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeReg:
|
||||
var downloadedFileput *os.File
|
||||
|
||||
downloadedFileput, err = os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = io.Copy(downloadedFileput, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = downloadedFileput.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeSymlink:
|
||||
dest := filepath.Join(dst, header.Name)
|
||||
source := header.Linkname
|
||||
if err := os.Symlink(source, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func download(artifact, dest string) (*os.File, error) {
|
||||
if err := os.MkdirAll(filepath.Dir(dest), 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
downloadedFile, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the data
|
||||
resp, err := http.Get(artifact)
|
||||
if err != nil {
|
||||
return downloadedFile, err
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
// nolint: errcheck
|
||||
downloadedFile.Close()
|
||||
return nil, errors.Errorf("failed to download %s, got %d", artifact, resp.StatusCode)
|
||||
}
|
||||
|
||||
// Write the body to file
|
||||
_, err = io.Copy(downloadedFile, resp.Body)
|
||||
if err != nil {
|
||||
return downloadedFile, err
|
||||
}
|
||||
|
||||
// Reset downloadedFile file position to 0 so we can immediately read from it
|
||||
_, err = downloadedFile.Seek(0, 0)
|
||||
|
||||
// TODO add support for checksum validation of downloaded file
|
||||
|
||||
return downloadedFile, err
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
* 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
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
@ -18,15 +18,15 @@ import (
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type validateSuite struct {
|
||||
type manifestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestValidateSuite(t *testing.T) {
|
||||
suite.Run(t, new(validateSuite))
|
||||
func TestManifestSuite(t *testing.T) {
|
||||
suite.Run(t, new(manifestSuite))
|
||||
}
|
||||
|
||||
func (suite *validateSuite) TestNewManifest() {
|
||||
func (suite *manifestSuite) TestNewManifest() {
|
||||
// Test with whole data
|
||||
data := &userdata.UserData{}
|
||||
err := yaml.Unmarshal([]byte(testConfig), data)
|
||||
@ -36,26 +36,7 @@ func (suite *validateSuite) TestNewManifest() {
|
||||
assert.Equal(suite.T(), 2, len(manifests.Targets["/dev/sda"]))
|
||||
}
|
||||
|
||||
func (suite *validateSuite) TestVerifyDevice() {
|
||||
// Start off with success and then remove bits
|
||||
data := &userdata.UserData{}
|
||||
err := yaml.Unmarshal([]byte(testConfig), data)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Require().NoError(VerifyBootDevice(data))
|
||||
suite.Require().NoError(VerifyDataDevice(data))
|
||||
|
||||
// No impact because we can infer all data from the data device and
|
||||
// defaults.
|
||||
data.Install.Boot = nil
|
||||
suite.Require().NoError(VerifyBootDevice(data))
|
||||
data.Install.Data = &userdata.InstallDevice{
|
||||
Device: "/dev/sda",
|
||||
}
|
||||
suite.Require().NoError(VerifyDataDevice(data))
|
||||
}
|
||||
|
||||
func (suite *validateSuite) TestTargetInstall() {
|
||||
func (suite *manifestSuite) TestTargetInstall() {
|
||||
// Create Temp dirname for mountpoint
|
||||
dir, err := ioutil.TempDir("", "talostest")
|
||||
suite.Require().NoError(err)
|
||||
@ -95,7 +76,7 @@ func (suite *validateSuite) TestTargetInstall() {
|
||||
},
|
||||
}
|
||||
|
||||
suite.Require().NoError(target.Install())
|
||||
suite.Require().NoError(target.Save())
|
||||
|
||||
for _, expectedFile := range target.Assets {
|
||||
// Verify downloaded/copied file is at the appropriate location
|
83
internal/pkg/installer/verify.go
Normal file
83
internal/pkg/installer/verify.go
Normal file
@ -0,0 +1,83 @@
|
||||
/* 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 installer
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
// VerifyDataDevice verifies the supplied data device options.
|
||||
func VerifyDataDevice(data *userdata.UserData) (err error) {
|
||||
// Set data device to root device if not specified
|
||||
if data.Install.Data == nil {
|
||||
data.Install.Data = &userdata.InstallDevice{}
|
||||
}
|
||||
|
||||
if data.Install.Data.Device == "" {
|
||||
return errors.New("a data device is required")
|
||||
}
|
||||
|
||||
if !data.Install.Force {
|
||||
if err = VerifyDiskAvailability(constants.DataPartitionLabel); err != nil {
|
||||
return errors.Wrap(err, "failed to verify disk availability")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyBootDevice verifies the supplied boot device options.
|
||||
func VerifyBootDevice(data *userdata.UserData) (err error) {
|
||||
if data.Install.Boot == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if data.Install.Boot.Device == "" {
|
||||
// We can safely assume data device is defined at this point
|
||||
// because VerifyDataDevice should have been called first in
|
||||
// in the chain
|
||||
data.Install.Boot.Device = data.Install.Data.Device
|
||||
}
|
||||
|
||||
if data.Install.Boot.Size == 0 {
|
||||
data.Install.Boot.Size = DefaultSizeBootDevice
|
||||
}
|
||||
|
||||
if data.Install.Boot.Kernel == "" {
|
||||
data.Install.Boot.Kernel = DefaultKernelURL
|
||||
}
|
||||
|
||||
if data.Install.Boot.Initramfs == "" {
|
||||
data.Install.Boot.Initramfs = DefaultInitramfsURL
|
||||
}
|
||||
|
||||
if !data.Install.Force {
|
||||
if err = VerifyDiskAvailability(constants.BootPartitionLabel); err != nil {
|
||||
return errors.Wrap(err, "failed to verify disk availability")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyDiskAvailability verifies that no filesystems currently exist with
|
||||
// the labels used by the OS.
|
||||
func VerifyDiskAvailability(label string) (err error) {
|
||||
var dev *probe.ProbedBlockDevice
|
||||
if dev, err = probe.GetDevWithFileSystemLabel(label); err != nil {
|
||||
// We return here because we only care if we can discover the
|
||||
// device successfully and confirm that the disk is not in use.
|
||||
// TODO(andrewrynhard): We should return a custom error type here
|
||||
// that we can use to confirm the device was not found.
|
||||
return nil
|
||||
}
|
||||
if dev.SuperBlock != nil {
|
||||
return errors.Errorf("target install device %s is not empty, found existing %s file system", label, dev.SuperBlock.Type())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -13,7 +13,7 @@ import (
|
||||
// NewDefaultCmdline returns a set of kernel parameters that serve as the base
|
||||
// for all Talos installations.
|
||||
// nolint: golint
|
||||
func NewDefaultCmdline() *cmdline {
|
||||
func NewDefaultCmdline() *Cmdline {
|
||||
cmdline := NewCmdline("")
|
||||
cmdline.Append("page_poison", "1")
|
||||
cmdline.Append("slab_nomerge", "")
|
||||
@ -122,17 +122,18 @@ func (p Parameters) String() string {
|
||||
return strings.TrimRight(s, " ")
|
||||
}
|
||||
|
||||
type cmdline struct {
|
||||
// Cmdline represents a set of kernel parameters.
|
||||
type Cmdline struct {
|
||||
sync.Mutex
|
||||
Parameters
|
||||
}
|
||||
|
||||
var instance *cmdline
|
||||
var instance *Cmdline
|
||||
var once sync.Once
|
||||
|
||||
// Cmdline returns a representation of /proc/cmdline.
|
||||
// nolint: golint
|
||||
func Cmdline() *cmdline {
|
||||
func ProcCmdline() *Cmdline {
|
||||
once.Do(func() {
|
||||
var err error
|
||||
if instance, err = read(); err != nil {
|
||||
@ -146,15 +147,15 @@ func Cmdline() *cmdline {
|
||||
// NewCmdline initializes and returns a representation of the cmdline values
|
||||
// specified by `parameters`.
|
||||
// nolint: golint
|
||||
func NewCmdline(parameters string) *cmdline {
|
||||
func NewCmdline(parameters string) *Cmdline {
|
||||
parsed := parse(parameters)
|
||||
c := &cmdline{sync.Mutex{}, parsed}
|
||||
c := &Cmdline{sync.Mutex{}, parsed}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Get gets a kernel parameter.
|
||||
func (c *cmdline) Get(k string) (value *Parameter) {
|
||||
func (c *Cmdline) Get(k string) (value *Parameter) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
for _, value := range c.Parameters {
|
||||
@ -167,7 +168,7 @@ func (c *cmdline) Get(k string) (value *Parameter) {
|
||||
}
|
||||
|
||||
// Set sets a kernel parameter.
|
||||
func (c *cmdline) Set(k string, v *Parameter) {
|
||||
func (c *Cmdline) Set(k string, v *Parameter) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
for i, value := range c.Parameters {
|
||||
@ -179,7 +180,7 @@ func (c *cmdline) Set(k string, v *Parameter) {
|
||||
}
|
||||
|
||||
// Append appends a kernel parameter.
|
||||
func (c *cmdline) Append(k string, v string) {
|
||||
func (c *Cmdline) Append(k string, v string) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
for _, value := range c.Parameters {
|
||||
@ -192,7 +193,7 @@ func (c *cmdline) Append(k string, v string) {
|
||||
}
|
||||
|
||||
// AppendAll appends a set of kernel parameters.
|
||||
func (c *cmdline) AppendAll(args []string) error {
|
||||
func (c *Cmdline) AppendAll(args []string) error {
|
||||
parameters := parse(strings.Join(args, " "))
|
||||
c.Parameters = append(c.Parameters, parameters...)
|
||||
|
||||
@ -200,7 +201,7 @@ func (c *cmdline) AppendAll(args []string) error {
|
||||
}
|
||||
|
||||
// Bytes returns the byte slice representation of the cmdline struct.
|
||||
func (c *cmdline) Bytes() []byte {
|
||||
func (c *Cmdline) Bytes() []byte {
|
||||
return []byte(c.String())
|
||||
}
|
||||
|
||||
@ -231,7 +232,7 @@ func parse(parameters string) (parsed Parameters) {
|
||||
return parsed
|
||||
}
|
||||
|
||||
func read() (c *cmdline, err error) {
|
||||
func read() (c *Cmdline, err error) {
|
||||
var parameters []byte
|
||||
parameters, err = ioutil.ReadFile("/proc/cmdline")
|
||||
if err != nil {
|
||||
@ -239,7 +240,7 @@ func read() (c *cmdline, err error) {
|
||||
}
|
||||
|
||||
parsed := parse(string(parameters))
|
||||
c = &cmdline{sync.Mutex{}, parsed}
|
||||
c = &Cmdline{sync.Mutex{}, parsed}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
32
internal/pkg/kernel/sysctl/sysctl.go
Normal file
32
internal/pkg/kernel/sysctl/sysctl.go
Normal file
@ -0,0 +1,32 @@
|
||||
/* 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 sysctl
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SystemProperty represents a kernel system property.
|
||||
type SystemProperty struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
// WriteSystemProperty writes a value to a key under /proc/sys.
|
||||
func WriteSystemProperty(prop *SystemProperty) error {
|
||||
return ioutil.WriteFile(prop.Path(), []byte(prop.Value), 0644)
|
||||
}
|
||||
|
||||
// ReadSystemProperty reads a value from a key under /proc/sys.
|
||||
func ReadSystemProperty(prop *SystemProperty) ([]byte, error) {
|
||||
return ioutil.ReadFile(prop.Path())
|
||||
}
|
||||
|
||||
// Path returns the path to the systctl file under /proc/sys.
|
||||
func (prop *SystemProperty) Path() string {
|
||||
return path.Join("/proc/sys", strings.Replace(prop.Key, ".", "/", -1))
|
||||
}
|
41
internal/pkg/mount/manager/cgroups/cgroups.go
Normal file
41
internal/pkg/mount/manager/cgroups/cgroups.go
Normal file
@ -0,0 +1,41 @@
|
||||
/* 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 cgroups
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// MountPoints returns the cgroup mount points
|
||||
func MountPoints() (mountpoints *mount.Points, err error) {
|
||||
base := "/sys/fs/cgroup"
|
||||
cgroups := mount.NewMountPoints()
|
||||
cgroups.Set("dev", mount.NewMountPoint("tmpfs", base, "tmpfs", unix.MS_NOSUID|unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_RELATIME, "mode=755"))
|
||||
|
||||
controllers := []string{
|
||||
"blkio",
|
||||
"cpu",
|
||||
"cpuacct",
|
||||
"cpuset",
|
||||
"devices",
|
||||
"freezer",
|
||||
"hugetlb",
|
||||
"memory",
|
||||
"net_cls",
|
||||
"net_prio",
|
||||
"perf_event",
|
||||
"pids",
|
||||
}
|
||||
for _, c := range controllers {
|
||||
p := path.Join(base, c)
|
||||
cgroups.Set(c, mount.NewMountPoint(c, p, "cgroup", unix.MS_NOSUID|unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_RELATIME, c))
|
||||
}
|
||||
|
||||
return cgroups, nil
|
||||
}
|
92
internal/pkg/mount/manager/manager.go
Normal file
92
internal/pkg/mount/manager/manager.go
Normal file
@ -0,0 +1,92 @@
|
||||
/* 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 manager
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
)
|
||||
|
||||
// Manager represents a management layer for a set of mountpoints.
|
||||
type Manager struct {
|
||||
mountpoints *mount.Points
|
||||
}
|
||||
|
||||
// NewManager initializes and returns a Manager.
|
||||
func NewManager(mountpoints *mount.Points) *Manager {
|
||||
m := &Manager{
|
||||
mountpoints: mountpoints,
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// MountAll mounts the device(s).
|
||||
func (m *Manager) MountAll() (err error) {
|
||||
iter := m.mountpoints.Iter()
|
||||
|
||||
// Mount the device(s).
|
||||
|
||||
for iter.Next() {
|
||||
mountpoint := iter.Value()
|
||||
// Repair the disk's partition table.
|
||||
if mountpoint.Resize {
|
||||
if err = mountpoint.ResizePartition(); err != nil {
|
||||
return errors.Wrap(err, "resize")
|
||||
}
|
||||
}
|
||||
|
||||
if err = mountpoint.Mount(); err != nil {
|
||||
return errors.Wrap(err, "mount")
|
||||
}
|
||||
|
||||
// Grow the filesystem to the maximum allowed size.
|
||||
if mountpoint.Resize {
|
||||
if err = mountpoint.GrowFilesystem(); err != nil {
|
||||
return errors.Wrap(err, "grow")
|
||||
}
|
||||
}
|
||||
}
|
||||
if iter.Err() != nil {
|
||||
return iter.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmountAll unmounts the device(s).
|
||||
func (m *Manager) UnmountAll() (err error) {
|
||||
iter := m.mountpoints.IterRev()
|
||||
for iter.Next() {
|
||||
mountpoint := iter.Value()
|
||||
if err = mountpoint.Unmount(); err != nil {
|
||||
return errors.Wrap(err, "unomunt")
|
||||
}
|
||||
}
|
||||
if iter.Err() != nil {
|
||||
return iter.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveAll moves the device(s).
|
||||
// TODO(andrewrynhard): We need to skip calling the move method on mountpoints
|
||||
// that are a child of another mountpoint. The kernel will handle moving the
|
||||
// child mountpoints for us.
|
||||
func (m *Manager) MoveAll(prefix string) (err error) {
|
||||
iter := m.mountpoints.Iter()
|
||||
for iter.Next() {
|
||||
mountpoint := iter.Value()
|
||||
if err = mountpoint.Move(prefix); err != nil {
|
||||
return errors.Wrapf(err, "move")
|
||||
}
|
||||
}
|
||||
if iter.Err() != nil {
|
||||
return iter.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
* 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 mount_test
|
||||
package manager_test
|
||||
|
||||
import "testing"
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 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
|
||||
package owned
|
||||
|
||||
import (
|
||||
"log"
|
||||
@ -11,34 +11,11 @@ import (
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Mount discovers the appropriate partitions by label and mounts them up
|
||||
// to the appropriate mountpoint.
|
||||
// TODO: See if we can consolidate this with rootfs/mount
|
||||
func Mount(data *userdata.UserData) (err error) {
|
||||
var mp *mount.Points
|
||||
if mp, err = mountpoints(data.Install.Boot.InstallDevice.Device); err != nil {
|
||||
return errors.Errorf("error initializing block devices: %v", err)
|
||||
}
|
||||
|
||||
iter := mp.Iter()
|
||||
for iter.Next() {
|
||||
if err = mount.WithRetry(iter.Value(), mount.WithPrefix(constants.NewRoot)); err != nil {
|
||||
return errors.Errorf("error mounting partitions: %v", err)
|
||||
}
|
||||
}
|
||||
if iter.Err() != nil {
|
||||
return iter.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// nolint: dupl
|
||||
func mountpoints(devpath string) (mountpoints *mount.Points, err error) {
|
||||
// MountPointsForDevice returns the mountpoints required to boot the system.
|
||||
func MountPointsForDevice(devpath string) (mountpoints *mount.Points, err error) {
|
||||
mountpoints = mount.NewMountPoints()
|
||||
for _, name := range []string{constants.DataPartitionLabel, constants.BootPartitionLabel} {
|
||||
var target string
|
||||
@ -56,13 +33,38 @@ func mountpoints(devpath string) (mountpoints *mount.Points, err error) {
|
||||
log.Println("WARNING: no ESP partition was found")
|
||||
continue
|
||||
}
|
||||
return nil, errors.Errorf("failed to find device with label %s: %v", name, err)
|
||||
return nil, errors.Errorf("probe device for filesystem %s: %v", name, err)
|
||||
}
|
||||
|
||||
mountpoint := mount.NewMountPoint(dev.Path, target, dev.SuperBlock.Type(), unix.MS_NOATIME, "")
|
||||
|
||||
mountpoints.Set(name, mountpoint)
|
||||
}
|
||||
|
||||
return mountpoints, nil
|
||||
}
|
||||
|
||||
// MountPointsFromLabels returns the mountpoints required to boot the system.
|
||||
func MountPointsFromLabels() (mountpoints *mount.Points, err error) {
|
||||
mountpoints = mount.NewMountPoints()
|
||||
for _, name := range []string{constants.DataPartitionLabel, constants.BootPartitionLabel} {
|
||||
var target string
|
||||
switch name {
|
||||
case constants.DataPartitionLabel:
|
||||
target = constants.DataMountPoint
|
||||
case constants.BootPartitionLabel:
|
||||
target = constants.BootMountPoint
|
||||
}
|
||||
|
||||
var dev *probe.ProbedBlockDevice
|
||||
if dev, err = probe.GetDevWithFileSystemLabel(name); err != nil {
|
||||
if name == constants.BootPartitionLabel {
|
||||
// A bootloader is not always required.
|
||||
log.Println("WARNING: no ESP partition was found")
|
||||
continue
|
||||
}
|
||||
return nil, errors.Errorf("find device with label %s: %v", name, err)
|
||||
}
|
||||
mountpoint := mount.NewMountPoint(dev.Path, target, dev.SuperBlock.Type(), unix.MS_NOATIME, "")
|
||||
mountpoints.Set(name, mountpoint)
|
||||
}
|
||||
return mountpoints, nil
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
* 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 mount_test
|
||||
package owned_test
|
||||
|
||||
import "testing"
|
||||
|
25
internal/pkg/mount/manager/squashfs/squashfs.go
Normal file
25
internal/pkg/mount/manager/squashfs/squashfs.go
Normal file
@ -0,0 +1,25 @@
|
||||
/* 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 squashfs
|
||||
|
||||
import (
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
"golang.org/x/sys/unix"
|
||||
"gopkg.in/freddierice/go-losetup.v1"
|
||||
)
|
||||
|
||||
// MountPoints returns the mountpoints required to boot the system.
|
||||
func MountPoints(prefix string) (mountpoints *mount.Points, err error) {
|
||||
var dev losetup.Device
|
||||
dev, err = losetup.Attach("/"+constants.RootfsAsset, 0, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
squashfs := mount.NewMountPoints()
|
||||
squashfs.Set("squashfs", mount.NewMountPoint(dev.Path(), "/", "squashfs", unix.MS_RDONLY, "", mount.WithPrefix(prefix), mount.WithReadOnly(true), mount.WithShared(true)))
|
||||
|
||||
return squashfs, nil
|
||||
}
|
14
internal/pkg/mount/manager/squashfs/squashfs_test.go
Normal file
14
internal/pkg/mount/manager/squashfs/squashfs_test.go
Normal file
@ -0,0 +1,14 @@
|
||||
/* 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 squashfs_test
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
// added for accurate coverage estimation
|
||||
//
|
||||
// please remove it once any unit-test is added
|
||||
// for this package
|
||||
}
|
31
internal/pkg/mount/manager/virtual/virtual.go
Normal file
31
internal/pkg/mount/manager/virtual/virtual.go
Normal file
@ -0,0 +1,31 @@
|
||||
/* 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 virtual
|
||||
|
||||
import (
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// MountPoints returns the mountpoints required to boot the system.
|
||||
func MountPoints() (mountpoints *mount.Points, err error) {
|
||||
virtual := mount.NewMountPoints()
|
||||
virtual.Set("dev", mount.NewMountPoint("devtmpfs", "/dev", "devtmpfs", unix.MS_NOSUID, "mode=0755"))
|
||||
virtual.Set("proc", mount.NewMountPoint("proc", "/proc", "proc", unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV, ""))
|
||||
virtual.Set("sys", mount.NewMountPoint("sysfs", "/sys", "sysfs", 0, ""))
|
||||
virtual.Set("run", mount.NewMountPoint("tmpfs", "/run", "tmpfs", 0, ""))
|
||||
virtual.Set("tmp", mount.NewMountPoint("tmpfs", "/tmp", "tmpfs", 0, ""))
|
||||
|
||||
return virtual, nil
|
||||
}
|
||||
|
||||
// SubMountPoints returns the mountpoints required to boot the system.
|
||||
func SubMountPoints() (mountpoints *mount.Points, err error) {
|
||||
virtual := mount.NewMountPoints()
|
||||
virtual.Set("devshm", mount.NewMountPoint("tmpfs", "/dev/shm", "tmpfs", unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_RELATIME, ""))
|
||||
virtual.Set("devpts", mount.NewMountPoint("devpts", "/dev/pts", "devpts", unix.MS_NOSUID|unix.MS_NOEXEC, "ptmxmode=000,mode=620,gid=5"))
|
||||
|
||||
return virtual, nil
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
* 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_test
|
||||
package virtual_test
|
||||
|
||||
import "testing"
|
||||
|
@ -10,6 +10,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/filesystem/xfs"
|
||||
gptpartition "github.com/talos-systems/talos/internal/pkg/blockdevice/table/gpt/partition"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/util"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
@ -20,6 +25,7 @@ type Point struct {
|
||||
fstype string
|
||||
flags uintptr
|
||||
data string
|
||||
*Options
|
||||
}
|
||||
|
||||
// PointMap represents a unique set of mount points.
|
||||
@ -32,13 +38,15 @@ type Points struct {
|
||||
}
|
||||
|
||||
// NewMountPoint initializes and returns a Point struct.
|
||||
func NewMountPoint(source string, target string, fstype string, flags uintptr, data string) *Point {
|
||||
func NewMountPoint(source string, target string, fstype string, flags uintptr, data string, setters ...Option) *Point {
|
||||
opts := NewDefaultOptions(setters...)
|
||||
return &Point{
|
||||
source: source,
|
||||
target: target,
|
||||
fstype: fstype,
|
||||
flags: flags,
|
||||
data: data,
|
||||
Options: opts,
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,22 +82,20 @@ func (p *Point) Data() string {
|
||||
return p.data
|
||||
}
|
||||
|
||||
// WithRetry attempts to retry a mount on EBUSY. It will attempt a retry
|
||||
// Mount attempts to retry a mount on EBUSY. It will attempt a retry
|
||||
// every 100 milliseconds over the course of 5 seconds.
|
||||
func WithRetry(mountpoint *Point, setters ...Option) (err error) {
|
||||
opts := NewDefaultOptions(setters...)
|
||||
|
||||
if opts.ReadOnly {
|
||||
mountpoint.flags |= unix.MS_RDONLY
|
||||
func (p *Point) Mount() (err error) {
|
||||
if p.ReadOnly {
|
||||
p.flags |= unix.MS_RDONLY
|
||||
}
|
||||
|
||||
target := path.Join(opts.Prefix, mountpoint.target)
|
||||
target := path.Join(p.Prefix, p.target)
|
||||
|
||||
if err = os.MkdirAll(target, os.ModeDir); err != nil {
|
||||
return errors.Errorf("error creating mount point directory %s: %v", target, err)
|
||||
}
|
||||
|
||||
retry := func(source string, target string, fstype string, flags uintptr, data string) error {
|
||||
retry := func(source string, target string, fstype string, flags uintptr, data string) (err error) {
|
||||
for i := 0; i < 50; i++ {
|
||||
if err = unix.Mount(source, target, fstype, flags, data); err != nil {
|
||||
switch err {
|
||||
@ -103,14 +109,15 @@ func WithRetry(mountpoint *Point, setters ...Option) (err error) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Errorf("mount timeout: %v", err)
|
||||
}
|
||||
|
||||
if err = retry(mountpoint.source, target, mountpoint.fstype, mountpoint.flags, mountpoint.data); err != nil {
|
||||
if err = retry(p.source, target, p.fstype, p.flags, p.data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.Shared {
|
||||
if p.Shared {
|
||||
if err = retry("", target, "", unix.MS_SHARED, ""); err != nil {
|
||||
return errors.Errorf("error mounting shared mount point %s: %v", target, err)
|
||||
}
|
||||
@ -120,11 +127,9 @@ func WithRetry(mountpoint *Point, setters ...Option) (err error) {
|
||||
|
||||
}
|
||||
|
||||
// UnWithRetry attempts to retry an unmount on EBUSY. It will attempt a
|
||||
// Unmount attempts to retry an unmount on EBUSY. It will attempt a
|
||||
// retry every 100 milliseconds over the course of 5 seconds.
|
||||
func UnWithRetry(mountpoint *Point, setters ...Option) (err error) {
|
||||
opts := NewDefaultOptions(setters...)
|
||||
|
||||
func (p *Point) Unmount() (err error) {
|
||||
retry := func(target string, flags int) error {
|
||||
for i := 0; i < 50; i++ {
|
||||
if err = unix.Unmount(target, flags); err != nil {
|
||||
@ -141,10 +146,74 @@ func UnWithRetry(mountpoint *Point, setters ...Option) (err error) {
|
||||
return errors.Errorf("mount timeout: %v", err)
|
||||
}
|
||||
|
||||
target := path.Join(opts.Prefix, mountpoint.target)
|
||||
target := path.Join(p.Prefix, p.target)
|
||||
if err := retry(target, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Move moves a mountpoint to a new location with a prefix.
|
||||
func (p *Point) Move(prefix string) (err error) {
|
||||
target := p.Target()
|
||||
mountpoint := NewMountPoint(target, target, "", unix.MS_MOVE, "", WithPrefix(prefix))
|
||||
if err = mountpoint.Mount(); err != nil {
|
||||
return errors.Errorf("error moving mount point %s: %v", target, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResizePartition resizes a partition to the maximum size allowed.
|
||||
func (p *Point) ResizePartition() (err error) {
|
||||
var devname string
|
||||
if devname, err = util.DevnameFromPartname(p.Source()); err != nil {
|
||||
return err
|
||||
}
|
||||
bd, err := blockdevice.Open("/dev/" + devname)
|
||||
if err != nil {
|
||||
return errors.Errorf("error opening block device %q: %v", devname, err)
|
||||
}
|
||||
// nolint: errcheck
|
||||
defer bd.Close()
|
||||
|
||||
pt, err := bd.PartitionTable(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := pt.Repair(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, partition := range pt.Partitions() {
|
||||
if partition.(*gptpartition.Partition).Name == constants.DataPartitionLabel {
|
||||
if err := pt.Resize(partition); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := pt.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// NB: Rereading the partition table requires that all partitions be
|
||||
// unmounted or it will fail with EBUSY.
|
||||
if err := bd.RereadPartitionTable(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GrowFilesystem grows a partition's filesystem to the maximum size allowed.
|
||||
// NB: An XFS partition MUST be mounted, or this will fail.
|
||||
func (p *Point) GrowFilesystem() (err error) {
|
||||
if err = xfs.GrowFS(p.Target()); err != nil {
|
||||
return errors.Wrap(err, "xfs_growfs")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -6,9 +6,11 @@ package mount
|
||||
|
||||
// Options is the functional options struct.
|
||||
type Options struct {
|
||||
Loopback string
|
||||
Prefix string
|
||||
ReadOnly bool
|
||||
Shared bool
|
||||
Resize bool
|
||||
}
|
||||
|
||||
// Option is the functional option func.
|
||||
@ -35,9 +37,18 @@ func WithShared(o bool) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithResize indicates that a the partition for a given mount point should be
|
||||
// resized to the maximum allowed.
|
||||
func WithResize(o bool) Option {
|
||||
return func(args *Options) {
|
||||
args.Resize = o
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultOptions initializes a Options struct with default values.
|
||||
func NewDefaultOptions(setters ...Option) *Options {
|
||||
opts := &Options{
|
||||
Loopback: "",
|
||||
Prefix: "",
|
||||
ReadOnly: false,
|
||||
Shared: false,
|
||||
|
118
internal/pkg/mount/switchroot/switchroot.go
Normal file
118
internal/pkg/mount/switchroot/switchroot.go
Normal file
@ -0,0 +1,118 @@
|
||||
/* 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 switchroot
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount/manager"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Switch moves the rootfs to a specified directory. See
|
||||
// https://github.com/karelzak/util-linux/blob/master/sys-utils/switch_root.c.
|
||||
// nolint: gocyclo
|
||||
func Switch(prefix string, virtual *manager.Manager) (err error) {
|
||||
log.Println("moving virtual devices to the new rootfs")
|
||||
if err = virtual.MoveAll(prefix); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("changing working directory into %s", prefix)
|
||||
if err = unix.Chdir(prefix); err != nil {
|
||||
return errors.Wrapf(err, "error changing working directory to %s", prefix)
|
||||
}
|
||||
|
||||
var old *os.File
|
||||
if old, err = os.Open("/"); err != nil {
|
||||
return errors.Wrap(err, "error opening /")
|
||||
}
|
||||
// nolint: errcheck
|
||||
defer old.Close()
|
||||
|
||||
log.Printf("moving %s to /", prefix)
|
||||
if err = unix.Mount(prefix, "/", "", unix.MS_MOVE, ""); err != nil {
|
||||
return errors.Wrap(err, "error moving /")
|
||||
}
|
||||
|
||||
log.Println("changing root directory")
|
||||
if err = unix.Chroot("."); err != nil {
|
||||
return errors.Wrap(err, "error chroot")
|
||||
}
|
||||
|
||||
log.Println("cleaning up initramfs")
|
||||
if err = recursiveDelete(int(old.Fd())); err != nil {
|
||||
return errors.Wrap(err, "error deleting initramfs")
|
||||
}
|
||||
|
||||
// Note that /sbin/init is machined. We call it init since this is the
|
||||
// convention.
|
||||
log.Println("executing /sbin/init")
|
||||
if err = unix.Exec("/sbin/init", []string{"/sbin/init"}, []string{}); err != nil {
|
||||
return errors.Wrap(err, "error executing /sbin/init")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func recursiveDelete(fd int) error {
|
||||
parentDev, err := getDev(fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dir := os.NewFile(uintptr(fd), "__ignored__")
|
||||
// nolint: errcheck
|
||||
defer dir.Close()
|
||||
names, err := dir.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
if err := recusiveDeleteInner(fd, parentDev, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func recusiveDeleteInner(parentFd int, parentDev uint64, childName string) error {
|
||||
childFd, err := unix.Openat(parentFd, childName, unix.O_DIRECTORY|unix.O_NOFOLLOW, unix.O_RDWR)
|
||||
if err != nil {
|
||||
if err := unix.Unlinkat(parentFd, childName, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// nolint: errcheck
|
||||
defer unix.Close(childFd)
|
||||
|
||||
if childFdDev, err := getDev(childFd); err != nil {
|
||||
return err
|
||||
} else if childFdDev != parentDev {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := recursiveDelete(childFd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := unix.Unlinkat(parentFd, childName, unix.AT_REMOVEDIR); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDev(fd int) (dev uint64, err error) {
|
||||
var stat unix.Stat_t
|
||||
|
||||
if err := unix.Fstat(fd, &stat); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return stat.Dev, nil
|
||||
}
|
14
internal/pkg/mount/switchroot/switchroot_test.go
Normal file
14
internal/pkg/mount/switchroot/switchroot_test.go
Normal file
@ -0,0 +1,14 @@
|
||||
/* 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 switchroot_test
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
// added for accurate coverage estimation
|
||||
//
|
||||
// please remove it once any unit-test is added
|
||||
// for this package
|
||||
}
|
@ -116,7 +116,7 @@ func (service *Service) dhclient4(ctx context.Context, ifname string, modifiers
|
||||
|
||||
// Ignore DHCP-offered hostname if the kernel parameter is set
|
||||
var kernHostname *string
|
||||
if kernHostname = kernel.Cmdline().Get(constants.KernelParamHostname).First(); kernHostname != nil {
|
||||
if kernHostname = kernel.ProcCmdline().Get(constants.KernelParamHostname).First(); kernHostname != nil {
|
||||
hostname = *kernHostname
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ func ifup(ifname string, mtu int) (err error) {
|
||||
|
||||
func defaultInterface() string {
|
||||
netif := DefaultInterface
|
||||
if option := kernel.Cmdline().Get(constants.KernelParamDefaultInterface).First(); option != nil {
|
||||
if option := kernel.ProcCmdline().Get(constants.KernelParamDefaultInterface).First(); option != nil {
|
||||
netif = *option
|
||||
}
|
||||
|
||||
|
@ -6,26 +6,12 @@ package proc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"code.cloudfoundry.org/bytefmt"
|
||||
"github.com/prometheus/procfs"
|
||||
)
|
||||
|
||||
// SystemProperty represents a kernel system property.
|
||||
type SystemProperty struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
// WriteSystemProperty writes a value to a key under /proc/sys.
|
||||
func WriteSystemProperty(prop *SystemProperty) error {
|
||||
keyPath := path.Join("/proc/sys", strings.Replace(prop.Key, ".", "/", -1))
|
||||
return ioutil.WriteFile(keyPath, []byte(prop.Value), 0644)
|
||||
}
|
||||
|
||||
// ProcessList contains all of the process stats we want
|
||||
// to display via top
|
||||
type ProcessList struct {
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/pkg/kernel"
|
||||
"github.com/talos-systems/talos/internal/pkg/proc"
|
||||
"github.com/talos-systems/talos/internal/pkg/kernel/sysctl"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -28,7 +28,7 @@ func EnforceKSPPKernelParameters() error {
|
||||
var result *multierror.Error
|
||||
for _, values := range RequiredKSPPKernelParameters {
|
||||
var val *string
|
||||
if val = kernel.Cmdline().Get(values.Key()).First(); val == nil {
|
||||
if val = kernel.ProcCmdline().Get(values.Key()).First(); val == nil {
|
||||
result = multierror.Append(result, errors.Errorf("KSPP kernel parameter %s is required", values.Key()))
|
||||
continue
|
||||
}
|
||||
@ -45,7 +45,7 @@ func EnforceKSPPKernelParameters() error {
|
||||
// EnforceKSPPSysctls verifies that all required KSPP kernel sysctls are set
|
||||
// with the right value.
|
||||
func EnforceKSPPSysctls() (err error) {
|
||||
props := []*proc.SystemProperty{
|
||||
props := []*sysctl.SystemProperty{
|
||||
{
|
||||
Key: "kernel.kptr_restrict",
|
||||
Value: "1",
|
||||
@ -82,7 +82,7 @@ func EnforceKSPPSysctls() (err error) {
|
||||
}
|
||||
|
||||
for _, prop := range props {
|
||||
if err = proc.WriteSystemProperty(prop); err != nil {
|
||||
if err = sysctl.WriteSystemProperty(prop); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -14,9 +14,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/bootloader/syslinux"
|
||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||
"github.com/talos-systems/talos/internal/pkg/install"
|
||||
"github.com/talos-systems/talos/internal/pkg/installer/bootloader/syslinux"
|
||||
"github.com/talos-systems/talos/internal/pkg/installer/manifest"
|
||||
"github.com/talos-systems/talos/internal/pkg/kernel"
|
||||
"github.com/talos-systems/talos/internal/pkg/kubernetes"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
@ -83,31 +83,31 @@ func NewUpgrade(url string) (err error) {
|
||||
}
|
||||
|
||||
func upgradeBoot(url string) error {
|
||||
bootTarget := install.Target{
|
||||
bootTarget := manifest.Target{
|
||||
Label: constants.BootPartitionLabel,
|
||||
MountPoint: constants.BootMountPoint,
|
||||
Assets: []*install.Asset{},
|
||||
Assets: []*manifest.Asset{},
|
||||
}
|
||||
|
||||
// Kernel
|
||||
bootTarget.Assets = append(bootTarget.Assets, &install.Asset{
|
||||
bootTarget.Assets = append(bootTarget.Assets, &manifest.Asset{
|
||||
Source: url + "/" + constants.KernelAsset,
|
||||
Destination: filepath.Join("/", "default", constants.KernelAsset),
|
||||
})
|
||||
|
||||
// Initramfs
|
||||
bootTarget.Assets = append(bootTarget.Assets, &install.Asset{
|
||||
bootTarget.Assets = append(bootTarget.Assets, &manifest.Asset{
|
||||
Source: url + "/" + constants.InitramfsAsset,
|
||||
Destination: filepath.Join("/", "default", constants.InitramfsAsset),
|
||||
})
|
||||
|
||||
var err error
|
||||
if err = bootTarget.Install(); err != nil {
|
||||
if err = bootTarget.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Figure out a method to update kernel args
|
||||
nextCmdline := kernel.NewCmdline(kernel.Cmdline().String())
|
||||
nextCmdline := kernel.NewCmdline(kernel.ProcCmdline().String())
|
||||
|
||||
// Set the initrd kernel paramaeter.
|
||||
initParam := kernel.NewParameter("initrd")
|
||||
|
Loading…
x
Reference in New Issue
Block a user