feat: add grub bootloader

This moves to using grub instead of syslinux.

BREAKING CHANGE: Single node upgrades will fail in this change. This
will also break the A/B fallback setup since this version introduces
an entirely new partition scheme, that any fallback will not know about.
We plan on addressing these issues in a follow up change.

Signed-off-by: Andrew Rynhard <andrew@rynhard.io>
This commit is contained in:
Andrew Rynhard 2020-08-18 15:52:26 -07:00 committed by Andrew Rynhard
parent 2f99f551e7
commit 1a4059a553
28 changed files with 1054 additions and 300 deletions

View File

@ -220,10 +220,10 @@ local golint = Step("lint-go", depends_on=[check_dirty]);
local markdownlint = Step("lint-markdown", depends_on=[check_dirty]);
local protobuflint = Step("lint-protobuf", depends_on=[check_dirty]);
local image_aws = Step("image-aws", depends_on=[installer]);
local image_azure = Step("image-azure", depends_on=[image_aws]);
local image_digital_ocean = Step("image-digital-ocean", depends_on=[image_azure]);
local image_gcp = Step("image-gcp", depends_on=[image_digital_ocean]);
local image_vmware = Step("image-vmware", depends_on=[image_gcp]);
local image_azure = Step("image-azure", depends_on=[installer]);
local image_digital_ocean = Step("image-digital-ocean", depends_on=[installer]);
local image_gcp = Step("image-gcp", depends_on=[installer]);
local image_vmware = Step("image-vmware", depends_on=[installer]);
local unit_tests = Step("unit-tests", depends_on=[initramfs]);
local unit_tests_race = Step("unit-tests-race", depends_on=[initramfs]);
local e2e_docker = Step("e2e-docker-short", depends_on=[talos, talosctl_linux, unit_tests, unit_tests_race], target="e2e-docker", environment={"SHORT_INTEGRATION_TEST": "yes"});
@ -499,7 +499,7 @@ local release = {
when: {
event: ['tag'],
},
depends_on: [kernel.name, iso.name, boot.name, image_gcp.name, image_azure.name, image_aws.name, push.name, release_notes.name]
depends_on: [kernel.name, iso.name, boot.name, image_gcp.name, image_azure.name, image_aws.name, image_vmware.name, image_digital_ocean.name, push.name, release_notes.name]
};
local release_steps = default_steps + [

View File

@ -229,30 +229,39 @@ FROM base AS talosctl-linux-amd64-build
ARG SHA
ARG TAG
ARG ARTIFACTS
ARG USERNAME
ARG REGISTRY
ARG VERSION_PKG="github.com/talos-systems/talos/pkg/version"
ARG CONSTANTS_PKG="github.com/talos-systems/talos/pkg/machinery/constants"
ARG MGMT_HELPERS_PKG="github.com/talos-systems/talos/cmd/talosctl/pkg/mgmt/helpers"
WORKDIR /src/cmd/talosctl
RUN --mount=type=cache,target=/.cache/go-build GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG} -X ${MGMT_HELPERS_PKG}.ArtifactsPath=${ARTIFACTS}" -o /talosctl-linux-amd64
RUN --mount=type=cache,target=/.cache/go-build GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG} -X ${CONSTANTS_PKG}.Username=${USERNAME} -X ${CONSTANTS_PKG}.Registry=${REGISTRY} -X ${MGMT_HELPERS_PKG}.ArtifactsPath=${ARTIFACTS}" -o /talosctl-linux-amd64
RUN chmod +x /talosctl-linux-amd64
FROM base AS talosctl-linux-arm64-build
ARG SHA
ARG TAG
ARG ARTIFACTS
ARG USERNAME
ARG REGISTRY
ARG VERSION_PKG="github.com/talos-systems/talos/pkg/version"
ARG CONSTANTS_PKG="github.com/talos-systems/talos/pkg/machinery/constants"
ARG MGMT_HELPERS_PKG="github.com/talos-systems/talos/cmd/talosctl/pkg/mgmt/helpers"
WORKDIR /src/cmd/talosctl
RUN --mount=type=cache,target=/.cache/go-build GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG} -X ${MGMT_HELPERS_PKG}.ArtifactsPath=${ARTIFACTS}" -o /talosctl-linux-arm64
RUN --mount=type=cache,target=/.cache/go-build GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG} -X ${CONSTANTS_PKG}.Username=${USERNAME} -X ${CONSTANTS_PKG}.Registry=${REGISTRY} -X ${MGMT_HELPERS_PKG}.ArtifactsPath=${ARTIFACTS}" -o /talosctl-linux-arm64
RUN chmod +x /talosctl-linux-arm64
FROM base AS talosctl-linux-armv7-build
ARG SHA
ARG TAG
ARG ARTIFACTS
ARG USERNAME
ARG REGISTRY
ARG VERSION_PKG="github.com/talos-systems/talos/pkg/version"
ARG CONSTANTS_PKG="github.com/talos-systems/talos/pkg/machinery/constants"
ARG MGMT_HELPERS_PKG="github.com/talos-systems/talos/cmd/talosctl/pkg/mgmt/helpers"
WORKDIR /src/cmd/talosctl
RUN --mount=type=cache,target=/.cache/go-build GOOS=linux GOARCH=arm GOARM=7 go build -ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG} -X ${MGMT_HELPERS_PKG}.ArtifactsPath=${ARTIFACTS}" -o /talosctl-linux-armv7
RUN --mount=type=cache,target=/.cache/go-build GOOS=linux GOARCH=arm GOARM=7 go build -ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG} -X ${CONSTANTS_PKG}.Username=${USERNAME} -X ${CONSTANTS_PKG}.Registry=${REGISTRY} -X ${MGMT_HELPERS_PKG}.ArtifactsPath=${ARTIFACTS}" -o /talosctl-linux-armv7
RUN chmod +x /talosctl-linux-armv7
FROM scratch AS talosctl-linux
@ -264,10 +273,13 @@ FROM base AS talosctl-darwin-build
ARG SHA
ARG TAG
ARG ARTIFACTS
ARG USERNAME
ARG REGISTRY
ARG VERSION_PKG="github.com/talos-systems/talos/pkg/version"
ARG CONSTANTS_PKG="github.com/talos-systems/talos/pkg/machinery/constants"
ARG MGMT_HELPERS_PKG="github.com/talos-systems/talos/cmd/talosctl/pkg/mgmt/helpers"
WORKDIR /src/cmd/talosctl
RUN --mount=type=cache,target=/.cache/go-build GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG} -X ${MGMT_HELPERS_PKG}.ArtifactsPath=${ARTIFACTS}" -o /talosctl-darwin-amd64
RUN --mount=type=cache,target=/.cache/go-build GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG} -X ${CONSTANTS_PKG}.Username=${USERNAME} -X ${CONSTANTS_PKG}.Registry=${REGISTRY} -X ${MGMT_HELPERS_PKG}.ArtifactsPath=${ARTIFACTS}" -o /talosctl-darwin-amd64
RUN chmod +x /talosctl-darwin-amd64
FROM scratch AS talosctl-darwin
@ -298,7 +310,6 @@ COPY --from=docker.io/autonomy/open-iscsi:v0.2.0-34-g7e57ef7 / /rootfs
COPY --from=docker.io/autonomy/open-isns:v0.2.0-34-g7e57ef7 / /rootfs
COPY --from=docker.io/autonomy/runc:v0.2.0-34-g7e57ef7 / /rootfs
COPY --from=docker.io/autonomy/socat:v0.2.0-34-g7e57ef7 / /rootfs
COPY --from=docker.io/autonomy/syslinux:v0.2.0-34-g7e57ef7 / /rootfs
COPY --from=docker.io/autonomy/xfsprogs:v0.2.0-34-g7e57ef7 / /rootfs
COPY --from=docker.io/autonomy/util-linux:v0.2.0-34-g7e57ef7 /lib/libblkid.* /rootfs/lib/
COPY --from=docker.io/autonomy/util-linux:v0.2.0-34-g7e57ef7 /lib/libuuid.* /rootfs/lib/
@ -366,17 +377,17 @@ WORKDIR /src/cmd/installer
RUN --mount=type=cache,target=/.cache/go-build go build -ldflags "-s -w -X ${VERSION_PKG}.Name=Talos -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG}" -o /installer
RUN chmod +x /installer
FROM alpine:3.8 AS installer
FROM alpine:3.11 AS installer
RUN apk add --no-cache --update \
bash \
ca-certificates \
cdrkit \
efibootmgr \
qemu-img \
syslinux \
util-linux \
xfsprogs
COPY --from=docker.io/autonomy/grub:v0.2.0-34-g7e57ef7 / /
COPY --from=kernel /vmlinuz /usr/install/vmlinuz
COPY --from=rootfs /usr/lib/syslinux/ /usr/lib/syslinux
COPY --from=initramfs /initramfs.xz /usr/install/initramfs.xz
COPY --from=installer-build /installer /bin/installer
RUN ln -s /bin/installer /bin/talosctl
@ -429,9 +440,12 @@ RUN --security=insecure --mount=type=cache,id=testspace,target=/tmp --mount=type
FROM base AS integration-test-linux-build
ARG SHA
ARG TAG
ARG USERNAME
ARG REGISTRY
ARG VERSION_PKG="github.com/talos-systems/talos/pkg/version"
ARG CONSTANTS_PKG="github.com/talos-systems/talos/pkg/machinery/constants"
RUN --mount=type=cache,target=/.cache/go-build GOOS=linux GOARCH=amd64 go test -c \
-ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG}" \
-ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG} -X ${CONSTANTS_PKG}.Username=${USERNAME} -X ${CONSTANTS_PKG}.Registry=${REGISTRY}" \
-tags integration,integration_api,integration_cli,integration_k8s \
./internal/integration
@ -441,9 +455,12 @@ COPY --from=integration-test-linux-build /src/integration.test /integration-test
FROM base AS integration-test-darwin-build
ARG SHA
ARG TAG
ARG USERNAME
ARG REGISTRY
ARG VERSION_PKG="github.com/talos-systems/talos/pkg/version"
ARG CONSTANTS_PKG="github.com/talos-systems/talos/pkg/machinery/constants"
RUN --mount=type=cache,target=/.cache/go-build GOOS=darwin GOARCH=amd64 go test -c \
-ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG}" \
-ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG} -X ${CONSTANTS_PKG}.Username=${USERNAME} -X ${CONSTANTS_PKG}.Registry=${REGISTRY}" \
-tags integration,integration_api,integration_cli,integration_k8s \
./internal/integration
@ -455,11 +472,14 @@ COPY --from=integration-test-darwin-build /src/integration.test /integration-tes
FROM base AS integration-test-provision-linux-build
ARG SHA
ARG TAG
ARG USERNAME
ARG REGISTRY
ARG VERSION_PKG="github.com/talos-systems/talos/pkg/version"
ARG CONSTANTS_PKG="github.com/talos-systems/talos/pkg/machinery/constants"
ARG MGMT_HELPERS_PKG="github.com/talos-systems/talos/cmd/talosctl/pkg/mgmt/helpers"
ARG ARTIFACTS
RUN --mount=type=cache,target=/.cache/go-build GOOS=linux GOARCH=amd64 go test -c \
-ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG} -X ${MGMT_HELPERS_PKG}.ArtifactsPath=${ARTIFACTS}" \
-ldflags "-s -w -X ${VERSION_PKG}.Name=Client -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG} -X ${CONSTANTS_PKG}.Username=${USERNAME} -X ${CONSTANTS_PKG}.Registry=${REGISTRY} -X ${MGMT_HELPERS_PKG}.ArtifactsPath=${ARTIFACTS}" \
-tags integration,integration_provision \
./internal/integration

