feat: add osinstall cli utility (#368)

This commit is contained in:
Brad Beam 2019-02-23 15:18:52 -06:00 committed by Andrew Rynhard
parent 59e8fc2bce
commit 8ee9022b71
16 changed files with 862 additions and 6 deletions

View File

@ -25,6 +25,7 @@ policies:
- kernel
- osctl
- osd
- osinstall
- rootfs
- tools
- '*'

View File

@ -307,3 +307,15 @@ ARG APP
FROM scratch AS blockd
COPY --from=blockd-build /blockd /blockd
ENTRYPOINT ["/blockd"]
# The osinstall target builds the installer binaries.
FROM base AS osinstall-linux-amd64-build
ARG SHA
ARG TAG
ARG VERSION_PKG="github.com/autonomy/talos/internal/pkg/version"
WORKDIR /src/internal/app/osinstall
RUN GOOS=linux GOARCH=amd64 go build -a -ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG}" -o /osinstall-linux-amd64
RUN chmod +x /osinstall-linux-amd64
FROM scratch AS osinstall-linux-amd64
COPY --from=osinstall-linux-amd64-build /osinstall-linux-amd64 /osinstall-linux-amd64

View File

@ -34,7 +34,7 @@ COMMON_ARGS += --frontend-opt build-arg:GOLANG_VERSION=$(GOLANG_VERSION)
COMMON_ARGS += --frontend-opt build-arg:SHA=$(SHA)
COMMON_ARGS += --frontend-opt build-arg:TAG=$(TAG)
all: ci kernel initramfs rootfs osctl-linux-amd64 osctl-darwin-amd64 test lint docs installer
all: ci kernel initramfs rootfs osctl-linux-amd64 osctl-darwin-amd64 osinstall-linux-amd64 test lint docs installer
.PHONY: builddeps
builddeps: gitmeta buildctl
@ -165,6 +165,14 @@ osctl-darwin-amd64:
--frontend-opt target=$@ \
$(COMMON_ARGS)
osinstall-linux-amd64:
@buildctl --addr $(BUILDKIT_HOST) \
build \
--exporter=local \
--exporter-opt output=build \
--frontend-opt target=$@ \
$(COMMON_ARGS)
udevd:
@buildctl --addr $(BUILDKIT_HOST) \
build \

View File

