Andrey Smirnov 9379cf9ee1 refactor: expose provision as public package
This change is only moving packages and updating import paths.

Goal: expose `internal/pkg/provision` as `pkg/provision` to enable other
projects to import Talos provisioning library.

As cluster checks are almost always required as part of provisioning
process, package `internal/pkg/cluster` was also made public as
`pkg/cluster`.

Other changes were direct dependencies discovered by `importvet` which
were updated.

Public packages (useful, general purpose packages with stable API):

* `internal/pkg/conditions` -> `pkg/conditions`
* `internal/pkg/tail` -> `pkg/tail`

Private packages (used only on provisioning library internally):

* `internal/pkg/inmemhttp` -> `pkg/provision/internal/inmemhttp`
* `internal/pkg/kernel/vmlinuz` -> `pkg/provision/internal/vmlinuz`
* `internal/pkg/cniutils` -> `pkg/provision/internal/cniutils`

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
2020-08-12 05:12:05 -07:00

247 lines
5.4 KiB
Go

// 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 firecracker
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/vfat"
"github.com/talos-systems/talos/pkg/blockdevice/table"
"github.com/talos-systems/talos/pkg/blockdevice/table/gpt"
"github.com/talos-systems/talos/pkg/constants"
"github.com/talos-systems/talos/pkg/provision/internal/vmlinuz"
)
const diskImageSectorSize = 512
// BootLoader extracts kernel (vmlinux) and initrd (initramfs.xz) from Talos disk image.
type BootLoader struct {
diskF *os.File
bootPartitionReader *io.SectionReader
bootFs *vfat.FileSystem
kernelTempPath, initrdTempPath string
}
// BootAssets is what BootLoader extracts from the disk image.
type BootAssets struct {
KernelPath string
InitrdPath string
}
// NewBootLoader creates boot loader for the disk image.
func NewBootLoader(diskImage string) (*BootLoader, error) {
b := &BootLoader{}
var err error
b.diskF, err = os.Open(diskImage)
if err != nil {
return nil, err
}
return b, nil
}
// ExtractAssets from disk image.
//
// nolint: gocyclo
func (b *BootLoader) ExtractAssets() (assets BootAssets, err error) {
if err = b.findBootPartition(); err != nil {
return assets, err
}
if err = b.openFilesystem(); err != nil {
return assets, err
}
var label string
if label, err = b.findLabel(); err != nil {
return assets, err
}
if err := b.extractKernel(label); err != nil {
return assets, err
}
if err := b.extractInitrd(label); err != nil {
return assets, err
}
assets = BootAssets{
KernelPath: b.kernelTempPath,
InitrdPath: b.initrdTempPath,
}
return assets, nil
}
// Close the bootloader.
func (b *BootLoader) Close() error {
if b.kernelTempPath != "" {
os.Remove(b.kernelTempPath) //nolint: errcheck
b.kernelTempPath = ""
}
if b.initrdTempPath != "" {
os.Remove(b.initrdTempPath) //nolint: errcheck
b.initrdTempPath = ""
}
if b.diskF != nil {
if err := b.diskF.Close(); err != nil {
return err
}
b.diskF = nil
}
return nil
}
func (b *BootLoader) findBootPartition() error {
diskTable, err := gpt.NewGPT("vda", b.diskF)
if err != nil {
return fmt.Errorf("error creating GPT object: %w", err)
}
if err = diskTable.Read(); err != nil {
return fmt.Errorf("error reading GPT: %w", err)
}
var bootPartition table.Partition
for _, part := range diskTable.Partitions() {
// TODO: should we do better matching here
if part.No() == 1 {
bootPartition = part
break
}
}
if bootPartition == nil {
return fmt.Errorf("no boot partition found")
}
b.bootPartitionReader = io.NewSectionReader(b.diskF, bootPartition.Start()*diskImageSectorSize, bootPartition.Length()*diskImageSectorSize)
return nil
}
func (b *BootLoader) openFilesystem() error {
sb := &vfat.SuperBlock{}
if _, err := b.bootPartitionReader.Seek(sb.Offset(), io.SeekStart); err != nil {
return fmt.Errorf("error seeking boot partition: %w", err)
}
err := binary.Read(b.bootPartitionReader, binary.BigEndian, sb)
if err != nil {
return fmt.Errorf("error reading vfat superblock: %w", err)
}
if !sb.Is() {
return fmt.Errorf("corrupt vfat superblock")
}
b.bootFs, err = vfat.NewFileSystem(b.bootPartitionReader, sb)
if err != nil {
return fmt.Errorf("error initializing FAT32 filesystem: %w", err)
}
return nil
}
func (b *BootLoader) findLabel() (label string, err error) {
// Parse the syslinux.cfg first, for backwards compatibility.
var cfg *vfat.File
if cfg, err = b.bootFs.Open("/syslinux/syslinux.cfg"); err != nil {
return label, fmt.Errorf("failed to open syslinux.cfg: %w", err)
}
buf := new(bytes.Buffer)
if _, err = buf.ReadFrom(cfg); err != nil {
return label, err
}
re := regexp.MustCompile(`^DEFAULT\s(.*)`)
matches := re.FindSubmatch(buf.Bytes())
if len(matches) != 2 {
return label, fmt.Errorf("expected 2 matches, got %d", len(matches))
}
label = string(matches[1])
return label, nil
}
func (b *BootLoader) extractKernel(label string) error {
path := filepath.Join("/", label, constants.KernelAsset)
r, err := b.bootFs.Open(path)
if err != nil {
return fmt.Errorf("failed to open kernel asset %q: %w", path, err)
}
kernelR, err := vmlinuz.Decompress(bufio.NewReader(r))
if err != nil {
return fmt.Errorf("error decompressing kernel: %w", err)
}
defer kernelR.Close() //nolint: errcheck
tempF, err := ioutil.TempFile("", "talos")
if err != nil {
return fmt.Errorf("error creating temporary kernel image file: %w", err)
}
defer tempF.Close() //nolint: errcheck
if _, err = io.Copy(tempF, kernelR); err != nil {
return fmt.Errorf("error extracting kernel: %w", err)
}
b.kernelTempPath = tempF.Name()
return nil
}
func (b *BootLoader) extractInitrd(label string) error {
path := filepath.Join("/", label, constants.InitramfsAsset)
r, err := b.bootFs.Open(path)
if err != nil {
return fmt.Errorf("failed to open initrd %q: %w", path, err)
}
tempF, err := ioutil.TempFile("", "talos")
if err != nil {
return fmt.Errorf("error creating temporary initrd file: %w", err)
}
defer tempF.Close() //nolint: errcheck
if _, err = io.Copy(tempF, r); err != nil {
return fmt.Errorf("error extracting initrd: %w", err)
}
b.initrdTempPath = tempF.Name()
return nil
}