Skip to content

Commit

Permalink
feat: implement Install for imager overlays
Browse files Browse the repository at this point in the history
Implement `Install` for imager overlays.
Also add support for generating installers.

Depends on: #8377

Fixes: #8350
Fixes: #8351
Fixes: #8350

Signed-off-by: Noel Georgi <git@frezbo.dev>
  • Loading branch information
frezbo committed Mar 12, 2024
1 parent cd5a5a4 commit d118a85
Show file tree
Hide file tree
Showing 20 changed files with 400 additions and 138 deletions.
17 changes: 2 additions & 15 deletions .drone.jsonnet
Expand Up @@ -640,7 +640,6 @@ local integration_qemu_csi = Step('e2e-csi', target='e2e-qemu', privileged=true,
});

local integration_images = Step('images', target='images', depends_on=[load_artifacts], environment={ IMAGE_REGISTRY: local_registry });
local integration_sbcs = Step('sbcs', target='sbcs', depends_on=[integration_images], environment={ IMAGE_REGISTRY: local_registry });
local integration_cloud_images = Step('cloud-images', depends_on=[integration_images], environment=creds_env_vars);

local integration_reproducibility_test = Step('reproducibility-test', target='reproducibility-test', depends_on=[load_artifacts], environment={ IMAGE_REGISTRY: local_registry });
Expand Down Expand Up @@ -704,7 +703,7 @@ local integration_pipelines = [
Pipeline('integration-qemu-encrypted-vip', default_pipeline_steps + [integration_qemu_encrypted_vip]) + integration_trigger(['integration-qemu-encrypted-vip']),
Pipeline('integration-qemu-race', default_pipeline_steps + [build_race, integration_qemu_race]) + integration_trigger(['integration-qemu-race']),
Pipeline('integration-qemu-csi', default_pipeline_steps + [integration_qemu_csi]) + integration_trigger(['integration-qemu-csi']),
Pipeline('integration-images', default_pipeline_steps + [integration_images, integration_sbcs]) + integration_trigger(['integration-images']),
Pipeline('integration-images', default_pipeline_steps + [integration_images]) + integration_trigger(['integration-images']),
Pipeline('integration-reproducibility-test', default_pipeline_steps + [integration_reproducibility_test]) + integration_trigger(['integration-reproducibility']),
Pipeline('integration-cloud-images', default_pipeline_steps + [integration_images, integration_cloud_images]) + literal_trigger(['integration-cloud-images']),
Pipeline('image-factory', default_pipeline_steps + [
Expand Down Expand Up @@ -738,7 +737,7 @@ local integration_pipelines = [
Pipeline('cron-integration-qemu-encrypted-vip', default_pipeline_steps + [integration_qemu_encrypted_vip], [default_cron_pipeline]) + cron_trigger(['thrice-daily', 'nightly']),
Pipeline('cron-integration-qemu-race', default_pipeline_steps + [build_race, integration_qemu_race], [default_cron_pipeline]) + cron_trigger(['nightly']),
Pipeline('cron-integration-qemu-csi', default_pipeline_steps + [integration_qemu_csi], [default_cron_pipeline]) + cron_trigger(['nightly']),
Pipeline('cron-integration-images', default_pipeline_steps + [integration_images, integration_sbcs], [default_cron_pipeline]) + cron_trigger(['nightly']),
Pipeline('cron-integration-images', default_pipeline_steps + [integration_images], [default_cron_pipeline]) + cron_trigger(['nightly']),
Pipeline('cron-integration-reproducibility-test', default_pipeline_steps + [integration_reproducibility_test], [default_cron_pipeline]) + cron_trigger(['nightly']),
Pipeline('cron-image-factory', default_pipeline_steps + [
integration_factory_16_iso,
Expand Down Expand Up @@ -891,7 +890,6 @@ local conformance_pipelines = [

local cloud_images = Step('cloud-images', depends_on=[e2e_docker, e2e_qemu], environment=creds_env_vars);
local images = Step('images', target='images', depends_on=[iso, images_essential, save_artifacts], environment={ IMAGE_REGISTRY: local_registry });
local sbcs = Step('sbcs', target='sbcs', depends_on=[images], environment={ IMAGE_REGISTRY: local_registry });

// TODO(andrewrynhard): We should run E2E tests on a release.
local release = {
Expand Down Expand Up @@ -921,15 +919,6 @@ local release = {
'_out/metal-arm64.iso',
'_out/metal-amd64.raw.xz',
'_out/metal-arm64.raw.xz',
'_out/metal-rpi_generic-arm64.raw.xz',
'_out/metal-rockpi_4-arm64.raw.xz',
'_out/metal-rockpi_4c-arm64.raw.xz',
'_out/metal-rock64-arm64.raw.xz',
'_out/metal-pine64-arm64.raw.xz',
'_out/metal-bananapi_m64-arm64.raw.xz',
'_out/metal-libretech_all_h3_cc_h5-arm64.raw.xz',
'_out/metal-jetson_nano-arm64.raw.xz',
'_out/metal-nanopi_r4s-arm64.raw.xz',
'_out/nocloud-amd64.raw.xz',
'_out/nocloud-arm64.raw.xz',
'_out/opennebula-amd64.raw.xz',
Expand Down Expand Up @@ -973,7 +962,6 @@ local release = {
cloud_images.name,
talosctl_cni_bundle.name,
images.name,
sbcs.name,
iso.name,
push.name,
release_notes.name,
Expand All @@ -982,7 +970,6 @@ local release = {

local release_steps = default_steps + [
images,
sbcs,
cloud_images,
release,
];
Expand Down
7 changes: 0 additions & 7 deletions Dockerfile
Expand Up @@ -32,8 +32,6 @@ ARG PKG_RUNC
ARG PKG_XFSPROGS
ARG PKG_UTIL_LINUX
ARG PKG_KMOD
ARG PKG_U_BOOT
ARG PKG_RASPBERYPI_FIRMWARE
ARG PKG_KERNEL
ARG PKG_TALOSCTL_CNI_BUNDLE_INSTALL

Expand Down Expand Up @@ -114,9 +112,6 @@ FROM ${PKG_KERNEL} AS pkg-kernel
FROM --platform=amd64 ${PKG_KERNEL} AS pkg-kernel-amd64
FROM --platform=arm64 ${PKG_KERNEL} AS pkg-kernel-arm64

FROM --platform=arm64 ${PKG_U_BOOT} AS pkg-u-boot-arm64
FROM --platform=arm64 ${PKG_RASPBERYPI_FIRMWARE} AS pkg-raspberrypi-firmware-arm64

# Resolve package images using ${EXTRAS} to be used later in COPY --from=.

FROM ${PKG_TALOSCTL_CNI_BUNDLE_INSTALL} AS extras-talosctl-cni-bundle-install
Expand Down Expand Up @@ -780,8 +775,6 @@ COPY --from=pkg-kernel-arm64 /dtb /usr/install/arm64/dtb
COPY --from=initramfs-archive-arm64 /initramfs.xz /usr/install/arm64/initramfs.xz
COPY --from=pkg-sd-boot-arm64 /linuxaa64.efi.stub /usr/install/arm64/systemd-stub.efi
COPY --from=pkg-sd-boot-arm64 /systemd-bootaa64.efi /usr/install/arm64/systemd-boot.efi
COPY --from=pkg-u-boot-arm64 / /usr/install/arm64/u-boot
COPY --from=pkg-raspberrypi-firmware-arm64 / /usr/install/arm64/raspberrypi-firmware

FROM scratch AS install-artifacts-all
COPY --from=install-artifacts-amd64 / /
Expand Down
8 changes: 0 additions & 8 deletions Makefile
Expand Up @@ -45,8 +45,6 @@ PKG_RUNC ?= $(PKGS_PREFIX)/runc:$(PKGS)
PKG_XFSPROGS ?= $(PKGS_PREFIX)/xfsprogs:$(PKGS)
PKG_UTIL_LINUX ?= $(PKGS_PREFIX)/util-linux:$(PKGS)
PKG_KMOD ?= $(PKGS_PREFIX)/kmod:$(PKGS)
PKG_U_BOOT ?= $(PKGS_PREFIX)/u-boot:$(PKGS)
PKG_RASPBERYPI_FIRMWARE ?= $(PKGS_PREFIX)/raspberrypi-firmware:$(PKGS)
PKG_KERNEL ?= $(PKGS_PREFIX)/kernel:$(PKGS)
PKG_TALOSCTL_CNI_BUNDLE_INSTALL ?= $(PKGS_PREFIX)/talosctl-cni-bundle-install:$(EXTRAS)

Expand Down Expand Up @@ -376,12 +374,6 @@ images-essential: image-aws image-gcp image-metal secureboot-installer ## Builds

images: image-aws image-azure image-digital-ocean image-exoscale image-gcp image-hcloud image-iso image-metal image-nocloud image-opennebula image-openstack image-oracle image-scaleway image-upcloud image-vmware image-vultr ## Builds all known images (AWS, Azure, DigitalOcean, Exoscale, GCP, HCloud, Metal, NoCloud, OpenNebula, Openstack, Oracle, Scaleway, UpCloud, Vultr and VMware).

sbc-%: ## Builds the specified SBC image. Valid options are rpi_generic, rock64, bananapi_m64, libretech_all_h3_cc_h5, rockpi_4, rockpi_4c, pine64, jetson_nano and nanopi_r4s (e.g. sbc-rpi_generic)
@docker pull $(REGISTRY_AND_USERNAME)/imager:$(IMAGE_TAG)
@docker run --rm -t -v /dev:/dev -v $(PWD)/$(ARTIFACTS):/out --network=host --privileged $(REGISTRY_AND_USERNAME)/imager:$(IMAGE_TAG) $* --arch arm64 $(IMAGER_ARGS)

sbcs: sbc-rpi_generic sbc-rock64 sbc-bananapi_m64 sbc-libretech_all_h3_cc_h5 sbc-rockpi_4 sbc-rockpi_4c sbc-pine64 sbc-jetson_nano sbc-nanopi_r4s ## Builds all known SBC images (Raspberry Pi 4, Rock64, Banana Pi M64, Radxa ROCK Pi 4, Radxa ROCK Pi 4c, Pine64, Libre Computer Board ALL-H3-CC, Jetson Nano and Nano Pi R4S).

.PHONY: iso
iso: image-iso ## Builds the ISO and outputs it to the artifact directory.

Expand Down
41 changes: 41 additions & 0 deletions cmd/installer/cmd/imager/root.go
Expand Up @@ -6,10 +6,12 @@
package imager

import (
"bytes"
"context"
"fmt"
"os"
"runtime"
"strings"

"github.com/dustin/go-humanize"
"github.com/siderolabs/gen/xslices"
Expand All @@ -22,6 +24,7 @@ import (
"github.com/siderolabs/talos/pkg/imager"
"github.com/siderolabs/talos/pkg/imager/profile"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/overlay"
"github.com/siderolabs/talos/pkg/reporter"
)

Expand All @@ -39,6 +42,7 @@ var cmdFlags struct {
TarToStdout bool
OverlayName string
OverlayImage string
OverlayOptions []string
}

// rootCmd represents the base command when called without any subcommands.
Expand Down Expand Up @@ -76,12 +80,47 @@ var rootCmd = &cobra.Command{
},
}

extraOverlayOptions := overlay.ExtraOptions{}

for _, option := range cmdFlags.OverlayOptions {
if strings.HasPrefix(option, "@") {
data, err := os.ReadFile(option[1:])
if err != nil {
return err
}

decoder := yaml.NewDecoder(bytes.NewReader(data))
decoder.KnownFields(true)

if err := decoder.Decode(&extraOverlayOptions); err != nil {
return err
}

continue

}

k, v, _ := strings.Cut(option, "=")

if strings.HasPrefix(v, "@") {
data, err := os.ReadFile(v[1:])
if err != nil {
return err
}

v = string(data)
}

extraOverlayOptions[k] = v
}

if cmdFlags.OverlayName != "" || cmdFlags.OverlayImage != "" {
prof.Overlay = &profile.OverlayOptions{
Name: cmdFlags.OverlayName,
Image: profile.ContainerAsset{
ImageRef: cmdFlags.OverlayImage,
},
ExtraOptions: extraOverlayOptions,
}
}

Expand Down Expand Up @@ -176,6 +215,8 @@ func init() {
rootCmd.PersistentFlags().BoolVar(&cmdFlags.TarToStdout, "tar-to-stdout", false, "Tar output and send to stdout")
rootCmd.PersistentFlags().StringVar(&cmdFlags.OverlayName, "overlay-name", "", "The name of the overlay to use")
rootCmd.PersistentFlags().StringVar(&cmdFlags.OverlayImage, "overlay-image", "", "The image reference to the overlay")
rootCmd.PersistentFlags().StringArrayVar(&cmdFlags.OverlayOptions, "overlay-option", []string{}, "Extra options to pass to the overlay")
rootCmd.MarkFlagsMutuallyExclusive("board", "overlay-name")
rootCmd.MarkFlagsMutuallyExclusive("board", "overlay-image")
rootCmd.MarkFlagsMutuallyExclusive("board", "overlay-option")
}
3 changes: 1 addition & 2 deletions cmd/installer/cmd/installer/root.go
Expand Up @@ -52,13 +52,12 @@ var options = &install.Options{}

var bootloader bool

//nolint:goconst
func init() {
rootCmd.PersistentFlags().StringVar(&options.ConfigSource, "config", "", "The value of "+constants.KernelParamConfig)
rootCmd.PersistentFlags().StringVar(&options.Disk, "disk", "", "The path to the disk to install to")
rootCmd.PersistentFlags().StringVar(&options.Platform, "platform", "", "The value of "+constants.KernelParamPlatform)
rootCmd.PersistentFlags().StringVar(&options.Arch, "arch", runtime.GOARCH, "The target architecture")
rootCmd.PersistentFlags().StringVar(&options.Board, "board", constants.BoardNone, "The value of "+constants.KernelParamBoard)
rootCmd.PersistentFlags().StringVar(&options.Board, "board", constants.BoardNone, "Deprecated: no op")
rootCmd.PersistentFlags().StringArrayVar(&options.ExtraKernelArgs, "extra-kernel-arg", []string{}, "Extra argument to pass to the kernel")
rootCmd.PersistentFlags().BoolVar(&bootloader, "bootloader", true, "Deprecated: no op")
rootCmd.PersistentFlags().BoolVar(&options.Upgrade, "upgrade", false, "Indicates that the install is being performed by an upgrade")
Expand Down
96 changes: 81 additions & 15 deletions cmd/installer/pkg/install/install.go
Expand Up @@ -5,42 +5,50 @@
package install

import (
"bytes"
"context"
"errors"
"fmt"
"log"
"os"
"path/filepath"
"syscall"
"time"

"github.com/siderolabs/go-blockdevice/blockdevice"
"github.com/siderolabs/go-procfs/procfs"
"github.com/siderolabs/go-retry/retry"
"gopkg.in/yaml.v3"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/board"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader"
bootloaderoptions "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options"
"github.com/siderolabs/talos/internal/pkg/meta"
"github.com/siderolabs/talos/internal/pkg/mount"
"github.com/siderolabs/talos/pkg/imager/overlay/executor"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/kernel"
"github.com/siderolabs/talos/pkg/machinery/overlay"
"github.com/siderolabs/talos/pkg/machinery/version"
)

// Options represents the set of options available for an install.
type Options struct {
ConfigSource string
Disk string
Platform string
Arch string
Board string
ExtraKernelArgs []string
Upgrade bool
Force bool
Zero bool
LegacyBIOSSupport bool
MetaValues MetaValues
ConfigSource string
Disk string
Platform string
Arch string
Board string
ExtraKernelArgs []string
Upgrade bool
Force bool
Zero bool
LegacyBIOSSupport bool
MetaValues MetaValues
OverlayInstaller overlay.Installer[overlay.ExtraOptions]
OverlayExtractedDir string
ExtraOptions overlay.ExtraOptions

// Options specific for the image creation mode.
ImageSecureboot bool
Expand Down Expand Up @@ -68,7 +76,15 @@ func (m Mode) IsImage() bool {
}

// Install installs Talos.
func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options) (err error) {
//
//nolint:gocyclo
func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options) error {
overlayPresent := overlayPresent()

if b := getBoard(); b != constants.BoardNone && !overlayPresent {
return fmt.Errorf("using standard installer image is not supported for board: %s, use an installer with overlay", b)
}

cmdline := procfs.NewCmdline("")
cmdline.Append(constants.KernelParamPlatform, p.Name())

Expand All @@ -79,7 +95,7 @@ func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options)
cmdline.SetAll(p.KernelArgs().Strings())

// first defaults, then extra kernel args to allow extra kernel args to override defaults
if err = cmdline.AppendAll(kernel.DefaultArgs); err != nil {
if err := cmdline.AppendAll(kernel.DefaultArgs); err != nil {
return err
}

Expand All @@ -91,7 +107,7 @@ func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options)

var b runtime.Board

b, err = board.NewBoard(opts.Board)
b, err := board.NewBoard(opts.Board)
if err != nil {
return err
}
Expand All @@ -101,7 +117,7 @@ func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options)
cmdline.SetAll(b.KernelArgs().Strings())
}

if err = cmdline.AppendAll(
if err := cmdline.AppendAll(
opts.ExtraKernelArgs,
procfs.WithOverwriteArgs("console"),
procfs.WithOverwriteArgs(constants.KernelParamPlatform),
Expand All @@ -110,6 +126,25 @@ func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options)
return err
}

if overlayPresent {
extraOptionsBytes, err := os.ReadFile(constants.ImagerOverlayExtraOptionsPath)
if err != nil {
return err
}

var extraOptions overlay.ExtraOptions

decoder := yaml.NewDecoder(bytes.NewReader(extraOptionsBytes))
decoder.KnownFields(true)

if err := decoder.Decode(&extraOptions); err != nil {
return fmt.Errorf("failed to decode extra options: %w", err)
}

opts.OverlayInstaller = executor.New(constants.ImagerOverlayInstallerDefaultPath)
opts.ExtraOptions = extraOptions
}

i, err := NewInstaller(ctx, cmdline, mode, opts)
if err != nil {
return err
Expand Down Expand Up @@ -302,6 +337,17 @@ func (i *Installer) Install(ctx context.Context, mode Mode) (err error) {
}
}

if i.options.OverlayInstaller != nil {
if err = i.options.OverlayInstaller.Install(overlay.InstallOptions[overlay.ExtraOptions]{
InstallDisk: i.options.Disk,
MountPrefix: i.options.MountPrefix,
ArtifactsPath: filepath.Join(i.options.OverlayExtractedDir, constants.ImagerOverlayArtifactsPath),
ExtraOptions: i.options.ExtraOptions,
}); err != nil {
return err
}
}

if mode == ModeUpgrade || len(i.options.MetaValues.values) > 0 {
var (
metaState *meta.Meta
Expand Down Expand Up @@ -394,3 +440,23 @@ func retryBlockdeviceOpen(device string) (*blockdevice.BlockDevice, error) {

return bd, err
}

func overlayPresent() bool {
_, err := os.Stat(constants.ImagerOverlayInstallerDefaultPath)

return err == nil
}

func getBoard() string {
cmdline := procfs.ProcCmdline()
if cmdline == nil {
return constants.BoardNone
}

board := cmdline.Get(constants.KernelParamBoard)
if board == nil {
return constants.BoardNone
}

return *board.First()
}

0 comments on commit d118a85

Please sign in to comment.