feat(init): add support for installing to a device (#225)

This commit is contained in:
Brad Beam 2018-11-30 08:21:08 -06:00 committed by Andrew Rynhard
parent a9c91d39f5
commit 79c96cf229
14 changed files with 832 additions and 60 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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