feat: install bootloader to block device (#455)

Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
This commit is contained in:
Andrew Rynhard 2019-03-18 14:01:58 -07:00 committed by GitHub
parent 6ae6118d2e
commit 31a00ef73a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 190 additions and 21 deletions

View File

@ -39,6 +39,9 @@ RUN ln -s /toolchain/etc/ssl/certs/ca-certificates /etc/ssl/certs/ca-certificate
# fhs
COPY hack/scripts/fhs.sh /bin
RUN fhs.sh /rootfs
# ca-certificates
RUN mkdir -p /rootfs/etc/ssl/certs
RUN curl -o /rootfs/etc/ssl/certs/ca-certificates.crt https://curl.haxx.se/ca/cacert.pem
# xfsprogs
WORKDIR /tmp/xfsprogs
RUN curl -L https://www.kernel.org/pub/linux/utils/fs/xfs/xfsprogs/xfsprogs-4.18.0.tar.xz | tar -xJ --strip-components=1
@ -66,7 +69,7 @@ RUN curl -L https://www.kernel.org/pub/linux/utils/boot/syslinux/syslinux-6.03.t
RUN ln -s /toolchain/bin/pwd /bin/pwd && \
make installer && \
cp /tmp/syslinux/bios/extlinux/extlinux /rootfs/bin && \
cp /tmp/syslinux/bios/mbr/gptmbr.bin /rootfs/share
cp /tmp/syslinux/efi64/mbr/gptmbr.bin /rootfs/share
# golang
ENV GOROOT /toolchain/usr/local/go
ENV GOPATH /toolchain/go
@ -157,9 +160,6 @@ RUN ../configure \
RUN make -j $(($(nproc) / 2))
RUN make install DESTDIR=/rootfs
RUN make install DESTDIR=/toolchain
# ca-certificates
RUN mkdir -p /rootfs/etc/ssl/certs
RUN curl -o /rootfs/etc/ssl/certs/ca-certificates.crt https://curl.haxx.se/ca/cacert.pem
# crictl
RUN curl -L https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.13.0/crictl-v1.13.0-linux-amd64.tar.gz | tar -xz -C /rootfs/bin
# containerd

View File

@ -83,6 +83,15 @@ 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, dupl
func (b *BareMetal) Install(data *userdata.UserData) error {
return install.Install(data)
func (b *BareMetal) Install(data *userdata.UserData) (err error) {
var cmdlineBytes []byte
cmdlineBytes, err = kernel.ReadProcCmdline()
if err != nil {
return err
}
if err = install.Install(string(cmdlineBytes), data); err != nil {
return errors.Wrap(err, "failed to install to bare metal")
}
return nil
}

View File

@ -5,6 +5,7 @@
package mount
import (
"log"
"os"
"path"
@ -237,6 +238,7 @@ func mountpoints() (mountpoints *mount.Points, err error) {
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)

View File

@ -14,6 +14,10 @@ import (
"github.com/spf13/cobra"
)
var (
kernelParams string
)
// installCmd reads in a userData file and attempts to parse it
var installCmd = &cobra.Command{
Use: "install",
@ -38,7 +42,7 @@ var installCmd = &cobra.Command{
log.Fatal(err)
}
err = install.Install(ud)
err = install.Install(kernelParams, ud)
if err != nil {
log.Fatal(err)
}
@ -46,6 +50,7 @@ var installCmd = &cobra.Command{
}
func init() {
installCmd.Flags().StringVarP(&kernelParams, "kernel-parameters", "k", "", "kernel parameter flags")
installCmd.Flags().StringVarP(&userdataFile, "userdata", "u", "", "path or url of userdata file")
rootCmd.AddCommand(installCmd)
}

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 bootloader
// Bootloader describes a bootloader.
type Bootloader interface {
Prepare(string) error
Install(string) error
}

View File

@ -0,0 +1,93 @@
/* 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 syslinux
import (
"bytes"
"io/ioutil"
"log"
"os"
"os/exec"
"text/template"
"github.com/autonomy/talos/internal/pkg/constants"
"github.com/autonomy/talos/internal/pkg/version"
)
const extlinuxConfig = `DEFAULT Talos
SAY Talos ({{ .Version }}) by Autonomy
LABEL Talos
KERNEL /vmlinuz
INITRD /initramfs.xz
APPEND {{ .Append }}`
const gptmbrbin = "/usr/share/gptmbr.bin"
// Syslinux represents the syslinux bootloader.
type Syslinux struct{}
// Prepare implements the Bootloader interface. It works by invoking writing
// gptmbr.bin to a block device.
func Prepare(dev string) (err error) {
b, err := ioutil.ReadFile(gptmbrbin)
if err != nil {
return err
}
f, err := os.OpenFile(dev, os.O_WRONLY, os.ModeDevice)
if err != nil {
return err
}
// nolint: errcheck
defer f.Close()
if _, err := f.Write(b); err != nil {
return err
}
return nil
}
// Install implements the Bootloader interface. It sets up extlinux with the
// specified kernel parameters.
func Install(args string) (err error) {
aux := struct {
Version string
Append string
}{
Version: version.Tag,
Append: args,
}
b := []byte{}
wr := bytes.NewBuffer(b)
t := template.Must(template.New("extlinux").Parse(extlinuxConfig))
if err = t.Execute(wr, aux); err != nil {
return err
}
if err = os.MkdirAll(constants.NewRoot+"/boot/extlinux", os.ModeDir); err != nil {
return err
}
log.Println("writing extlinux.conf to disk")
if err = ioutil.WriteFile(constants.NewRoot+"/boot/extlinux/extlinux.conf", wr.Bytes(), 0600); err != nil {
return err
}
if err = cmd("extlinux", "--install", constants.NewRoot+"/boot/extlinux"); err != nil {
return err
}
return nil
}
func cmd(name string, args ...string) error {
cmd := exec.Command(name, args...)
err := cmd.Start()
if err != nil {
return err
}
return cmd.Wait()
}

View File

@ -57,5 +57,5 @@ func (sb *SuperBlock) Offset() int64 {
// Type implements the SuperBlocker interface.
func (sb *SuperBlock) Type() string {
return "fat32"
return "vfat"
}

View File

@ -15,7 +15,7 @@ func MakeFS(partname string, setters ...Option) error {
args := []string{}
if opts.Label != "" {
args = append(args, "-n", opts.Label)
args = append(args, "-F", "32", "-n", opts.Label)
}
args = append(args, partname)

View File

@ -285,10 +285,9 @@ func (gpt *GPT) Add(size uint64, setters ...interface{}) (table.Partition, error
ID: uuid,
FirstLBA: start,
LastLBA: end,
// TODO(andrewrynhard): Flags should be an option.
Flags: 0,
Name: opts.Name,
Number: int32(len(gpt.partitions) + 1),
Flags: opts.Flags,
Name: opts.Name,
Number: int32(len(gpt.partitions) + 1),
}
gpt.partitions = append(gpt.partitions, partition)

View File

@ -10,9 +10,10 @@ import (
// Options is the functional options struct.
type Options struct {
Type uuid.UUID
Name string
Test bool
Type uuid.UUID
Name string
Flags uint64
Test bool
}
// Option is the functional option func.
@ -35,6 +36,15 @@ func WithPartitionName(o string) Option {
}
}
// WithLegacyBIOSBootableAttribute marks the partition as bootable.
func WithLegacyBIOSBootableAttribute(o bool) Option {
return func(args *Options) {
if o == true {
args.Flags = 4
}
}
}
// WithPartitionTest allows us to disable the IsNew partition
// check. This is only intended to be used for tests.
func WithPartitionTest(t bool) Option {

View File

@ -16,6 +16,7 @@ import (
"path/filepath"
"strings"
"github.com/autonomy/talos/internal/pkg/blockdevice/bootloader/syslinux"
"github.com/autonomy/talos/internal/pkg/constants"
"github.com/autonomy/talos/internal/pkg/userdata"
"github.com/pkg/errors"
@ -24,7 +25,7 @@ import (
// Install fetches the necessary data locations and copies or extracts
// to the target locations
// nolint: gocyclo
func Install(data *userdata.UserData) (err error) {
func Install(args string, data *userdata.UserData) (err error) {
if data.Install == nil {
return nil
}
@ -46,6 +47,10 @@ func Install(data *userdata.UserData) (err error) {
previousMountPoint = dest
}
if err = os.MkdirAll(dest, os.ModeDir); err != nil {
return err
}
// Extract artifact if necessary, otherwise place at root of partition/filesystem
for _, artifact := range urls {
switch {
@ -120,6 +125,11 @@ func Install(data *userdata.UserData) (err error) {
}
}
}
if err = syslinux.Install(args); err != nil {
return err
}
return nil
}

View File

@ -12,6 +12,8 @@ import (
"strconv"
"github.com/autonomy/talos/internal/pkg/blockdevice"
"github.com/autonomy/talos/internal/pkg/blockdevice/bootloader/syslinux"
"github.com/autonomy/talos/internal/pkg/blockdevice/filesystem/vfat"
"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"
@ -178,6 +180,11 @@ func Prepare(data *userdata.UserData) (err error) {
continue
}
if label == constants.BootPartitionLabel {
if err = syslinux.Prepare(device); err != nil {
return err
}
}
var bd *blockdevice.BlockDevice
bd, err = blockdevice.Open(device, blockdevice.WithNewGPT(data.Install.Wipe))
@ -241,7 +248,7 @@ func Prepare(data *userdata.UserData) (err error) {
for _, dev := range devices {
// Create the filesystem
log.Printf("Formatting Partition %s - %s\n", dev.Name, dev.Label)
log.Printf("Formatting Partition %s - %s\n", dev.PartitionName, dev.Label)
err = dev.Format()
if err != nil {
return err
@ -296,11 +303,15 @@ 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
var (
typeID string
legacyBIOSBootable bool
)
switch d.Label {
case constants.BootPartitionLabel:
// EFI System Partition
typeID = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
legacyBIOSBootable = true
case constants.RootPartitionLabel:
// Root Partition
switch runtime.GOARCH {
@ -318,7 +329,13 @@ func (d *Device) Partition() error {
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))
part, err := d.PartitionTable.Add(
uint64(d.Size),
partition.WithPartitionType(typeID),
partition.WithPartitionName(d.Label),
partition.WithLegacyBIOSBootableAttribute(legacyBIOSBootable),
partition.WithPartitionTest(d.Test),
)
if err != nil {
return err
}
@ -330,5 +347,8 @@ func (d *Device) Partition() error {
// Format creates a xfs filesystem on the device/partition
func (d *Device) Format() error {
if d.Label == constants.BootPartitionLabel {
return vfat.MakeFS(d.PartitionName, vfat.WithLabel(d.Label))
}
return xfs.MakeFS(d.PartitionName, xfs.WithLabel(d.Label), xfs.WithForce(d.Force))
}

View File

@ -9,11 +9,21 @@ import (
"strings"
)
// ReadProcCmdline reads /proc/cmdline.
func ReadProcCmdline() (cmdlineBytes []byte, err error) {
cmdlineBytes, err = ioutil.ReadFile("/proc/cmdline")
if err != nil {
return nil, err
}
return cmdlineBytes, nil
}
// ParseProcCmdline parses /proc/cmdline and returns a map reprentation of the
// kernel parameters.
func ParseProcCmdline() (cmdline map[string]string, err error) {
var cmdlineBytes []byte
cmdlineBytes, err = ioutil.ReadFile("/proc/cmdline")
cmdlineBytes, err = ReadProcCmdline()
if err != nil {
return
}