Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[uki] Lyfecycle #148

Merged
merged 18 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions internal/agent/hooks/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ var FirstBoot = []Interface{
&GrubPostInstallOptions{},
}

// AfterUkiInstall sets which Hooks to run after uki runs the install action
var AfterUkiInstall = []Interface{}

// AfterUkiReset sets which Hooks to run after uki runs the install action
var AfterUkiReset = []Interface{}

// AfterUkiUpgrade sets which Hooks to run after uki runs the install action
var AfterUkiUpgrade = []Interface{}

func Run(c config.Config, spec v1.Spec, hooks ...Interface) error {
for _, h := range hooks {
if err := h.Run(c, spec); err != nil {
Expand Down
107 changes: 107 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/kairos-io/kairos-agent/v2/internal/webui"
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
"github.com/kairos-io/kairos-agent/v2/pkg/uki"
"github.com/kairos-io/kairos-sdk/bundles"
"github.com/kairos-io/kairos-sdk/collector"
"github.com/kairos-io/kairos-sdk/machine"
Expand Down Expand Up @@ -651,6 +652,112 @@ The validate command expects a configuration file as its only argument. Local fi
return nil
},
},
{
Name: "uki",
Usage: "UKI subcommands",
Description: "UKI subcommands",
// we could set the flag --source at this level so we could have the flag for all subcommands but that translates into an ugly command
// in which you need to put the source flag before the subcommand, which is a mess. Just bad UX.
// command level: kairos-agent uki --source oci:whatever install
// subcommand level: kairos-agent uki install --source oci:whatever
Subcommands: []*cli.Command{
{
Name: "install",
Usage: "Install to disk",
UsageText: "install [--device DEVICE]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "source",
Usage: "Source for install. Composed of `type:address`. Accepts `file:`,`dir:` or `oci:` for the type of source.\nFor example `file:/var/share/myimage.tar`, `dir:/tmp/extracted` or `oci:repo/image:tag`",
Action: func(c *cli.Context, s string) error {
return validateSource(s)
},
},
&cli.StringFlag{
Name: "device",
},
},
Action: func(c *cli.Context) error {
config, err := agentConfig.Scan(collector.Directories(configScanDir...), collector.NoLogs)
if err != nil {
return err
}
// Load the spec from the config
installSpec, err := agentConfig.ReadUkiInstallSpecFromConfig(config)
if err != nil {
return err
}

if c.String("device") != "" {
installSpec.Target = c.String("device")
}

installAction := uki.NewInstallAction(config, installSpec)
return installAction.Run()
},
},
{
Name: "upgrade",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "source",
Usage: "Source for upgrade. Composed of `type:address`. Accepts `file:`,`dir:` or `oci:` for the type of source.\nFor example `file:/var/share/myimage.tar`, `dir:/tmp/extracted` or `oci:repo/image:tag`",
Action: func(c *cli.Context, s string) error {
return validateSource(s)
},
},
},
Before: func(c *cli.Context) error {
return fmt.Errorf("not implemented")
},
Action: func(c *cli.Context) error {
config, err := agentConfig.Scan(collector.Directories(configScanDir...), collector.NoLogs, collector.StrictValidation(c.Bool("strict-validation")))
if err != nil {
return err
}

// Load the spec from the config
upgradeSpec, err := agentConfig.ReadUkiUpgradeFromConfig(config)
if err != nil {
return err
}

upgradeAction := uki.NewUpgradeAction(config, upgradeSpec)
return upgradeAction.Run()
},
},
{
Name: "reset",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "source",
Usage: "Source for upgrade. Composed of `type:address`. Accepts `file:`,`dir:` or `oci:` for the type of source.\nFor example `file:/var/share/myimage.tar`, `dir:/tmp/extracted` or `oci:repo/image:tag`",
Action: func(c *cli.Context, s string) error {
return validateSource(s)
},
},
},
Before: func(c *cli.Context) error {
return fmt.Errorf("not implemented")
},
Action: func(c *cli.Context) error {
config, err := agentConfig.Scan(collector.Directories(configScanDir...), collector.NoLogs, collector.StrictValidation(c.Bool("strict-validation")))
if err != nil {
return err
}

// Load the spec from the config
resetSpec, err := agentConfig.ReadUkiResetSpecFromConfig(config)
if err != nil {
return err
}

resetAction := uki.NewResetAction(config, resetSpec)
return resetAction.Run()
},
},
},
},
}