@ -96,7 +96,7 @@ func (b *BareMetal) Prepare(data *userdata.UserData) (err error) {
// Install provides the functionality to install talos by
// download the necessary bits and write them to a target device
// nolint: gocyclo
// nolint: gocyclo, dupl
func (b *BareMetal) Install(data *userdata.UserData) error {
var err error
@ -333,6 +333,7 @@ func NewDevice(name string, label string, size uint, force bool, test bool, data
}
// Partition creates a new partition on the specified device
// nolint: dupl
func (d *Device) Partition() error {
var typeID string
switch d.Label {
@ -449,7 +450,7 @@ func (d *Device) Unmount() error {
}
// Simple extract function
// nolint: gocyclo
// nolint: gocyclo, dupl
func untar(tarball *os.File, dst string) error {
var input io.Reader

View File

@ -215,15 +215,18 @@ func (i *Initializer) Switch() (err error) {
return nil
}
// nolint: dupl
func mountpoints() (mountpoints *mount.Points, err error) {
mountpoints = mount.NewMountPoints()
for _, name := range []string{constants.RootPartitionLabel, constants.DataPartitionLabel} {
for _, name := range []string{constants.RootPartitionLabel, constants.DataPartitionLabel, constants.BootPartitionLabel} {
var target string
switch name {
case constants.RootPartitionLabel:
target = "/"
target = constants.RootMountPoint
case constants.DataPartitionLabel:
target = "/var"
target = constants.DataMountPoint
case constants.BootPartitionLabel:
target = constants.BootMountPoint
}
var dev *probe.ProbedBlockDevice

View File

@ -0,0 +1,51 @@
/* 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/. */
// nolint: dupl,golint
package cmd
import (
"log"
"github.com/autonomy/talos/internal/app/osinstall/internal/userdata"
"github.com/autonomy/talos/internal/pkg/install"
udata "github.com/autonomy/talos/internal/pkg/userdata"
"github.com/spf13/cobra"
)
// installCmd reads in a userData file and attempts to parse it
var installCmd = &cobra.Command{
Use: "install",
Short: "Install Talos to a specified disk",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
var ud *udata.UserData
var err error
if userdataFile != "" {
ud, err = userdata.UserData(userdataFile)
if err != nil {
log.Fatal(err)
}
err = install.Prepare(ud)
if err != nil {
log.Fatal(err)
}
}
err = install.Mount()
if err != nil {
log.Fatal(err)
}
err = install.Install(ud)
if err != nil {
log.Fatal(err)
}
},
}
func init() {
installCmd.Flags().StringVarP(&userdataFile, "userdata", "u", "", "path or url of userdata file")
rootCmd.AddCommand(installCmd)
}

View File

@ -0,0 +1,30 @@
/* 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 cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var userdataFile string
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "osinstall",
Short: "A CLI for installing Talos nodes",
Long: ``,
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

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/. */
// nolint: dupl,golint
package cmd
import (
"log"
"github.com/autonomy/talos/internal/app/osinstall/internal/userdata"
"github.com/spf13/cobra"
)
// validateCmd reads in a userData file and attempts to parse it
var validateCmd = &cobra.Command{
Use: "validate",
Short: "Validate userdata",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
ud, err := userdata.UserData(userdataFile)
if err != nil {
log.Fatal(err)
}
log.Println(ud)
},
}
func init() {
validateCmd.Flags().StringVarP(&userdataFile, "userdata", "u", "", "path or url of userdata file")
rootCmd.AddCommand(validateCmd)
}

View File

@ -0,0 +1,39 @@
/* 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 cmd
import (
"fmt"
"os"
"github.com/autonomy/talos/internal/pkg/version"
"github.com/spf13/cobra"
)
var (
shortVersion bool
)
// versionCmd represents the version command
var versionCmd = &cobra.Command{
Use: "version",
Short: "Prints the version",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
if shortVersion {
version.PrintShortVersion()
} else {
if err := version.PrintLongVersion(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
},
}
func init() {
versionCmd.Flags().BoolVar(&shortVersion, "short", false, "Print the short version")
rootCmd.AddCommand(versionCmd)
}

View File

@ -0,0 +1,23 @@
/* 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 userdata
import (
"strings"
ud "github.com/autonomy/talos/internal/pkg/userdata"
)
// UserData provides an abstraction to call the appropriate method to
// load user data
// TODO: Merge this in to internal/pkg/userdata
func UserData(location string) (userData *ud.UserData, err error) {
if strings.HasPrefix(location, "http") {
userData, err = ud.Download(location, nil)
} else {
userData, err = ud.Open(location)
}
return userData, err
}

View File

@ -0,0 +1,11 @@
/* 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 main
import "github.com/autonomy/talos/internal/app/osinstall/cmd"
func main() {
cmd.Execute()
}

View File

@ -99,3 +99,8 @@ func (bd *BlockDevice) RereadPartitionTable() error {
return nil
}
// Device returns the backing file for the block device
func (bd *BlockDevice) Device() *os.File {
return bd.f
}

View File

@ -20,14 +20,26 @@ const (
// the boot path.
BootPartitionLabel = "ESP"
// BootMountPoint is the label of the partition to use for mounting at
// the boot path.
BootMountPoint = "/boot"
// DataPartitionLabel is the label of the partition to use for mounting at
// the data path.
DataPartitionLabel = "DATA"
// DataMountPoint is the label of the partition to use for mounting at
// the data path.
DataMountPoint = "/var"
// RootPartitionLabel is the label of the partition to use for mounting at
// the root path.
RootPartitionLabel = "ROOT"
// RootMountPoint is the label of the partition to use for mounting at
// the root path.
RootMountPoint = "/"
// PATH defines all locations where executables are stored.
PATH = "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/opt/cni/bin"

View File

@ -0,0 +1,228 @@
/* 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"
"log"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"github.com/autonomy/talos/internal/pkg/constants"
"github.com/autonomy/talos/internal/pkg/userdata"
"github.com/pkg/errors"
)
// Install fetches the necessary data locations and copies or extracts
// to the target locations
// nolint: gocyclo
func Install(data *userdata.UserData) (err error) {
dataURLs := make(map[string][]string)
dataURLs[path.Join(constants.NewRoot, constants.RootMountPoint)] = data.Install.Root.Data
if data.Install.Boot != nil {
dataURLs[path.Join(constants.NewRoot, constants.BootMountPoint)] = data.Install.Boot.Data
}
var sourceFile *os.File
var destFile *os.File
var previousMountPoint string
for dest, urls := range dataURLs {
if dest != previousMountPoint {
log.Printf("Downloading assets for %s\n", dest)
previousMountPoint = dest
}
// Extract artifact if necessary, otherwise place at root of partition/filesystem
for _, artifact := range urls {
log.Println(artifact)
switch {
case strings.HasPrefix(artifact, "http"):
var u *url.URL
log.Printf("Downloading %s\n", artifact)
u, err = url.Parse(artifact)
if err != nil {
return err
}
sourceFile, err = download(u, dest)
if err != nil {
return err
}
// TODO add support for checksum validation of downloaded file
case strings.HasPrefix(artifact, "/"):
sourceFile, err = os.Open(artifact)
if err != nil {
return err
}
destFile, err = os.Create(filepath.Join(dest, filepath.Base(artifact)))
if err != nil {
return err
}
default:
// Local directories/links
link := strings.Split(artifact, ":")
if len(link) == 1 {
if err = os.MkdirAll(filepath.Join(dest, artifact), 0755); err != nil {
return err
}
} else {
if err = os.Symlink(link[1], filepath.Join(dest, link[0])); err != nil && !os.IsExist(err) {
return err
}
}
}
switch {
case strings.HasSuffix(sourceFile.Name(), ".tar") || strings.HasSuffix(sourceFile.Name(), ".tar.gz"):
log.Printf("Extracting %s\n", artifact)
err = untar(sourceFile, dest)
if err != nil {
return err
}
case strings.HasPrefix(sourceFile.Name(), "/"):
if _, err = io.Copy(destFile, sourceFile); err != nil {
return err
}
if err = destFile.Close(); err != nil {
return err
}
default:
}
if err = sourceFile.Close(); err != nil {
return err
}
if err = os.Remove(sourceFile.Name()); 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 *url.URL, base string) (*os.File, error) {
downloadedFile, 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 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)
return downloadedFile, err
}

View File

@ -0,0 +1,68 @@
/* 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/autonomy/talos/internal/pkg/blockdevice/probe"
"github.com/autonomy/talos/internal/pkg/constants"
"github.com/autonomy/talos/internal/pkg/mount"
"github.com/pkg/errors"
"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() (err error) {
log.Println("Discovering mountpoints")
var mp *mount.Points
if mp, err = mountpoints(); err != nil {
return errors.Errorf("error initializing block devices: %v", err)
}
log.Println("Attempting to mount filesystems")
iter := mp.Iter()
for iter.Next() {
log.Println("- ", iter.Value())
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() (mountpoints *mount.Points, err error) {
mountpoints = mount.NewMountPoints()
for _, name := range []string{constants.RootPartitionLabel, constants.DataPartitionLabel, constants.BootPartitionLabel} {
var target string
switch name {
case constants.RootPartitionLabel:
target = constants.RootMountPoint
case constants.DataPartitionLabel:
target = constants.DataMountPoint
case constants.BootPartitionLabel:
target = constants.BootMountPoint
}
var dev *probe.ProbedBlockDevice
if dev, err = probe.GetDevWithFileSystemLabel(name); err != nil {
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
}

View File

@ -0,0 +1,332 @@
/* 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 (
"io"
"log"
"os"
"runtime"
"strconv"
"github.com/autonomy/talos/internal/pkg/blockdevice"
"github.com/autonomy/talos/internal/pkg/blockdevice/filesystem/xfs"
"github.com/autonomy/talos/internal/pkg/blockdevice/probe"
"github.com/autonomy/talos/internal/pkg/blockdevice/table"
"github.com/autonomy/talos/internal/pkg/blockdevice/table/gpt/partition"
"github.com/autonomy/talos/internal/pkg/constants"
"github.com/autonomy/talos/internal/pkg/userdata"
"github.com/autonomy/talos/internal/pkg/version"
"github.com/pkg/errors"
)
// 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) {
log.Println("preparing ")
// Root Device Init
if data.Install.Root.Device == "" {
return errors.Errorf("%s", "install.rootdevice is required")
}
if data.Install.Root.Size == 0 {
// Set to 1G default for funzies
data.Install.Root.Size = 2048 * 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/"+version.Tag+"/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
}
// 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 512MB default for funzies
data.Install.Boot.Size = 512 * 1000 * 1000
}
if len(data.Install.Boot.Data) == 0 {
data.Install.Boot.Data = append(data.Install.Boot.Data, "https://github.com/autonomy/talos/releases/download/"+version.Tag+"/vmlinuz")
data.Install.Boot.Data = append(data.Install.Boot.Data, "https://github.com/autonomy/talos/releases/download/"+version.Tag+"/initramfs.xz")
}
}
// Verify that the disks are unused
// Maybe a simple check against bd.UUID is more appropriate?
if !data.Install.Wipe {
var dev *probe.ProbedBlockDevice
for _, device := range []string{data.Install.Boot.Device, data.Install.Root.Device, data.Install.Data.Device} {
dev, err = probe.GetDevWithFileSystemLabel(device)
if err != nil {
// We continue 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.
continue
}
if dev.SuperBlock != nil {
return errors.Errorf("target install device %s is not empty, found existing %s file system", device, dev.SuperBlock.Type())
}
}
}
// 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
if data.Install.Wipe {
log.Println("Preparing to zero out devices")
var zero *os.File
zero, err = os.Open("/dev/zero")
if err != nil {
return err
}
log.Println("Calculating total disk usage")
diskSizes := make(map[string]uint, len(devices))
for _, dev := range devices {
// Adding 264*512b to cover partition table size
// In theory, a GUID Partition Table disk can be up to 264 sectors in a single logical block in length.
// Logical blocks are commonly 512 bytes or one sector in size.
// TODO verify this against gpt.go
diskSizes[dev.Name] += dev.Size + 164010
}
log.Println("Zeroing out each disk")
var f *os.File
for dev, size := range diskSizes {
f, err = os.OpenFile(dev, os.O_RDWR, os.ModeDevice)
if err != nil {
return err
}
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
}
}
// 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.
log.Println("Opening block devices in preparation for partitioning")
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
}
// devices = Device
if data.Install.Wipe {
for _, label := range []string{constants.BootPartitionLabel, constants.RootPartitionLabel, constants.DataPartitionLabel} {
// Wipe disk
// Partition the disk
log.Printf("Partitioning %s - %s\n", devices[label].Name, label)
err = devices[label].Partition()
if err != nil {
return err
}
}
}
// Installation/preparation necessary
if data.Install != nil {
// uniqueDevices = blockdevice
seen := make(map[string]interface{})
for _, dev := range devices {
if _, ok := seen[dev.Name]; ok {
continue
}
seen[dev.Name] = nil
err = dev.PartitionTable.Write()
if err != nil {
return err
}
// Create the device files
log.Printf("Reread Partition Table %s\n", dev.Name)
if err = dev.BlockDevice.RereadPartitionTable(); err != nil {
log.Println("break here?")
return err
}
}
for _, dev := range devices {
// Create the filesystem
log.Printf("Formatting Partition %s - %s\n", dev.Name, dev.Label)
err = dev.Format()
if err != nil {
return err
}
}
}
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 creates 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
// nolint: dupl
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 errors.Errorf("%s", "unsupported cpu architecture")
}
case constants.DataPartitionLabel:
// Data Partition
typeID = "AF3DC60F-8384-7247-8E79-3D69D8477DE4"
default:
return errors.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, xfs.WithLabel(d.Label), xfs.WithForce(d.Force))
}