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:
Andrew Rynhard 2019-07-31 17:16:10 +00:00
parent 9c63f4ed0a
commit ca35b85300
54 changed files with 1378 additions and 1244 deletions

View File

@ -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)
}

View File

@ -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
}

View File

@ -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"))
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View 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
}

View File

@ -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"

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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"})
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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(),
),
)

View File

@ -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
}

View File

@ -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
}

View 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
}

View 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
`

View File

@ -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
// Manifest represents the instructions for preparing all block devices
// for an installation.
type Manifest struct {
Targets map[string][]*Target
}
var exists bool
if exists, err = Exists(data.Install.Boot.InstallDevice.Device); err != nil {
return err
// 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
}
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
}
// 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
}
// 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")
}
}
// 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
if err = bd.RereadPartitionTable(); err != nil {
return err
}
// Asset represents a file required by a target.
type Asset struct {
Source string
Destination string
for _, target := range targets {
if err = target.Format(); err != nil {
return errors.Wrap(err, "failed to format device")
}
}
}
// Partition creates a new partition on the specified device
return nil
}
// 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
}

View File

@ -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

View 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
}

View File

@ -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
}

View 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))
}

View 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
}

View 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
}

View File

@ -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"

View File

@ -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
}

View File

@ -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"

View 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
}

View 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
}

View 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
}

View File

@ -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"

View File

@ -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
}

View File

@ -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,

View 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
}

View 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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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")