Skip to content

Commit

Permalink
Add reset for uki (#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
Itxaka authored Feb 2, 2024
1 parent 7efda3d commit 33c8e8f
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 87 deletions.
6 changes: 0 additions & 6 deletions internal/agent/hooks/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,6 @@ var AfterUkiInstall = []Interface{
&KcryptUKI{},
}

// 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
138 changes: 108 additions & 30 deletions internal/agent/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package agent
import (
"encoding/json"
"fmt"
"os"
"github.com/kairos-io/kairos-agent/v2/pkg/uki"
internalutils "github.com/kairos-io/kairos-agent/v2/pkg/utils"
"strings"
"sync"
"time"

Expand All @@ -20,13 +22,81 @@ import (
"github.com/mudler/go-pluggable"
)

func Reset(reboot, unattended bool, dir ...string) error {
func Reset(reboot, unattended, resetOem bool, dir ...string) error {
// In both cases we want
if internalutils.UkiBootMode() == internalutils.UkiHDD {
return resetUki(reboot, unattended, resetOem, dir...)
} else if internalutils.UkiBootMode() == internalutils.UkiRemovableMedia {
return fmt.Errorf("reset is not supported on removable media, please run reset from the installed system recovery entry")
} else {
return reset(reboot, unattended, resetOem, dir...)
}
}

func reset(reboot, unattended, resetOem bool, dir ...string) error {
cfg, err := sharedReset(reboot, unattended, resetOem, dir...)
if err != nil {
return err
}
// Load the installation Config from the cloud-config data
resetSpec, err := config.ReadResetSpecFromConfig(cfg)
if err != nil {
return err
}

err = resetSpec.Sanitize()
if err != nil {
return err
}

resetAction := action.NewResetAction(cfg, resetSpec)
if err = resetAction.Run(); err != nil {
cfg.Logger.Errorf("failed to reset: %s", err)
return err
}

bus.Manager.Publish(sdk.EventAfterReset, sdk.EventPayload{}) //nolint:errcheck

return hook.Run(*cfg, resetSpec, hook.AfterReset...)
}

func resetUki(reboot, unattended, resetOem bool, dir ...string) error {
cfg, err := sharedReset(reboot, unattended, resetOem, dir...)
if err != nil {
return err
}
// Load the installation Config from the cloud-config data
resetSpec, err := config.ReadUkiResetSpecFromConfig(cfg)
if err != nil {
return err
}

err = resetSpec.Sanitize()
if err != nil {
return err
}

resetAction := uki.NewResetAction(cfg, resetSpec)
if err = resetAction.Run(); err != nil {
cfg.Logger.Errorf("failed to reset uki: %s", err)
return err
}

bus.Manager.Publish(sdk.EventAfterReset, sdk.EventPayload{}) //nolint:errcheck

return hook.Run(*cfg, resetSpec, hook.AfterReset...)
}

// sharedReset is the common reset code for both uki and non-uki
// sets the config, runs the event handler, publish the envent and gets the config
func sharedReset(reboot, unattended, resetOem bool, dir ...string) (c *config.Config, err error) {
bus.Manager.Initialize()
var optionsFromEvent map[string]string

// This config is only for reset branding.
agentConfig, err := LoadConfig()
if err != nil {
return err
return c, err
}

if !unattended {
Expand Down Expand Up @@ -58,8 +128,6 @@ func Reset(reboot, unattended bool, dir ...string) error {

ensureDataSourceReady()

optionsFromEvent := map[string]string{}

// This gets the options from an event that can be sent by anyone.
// This should override the default config as it's much more dynamic
bus.Manager.Response(sdk.EventBeforeReset, func(p *pluggable.Plugin, r *pluggable.EventResponse) {
Expand All @@ -71,49 +139,59 @@ func Reset(reboot, unattended bool, dir ...string) error {

bus.Manager.Publish(sdk.EventBeforeReset, sdk.EventPayload{}) //nolint:errcheck

c, err := config.Scan(collector.Directories(dir...))
if err != nil {
return err
}
// Prepare a config from the cli flags
r := ExtraConfigReset{}
r.Reset.ResetOem = resetOem

utils.SetEnv(c.Env)
if resetOem {
r.Reset.ResetOem = true
}

// Load the installation Config from the cloud-config data
resetSpec, err := config.ReadResetSpecFromConfig(c)
if err != nil {
return err
if reboot {
r.Reset.Reboot = true
}

// Override the config with the event options
// Go over the possible options sent via event
if len(optionsFromEvent) > 0 {
if p := optionsFromEvent["reset-persistent"]; p != "" {
resetSpec.FormatPersistent = p == "true"
r.Reset.ResetPersistent = p == "true"
}
if o := optionsFromEvent["reset-oem"]; o != "" {
resetSpec.FormatOEM = o == "true"
}
if s := optionsFromEvent["strict"]; s != "" {
c.Strict = s == "true"
r.Reset.ResetOem = o == "true"
}
}

// Override with flags
if reboot {
resetSpec.Reboot = reboot
d, err := json.Marshal(r)
if err != nil {
c.Logger.Errorf("failed to marshal reset cmdline flags/event options: %s", err)
return c, err
}
cliConf := string(d)

err = resetSpec.Sanitize()
// cliconf goes last so it can override the rest of the config files
c, err = config.Scan(collector.Directories(dir...), collector.Readers(strings.NewReader(cliConf)))
if err != nil {
return err
return c, err
}

resetAction := action.NewResetAction(c, resetSpec)
if err := resetAction.Run(); err != nil {
fmt.Println(err)
os.Exit(1)
// Set strict validation from the event
if len(optionsFromEvent) > 0 {
if s := optionsFromEvent["strict"]; s != "" {
c.Strict = s == "true"
}
}

bus.Manager.Publish(sdk.EventAfterReset, sdk.EventPayload{}) //nolint:errcheck
utils.SetEnv(c.Env)

return c, nil
}

return hook.Run(*c, resetSpec, hook.AfterReset...)
// ExtraConfigReset is the struct that holds the reset options that come from flags and events
type ExtraConfigReset struct {
Reset struct {
ResetOem bool `json:"reset-oem,omitempty"`
ResetPersistent bool `json:"reset-persistent,omitempty"`
Reboot bool `json:"reboot,omitempty"`
} `json:"reset"`
}
49 changes: 17 additions & 32 deletions internal/agent/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,6 @@ func upgrade(source string, force, strictValidations bool, dirs []string, preRel
return err
}

if upgradeSpec.Reboot {
utils.Reboot()
}

if upgradeSpec.PowerOff {
utils.PowerOFF()
}

return hook.Run(*c, upgradeSpec, hook.AfterUpgrade...)
}

Expand Down Expand Up @@ -138,24 +130,16 @@ func newerReleases() (versioneer.TagList, error) {
if err != nil {
return tagList, err
}
//fmt.Printf("tagList.OtherAnyVersion() = %#v\n", tagList.OtherAnyVersion().Tags)
//fmt.Printf("tagList.Images() = %#v\n", tagList.Images().Tags)
// fmt.Println("Tags")
// tagList.NewerAnyVersion().Print()
// fmt.Println("---------------------------")

return tagList.NewerAnyVersion().RSorted(), nil
}

// generateUpgradeConfForCLIArgs creates a kairos configuration for `--source` and `--recovery`
// command line arguments. It will be added to the rest of the configurations.
func generateUpgradeConfForCLIArgs(source string, upgradeRecovery bool) (string, error) {
upgrade := map[string](map[string]interface{}){
"upgrade": {},
}
upgradeConfig := ExtraConfigUpgrade{}

if upgradeRecovery {
upgrade["upgrade"]["recovery"] = "true"
upgradeConfig.Upgrade.Recovery = true
}

// Set uri both for active and recovery because we don't know what we are
Expand All @@ -164,12 +148,8 @@ func generateUpgradeConfForCLIArgs(source string, upgradeRecovery bool) (string,
// have access to that yet, we just set both uri values which shouldn't matter
// anyway, the right one will be used later in the process.
if source != "" {
upgrade["upgrade"]["recovery-system"] = map[string]string{
"uri": source,
}
upgrade["upgrade"]["system"] = map[string]string{
"uri": source,
}
upgradeConfig.Upgrade.RecoverySystem.URI = source
upgradeConfig.Upgrade.System.URI = source
}

d, err := json.Marshal(upgrade)
Expand Down Expand Up @@ -254,13 +234,18 @@ func upgradeUki(source string, dirs []string, strictValidations bool) error {
return err
}

if upgradeSpec.Reboot {
utils.Reboot()
}

if upgradeSpec.PowerOff {
utils.PowerOFF()
}

return hook.Run(*c, upgradeSpec, hook.AfterUpgrade...)
}

// ExtraConfigUpgrade is the struct that holds the upgrade options that come from flags and events
type ExtraConfigUpgrade struct {
Upgrade struct {
Recovery bool `json:"recovery,omitempty"`
RecoverySystem struct {
URI string `json:"uri,omitempty"`
} `json:"recovery-system,omitempty"`
System struct {
URI string `json:"uri,omitempty"`
} `json:"system,omitempty"`
} `json:"upgrade,omitempty"`
}
7 changes: 6 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,15 +564,20 @@ This command is meant to be used from the boot GRUB menu, but can likely be used
Name: "unattended",
Usage: "Do not wait for user input and provide ttys after reset. Also sets the fast mode (do not wait 60 seconds before reset)",
},
&cli.BoolFlag{
Name: "reset-oem",
Usage: "Reset the OEM partition. Warning: this will delete any persistent data on the OEM partition.",
},
},
Before: func(c *cli.Context) error {
return checkRoot()
},
Action: func(c *cli.Context) error {
reboot := c.Bool("reboot")
unattended := c.Bool("unattended")
resetOem := c.Bool("reset-oem")

return agent.Reset(reboot, unattended, configScanDir...)
return agent.Reset(reboot, unattended, resetOem, configScanDir...)
},
Usage: "Starts kairos reset mode",
Description: `
Expand Down
31 changes: 28 additions & 3 deletions pkg/config/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,33 @@ func ReadResetSpecFromConfig(c *Config) (*v1.ResetSpec, error) {
return resetSpec, nil
}

func NewUkiResetSpec(cfg *Config) (spec *v1.ResetUkiSpec, err error) {
spec = &v1.ResetUkiSpec{
FormatPersistent: true, // Persistent is formatted by default
Partitions: v1.ElementalPartitions{},
}

_, ukiBootMode := cfg.Fs.Stat("/run/cos/uki_boot_mode")
if !BootedFrom(cfg.Runner, "rd.immucore.uki") && ukiBootMode == nil {
return spec, fmt.Errorf("uki reset can only be called from the recovery installed system")
}

// Fill persistent partition
spec.Partitions.Persistent = partitions.GetPartitionViaDM(cfg.Fs, constants.PersistentLabel)
spec.Partitions.OEM = partitions.GetPartitionViaDM(cfg.Fs, constants.OEMLabel)

if spec.Partitions.Persistent == nil {
return spec, fmt.Errorf("persistent partition not found")
}
if spec.Partitions.OEM == nil {
return spec, fmt.Errorf("oem partition not found")
}

// Fill oem partition
err = unmarshallFullSpec(cfg, "reset", spec)
return spec, err
}

// ReadInstallSpecFromConfig will return a proper v1.InstallSpec based on an agent Config
func ReadInstallSpecFromConfig(c *Config) (*v1.InstallSpec, error) {
sp, err := ReadSpecFromCloudConfig(c, "install")
Expand Down Expand Up @@ -568,7 +595,6 @@ func NewUkiInstallSpec(cfg *Config) (*v1.InstallUkiSpec, error) {
Flags: []string{},
}

// TODO: Which key to use? install or install-uki?
err := unmarshallFullSpec(cfg, "install", spec)
// TODO: Get the actual source size to calculate the image size and partitions size for at least 3 UKI images
// Add default values for the skip partitions for our default entries
Expand Down Expand Up @@ -760,8 +786,7 @@ func ReadSpecFromCloudConfig(r *Config, spec string) (v1.Spec, error) {
case "install-uki":
sp, err = NewUkiInstallSpec(r)
case "reset-uki":
// TODO: Fill with proper defaults
sp = &v1.ResetUkiSpec{}
sp, err = NewUkiResetSpec(r)
case "upgrade-uki":
sp, err = NewUkiUpgradeSpec(r)
default:
Expand Down
9 changes: 8 additions & 1 deletion pkg/elemental/elemental.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,14 @@ func NewElemental(config *agentConfig.Config) *Elemental {

// FormatPartition will format an already existing partition
func (e *Elemental) FormatPartition(part *v1.Partition, opts ...string) error {
e.config.Logger.Infof("Formatting '%s' partition", part.Name)
var name string
// Nice display name for logs
if part.Name == "" {
name = part.FilesystemLabel
} else {
name = part.Name
}
e.config.Logger.Infof("Formatting '%s' partition", name)
return partitioner.FormatDevice(e.config.Runner, part.Path, part.FS, part.FilesystemLabel, opts...)
}

Expand Down
7 changes: 5 additions & 2 deletions pkg/types/v1/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,8 +541,11 @@ 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"`
FormatPersistent bool `yaml:"reset-persistent,omitempty" mapstructure:"reset-persistent"`
FormatOEM bool `yaml:"reset-oem,omitempty" mapstructure:"reset-oem"`
Reboot bool `yaml:"reboot,omitempty" mapstructure:"reboot"`
PowerOff bool `yaml:"poweroff,omitempty" mapstructure:"poweroff"`
Partitions ElementalPartitions
}

func (i *ResetUkiSpec) Sanitize() error {
Expand Down
Loading

0 comments on commit 33c8e8f

Please sign in to comment.