func main() {
Expand Down
75 changes: 75 additions & 0 deletions pkg/config/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,73 @@ func ReadInstallSpecFromConfig(c *Config) (*v1.InstallSpec, error) {
return installSpec, nil
}

// ReadUkiResetSpecFromConfig will return a proper v1.ResetUkiSpec based on an agent Config
func ReadUkiResetSpecFromConfig(c *Config) (*v1.ResetUkiSpec, error) {
sp, err := ReadSpecFromCloudConfig(c, "reset-uki")
if err != nil {
return &v1.ResetUkiSpec{}, err
}
resetSpec := sp.(*v1.ResetUkiSpec)
return resetSpec, nil
}

func NewUkiInstallSpec(cfg *Config) (*v1.InstallUkiSpec, error) {
spec := &v1.InstallUkiSpec{
Target: cfg.Install.Device,
}

// Calculate the partitions afterwards so they use the image sizes for the final partition sizes
spec.Partitions.EFI = &v1.Partition{
FilesystemLabel: constants.EfiLabel,
Size: constants.ImgSize, // TODO: Fix this and set proper size based on the source size
Name: constants.EfiPartName,
FS: constants.EfiFs,
MountPoint: constants.EfiDir,
Flags: []string{"esp"},
}
spec.Partitions.OEM = &v1.Partition{
FilesystemLabel: constants.OEMLabel,
Size: constants.OEMSize,
Name: constants.OEMPartName,
FS: constants.LinuxFs,
MountPoint: constants.OEMDir,
Flags: []string{},
}
spec.Partitions.Persistent = &v1.Partition{
FilesystemLabel: constants.PersistentLabel,
Size: constants.PersistentSize,
Name: constants.PersistentPartName,
FS: constants.LinuxFs,
MountPoint: constants.PersistentDir,
Flags: []string{},
}

// TODO: Which key to use? install or install-uki?
err := unmarshallFullSpec(cfg, "install", spec)

return spec, err
}

// ReadUkiInstallSpecFromConfig will return a proper v1.InstallUkiSpec based on an agent Config
func ReadUkiInstallSpecFromConfig(c *Config) (*v1.InstallUkiSpec, error) {
sp, err := ReadSpecFromCloudConfig(c, "install-uki")
if err != nil {
return &v1.InstallUkiSpec{}, err
}
installSpec := sp.(*v1.InstallUkiSpec)
return installSpec, nil
}

// ReadUkiUpgradeFromConfig will return a proper v1.UpgradeUkiSpec based on an agent Config
func ReadUkiUpgradeFromConfig(c *Config) (*v1.UpgradeUkiSpec, error) {
sp, err := ReadSpecFromCloudConfig(c, "upgrade-uki")
if err != nil {
return &v1.UpgradeUkiSpec{}, err
}
upgradeSpec := sp.(*v1.UpgradeUkiSpec)
return upgradeSpec, nil
}

// GetSourceSize will try to gather the actual size of the source
// Useful to create the exact size of images and by side effect the partition size
// This helps adjust the size to be juuuuust right.
Expand Down Expand Up @@ -571,6 +638,14 @@ func ReadSpecFromCloudConfig(r *Config, spec string) (v1.Spec, error) {
sp, err = NewUpgradeSpec(r)
case "reset":
sp, err = NewResetSpec(r)
case "install-uki":
sp, err = NewUkiInstallSpec(r)
case "reset-uki":
// TODO: Fill with proper defaults
sp = &v1.ResetUkiSpec{}
case "upgrade-uki":
// TODO: Fill with proper defaults
sp = &v1.UpgradeUkiSpec{}
default:
return nil, fmt.Errorf("spec not valid: %s", spec)
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ const (
SignedShim = "shim.efi"

Rsync = "rsync"

UkiSource = "/run/install/uki"
UkiCdromSource = "/run/install/cdrom"
)

func GetCloudInitPaths() []string {
Expand Down
12 changes: 6 additions & 6 deletions pkg/elemental/elemental.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,27 +50,27 @@ func (e *Elemental) FormatPartition(part *v1.Partition, opts ...string) error {
// PartitionAndFormatDevice creates a new empty partition table on target disk
// and applies the configured disk layout by creating and formatting all
// required partitions
func (e *Elemental) PartitionAndFormatDevice(i *v1.InstallSpec) error {
func (e *Elemental) PartitionAndFormatDevice(i v1.SharedInstallSpec) error {
disk := partitioner.NewDisk(
i.Target,
i.GetTarget(),
partitioner.WithRunner(e.config.Runner),
partitioner.WithFS(e.config.Fs),
partitioner.WithLogger(e.config.Logger),
)

if !disk.Exists() {
e.config.Logger.Errorf("Disk %s does not exist", i.Target)
return fmt.Errorf("disk %s does not exist", i.Target)
e.config.Logger.Errorf("Disk %s does not exist", i.GetTarget())
return fmt.Errorf("disk %s does not exist", i.GetTarget())
}

e.config.Logger.Infof("Partitioning device...")
out, err := disk.NewPartitionTable(i.PartTable)
out, err := disk.NewPartitionTable(i.GetPartTable())
if err != nil {
e.config.Logger.Errorf("Failed creating new partition table: %s", out)
return err
}

parts := i.Partitions.PartitionsByInstallOrder(i.ExtraPartitions)
parts := i.GetPartitions().PartitionsByInstallOrder(i.GetExtraPartitions())
return e.createPartitions(disk, parts)
}

Expand Down
63 changes: 61 additions & 2 deletions pkg/types/v1/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ type Spec interface {
ShouldShutdown() bool
}

// SharedInstallSpec is the interface that Install specs need to implement
type SharedInstallSpec interface {
GetPartTable() string
GetTarget() string
GetPartitions() ElementalPartitions
GetExtraPartitions() PartitionList
}

// InstallSpec struct represents all the installation action details
type InstallSpec struct {
Target string `yaml:"device,omitempty" mapstructure:"device"`
Expand Down Expand Up @@ -106,8 +114,12 @@ func (i *InstallSpec) Sanitize() error {
return i.Partitions.SetFirmwarePartitions(i.Firmware, i.PartTable)
}

func (i *InstallSpec) ShouldReboot() bool { return i.Reboot }
func (i *InstallSpec) ShouldShutdown() bool { return i.PowerOff }
func (i *InstallSpec) ShouldReboot() bool { return i.Reboot }
func (i *InstallSpec) ShouldShutdown() bool { return i.PowerOff }
func (i *InstallSpec) GetTarget() string { return i.Target }
func (i *InstallSpec) GetPartTable() string { return i.PartTable }
func (i *InstallSpec) GetPartitions() ElementalPartitions { return i.Partitions }
func (i *InstallSpec) GetExtraPartitions() PartitionList { return i.ExtraPartitions }

// ResetSpec struct represents all the reset action details
type ResetSpec struct {
Expand Down Expand Up @@ -476,3 +488,50 @@ type DockerImageMeta struct {
Digest string `yaml:"digest,omitempty"`
Size int64 `yaml:"size,omitempty"`
}

type InstallUkiSpec struct {
Target string `yaml:"device,omitempty" mapstructure:"device"`
Reboot bool `yaml:"reboot,omitempty" mapstructure:"reboot"`
PowerOff bool `yaml:"poweroff,omitempty" mapstructure:"poweroff"`
Partitions ElementalPartitions `yaml:"partitions,omitempty" mapstructure:"partitions"`
ExtraPartitions PartitionList `yaml:"extra-partitions,omitempty" mapstructure:"extra-partitions"`
CloudInit []string `yaml:"cloud-init,omitempty" mapstructure:"cloud-init"`
}

func (i *InstallUkiSpec) Sanitize() error {
var err error
return err
}

func (i *InstallUkiSpec) ShouldReboot() bool { return i.Reboot }
func (i *InstallUkiSpec) ShouldShutdown() bool { return i.PowerOff }
func (i *InstallUkiSpec) GetTarget() string { return i.Target }
func (i *InstallUkiSpec) GetPartTable() string { return "gpt" }
func (i *InstallUkiSpec) GetPartitions() ElementalPartitions { return i.Partitions }
func (i *InstallUkiSpec) GetExtraPartitions() PartitionList { return i.ExtraPartitions }

type UpgradeUkiSpec struct {
Reboot bool `yaml:"reboot,omitempty" mapstructure:"reboot"`
PowerOff bool `yaml:"poweroff,omitempty" mapstructure:"poweroff"`
}

func (i *UpgradeUkiSpec) Sanitize() error {
var err error
return err
}

func (i *UpgradeUkiSpec) ShouldReboot() bool { return i.Reboot }
func (i *UpgradeUkiSpec) ShouldShutdown() bool { return i.PowerOff }

type ResetUkiSpec struct {
Reboot bool `yaml:"reboot,omitempty" mapstructure:"reboot"`
PowerOff bool `yaml:"poweroff,omitempty" mapstructure:"poweroff"`
}

func (i *ResetUkiSpec) Sanitize() error {
var err error
return err
}

func (i *ResetUkiSpec) ShouldReboot() bool { return i.Reboot }
func (i *ResetUkiSpec) ShouldShutdown() bool { return i.PowerOff }
Loading