feat(init): add support for installing to a device (#225)
This commit is contained in:
parent
a9c91d39f5
commit
79c96cf229
@ -43,9 +43,10 @@ func kmsg(prefix string) (*os.File, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func initram() error {
|
||||
// Read the block devices and populate the mount point definitions.
|
||||
if err := mount.Init(constants.NewRoot); err != nil {
|
||||
// Read the special filesystems and populate the mount point definitions.
|
||||
if err := mount.InitSpecial(constants.NewRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
// Setup logging to /dev/kmsg.
|
||||
@ -66,6 +67,14 @@ func initram() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Perform rootfs/datafs installation if defined
|
||||
if err := p.Install(data); err != nil {
|
||||
return err
|
||||
}
|
||||
// Read the block devices and populate the mount point definitions.
|
||||
if err := mount.InitBlock(constants.NewRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("preparing the node for the platform: %s", p.Name())
|
||||
// Perform any tasks required by a particular platform.
|
||||
if err := p.Prepare(data); err != nil {
|
||||
|
@ -12,6 +12,10 @@ const (
|
||||
// NewRoot is the path where the switchroot target is mounted.
|
||||
NewRoot = "/root"
|
||||
|
||||
// BootPartitionLabel is the label of the partition to use for mounting at
|
||||
// the boot path.
|
||||
BootPartitionLabel = "ESP"
|
||||
|
||||
// DataPartitionLabel is the label of the partition to use for mounting at
|
||||
// the data path.
|
||||
DataPartitionLabel = "DATA"
|
||||
|
@ -6,12 +6,21 @@ import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// GrowFS expands an XFS filesystem to the maximum possible. The partition
|
||||
// GrowFS expands a XFS filesystem to the maximum possible. The partition
|
||||
// MUST be mounted, or this will fail.
|
||||
func GrowFS(partname string) error {
|
||||
return cmd("xfs_growfs", "-d", partname)
|
||||
}
|
||||
|
||||
// MakeFS creates a XFS filesystem on the specified partition
|
||||
func MakeFS(partname string, force bool) error {
|
||||
if force {
|
||||
return cmd("mkfs.xfs", "-f", partname)
|
||||
}
|
||||
|
||||
return cmd("mkfs.xfs", partname)
|
||||
}
|
||||
|
||||
func cmd(name string, arg ...string) error {
|
||||
cmd := exec.Command(name, arg...)
|
||||
cmd.Stdout = os.Stdout
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/autonomy/talos/src/initramfs/cmd/init/pkg/constants"
|
||||
@ -26,8 +25,6 @@ var (
|
||||
blockdevices map[string]*Point
|
||||
}
|
||||
|
||||
once sync.Once
|
||||
|
||||
special = map[string]*Point{
|
||||
"dev": {"devtmpfs", "/dev", "devtmpfs", unix.MS_NOSUID, "mode=0755"},
|
||||
"proc": {"proc", "/proc", "proc", unix.MS_NOSUID | unix.MS_NOEXEC | unix.MS_NODEV, ""},
|
||||
@ -48,27 +45,32 @@ type Point struct {
|
||||
|
||||
// BlockDevice represents the metadata on a block device probed by libblkid.
|
||||
type BlockDevice struct {
|
||||
dev string
|
||||
TYPE string
|
||||
UUID string
|
||||
LABEL string
|
||||
dev string
|
||||
TYPE string
|
||||
UUID string
|
||||
LABEL string
|
||||
PARTLABEL string
|
||||
PARTUUID string
|
||||
}
|
||||
|
||||
// Init initializes the mount points.
|
||||
func Init(s string) (err error) {
|
||||
once.Do(func() {
|
||||
instance = struct {
|
||||
special map[string]*Point
|
||||
blockdevices map[string]*Point
|
||||
}{
|
||||
special,
|
||||
map[string]*Point{},
|
||||
}
|
||||
})
|
||||
|
||||
if err = mountSpecialDevices(); err != nil {
|
||||
return
|
||||
// init initializes the instance metadata
|
||||
func init() {
|
||||
instance = struct {
|
||||
special map[string]*Point
|
||||
blockdevices map[string]*Point
|
||||
}{
|
||||
special,
|
||||
map[string]*Point{},
|
||||
}
|
||||
}
|
||||
|
||||
// InitSpecial initializes the special device mount points.
|
||||
func InitSpecial(s string) (err error) {
|
||||
return mountSpecialDevices()
|
||||
}
|
||||
|
||||
// InitBlock initializes the block device mount points.
|
||||
func InitBlock(s string) (err error) {
|
||||
blockdevices, err := probe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error probing block devices: %v", err)
|
||||
@ -150,16 +152,12 @@ func Mount(s string) error {
|
||||
|
||||
// Unmount unmounts the ROOT and DATA block devices.
|
||||
func Unmount() error {
|
||||
mountpoint, ok := instance.blockdevices[constants.DataPartitionLabel]
|
||||
if ok {
|
||||
if err := unix.Unmount(mountpoint.target, 0); err != nil {
|
||||
return fmt.Errorf("unmount mount point %s: %v", mountpoint.target, err)
|
||||
}
|
||||
}
|
||||
mountpoint, ok = instance.blockdevices[constants.RootPartitionLabel]
|
||||
if ok {
|
||||
if err := unix.Unmount(mountpoint.target, 0); err != nil {
|
||||
return fmt.Errorf("unmount mount point %s: %v", mountpoint.target, err)
|
||||
for _, disk := range []string{constants.RootPartitionLabel, constants.DataPartitionLabel} {
|
||||
mountpoint, ok := instance.blockdevices[disk]
|
||||
if ok {
|
||||
if err := unix.Unmount(mountpoint.target, 0); err != nil {
|
||||
return fmt.Errorf("unmount mount point %s: %v", mountpoint.target, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,7 +208,7 @@ func fixDataPartition(blockdevices []*BlockDevice) error {
|
||||
// nolint: errcheck
|
||||
defer bd.Close()
|
||||
|
||||
pt, err := bd.PartitionTable()
|
||||
pt, err := bd.PartitionTable(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -237,7 +235,7 @@ func fixDataPartition(blockdevices []*BlockDevice) error {
|
||||
|
||||
// Rereading the partition table requires that all partitions be unmounted
|
||||
// or it will fail with EBUSY.
|
||||
if err := bd.RereadPartitionTable(devname); err != nil {
|
||||
if err := bd.RereadPartitionTable(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -289,11 +287,10 @@ func mountBlockDevices(blockdevices []*BlockDevice, s string) (err error) {
|
||||
func probe() (b []*BlockDevice, err error) {
|
||||
b = []*BlockDevice{}
|
||||
|
||||
if err := appendBlockDeviceWithLabel(&b, constants.RootPartitionLabel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := appendBlockDeviceWithLabel(&b, constants.DataPartitionLabel); err != nil {
|
||||
return nil, err
|
||||
for _, disk := range []string{constants.RootPartitionLabel, constants.DataPartitionLabel} {
|
||||
if err := appendBlockDeviceWithLabel(&b, disk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return b, nil
|
||||
@ -309,7 +306,7 @@ func appendBlockDeviceWithLabel(b *[]*BlockDevice, value string) error {
|
||||
return fmt.Errorf("no device with attribute \"LABEL=%s\" found", value)
|
||||
}
|
||||
|
||||
blockDevice, err := probeDevice(devname)
|
||||
blockDevice, err := ProbeDevice(devname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to probe block device %q: %v", devname, err)
|
||||
}
|
||||
@ -319,7 +316,8 @@ func appendBlockDeviceWithLabel(b *[]*BlockDevice, value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func probeDevice(devname string) (*BlockDevice, error) {
|
||||
// ProbeDevice looks up UUID/TYPE/LABEL/PARTLABEL/PARTUUID from a block device
|
||||
func ProbeDevice(devname string) (*BlockDevice, error) {
|
||||
pr, err := blkid.NewProbeFromFilename(devname)
|
||||
defer blkid.FreeProbe(pr)
|
||||
if err != nil {
|
||||
@ -338,12 +336,22 @@ func probeDevice(devname string) (*BlockDevice, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
PARTLABEL, err := blkid.ProbeLookupValue(pr, "PARTLABEL", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
PARTUUID, err := blkid.ProbeLookupValue(pr, "PARTUUID", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BlockDevice{
|
||||
dev: devname,
|
||||
UUID: UUID,
|
||||
TYPE: TYPE,
|
||||
LABEL: LABEL,
|
||||
dev: devname,
|
||||
UUID: UUID,
|
||||
TYPE: TYPE,
|
||||
LABEL: LABEL,
|
||||
PARTLABEL: PARTLABEL,
|
||||
PARTUUID: PARTUUID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -3,14 +3,28 @@
|
||||
package baremetal
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/autonomy/talos/src/initramfs/cmd/init/pkg/constants"
|
||||
"github.com/autonomy/talos/src/initramfs/cmd/init/pkg/fs/xfs"
|
||||
"github.com/autonomy/talos/src/initramfs/cmd/init/pkg/kernel"
|
||||
"github.com/autonomy/talos/src/initramfs/cmd/init/pkg/mount"
|
||||
"github.com/autonomy/talos/src/initramfs/cmd/init/pkg/mount/blkid"
|
||||
"github.com/autonomy/talos/src/initramfs/pkg/blockdevice"
|
||||
"github.com/autonomy/talos/src/initramfs/pkg/blockdevice/table"
|
||||
"github.com/autonomy/talos/src/initramfs/pkg/blockdevice/table/gpt/partition"
|
||||
"github.com/autonomy/talos/src/initramfs/pkg/userdata"
|
||||
"golang.org/x/sys/unix"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
@ -74,3 +88,428 @@ func (b *BareMetal) UserData() (data userdata.UserData, err error) {
|
||||
func (b *BareMetal) Prepare(data userdata.UserData) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Install provides the functionality to install talos by
|
||||
// download the necessary bits and write them to a target device
|
||||
// nolint: gocyclo
|
||||
func (b *BareMetal) Install(data userdata.UserData) error {
|
||||
var err error
|
||||
|
||||
// No installation necessary
|
||||
if data.Install == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Root Device Init
|
||||
if data.Install.Root.Device == "" {
|
||||
return fmt.Errorf("%s", "install.rootdevice is required")
|
||||
}
|
||||
|
||||
if data.Install.Root.Size == 0 {
|
||||
// Set to 1G default for funzies
|
||||
data.Install.Root.Size = 1024 * 1000 * 1000 * 1000
|
||||
}
|
||||
|
||||
if len(data.Install.Root.Data) == 0 {
|
||||
// Should probably have a canonical location to fetch rootfs - github?/s3?
|
||||
// need to figure out how to download latest instead of hardcoding
|
||||
data.Install.Root.Data = append(data.Install.Root.Data, "https://github.com/autonomy/talos/releases/download/v0.1.0-alpha.13/rootfs.tar.gz")
|
||||
}
|
||||
|
||||
// Data Device Init
|
||||
if data.Install.Data.Device == "" {
|
||||
data.Install.Data.Device = data.Install.Root.Device
|
||||
}
|
||||
|
||||
if data.Install.Data.Size == 0 {
|
||||
// Set to 1G default for funzies
|
||||
data.Install.Data.Size = 1024 * 1000 * 1000 * 1000
|
||||
}
|
||||
|
||||
if len(data.Install.Data.Data) == 0 {
|
||||
// Stub out the dir structure for `/var`
|
||||
data.Install.Data.Data = append(data.Install.Data.Data, []string{"cache", "lib", "lib/misc", "log", "mail", "opt", "run", "spool", "tmp"}...)
|
||||
}
|
||||
|
||||
// Boot Device Init
|
||||
if data.Install.Boot != nil {
|
||||
if data.Install.Boot.Device == "" {
|
||||
data.Install.Boot.Device = data.Install.Root.Device
|
||||
}
|
||||
if data.Install.Boot.Size == 0 {
|
||||
// Set to 1G default for funzies
|
||||
data.Install.Boot.Size = 1024 * 1000 * 1000 * 1000
|
||||
}
|
||||
if len(data.Install.Data.Data) == 0 {
|
||||
data.Install.Boot.Data = append(data.Install.Boot.Data, "https://github.com/autonomy/talos/releases/download/v0.1.0-alpha.13/vmlinuz")
|
||||
data.Install.Boot.Data = append(data.Install.Boot.Data, "https://github.com/autonomy/talos/releases/download/v0.1.0-alpha.13/initramfs.xz")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the disks are unused
|
||||
// Maybe a simple check against bd.UUID is more appropriate?
|
||||
if !data.Install.Wipe {
|
||||
var bd *mount.BlockDevice
|
||||
for _, device := range []string{data.Install.Boot.Device, data.Install.Root.Device, data.Install.Data.Device} {
|
||||
bd, err = mount.ProbeDevice(device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bd.LABEL == "" || bd.TYPE == "" || bd.PARTLABEL == "" {
|
||||
return fmt.Errorf("%s: %s", "target install device is not empty", device)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Create a map of all the devices we need to be concerned with
|
||||
devices := make(map[string]*Device)
|
||||
labeldev := make(map[string]string)
|
||||
|
||||
// PR: Should we only allow boot device creation if data.Install.Wipe?
|
||||
if data.Install.Boot.Device != "" {
|
||||
devices[constants.BootPartitionLabel] = NewDevice(data.Install.Boot.Device, constants.BootPartitionLabel, data.Install.Boot.Size, data.Install.Wipe, false, data.Install.Boot.Data)
|
||||
labeldev[constants.BootPartitionLabel] = data.Install.Boot.Device
|
||||
}
|
||||
|
||||
devices[constants.RootPartitionLabel] = NewDevice(data.Install.Root.Device, constants.RootPartitionLabel, data.Install.Root.Size, data.Install.Wipe, false, data.Install.Root.Data)
|
||||
labeldev[constants.RootPartitionLabel] = data.Install.Root.Device
|
||||
|
||||
devices[constants.DataPartitionLabel] = NewDevice(data.Install.Data.Device, constants.DataPartitionLabel, data.Install.Data.Size, data.Install.Wipe, false, data.Install.Data.Data)
|
||||
labeldev[constants.DataPartitionLabel] = data.Install.Data.Device
|
||||
|
||||
// Use the below to only open a block device once
|
||||
uniqueDevices := make(map[string]*blockdevice.BlockDevice)
|
||||
|
||||
// Associate block device to a partition table. This allows us to
|
||||
// make use of a single partition table across an entire block device.
|
||||
partitionTables := make(map[*blockdevice.BlockDevice]table.PartitionTable)
|
||||
for label, device := range labeldev {
|
||||
if dev, ok := uniqueDevices[device]; ok {
|
||||
devices[label].BlockDevice = dev
|
||||
devices[label].PartitionTable = partitionTables[dev]
|
||||
continue
|
||||
}
|
||||
|
||||
var bd *blockdevice.BlockDevice
|
||||
|
||||
bd, err = blockdevice.Open(device, blockdevice.WithNewGPT(data.Install.Wipe))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
defer bd.Close()
|
||||
|
||||
var pt table.PartitionTable
|
||||
pt, err = bd.PartitionTable(!data.Install.Wipe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uniqueDevices[device] = bd
|
||||
partitionTables[bd] = pt
|
||||
|
||||
devices[label].BlockDevice = bd
|
||||
devices[label].PartitionTable = pt
|
||||
}
|
||||
|
||||
for _, dev := range devices {
|
||||
// Partition the disk
|
||||
err = dev.Partition()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// Create the device files
|
||||
err = dev.BlockDevice.RereadPartitionTable()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// Create the filesystem
|
||||
err = dev.Format()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// Mount up the new filesystem
|
||||
err = dev.Mount()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// Install the necessary bits/files
|
||||
// // download / copy kernel bits to boot
|
||||
// // download / extract rootfsurl
|
||||
// // handle data dirs creation
|
||||
err = dev.Install()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// Unmount the disk so we can proceed to the next phase
|
||||
// as if there was no installation phase
|
||||
err = dev.Unmount()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Device represents a single partition.
|
||||
type Device struct {
|
||||
DataURLs []string
|
||||
Label string
|
||||
MountBase string
|
||||
Name string
|
||||
|
||||
// This seems overkill to save partition table
|
||||
// when we can get partition table from BlockDevice
|
||||
// but we want to have a shared partition table for each
|
||||
// device so we can properly append partitions and have
|
||||
// an atomic write partition operation
|
||||
PartitionTable table.PartitionTable
|
||||
|
||||
// This guy might be overkill but we can clean up later
|
||||
// Made up of Name + part.No(), so maybe it's worth
|
||||
// just storing part.No() and adding a method d.PartName()
|
||||
PartitionName string
|
||||
|
||||
Size uint
|
||||
|
||||
BlockDevice *blockdevice.BlockDevice
|
||||
|
||||
Force bool
|
||||
Test bool
|
||||
}
|
||||
|
||||
// NewDevice create a Device with basic metadata. BlockDevice and PartitionTable
|
||||
// need to be set outsite of this.
|
||||
func NewDevice(name string, label string, size uint, force bool, test bool, data []string) *Device {
|
||||
return &Device{
|
||||
DataURLs: data,
|
||||
Force: force,
|
||||
Label: label,
|
||||
MountBase: "/tmp",
|
||||
Name: name,
|
||||
Size: size,
|
||||
Test: test,
|
||||
}
|
||||
}
|
||||
|
||||
// Partition creates a new partition on the specified device
|
||||
func (d *Device) Partition() error {
|
||||
var typeID string
|
||||
switch d.Label {
|
||||
case constants.BootPartitionLabel:
|
||||
// EFI System Partition
|
||||
typeID = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
|
||||
case constants.RootPartitionLabel:
|
||||
// Root Partition
|
||||
switch runtime.GOARCH {
|
||||
case "386":
|
||||
typeID = "44479540-F297-41B2-9AF7-D131D5F0458A"
|
||||
case "amd64":
|
||||
typeID = "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709"
|
||||
default:
|
||||
return fmt.Errorf("%s", "unsupported cpu architecture")
|
||||
}
|
||||
case constants.DataPartitionLabel:
|
||||
// Data Partition
|
||||
typeID = "AF3DC60F-8384-7247-8E79-3D69D8477DE4"
|
||||
default:
|
||||
return fmt.Errorf("%s", "unknown partition label")
|
||||
}
|
||||
|
||||
part, err := d.PartitionTable.Add(uint64(d.Size), partition.WithPartitionType(typeID), partition.WithPartitionName(d.Label), partition.WithPartitionTest(d.Test))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.PartitionName = d.Name + strconv.Itoa(int(part.No()))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format creates a xfs filesystem on the device/partition
|
||||
func (d *Device) Format() error {
|
||||
return xfs.MakeFS(d.PartitionName, d.Force)
|
||||
}
|
||||
|
||||
// Mount will create the mountpoint and mount the partition to MountBase/Label
|
||||
// ex, /tmp/DATA
|
||||
func (d *Device) Mount() error {
|
||||
var err error
|
||||
if err = os.MkdirAll(filepath.Join(d.MountBase, d.Label), os.ModeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = unix.Mount(d.PartitionName, filepath.Join(d.MountBase, d.Label), "xfs", 0, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Install downloads the necessary artifacts and creates the necessary directories
|
||||
// for installation of the OS
|
||||
// nolint: gocyclo
|
||||
func (d *Device) Install() error {
|
||||
mountpoint := filepath.Join(d.MountBase, d.Label)
|
||||
|
||||
for _, artifact := range d.DataURLs {
|
||||
// Extract artifact if necessary, otherwise place at root of partition/filesystem
|
||||
switch {
|
||||
case strings.HasPrefix(artifact, "http"):
|
||||
u, err := url.Parse(artifact)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := downloader(u, d.MountBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO add support for checksum validation of downloaded file
|
||||
|
||||
// nolint: errcheck
|
||||
defer os.Remove(out.Name())
|
||||
// nolint: errcheck
|
||||
defer out.Close()
|
||||
|
||||
switch {
|
||||
case strings.HasSuffix(artifact, ".tar") || strings.HasSuffix(artifact, ".tar.gz"):
|
||||
// extract tar
|
||||
err = untar(out, mountpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
// nothing special, download and go
|
||||
dst := strings.Split(artifact, "/")
|
||||
err = os.Rename(out.Name(), filepath.Join(mountpoint, dst[len(dst)-1]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Local directories
|
||||
// TODO: maybe look at url-ish style to provide
|
||||
// additional flexibility
|
||||
// file:// dir://
|
||||
if err := os.MkdirAll(artifact, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmount unmounts the partition
|
||||
func (d *Device) Unmount() error {
|
||||
return unix.Unmount(filepath.Join(d.MountBase, d.Label), 0)
|
||||
}
|
||||
|
||||
// Simple extract function
|
||||
// nolint: gocyclo
|
||||
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 output *os.File
|
||||
|
||||
output, err = os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = io.Copy(output, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = output.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func downloader(artifact *url.URL, base string) (*os.File, error) {
|
||||
out, err := os.Create(filepath.Join(base, filepath.Base(artifact.Path)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the data
|
||||
resp, err := http.Get(artifact.String())
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
// nolint: errcheck
|
||||
out.Close()
|
||||
return nil, fmt.Errorf("Failed to download %s, got %d", artifact, resp.StatusCode)
|
||||
}
|
||||
|
||||
// Write the body to file
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Reset out file position to 0 so we can immediately read from it
|
||||
_, err = out.Seek(0, 0)
|
||||
|
||||
return out, err
|
||||
}
|
||||
|
264
src/initramfs/cmd/init/pkg/platform/baremetal/baremetal_test.go
Normal file
264
src/initramfs/cmd/init/pkg/platform/baremetal/baremetal_test.go
Normal file
@ -0,0 +1,264 @@
|
||||
package baremetal
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/autonomy/talos/src/initramfs/cmd/init/pkg/constants"
|
||||
"github.com/autonomy/talos/src/initramfs/pkg/blockdevice"
|
||||
)
|
||||
|
||||
// nolint: gocyclo
|
||||
func TestUnTar(t *testing.T) {
|
||||
f, err := os.Open("testdata/talos_test.tar.gz")
|
||||
if err != nil {
|
||||
t.Error("Failed to open file", err)
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
defer f.Close()
|
||||
|
||||
out, err := ioutil.TempDir("", "testbaremetal")
|
||||
if err != nil {
|
||||
t.Error("Failed to open file", err)
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
defer os.RemoveAll(out)
|
||||
err = untar(f, out)
|
||||
if err != nil {
|
||||
t.Error("Failed to untar file", err)
|
||||
}
|
||||
|
||||
var files []os.FileInfo
|
||||
|
||||
err = filepath.Walk(out, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
t.Logf("prevent panic by handling failure accessing a path %q: %v\n", path, err)
|
||||
return err
|
||||
}
|
||||
// Skip PWD
|
||||
if info.IsDir() && info.Name() == filepath.Base(out) {
|
||||
return nil
|
||||
}
|
||||
|
||||
files = append(files, info)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Error("Failed to walk dir", err)
|
||||
}
|
||||
|
||||
expected := map[string]interface{}{"talos": nil, "talosdir": nil, "talosfile": nil}
|
||||
|
||||
if len(files) != len(expected) {
|
||||
t.Errorf("Did not get back expected number of files - expected %d got %d", len(expected), len(files))
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if _, ok := expected[file.Name()]; !ok {
|
||||
t.Errorf("Unexpected file %s", file.Name())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNewDevice(t *testing.T) {
|
||||
dev, ts := newdev(t, constants.DataPartitionLabel)
|
||||
|
||||
// nolint: errcheck
|
||||
defer ts.Close()
|
||||
// nolint: errcheck
|
||||
defer os.RemoveAll(dev.MountBase)
|
||||
|
||||
if err := dev.Install(); err != nil {
|
||||
t.Error("Install failed", err)
|
||||
}
|
||||
|
||||
var files []os.FileInfo
|
||||
err := filepath.Walk(filepath.Join(dev.MountBase, dev.Label), func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
t.Logf("prevent panic by handling failure accessing a path %q: %v\n", path, err)
|
||||
return err
|
||||
}
|
||||
// Skip PWD
|
||||
if info.IsDir() && info.Name() == dev.Label {
|
||||
return nil
|
||||
}
|
||||
|
||||
files = append(files, info)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Error("Failed to walk dir", err)
|
||||
}
|
||||
|
||||
expected := map[string]interface{}{"talos": nil, "talosdir": nil, "talosfile": nil}
|
||||
|
||||
if len(files) != len(expected) {
|
||||
t.Errorf("Did not get back expected number of files - expected %d got %d", len(expected), len(files))
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if _, ok := expected[file.Name()]; !ok {
|
||||
t.Errorf("Unexpected file %s", file.Name())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPartition(t *testing.T) {
|
||||
f := newbd(t)
|
||||
|
||||
// nolint: errcheck
|
||||
defer f.Close()
|
||||
// nolint: errcheck
|
||||
defer os.RemoveAll(f.Name())
|
||||
|
||||
dev := NewDevice(f.Name(), constants.RootPartitionLabel, 512*1000*1000, true, true, []string{})
|
||||
bd, err := blockdevice.Open(dev.Name, blockdevice.WithNewGPT(true))
|
||||
if err != nil {
|
||||
t.Error("Failed to create block device", err)
|
||||
}
|
||||
|
||||
pt, err := bd.PartitionTable(false)
|
||||
if err != nil {
|
||||
t.Error("Failed to get partition table", err)
|
||||
}
|
||||
dev.PartitionTable = pt
|
||||
|
||||
err = dev.Partition()
|
||||
if err != nil {
|
||||
t.Error("Failed to create new partition", err)
|
||||
}
|
||||
|
||||
err = dev.PartitionTable.Write()
|
||||
if err != nil {
|
||||
t.Error("Failed to write partition to disk", err)
|
||||
}
|
||||
|
||||
// Since we're testing with a file and not a device
|
||||
// there won't be a tailing `1` at the end to denote
|
||||
// the partition
|
||||
dev.PartitionName = dev.Name
|
||||
|
||||
/*
|
||||
this is janky
|
||||
|
||||
We're creating a partition on a file
|
||||
But we aren't actually creating a new file/device file
|
||||
to represent the new partition. So we're going to overwrite
|
||||
the entire disk.
|
||||
*/
|
||||
err = dev.Format()
|
||||
if err != nil {
|
||||
t.Error("Failed to format partition", err)
|
||||
}
|
||||
}
|
||||
|
||||
func newdev(t *testing.T, label string) (*Device, *httptest.Server) {
|
||||
// Set up a simple http server to serve a simple asset
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", http.FileServer(http.Dir("testdata")))
|
||||
ts := httptest.NewServer(mux)
|
||||
|
||||
// Set up a test for dir creation and file download
|
||||
dev := NewDevice("testdev", label, 1024, true, true, []string{"lala", ts.URL + "/talos_test.tar.gz"})
|
||||
|
||||
out, err := ioutil.TempDir("", "testbaremetal")
|
||||
if err != nil {
|
||||
t.Error("Failed to open file", err)
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
dev.MountBase = out
|
||||
|
||||
if err := os.MkdirAll(filepath.Join(out, dev.Label), 0755); err != nil {
|
||||
t.Fatalf("Failed to set up 'mountpoint' for %s", filepath.Join(out, dev.Label))
|
||||
}
|
||||
|
||||
return dev, ts
|
||||
}
|
||||
|
||||
func newbd(t *testing.T) *os.File {
|
||||
|
||||
tmpfile, err := ioutil.TempFile("", "testbaremetal")
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create tempfile", err)
|
||||
}
|
||||
|
||||
// Create a 3G sparse file so we can partition it
|
||||
if err = tmpfile.Truncate(3e9); err != nil {
|
||||
t.Fatal("Failed to truncate tempfile", err)
|
||||
}
|
||||
|
||||
_, err = tmpfile.Seek(0, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to reset tmpfile read position", err)
|
||||
}
|
||||
|
||||
return tmpfile
|
||||
}
|
||||
|
||||
// Unsure if this function is still needed
|
||||
// Leaving it in here in case we want to pick loopback device support
|
||||
// back up for testing
|
||||
/*
|
||||
func newloop(t *testing.T, backer *os.File) *os.File {
|
||||
err := unix.Mknod("/dev/loop1", 0660, 7)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create loopback device", err)
|
||||
}
|
||||
|
||||
loopFile, err := os.Open("/dev/loop1")
|
||||
if err != nil {
|
||||
t.Fatal("Failed to open loopback device", err)
|
||||
}
|
||||
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, loopFile.Fd(), 0x4C00, backer.Fd())
|
||||
if errno == 0 {
|
||||
type Info struct {
|
||||
Device uint64
|
||||
INode uint64
|
||||
RDevice uint64
|
||||
Offset uint64
|
||||
SizeLimit uint64
|
||||
Number uint32
|
||||
EncryptType uint32
|
||||
EncryptKeySize uint32
|
||||
Flags uint32
|
||||
FileName [64]byte
|
||||
CryptName [64]byte
|
||||
EncryptKey [32]byte
|
||||
Init [2]uint64
|
||||
}
|
||||
info := Info{}
|
||||
copy(info.FileName[:], []byte(backer.Name()))
|
||||
info.Offset = 0
|
||||
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, loopFile.Fd(), 0x4C04, uintptr(unsafe.Pointer(&info)))
|
||||
if errno == unix.ENXIO {
|
||||
// nolint: errcheck
|
||||
unix.Syscall(unix.SYS_IOCTL, loopFile.Fd(), 0x4C01, 0)
|
||||
t.Error("device not backed by a file")
|
||||
} else if errno != 0 {
|
||||
// nolint: errcheck
|
||||
unix.Syscall(unix.SYS_IOCTL, loopFile.Fd(), 0x4C01, 0)
|
||||
t.Errorf("could not get info about (err: %d): %v", errno, errno)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = loopFile.Seek(0, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to reset tmpfile read position", err)
|
||||
}
|
||||
|
||||
return loopFile
|
||||
}
|
||||
*/
|
BIN
src/initramfs/cmd/init/pkg/platform/baremetal/testdata/talos_test.tar.gz
vendored
Normal file
BIN
src/initramfs/cmd/init/pkg/platform/baremetal/testdata/talos_test.tar.gz
vendored
Normal file
Binary file not shown.
@ -147,3 +147,8 @@ func hostname() (err error) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Install installs talos if necessary
|
||||
func (a *AWS) Install(data userdata.UserData) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
@ -71,3 +71,8 @@ func (vmw *VMware) UserData() (data userdata.UserData, err error) {
|
||||
func (vmw *VMware) Prepare(data userdata.UserData) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Install installs talos
|
||||
func (vmw *VMware) Install(data userdata.UserData) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ type Platform interface {
|
||||
Name() string
|
||||
UserData() (userdata.UserData, error)
|
||||
Prepare(userdata.UserData) error
|
||||
Install(userdata.UserData) error
|
||||
}
|
||||
|
||||
// NewPlatform is a helper func for discovering the current platform.
|
||||
|
@ -35,6 +35,8 @@ func Open(devname string, setters ...Option) (*BlockDevice, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bd.f = f
|
||||
|
||||
if opts.CreateGPT {
|
||||
gpt := gpt.NewGPT(devname, f)
|
||||
table, e := gpt.New()
|
||||
@ -67,26 +69,26 @@ func (bd *BlockDevice) Close() error {
|
||||
}
|
||||
|
||||
// PartitionTable returns the block device partition table.
|
||||
func (bd *BlockDevice) PartitionTable() (table.PartitionTable, error) {
|
||||
func (bd *BlockDevice) PartitionTable(read bool) (table.PartitionTable, error) {
|
||||
if bd.table == nil {
|
||||
return nil, fmt.Errorf("missing partition table")
|
||||
}
|
||||
|
||||
if !read {
|
||||
return bd.table, nil
|
||||
}
|
||||
|
||||
return bd.table, bd.table.Read()
|
||||
}
|
||||
|
||||
// RereadPartitionTable invokes the BLKRRPART ioctl to have the kernel read the
|
||||
// partition table.
|
||||
func (bd *BlockDevice) RereadPartitionTable(devname string) error {
|
||||
f, err := os.Open(devname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (bd *BlockDevice) RereadPartitionTable() error {
|
||||
unix.Sync()
|
||||
if _, _, ret := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.BLKRRPART, 0); ret != 0 {
|
||||
if _, _, ret := unix.Syscall(unix.SYS_IOCTL, bd.f.Fd(), unix.BLKRRPART, 0); ret != 0 {
|
||||
return fmt.Errorf("re-read partition table: %v", ret)
|
||||
}
|
||||
if err := f.Sync(); err != nil {
|
||||
if err := bd.f.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
unix.Sync()
|
||||
|
@ -100,7 +100,7 @@ func (gpt *GPT) Write() error {
|
||||
}
|
||||
|
||||
if err := gpt.writeSecondary(partitions); err != nil {
|
||||
return errors.Errorf("failed to write primary table: %v", err)
|
||||
return errors.Errorf("failed to write secondary table: %v", err)
|
||||
}
|
||||
|
||||
for _, p := range gpt.Partitions() {
|
||||
@ -298,7 +298,7 @@ func (gpt *GPT) Add(size uint64, setters ...interface{}) (table.Partition, error
|
||||
}
|
||||
|
||||
partition := &partition.Partition{
|
||||
IsNew: true,
|
||||
IsNew: !opts.Test,
|
||||
Type: opts.Type,
|
||||
ID: uuid,
|
||||
FirstLBA: start,
|
||||
|
@ -8,17 +8,18 @@ import (
|
||||
type Options struct {
|
||||
Type uuid.UUID
|
||||
Name string
|
||||
Test bool
|
||||
}
|
||||
|
||||
// Option is the functional option func.
|
||||
type Option func(*Options)
|
||||
|
||||
// WithPartitionType sets the partition type.
|
||||
func WithPartitionType(o [16]byte) Option {
|
||||
func WithPartitionType(id string) Option {
|
||||
return func(args *Options) {
|
||||
// TODO: An Option should return an error.
|
||||
// nolint: errcheck
|
||||
guuid, _ := uuid.FromBytes(o[:])
|
||||
guuid, _ := uuid.Parse(id)
|
||||
args.Type = guuid
|
||||
}
|
||||
}
|
||||
@ -30,16 +31,25 @@ func WithPartitionName(o string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithPartitionTest allows us to disable the IsNew partition
|
||||
// check. This is only intended to be used for tests.
|
||||
func WithPartitionTest(t bool) Option {
|
||||
return func(args *Options) {
|
||||
args.Test = t
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultOptions initializes a Options struct with default values.
|
||||
func NewDefaultOptions(setters ...interface{}) *Options {
|
||||
// Default to data type "af3dc60f-8384-7247-8e79-3d69d8477de4"
|
||||
// TODO: An Option should return an error.
|
||||
// nolint: errcheck
|
||||
guuid, _ := uuid.FromBytes([]byte{0Xaf, 0X3d, 0Xc6, 0X0f, 0X83, 0X84, 0X72, 0X47, 0X8e, 0X79, 0X3d, 0X69, 0Xd8, 0X47, 0X7d, 0Xe4})
|
||||
guuid, _ := uuid.Parse("af3dc60f-8384-7247-8e79-3d69d8477de4")
|
||||
|
||||
opts := &Options{
|
||||
Type: guuid,
|
||||
Name: "",
|
||||
Test: false,
|
||||
}
|
||||
|
||||
for _, setter := range setters {
|
||||
|
@ -31,6 +31,7 @@ type UserData struct {
|
||||
Files []*File `yaml:"files"`
|
||||
Debug bool `yaml:"debug"`
|
||||
Env Env `yaml:"env,omitempty"`
|
||||
Install *Install `yaml:"install,omitempty"`
|
||||
}
|
||||
|
||||
// Security represents the set of options available to configure security.
|
||||
@ -76,6 +77,21 @@ type File struct {
|
||||
Path string `yaml:"path"`
|
||||
}
|
||||
|
||||
// Install represents the installation options for preparing a node
|
||||
type Install struct {
|
||||
Boot *InstallDevice `yaml:"boot,omitempty"`
|
||||
Root *InstallDevice `yaml:"root"`
|
||||
Data *InstallDevice `yaml:"data,omitempty"`
|
||||
Wipe bool `yaml:"wipe"`
|
||||
}
|
||||
|
||||
// InstallDevice represents the specific directions for each partition
|
||||
type InstallDevice struct {
|
||||
Device string `yaml:"device,omitempty"`
|
||||
Size uint `yaml:"size,omitempty"`
|
||||
Data []string `yaml:"data,omitempty"`
|
||||
}
|
||||
|
||||
// Init describes the configuration of the init service.
|
||||
type Init struct {
|
||||
ContainerRuntime string `yaml:"containerRuntime,omitempty"`
|
||||
|
Loading…
x
Reference in New Issue
Block a user