View File

@ -40,6 +40,7 @@ COMMON_ARGS += --build-arg=TAG=$(TAG)
COMMON_ARGS += --build-arg=ARTIFACTS=$(ARTIFACTS)
COMMON_ARGS += --build-arg=IMPORTVET=$(IMPORTVET)
COMMON_ARGS += --build-arg=TESTPKGS=$(TESTPKGS)
COMMON_ARGS += --build-arg=REGISTRY=$(REGISTRY)
COMMON_ARGS += --build-arg=USERNAME=$(USERNAME)
COMMON_ARGS += --build-arg=http_proxy=$(http_proxy)
COMMON_ARGS += --build-arg=https_proxy=$(https_proxy)
@ -148,7 +149,7 @@ talosctl-%:
talosctl: $(TALOSCTL_DEFAULT_TARGET) ## Builds the talosctl binary for the local machine.
image-%: ## Builds the specified image. Valid options are aws, azure, digital-ocean, gcp, and vmware (e.g. image-aws)
@docker run --rm -v /dev:/dev -v $(PWD)/$(ARTIFACTS):/out --privileged $(USERNAME)/installer:$(TAG) image --platform $*
@docker run --rm -v /dev:/dev -v $(PWD)/$(ARTIFACTS):/out --privileged $(REGISTRY)/$(USERNAME)/installer:$(TAG) image --platform $*
images: image-aws image-azure image-digital-ocean image-gcp image-vmware ## Builds all known images (AWS, Azure, Digital Ocean, GCP, and VMware).

View File

@ -17,12 +17,10 @@ import (
"golang.org/x/sys/unix"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/syslinux"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
"github.com/talos-systems/talos/internal/pkg/mount"
"github.com/talos-systems/talos/pkg/blockdevice"
"github.com/talos-systems/talos/pkg/blockdevice/probe"
"github.com/talos-systems/talos/pkg/blockdevice/table"
"github.com/talos-systems/talos/pkg/blockdevice/util"
"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/version"
)
@ -73,11 +71,13 @@ func Install(p runtime.Platform, seq runtime.Sequence, opts *Options) (err error
// Installer represents the installer logic. It serves as the entrypoint to all
// installation methods.
type Installer struct {
cmdline *procfs.Cmdline
options *Options
manifest *Manifest
Current string
Next string
cmdline *procfs.Cmdline
options *Options
manifest *Manifest
bootloader bootloader.Bootloader
Current string
Next string
bootPartitionFound bool
}
@ -89,36 +89,27 @@ func NewInstaller(cmdline *procfs.Cmdline, seq runtime.Sequence, opts *Options)
i = &Installer{
cmdline: cmdline,
options: opts,
bootloader: &grub.Grub{
BootDisk: opts.Disk,
},
}
var dev *probe.ProbedBlockDevice
if dev, err = probe.GetDevWithFileSystemLabel(constants.BootPartitionLabel); err != nil {
if dev, err = probe.DevForFileSystemLabel(opts.Disk, constants.BootPartitionLabel); err != nil {
i.bootPartitionFound = false
} else {
//nolint: errcheck
defer dev.Close()
i.bootPartitionFound = true
}
if seq == runtime.SequenceUpgrade && i.bootPartitionFound {
if err = os.MkdirAll("/boot", 0o777); err != nil {
return nil, err
}
if err = unix.Mount(dev.Path, "/boot", dev.SuperBlock.Type(), 0, ""); err != nil {
return nil, fmt.Errorf("failed to mount /boot: %w", err)
}
}
i.Current, i.Next, err = syslinux.Labels()
i.Current, i.Next, err = i.bootloader.Labels()
if err != nil {
return nil, err
}
label := i.Current
if seq == runtime.SequenceUpgrade && i.bootPartitionFound {
label = i.Next
}
label := i.Next
i.manifest, err = NewManifest(label, seq, i.options)
if err != nil {
@ -133,7 +124,31 @@ func NewInstaller(cmdline *procfs.Cmdline, seq runtime.Sequence, opts *Options)
//
// nolint: gocyclo
func (i *Installer) Install(seq runtime.Sequence) (err error) {
if seq != runtime.SequenceUpgrade || !i.bootPartitionFound {
if i.options.Force {
if i.bootPartitionFound {
var dev *probe.ProbedBlockDevice
if dev, err = probe.DevForFileSystemLabel(i.options.Disk, constants.BootPartitionLabel); err != nil {
return err
}
// Reset the partition table.
if err = dev.Reset(); err != nil {
return err
}
if err = dev.RereadPartitionTable(); err != nil {
return err
}
if err = dev.Close(); err != nil {
return err
}
}
// Zero the disk.
if i.options.Zero {
if err = zero(i.manifest); err != nil {
return fmt.Errorf("failed to wipe device(s): %w", err)
@ -141,89 +156,74 @@ func (i *Installer) Install(seq runtime.Sequence) (err error) {
}
// Partition and format the block device(s).
if err = i.manifest.ExecuteManifest(); err != nil {
return err
}
// Mount the partitions.
mountpoints := mount.NewMountPoints()
// look for mountpoints across all target devices
for dev := range i.manifest.Targets {
var mp *mount.Points
mp, err = mount.SystemMountPointsForDevice(dev)
if err != nil {
return err
}
iter := mp.Iter()
for iter.Next() {
mountpoints.Set(iter.Key(), iter.Value())
} else if !i.bootPartitionFound {
if i.options.Zero {
if err = zero(i.manifest); err != nil {
return fmt.Errorf("failed to wipe device(s): %w", err)
}
}
if err = mount.Mount(mountpoints); err != nil {
if err = i.manifest.ExecuteManifest(); err != nil {
return err
}
}
if seq == runtime.SequenceUpgrade {
var meta *bootloader.Meta
if meta, err = bootloader.NewMeta(); err != nil {
return err
}
// nolint: errcheck
defer mount.Unmount(mountpoints)
}
//nolint: errcheck
defer meta.Close()
if seq == runtime.SequenceUpgrade && i.bootPartitionFound && i.options.Force {
for dev, targets := range i.manifest.Targets {
var bd *blockdevice.BlockDevice
if ok := meta.SetTag(bootloader.AdvUpgrade, i.Current); !ok {
return fmt.Errorf("failed to set upgrade tag: %q", i.Current)
}
if bd, err = blockdevice.Open(dev); err != nil {
return err
}
// nolint: errcheck
defer bd.Close()
var pt table.PartitionTable
pt, err = bd.PartitionTable()
if err != nil {
return err
}
for _, target := range targets {
for _, part := range pt.Partitions() {
switch target.Label {
case constants.BootPartitionLabel, constants.EphemeralPartitionLabel:
target.PartitionName, err = util.PartPath(target.Device, int(part.No()))
if err != nil {
return err
}
}
}
if target.Label == constants.BootPartitionLabel {
continue
}
if err = target.Format(); err != nil {
return fmt.Errorf("failed to format device: %w", err)
}
}
if _, err = meta.Write(); err != nil {
return err
}
}
// Mount the partitions.
mountpoints := mount.NewMountPoints()
for dev := range i.manifest.Targets {
var mp *mount.Points
mp, err = mount.SystemMountPointsForDevice(dev)
if err != nil {
return err
}
iter := mp.Iter()
for iter.Next() {
mountpoints.Set(iter.Key(), iter.Value())
}
}
if err = mount.Mount(mountpoints); err != nil {
return err
}
defer func() {
e := mount.Unmount(mountpoints)
if e != nil {
log.Printf("failed to unmount: %v", e)
}
}()
// Install the assets.
for _, targets := range i.manifest.Targets {
for _, target := range targets {
switch target.Label {
case constants.BootPartitionLabel:
if err = syslinux.Prepare(target.Device); err != nil {
return err
}
case constants.EphemeralPartitionLabel:
continue
}
// Handle the download and extraction of assets.
if err = target.Save(); err != nil {
return err
@ -237,48 +237,33 @@ func (i *Installer) Install(seq runtime.Sequence) (err error) {
return nil
}
if seq != runtime.SequenceUpgrade || !i.bootPartitionFound {
i.cmdline.Append("initrd", filepath.Join("/", i.Current, constants.InitramfsAsset))
i.cmdline.Append("initrd", filepath.Join("/", i.Next, constants.InitramfsAsset))
syslinuxcfg := &syslinux.Cfg{
Default: i.Current,
Labels: []*syslinux.Label{
{
Root: i.Current,
Initrd: filepath.Join("/", i.Current, constants.InitramfsAsset),
Kernel: filepath.Join("/", i.Current, constants.KernelAsset),
Append: i.cmdline.String(),
},
grubcfg := &grub.Cfg{
Default: i.Next,
Labels: []*grub.Label{
{
Root: i.Next,
Initrd: filepath.Join("/", i.Next, constants.InitramfsAsset),
Kernel: filepath.Join("/", i.Next, constants.KernelAsset),
Append: i.cmdline.String(),
},
}
},
}
if err = syslinux.Install("", syslinuxcfg, seq, i.bootPartitionFound); err != nil {
return err
}
} else {
i.cmdline.Append("initrd", filepath.Join("/", i.Next, constants.InitramfsAsset))
if i.bootPartitionFound && i.Current != "" {
grubcfg.Fallback = i.Current
syslinuxcfg := &syslinux.Cfg{
Default: i.Next,
Labels: []*syslinux.Label{
{
Root: i.Next,
Initrd: filepath.Join("/", i.Next, constants.InitramfsAsset),
Kernel: filepath.Join("/", i.Next, constants.KernelAsset),
Append: i.cmdline.String(),
},
{
Root: i.Current,
Initrd: filepath.Join("/", i.Current, constants.InitramfsAsset),
Kernel: filepath.Join("/", i.Current, constants.KernelAsset),
Append: procfs.ProcCmdline().String(),
},
},
}
grubcfg.Labels = append(grubcfg.Labels, &grub.Label{
Root: i.Current,
Initrd: filepath.Join("/", i.Current, constants.InitramfsAsset),
Kernel: filepath.Join("/", i.Current, constants.KernelAsset),
Append: procfs.ProcCmdline().String(),
})
}
if err = syslinux.Install(i.Current, syslinuxcfg, seq, i.bootPartitionFound); err != nil {
return err
}
if err = i.bootloader.Install(i.Current, grubcfg, seq, i.bootPartitionFound); err != nil {
return err
}
if i.options.Save {

View File

@ -51,6 +51,10 @@ type Asset struct {
// NewManifest initializes and returns a Manifest.
func NewManifest(label string, sequence runtime.Sequence, opts *Options) (manifest *Manifest, err error) {
if label == "" {
return nil, fmt.Errorf("a label is required, got \"\"")
}
manifest = &Manifest{
Targets: map[string][]*Target{},
}
@ -74,13 +78,29 @@ func NewManifest(label string, sequence runtime.Sequence, opts *Options) (manife
manifest.Targets[opts.Disk] = []*Target{}
}
efiTarget := &Target{
Device: opts.Disk,
Label: constants.EFIPartitionLabel,
Size: 100 * 1024 * 1024,
Force: true,
Test: false,
}
biosTarget := &Target{
Device: opts.Disk,
Label: constants.BIOSGrubPartitionLabel,
Size: 1 * 1024 * 1024,
Force: true,
Test: false,
}
var bootTarget *Target
if opts.Bootloader {
bootTarget = &Target{
Device: opts.Disk,
Label: constants.BootPartitionLabel,
Size: 512 * 1024 * 1024,
Size: 300 * 1024 * 1024,
Force: true,
Test: false,
Assets: []*Asset{
@ -96,6 +116,22 @@ func NewManifest(label string, sequence runtime.Sequence, opts *Options) (manife
}
}
metaTarget := &Target{
Device: opts.Disk,
Label: constants.MetaPartitionLabel,
Size: 1 * 1024 * 1024,
Force: true,
Test: false,
}
stateTarget := &Target{
Device: opts.Disk,
Label: constants.StatePartitionLabel,
Size: 100 * 1024 * 1024,
Force: true,
Test: false,
}
ephemeralTarget := &Target{
Device: opts.Disk,
Label: constants.EphemeralPartitionLabel,
@ -104,7 +140,7 @@ func NewManifest(label string, sequence runtime.Sequence, opts *Options) (manife
Test: false,
}
for _, target := range []*Target{bootTarget, ephemeralTarget} {
for _, target := range []*Target{efiTarget, biosTarget, bootTarget, metaTarget, stateTarget, ephemeralTarget} {
if target == nil {
continue
}
@ -176,18 +212,27 @@ func (t *Target) Partition(bd *blockdevice.BlockDevice) (err error) {
opts := []interface{}{}
const (
EFISystemPartition = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
BIOSBootPartition = "21686148-6449-6E6F-744E-656564454649"
LinuxFilesystemData = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
)
switch t.Label {
case constants.EFIPartitionLabel:
opts = append(opts, partition.WithPartitionType(EFISystemPartition), partition.WithPartitionName(t.Label))
case constants.BIOSGrubPartitionLabel:
opts = append(opts, partition.WithPartitionType(BIOSBootPartition), partition.WithPartitionName(t.Label), partition.WithLegacyBIOSBootableAttribute(true))
case constants.BootPartitionLabel:
// EFI System Partition
typeID := "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
opts = append(opts, partition.WithPartitionType(typeID), partition.WithPartitionName(t.Label), partition.WithLegacyBIOSBootableAttribute(true))
opts = append(opts, partition.WithPartitionType(LinuxFilesystemData), partition.WithPartitionName(t.Label))
case constants.MetaPartitionLabel:
opts = append(opts, partition.WithPartitionType(LinuxFilesystemData), partition.WithPartitionName(t.Label))
case constants.StatePartitionLabel:
opts = append(opts, partition.WithPartitionType(LinuxFilesystemData), partition.WithPartitionName(t.Label))
case constants.EphemeralPartitionLabel:
// Ephemeral Partition
typeID := "AF3DC60F-8384-7247-8E79-3D69D8477DE4"
opts = append(opts, partition.WithPartitionType(typeID), partition.WithPartitionName(t.Label), partition.WithMaximumSize(true))
opts = append(opts, partition.WithPartitionType(LinuxFilesystemData), partition.WithPartitionName(t.Label), partition.WithMaximumSize(true))
default:
typeID := "AF3DC60F-8384-7247-8E79-3D69D8477DE4"
opts = append(opts, partition.WithPartitionType(typeID))
opts = append(opts, partition.WithPartitionType(LinuxFilesystemData))
if t.Size == 0 {
opts = append(opts, partition.WithMaximumSize(true))
@ -212,20 +257,47 @@ func (t *Target) Partition(bd *blockdevice.BlockDevice) (err error) {
}
// Format creates a filesystem on the device/partition.
//
//nolint: gocyclo
func (t *Target) Format() error {
if t.Label == constants.BootPartitionLabel {
switch t.Label {
case constants.EFIPartitionLabel:
log.Printf("formatting partition %q as %q with label %q\n", t.PartitionName, "fat", t.Label)
return vfat.MakeFS(t.PartitionName, vfat.WithLabel(t.Label))
case constants.BIOSGrubPartitionLabel:
return nil
case constants.BootPartitionLabel:
log.Printf("formatting partition %q as %q with label %q\n", t.PartitionName, "xfs", t.Label)
opts := []xfs.Option{xfs.WithForce(t.Force)}
if t.Label != "" {
opts = append(opts, xfs.WithLabel(t.Label))
}
return xfs.MakeFS(t.PartitionName, opts...)
case constants.MetaPartitionLabel:
return nil
case constants.StatePartitionLabel:
log.Printf("formatting partition %q as %q with label %q\n", t.PartitionName, "xfs", t.Label)
opts := []xfs.Option{xfs.WithForce(t.Force)}
if t.Label != "" {
opts = append(opts, xfs.WithLabel(t.Label))
}
return xfs.MakeFS(t.PartitionName, opts...)
case constants.EphemeralPartitionLabel:
log.Printf("formatting partition %q as %q with label %q\n", t.PartitionName, "xfs", t.Label)
opts := []xfs.Option{xfs.WithForce(t.Force)}
if t.Label != "" {
opts = append(opts, xfs.WithLabel(t.Label))
}
return xfs.MakeFS(t.PartitionName, opts...)
default:
return nil
}
log.Printf("formatting partition %q as %q with label %q\n", t.PartitionName, "xfs", t.Label)
opts := []xfs.Option{xfs.WithForce(t.Force)}
if t.Label != "" {
opts = append(opts, xfs.WithLabel(t.Label))
}
return xfs.MakeFS(t.PartitionName, opts...)
}
// Save copies the assets to the bootloader partition.

21
hack/test/run.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
set -e
while getopts c flag
do
case "${flag}" in
c) make clean;;
esac
done
make release-artifacts
make USERNAME=andrewrynhard TAG="${1}" installer talosctl _out/integration-test-provision-linux-amd64
docker push andrewrynhard/installer:"${1}"
sudo -E _out/integration-test-provision-linux-amd64 \
-talos.name local \
-talos.state /tmp/local \
-test.v \
-talos.crashdump=false \
-talos.talosctlpath=$PWD/_out/talosctl-linux-amd64 \
-test.run "TestIntegration/provision.UpgradeSuite.v0.6.0-beta.2-${1}"

View File

@ -33,7 +33,7 @@ import (
"google.golang.org/grpc"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/syslinux"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
"github.com/talos-systems/talos/internal/app/machined/pkg/system"
"github.com/talos-systems/talos/internal/pkg/containers"
taloscontainerd "github.com/talos-systems/talos/internal/pkg/containers/containerd"
@ -107,7 +107,11 @@ func (s *Server) Reboot(ctx context.Context, in *empty.Empty) (reply *machine.Re
func (s *Server) Rollback(ctx context.Context, in *machine.RollbackRequest) (reply *machine.RollbackResponse, err error) {
log.Printf("rollback via API received")
_, next, err := syslinux.Labels()
grub := &grub.Grub{
BootDisk: s.Controller.Runtime().Config().Machine().Install().Disk(),
}
_, next, err := grub.Labels()
if err != nil {
return nil, err
}
@ -116,7 +120,7 @@ func (s *Server) Rollback(ctx context.Context, in *machine.RollbackRequest) (rep
return nil, fmt.Errorf("cannot rollback to %q, label does not exist", next)
}
if err := syslinux.RevertTo(next); err != nil {
if err := grub.Default(next); err != nil {
return nil, fmt.Errorf("failed to revert bootloader: %v", err)
}

View File

@ -23,7 +23,7 @@ import (
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
v1alpha1runtime "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/syslinux"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader"
"github.com/talos-systems/talos/internal/app/machined/pkg/system"
"github.com/talos-systems/talos/internal/app/machined/pkg/system/services"
"github.com/talos-systems/talos/pkg/machinery/api/common"
@ -74,10 +74,20 @@ func handle(err error) {
log.Print(err)
}
if err := syslinux.Revert(); err != nil {
meta, err := bootloader.NewMeta()
if err != nil {
log.Printf("failed to open meta: %v", err)
return
}
if err = meta.Revert(); err != nil {
log.Printf("failed to revert upgrade: %v", err)
}
//nolint: errcheck
meta.Close()
if p := procfs.ProcCmdline().Get(constants.KernelParamPanic).First(); p != nil {
if *p == "0" {
log.Printf("panic=0 kernel flag found, sleeping forever")

View File

@ -2,11 +2,17 @@
// 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
package bootloader
import (
"encoding/binary"
"fmt"
"io"
"os"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
"github.com/talos-systems/talos/pkg/blockdevice/probe"
"github.com/talos-systems/talos/pkg/machinery/constants"
)
const (
@ -39,12 +45,94 @@ const (
AdvUpgrade
)
// Meta represents the meta reader.
type Meta struct {
*os.File
ADV
}
// ADV represents the Syslinux Auxiliary Data Vector.
type ADV []byte
// NewMeta initializes and returns a `Meta`.
func NewMeta() (meta *Meta, err error) {
var f *os.File
f, err = probe.GetPartitionWithName(constants.MetaPartitionLabel)
if err != nil {
return nil, err
}
adv, err := NewADV(f)
if err != nil {
return nil, err
}
return &Meta{
File: f,
ADV: adv,
}, nil
}
func (m *Meta) Read(b []byte) (int, error) {
return m.File.Read(b)
}
func (m *Meta) Write() (int, error) {
offset, err := m.File.Seek(-2*AdvSize, io.SeekEnd)
if err != nil {
return 0, err
}
n, err := m.File.WriteAt(m.ADV, offset)
if err != nil {
return n, err
}
if n != 2*AdvSize {
return n, fmt.Errorf("expected to write %d bytes, wrote %d", AdvLen*2, n)
}
return n, nil
}
// Revert reverts the default bootloader label to the previous installation.
//
// nolint: gocyclo
func (m *Meta) Revert() (err error) {
label, ok := m.ReadTag(AdvUpgrade)
if !ok {
return nil
}
if label == "" {
m.DeleteTag(AdvUpgrade)
if _, err = m.Write(); err != nil {
return err
}
return nil
}
g := &grub.Grub{}
if err = g.Default(label); err != nil {
return err
}
m.DeleteTag(AdvUpgrade)
if _, err = m.Write(); err != nil {
return err
}
return nil
}
// NewADV returns the Auxiliary Data Vector.
func NewADV(r io.ReadSeeker) (adv ADV, err error) {
_, err = r.Seek(-2*AdvSize, 2)
_, err = r.Seek(-2*AdvSize, io.SeekEnd)
if err != nil {
return nil, err
}

View File

@ -3,7 +3,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// nolint: dupl,lll,maligned,scopelint,testpackage
package syslinux
package bootloader
import (
"bytes"
@ -14,9 +14,9 @@ import (
)
func TestNewADV(t *testing.T) {
f, err := os.Open("testdata/ldlinux.sys")
f, err := os.Open("testdata/adv.sys")
if err != nil {
t.Errorf("failed to open test ldlinux.sys: %v", err)
t.Errorf("failed to open test adv.sys: %v", err)
}
// nolint: errcheck

View File

@ -4,8 +4,13 @@
package bootloader
import (
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
)
// Bootloader describes a bootloader.
type Bootloader interface {
Prepare(string) error
Install(string, interface{}) error
Labels() (string, string, error)
Install(string, interface{}, runtime.Sequence, bool) error
Default(string) error
}

View File

@ -0,0 +1,21 @@
// 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 grub
import "github.com/talos-systems/talos/pkg/machinery/constants"
const (
// BootA is a bootloader label.
BootA = "A"
// BootB is a bootloader label.
BootB = "B"
// GrubConfig is the path to the grub config.
GrubConfig = constants.BootMountPoint + "/grub/grub.cfg"
// GrubDeviceMap is the path to the grub device map.
GrubDeviceMap = constants.BootMountPoint + "/grub/device.map"
)

View File

@ -0,0 +1,192 @@
// 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 grub
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
goruntime "runtime"
"strings"
"text/template"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/pkg/blockdevice/probe"
"github.com/talos-systems/talos/pkg/blockdevice/util"
"github.com/talos-systems/talos/pkg/machinery/constants"
)
// Cfg reprsents the cfg file.
type Cfg struct {
Default string
Fallback string
Labels []*Label
}
// Label reprsents a label in the cfg file.
type Label struct {
Root string
Kernel string
Initrd string
Append string
}
const grubCfgTpl = `set default="{{ .Default }}"
{{ with .Fallback -}}
set fallback="{{ . }}"
{{- end }}
set timeout=0
{{ range $label := .Labels -}}
menuentry "{{ $label.Root }}" {
linux {{ $label.Kernel }} {{ $label.Append }}
initrd {{ $label.Initrd }}
}
{{- end }}
`
// Grub represents the grub bootloader.
type Grub struct {
BootDisk string
}
// Labels implements the Bootloader interface.
func (g *Grub) Labels() (current, next string, err error) {
var b []byte
if b, err = ioutil.ReadFile(GrubConfig); err != nil {
if errors.Is(err, os.ErrNotExist) {
next = BootA
return current, next, nil
}
return "", "", err
}
re := regexp.MustCompile(`^set default="(.*)"`)
matches := re.FindAllSubmatch(b, -1)
if len(matches) != 1 {
return "", "", fmt.Errorf("failed to find default")
}
if len(matches[0]) != 2 {
log.Printf("%+v", matches[0])
return "", "", fmt.Errorf("expected 2 matches, got %d", len(matches[0]))
}
current = string(matches[0][1])
switch current {
case BootA:
next = BootB
case BootB:
next = BootA
default:
return "", "", fmt.Errorf("unknown grub menuentry: %q", current)
}
return current, next, err
}
// Install implements the Bootloader interface. It sets up grub with the
// specified kernel parameters.
//
// nolint: gocyclo
func (g *Grub) Install(fallback string, config interface{}, sequence runtime.Sequence, bootPartitionFound bool) (err error) {
grubcfg, ok := config.(*Cfg)
if !ok {
return errors.New("expected a grub config")
}
if err = writeCfg(GrubConfig, grubcfg); err != nil {
return err
}
dev, err := probe.DevForFileSystemLabel(g.BootDisk, constants.BootPartitionLabel)
if err != nil {
return fmt.Errorf("failed to probe boot partition: %w", err)
}
// nolint: errcheck
defer dev.Close()
blk, err := util.DevnameFromPartname(dev.Path)
if err != nil {
return err
}
loopDevice := strings.HasPrefix(blk, "loop")
blk = fmt.Sprintf("/dev/%s", blk)
// default: run for GRUB default platform
platforms := []string{""}
if goruntime.GOARCH == "amd64" && loopDevice {
// building cloud image for amd64, install both BIOS & UEFI GRUB
platforms = []string{"x86_64-efi", "i386-pc"}
}
for _, platform := range platforms {
args := []string{"--boot-directory=" + constants.BootMountPoint, "--efi-directory=" + constants.EFIMountPoint}
if loopDevice {
args = append(args, "--no-nvram")
}
if platform != "" {
args = append(args, "--target="+platform)
}
args = append(args, blk)
log.Printf("executing: grub-install %s", strings.Join(args, " "))
cmd := exec.Command("grub-install", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err = cmd.Run(); err != nil {
return fmt.Errorf("failed to install grub: %w", err)
}
}
return nil
}
// Default implements the bootloader interface.
func (g *Grub) Default(label string) error {
return nil
}
func writeCfg(path string, grubcfg *Cfg) (err error) {
b := []byte{}
wr := bytes.NewBuffer(b)
t := template.Must(template.New("grub").Parse(grubCfgTpl))
if err = t.Execute(wr, grubcfg); err != nil {
return err
}
dir := filepath.Dir(path)
if err = os.MkdirAll(dir, os.ModeDir); err != nil {
return err
}
log.Printf("writing %s to disk", path)
if err = ioutil.WriteFile(path, wr.Bytes(), 0o600); err != nil {
return err
}
return nil
}

View File

@ -17,6 +17,7 @@ import (
"text/template"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader"
"github.com/talos-systems/talos/pkg/cmd"
"github.com/talos-systems/talos/pkg/machinery/constants"
@ -153,11 +154,11 @@ func Labels() (current, next string, err error) {
return current, next, err
}
// RevertTo reverts the default syslinx label to the previous installation.
// Default sets the default syslinx label.
//
// nolint: gocyclo
func RevertTo(label string) (err error) {
log.Printf("reverting default boot to %q", label)
func Default(label string) (err error) {
log.Printf("setting default label to %q", label)
var b []byte
@ -181,55 +182,6 @@ func RevertTo(label string) (err error) {
return nil
}
// Revert reverts the default syslinx label to the previous installation.
//
// nolint: gocyclo
func Revert() (err error) {
f, err := os.OpenFile(SyslinuxLdlinux, os.O_RDWR, 0o700)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil
}
return err
}
// nolint: errcheck
defer f.Close()
adv, err := NewADV(f)
if err != nil {
return err
}
label, ok := adv.ReadTag(AdvUpgrade)
if !ok {
return nil
}
if label == "" {
adv.DeleteTag(AdvUpgrade)
if _, err = f.Write(adv); err != nil {
return err
}
return nil
}
if err = RevertTo(label); err != nil {
return err
}
adv.DeleteTag(AdvUpgrade)
if _, err = f.Write(adv); err != nil {
return err
}
return nil
}
func writeCfg(base, path string, syslinuxcfg *Cfg) (err error) {
b := []byte{}
wr := bytes.NewBuffer(b)
@ -349,13 +301,13 @@ func setADV(ldlinux, fallback string) (err error) {
// nolint: errcheck
defer f.Close()
var adv ADV
var adv bootloader.ADV
if adv, err = NewADV(f); err != nil {
if adv, err = bootloader.NewADV(f); err != nil {
return err
}
if ok := adv.SetTag(AdvUpgrade, fallback); !ok {
if ok := adv.SetTag(bootloader.AdvUpgrade, fallback); !ok {
return fmt.Errorf("failed to set upgrade tag: %q", fallback)
}

View File

@ -85,8 +85,8 @@ func (*Sequencer) Initialize(r runtime.Runtime) []runtime.Phase {
// the config on disk.
).AppendWhen(
r.State().Machine().Installed(),
"mountBoot",
MountBootPartition,
"mountSystem",
MountStatePartition,
).Append(
"config",
LoadConfig,
@ -95,8 +95,8 @@ func (*Sequencer) Initialize(r runtime.Runtime) []runtime.Phase {
// need to mount the boot partition.
).AppendWhen(
r.State().Machine().Installed(),
"unmountBoot",
UnmountBootPartition,
"unmountSystem",
UnmountStatePartition,
).Append(
"resetNetwork",
ResetNetwork,
@ -131,14 +131,14 @@ func (*Sequencer) Install(r runtime.Runtime) []runtime.Phase {
"install",
Install,
).Append(
"mountBoot",
MountBootPartition,
"mountState",
MountStatePartition,
).Append(
"saveConfig",
SaveConfig,
).Append(
"unmountBoot",
UnmountBootPartition,
"unmountState",
UnmountStatePartition,
).Append(
"stopEverything",
StopAllServices,
@ -160,8 +160,8 @@ func (*Sequencer) Boot(r runtime.Runtime) []runtime.Phase {
phases = phases.AppendWhen(
r.State().Platform().Mode() != runtime.ModeContainer,
"mountBoot",
MountBootPartition,
"mountState",
MountStatePartition,
).Append(
"validateConfig",
ValidateConfig,
@ -272,8 +272,8 @@ func (*Sequencer) Reboot(r runtime.Runtime) []runtime.Phase {
UnmountPodMounts,
).Append(
"unmountSystem",
UnmountBootPartition,
UnmountEphemeralPartition,
UnmountStatePartition,
).Append(
"unmountBind",
UnmountSystemDiskBindMounts,
@ -330,8 +330,8 @@ func (*Sequencer) Reset(r runtime.Runtime, in *machineapi.ResetRequest) []runtim
UnmountPodMounts,
).Append(
"unmountSystem",
UnmountBootPartition,
UnmountEphemeralPartition,
UnmountStatePartition,
).Append(
"unmountBind",
UnmountSystemDiskBindMounts,
@ -370,8 +370,8 @@ func (*Sequencer) Shutdown(r runtime.Runtime) []runtime.Phase {
UnmountPodMounts,
).Append(
"unmountSystem",
UnmountBootPartition,
UnmountEphemeralPartition,
UnmountStatePartition,
).Append(
"unmountBind",
UnmountSystemDiskBindMounts,
@ -412,8 +412,8 @@ func (*Sequencer) Upgrade(r runtime.Runtime, in *machineapi.UpgradeRequest) []ru
UnmountPodMounts,
).Append(
"unmountSystem",
UnmountBootPartition,
UnmountEphemeralPartition,
UnmountStatePartition,
).Append(
"unmountBind",
UnmountSystemDiskBindMounts,

View File

@ -31,7 +31,8 @@ import (
installer "github.com/talos-systems/talos/cmd/installer/pkg/install"
"github.com/talos-systems/talos/internal/app/machined/internal/install"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/syslinux"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
"github.com/talos-systems/talos/internal/app/machined/pkg/system"
"github.com/talos-systems/talos/internal/app/machined/pkg/system/events"
"github.com/talos-systems/talos/internal/app/machined/pkg/system/services"
@ -96,7 +97,7 @@ func EnforceKSPPRequirements(seq runtime.Sequence, data interface{}) (runtime.Ta
// SetupSystemDirectory represents the SetupSystemDirectory task.
func SetupSystemDirectory(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
for _, p := range []string{constants.SystemEtcPath, constants.SystemRunPath, constants.SystemVarPath} {
for _, p := range []string{constants.SystemEtcPath, constants.SystemRunPath, constants.SystemVarPath, constants.StateMountPoint} {
if err = os.MkdirAll(p, 0o700); err != nil {
return err
}
@ -694,13 +695,17 @@ func VerifyInstallation(seq runtime.Sequence, data interface{}) (runtime.TaskExe
next string
)
current, next, err = syslinux.Labels()
grub := &grub.Grub{
BootDisk: r.Config().Machine().Install().Disk(),
}
current, next, err = grub.Labels()
if err != nil {
return err
}
if current == "" && next == "" {
return fmt.Errorf("syslinux.cfg is not configured")
return fmt.Errorf("bootloader is not configured")
}
return err
@ -1384,23 +1389,17 @@ func LabelNodeAsMaster(seq runtime.Sequence, data interface{}) (runtime.TaskExec
// UpdateBootloader represents the UpdateBootloader task.
func UpdateBootloader(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
f, err := os.OpenFile(syslinux.SyslinuxLdlinux, os.O_RDWR, 0o700)
meta, err := bootloader.NewMeta()
if err != nil {
return err
}
// nolint: errcheck
defer f.Close()
defer meta.Close()
adv, err := syslinux.NewADV(f)
if err != nil {
return err
}
if ok := adv.DeleteTag(syslinux.AdvUpgrade); ok {
if ok := meta.DeleteTag(bootloader.AdvUpgrade); ok {
logger.Println("removing fallback")
if _, err = f.Write(adv); err != nil {
if _, err = meta.Write(); err != nil {
return err
}
}
@ -1472,6 +1471,34 @@ func UnmountBootPartition(seq runtime.Sequence, data interface{}) (runtime.TaskE
}, "unmountBootPartition"
}
// MountEFIPartition mounts the EFI partition.
func MountEFIPartition(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
return mountSystemPartition(constants.EFIPartitionLabel)
}, "mountEFIPartition"
}
// UnmountEFIPartition unmounts the EFI partition.
func UnmountEFIPartition(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) error {
return unmountSystemPartition(constants.EFIPartitionLabel)
}, "unmountEFIPartition"
}
// MountStatePartition mounts the system partition.
func MountStatePartition(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
return mountSystemPartition(constants.StatePartitionLabel)
}, "mountStatePartition"
}
// UnmountStatePartition unmounts the system partition.
func UnmountStatePartition(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) error {
return unmountSystemPartition(constants.StatePartitionLabel)
}, "unmountStatePartition"
}
// MountEphermeralPartition mounts the ephemeral partition.
func MountEphermeralPartition(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) error {

View File

@ -30,7 +30,7 @@ import (
"github.com/talos-systems/net"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/syslinux"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform"
"github.com/talos-systems/talos/internal/app/machined/pkg/system/events"
"github.com/talos-systems/talos/internal/app/machined/pkg/system/health"
@ -364,22 +364,15 @@ func (e *Etcd) argsForInit(ctx context.Context, r runtime.Runtime) error {
var upgraded bool
if p.Mode() != runtime.ModeContainer {
var f *os.File
var meta *bootloader.Meta
if f, err = os.Open(syslinux.SyslinuxLdlinux); err != nil {
if meta, err = bootloader.NewMeta(); err != nil {
return err
}
// nolint: errcheck
defer f.Close()
defer meta.Close()
var adv syslinux.ADV
if adv, err = syslinux.NewADV(f); err != nil {
return err
}
_, upgraded = adv.ReadTag(syslinux.AdvUpgrade)
_, upgraded = meta.ReadTag(bootloader.AdvUpgrade)
}
primaryAddr, listenAddress, err := primaryAndListenAddresses()

View File

@ -438,6 +438,6 @@ func init() {
allSuites = append(allSuites,
&UpgradeSuite{specGen: upgradeBetweenTwoLastReleases, track: 0},
&UpgradeSuite{specGen: upgradeLastReleaseToCurrent, track: 1},
&UpgradeSuite{specGen: upgradeSingeNodePreserve, track: 0},
// &UpgradeSuite{specGen: upgradeSingeNodePreserve, track: 0},
)
}

View File

@ -22,7 +22,7 @@ import (
func SystemMountPointsForDevice(devpath string) (mountpoints *Points, err error) {
mountpoints = NewMountPoints()
for _, name := range []string{constants.EphemeralPartitionLabel, constants.BootPartitionLabel} {
for _, name := range []string{constants.EphemeralPartitionLabel, constants.BootPartitionLabel, constants.EFIPartitionLabel, constants.StatePartitionLabel} {
var target string
switch name {
@ -30,6 +30,10 @@ func SystemMountPointsForDevice(devpath string) (mountpoints *Points, err error)
target = constants.EphemeralMountPoint
case constants.BootPartitionLabel:
target = constants.BootMountPoint
case constants.EFIPartitionLabel:
target = constants.EFIMountPoint
case constants.StatePartitionLabel:
target = constants.StateMountPoint
}
var dev *probe.ProbedBlockDevice
@ -65,6 +69,10 @@ func SystemMountPointForLabel(label string, opts ...Option) (mountpoint *Point,
opts = append(opts, WithResize(true))
case constants.BootPartitionLabel:
target = constants.BootMountPoint
case constants.EFIPartitionLabel:
target = constants.EFIMountPoint
case constants.StatePartitionLabel:
target = constants.StateMountPoint
default:
return nil, fmt.Errorf("unknown label: %q", label)
}

View File

@ -7,6 +7,7 @@ package probe
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
@ -19,6 +20,7 @@ import (
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/iso9660"
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/vfat"
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/xfs"
gptpartition "github.com/talos-systems/talos/pkg/blockdevice/table/gpt/partition"
"github.com/talos-systems/talos/pkg/blockdevice/util"
"github.com/talos-systems/talos/pkg/retry"
@ -178,6 +180,103 @@ func probe(devpath string) (devpaths []string) {
return devpaths
}
// GetBlockDeviceWithPartitonName probes all known block device's partition
// table for a parition with the specified name.
func GetBlockDeviceWithPartitonName(name string) (bd *blockdevice.BlockDevice, err error) {
var infos []os.FileInfo
if infos, err = ioutil.ReadDir("/sys/block"); err != nil {
return nil, err
}
for _, info := range infos {
devpath := "/dev/" + info.Name()
if bd, err = blockdevice.Open(devpath); err != nil {
continue
}
pt, err := bd.PartitionTable()
if err != nil {
// nolint: errcheck
bd.Close()
if errors.Is(err, blockdevice.ErrMissingPartitionTable) {
continue
}
return nil, fmt.Errorf("failed to open partition table: %w", err)
}
for _, p := range pt.Partitions() {
if part, ok := p.(*gptpartition.Partition); ok {
if part.Name == name {
return bd, nil
}
}
}
// nolint: errcheck
bd.Close()
}
return nil, os.ErrNotExist
}
// GetPartitionWithName probes all known block device's partition
// table for a parition with the specified name.
//
//nolint: gocyclo
func GetPartitionWithName(name string) (f *os.File, err error) {
var infos []os.FileInfo
if infos, err = ioutil.ReadDir("/sys/block"); err != nil {
return nil, err
}
for _, info := range infos {
devpath := "/dev/" + info.Name()
var bd *blockdevice.BlockDevice
if bd, err = blockdevice.Open(devpath); err != nil {
continue
}
// nolint: errcheck
defer bd.Close()
pt, err := bd.PartitionTable()
if err != nil {
if errors.Is(err, blockdevice.ErrMissingPartitionTable) {
continue
}
return nil, fmt.Errorf("failed to open partition table: %w", err)
}
for _, p := range pt.Partitions() {
if part, ok := p.(*gptpartition.Partition); ok {
if part.Name == name {
partpath, err := util.PartPath(info.Name(), int(part.No()))
if err != nil {
return nil, err
}
f, err = os.OpenFile(partpath, os.O_RDWR|unix.O_CLOEXEC, os.ModeDevice)
if err != nil {
return nil, err
}
return f, nil
}
}
}
}
return nil, os.ErrNotExist
}
func probeFilesystem(devpath string) (probed []*ProbedBlockDevice, err error) {
for _, path := range probe(devpath) {
var (

View File

@ -14,6 +14,7 @@ import (
"github.com/google/uuid"
"github.com/talos-systems/talos/pkg/blockdevice/lba"
"github.com/talos-systems/talos/pkg/endianness"
"github.com/talos-systems/talos/pkg/serde"
)
@ -240,10 +241,20 @@ func (hdr *Header) Fields() []*serde.Field {
Length: 16,
// Contents: []byte{0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
return hdr.GUUID.MarshalBinary()
b, err := hdr.GUUID.MarshalBinary()
if err != nil {
return nil, err
}
return endianness.ToMiddleEndian(b)
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
guid, err := uuid.FromBytes(contents)
u, err := endianness.FromMiddleEndian(contents)
if err != nil {
return err
}
guid, err := uuid.FromBytes(u)
if err != nil {
return fmt.Errorf("invalid GUUID: %w", err)
}

View File

@ -47,17 +47,16 @@ func WithMaximumSize(o bool) Option {
func WithLegacyBIOSBootableAttribute(o bool) Option {
return func(args *Options) {
if o {
args.Flags = 4
args.Flags |= (1 << 2)
}
}
}
// 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.Parse("af3dc60f-8384-7247-8e79-3d69d8477de4")
guuid, _ := uuid.Parse("0FC63DAF-8483-4772-8E79-3D69D8477DE4")
opts := &Options{
Type: guuid,

View File

@ -13,6 +13,7 @@ import (
"github.com/google/uuid"
"golang.org/x/text/encoding/unicode"
"github.com/talos-systems/talos/pkg/endianness"
"github.com/talos-systems/talos/pkg/serde"
)
@ -65,14 +66,25 @@ func (prt *Partition) No() int32 {
func (prt *Partition) Fields() []*serde.Field {
return []*serde.Field{
// 16 bytes Partition type GUID
// nolint: dupl
{
Offset: 0,
Length: 16,
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
return prt.Type.MarshalBinary()
b, err := prt.Type.MarshalBinary()
if err != nil {
return nil, err
}
return endianness.ToMiddleEndian(b)
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
guid, err := uuid.FromBytes(contents)
u, err := endianness.FromMiddleEndian(contents)
if err != nil {
return err
}
guid, err := uuid.FromBytes(u)
if err != nil {
return fmt.Errorf("invalid GUUID: %w", err)
}
@ -85,14 +97,25 @@ func (prt *Partition) Fields() []*serde.Field {
},
},
// 16 bytes Unique partition GUID
// nolint: dupl
{
Offset: 16,
Length: 16,
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
return prt.ID.MarshalBinary()
b, err := prt.ID.MarshalBinary()
if err != nil {
return nil, err
}
return endianness.ToMiddleEndian(b)
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
guid, err := uuid.FromBytes(contents)
u, err := endianness.FromMiddleEndian(contents)
if err != nil {
return err
}
guid, err := uuid.FromBytes(u)
if err != nil {
return fmt.Errorf("invalid GUUID: %w", err)
}
@ -135,6 +158,13 @@ func (prt *Partition) Fields() []*serde.Field {
},
},
// 8 bytes Attribute flags (e.g. bit 60 denotes read-only)
// Known attributes are:
// 0: system partition
// 1: hide from EFI
// 2: legacy BIOS bootable
// 60: read-only
// 62: hidden
// 63: do not automount
{
Offset: 48,
Length: 8,

View File

@ -48,7 +48,7 @@ func DevnameFromPartname(partname string) (devname string, err error) {
case strings.HasPrefix(p, "nvme"):
fallthrough
case strings.HasPrefix(p, "loop"):
return strings.TrimRight(p, "p"+partno), nil
return strings.TrimSuffix(p, "p"+partno), nil
case strings.HasPrefix(p, "sd"):
fallthrough
case strings.HasPrefix(p, "hd"):
@ -56,7 +56,7 @@ func DevnameFromPartname(partname string) (devname string, err error) {
case strings.HasPrefix(p, "vd"):
fallthrough
case strings.HasPrefix(p, "xvd"):
return strings.TrimRight(p, partno), nil
return strings.TrimSuffix(p, partno), nil
default:
return "", fmt.Errorf("could not determine dev name from partition name: %s", p)
}

View File

@ -0,0 +1,94 @@
// 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 endianness
import (
"bytes"
"encoding/binary"
)
// ToMiddleEndian converts a byte slice representation of a UUID to a
// middle-endian byte slice representation of a UUID.
//
//nolint: dupl
func ToMiddleEndian(data []byte) (b []byte, err error) {
buf := bytes.NewBuffer(make([]byte, 0, 16))
timeLow := binary.BigEndian.Uint32(data[0:4])
if err := binary.Write(buf, binary.LittleEndian, timeLow); err != nil {
return nil, err
}
timeMid := binary.BigEndian.Uint16(data[4:6])
if err := binary.Write(buf, binary.LittleEndian, timeMid); err != nil {
return nil, err
}
timeHigh := binary.BigEndian.Uint16(data[6:8])
if err := binary.Write(buf, binary.LittleEndian, timeHigh); err != nil {
return nil, err
}
clockSeqHi := data[8:9][0]
if err := binary.Write(buf, binary.BigEndian, clockSeqHi); err != nil {
return nil, err
}
clockSeqLow := data[9:10][0]
if err := binary.Write(buf, binary.BigEndian, clockSeqLow); err != nil {
return nil, err
}
node := data[10:16]
if err := binary.Write(buf, binary.BigEndian, node); err != nil {
return nil, err
}
b = buf.Bytes()
return b, nil
}
// FromMiddleEndian converts a middle-endian byte slice representation of a
// UUID to a big-endian byte slice representation of a UUID.
//
//nolint: dupl
func FromMiddleEndian(data []byte) (b []byte, err error) {
buf := bytes.NewBuffer(make([]byte, 0, 16))
timeLow := binary.LittleEndian.Uint32(data[0:4])
if err := binary.Write(buf, binary.BigEndian, timeLow); err != nil {
return nil, err
}
timeMid := binary.LittleEndian.Uint16(data[4:6])
if err := binary.Write(buf, binary.BigEndian, timeMid); err != nil {
return nil, err
}
timeHigh := binary.LittleEndian.Uint16(data[6:8])
if err := binary.Write(buf, binary.BigEndian, timeHigh); err != nil {
return nil, err
}
clockSeqHi := data[8:9][0]
if err := binary.Write(buf, binary.BigEndian, clockSeqHi); err != nil {
return nil, err
}
clockSeqLow := data[9:10][0]
if err := binary.Write(buf, binary.BigEndian, clockSeqLow); err != nil {
return nil, err
}
node := data[10:16]
if err := binary.Write(buf, binary.BigEndian, node); err != nil {
return nil, err
}
b = buf.Bytes()
return b, nil
}

View File

@ -0,0 +1,92 @@
// 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,scopelint
package endianness_test
import (
"reflect"
"testing"
"github.com/talos-systems/talos/pkg/endianness"
)
var (
uuid = []byte{15, 198, 61, 175, 132, 131, 71, 114, 142, 121, 61, 105, 216, 71, 125, 228}
middle = []byte{175, 61, 198, 15, 131, 132, 114, 71, 142, 121, 61, 105, 216, 71, 125, 228}
)
func TestToMiddleEndian(t *testing.T) {
type args struct {
data []byte
}
tests := []struct {
name string
args args
wantB []byte
wantErr bool
}{
{
name: "valid",
args: args{
data: uuid,
},
wantB: middle,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotB, err := endianness.ToMiddleEndian(tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("ToMiddleEndian() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(gotB, tt.wantB) {
t.Errorf("ToMiddleEndian() = %v, want %v", gotB, tt.wantB)
}
})
}
}
func TestFromMiddleEndian(t *testing.T) {
type args struct {
data []byte
}
tests := []struct {
name string
args args
wantB []byte
wantErr bool
}{
{
name: "valid",
args: args{
data: middle,
},
wantB: uuid,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotB, err := endianness.FromMiddleEndian(tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("FromMiddleEndian() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(gotB, tt.wantB) {
t.Errorf("FromMiddleEndian() = %v, want %v", gotB, tt.wantB)
}
})
}
}

View File

@ -12,6 +12,26 @@ import (
"github.com/talos-systems/crypto/x509"
)
var (
// Username the is the default registry username.
Username string
// Registry is the default registry.
Registry string
// DefaultInstallerImageName is the default container image name for
// the installer.
DefaultInstallerImageName = Username + "/installer"
// DefaultInstallerImageRepository is the default container repository for
// the installer.
DefaultInstallerImageRepository = Registry + "/" + DefaultInstallerImageName
// DefaultTalosImageRepository is the default container repository for
// the talos image.
DefaultTalosImageRepository = Registry + "/" + Username + "/" + "talos"
)
const (
// DefaultKernelVersion is the default Linux kernel version
DefaultKernelVersion = "5.8.5-talos"
@ -45,9 +65,31 @@ const (
// NewRoot is the path where the switchroot target is mounted.
NewRoot = "/root"
// EFIPartitionLabel is the label of the partition to use for mounting at
// the boot path.
EFIPartitionLabel = "EFI"
// EFIMountPoint is the label of the partition to use for mounting at
// the boot path.
EFIMountPoint = BootMountPoint + "/EFI"
// BIOSGrubPartitionLabel is the label of the partition used by grub's second
// stage bootloader.
BIOSGrubPartitionLabel = "BIOS"
// MetaPartitionLabel is the label of the meta partition.
MetaPartitionLabel = "META"
// StatePartitionLabel is the label of the state partition.
StatePartitionLabel = "STATE"
// StateMountPoint is the label of the partition to use for mounting at
// the state path.
StateMountPoint = "/system/state"
// BootPartitionLabel is the label of the partition to use for mounting at
// the boot path.
BootPartitionLabel = "ESP"
BootPartitionLabel = "BOOT"
// BootMountPoint is the label of the partition to use for mounting at
// the boot path.
@ -190,7 +232,7 @@ const (
EtcdDataPath = "/var/lib/etcd"
// ConfigPath is the path to the downloaded config.
ConfigPath = "/boot/config.yaml"
ConfigPath = StateMountPoint + "/config.yaml"
// MetalConfigISOLabel is the volume label for ISO based configuration.
MetalConfigISOLabel = "metal-iso"
@ -280,18 +322,6 @@ const (
// SystemEtcPath is the path to the system etc directory.
SystemEtcPath = SystemPath + "/etc"
// DefaultInstallerImageName is the default container image name for
// the installer.
DefaultInstallerImageName = "autonomy/installer"
// DefaultInstallerImageRepository is the default container repository for
// the installer.
DefaultInstallerImageRepository = "docker.io/" + DefaultInstallerImageName
// DefaultTalosImageRepository is the default container repository for
// the talos image.
DefaultTalosImageRepository = "docker.io/autonomy/talos"
// DefaultCNI is the default CNI.
DefaultCNI = "flannel"