mirror of
https://github.com/kairos-io/kairos-agent.git
synced 2025-04-28 03:32:27 +00:00
Add reset for uki (#221)
This commit is contained in:
parent
7efda3d50e
commit
33c8e8f29c
@ -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 {
|
||||
|
@ -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"
|
||||
|
||||
@ -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 {
|
||||
@ -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) {
|
||||
@ -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
|
||||
|
||||
if resetOem {
|
||||
r.Reset.ResetOem = true
|
||||
}
|
||||
|
||||
utils.SetEnv(c.Env)
|
||||
|
||||
// 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"
|
||||
r.Reset.ResetOem = o == "true"
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// 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 c, err
|
||||
}
|
||||
|
||||
// Set strict validation from the event
|
||||
if len(optionsFromEvent) > 0 {
|
||||
if s := optionsFromEvent["strict"]; s != "" {
|
||||
c.Strict = s == "true"
|
||||
}
|
||||
}
|
||||
|
||||
// Override with flags
|
||||
if reboot {
|
||||
resetSpec.Reboot = reboot
|
||||
}
|
||||
utils.SetEnv(c.Env)
|
||||
|
||||
err = resetSpec.Sanitize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resetAction := action.NewResetAction(c, resetSpec)
|
||||
if err := resetAction.Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
bus.Manager.Publish(sdk.EventAfterReset, sdk.EventPayload{}) //nolint:errcheck
|
||||
|
||||
return hook.Run(*c, resetSpec, hook.AfterReset...)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
@ -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...)
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -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)
|
||||
@ -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
main.go
7
main.go
@ -564,6 +564,10 @@ 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()
|
||||
@ -571,8 +575,9 @@ This command is meant to be used from the boot GRUB menu, but can likely be used
|
||||
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: `
|
||||
|
@ -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")
|
||||
@ -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
|
||||
@ -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:
|
||||
|
@ -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...)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -1,11 +1,12 @@
|
||||
package uki
|
||||
|
||||
import (
|
||||
hook "github.com/kairos-io/kairos-agent/v2/internal/agent/hooks"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/elemental"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
elementalUtils "github.com/kairos-io/kairos-agent/v2/pkg/utils"
|
||||
events "github.com/kairos-io/kairos-sdk/bus"
|
||||
"github.com/kairos-io/kairos-sdk/utils"
|
||||
)
|
||||
|
||||
type ResetAction struct {
|
||||
@ -17,19 +18,50 @@ func NewResetAction(cfg *config.Config, spec *v1.ResetUkiSpec) *ResetAction {
|
||||
return &ResetAction{cfg: cfg, spec: spec}
|
||||
}
|
||||
|
||||
func (i *ResetAction) Run() (err error) {
|
||||
func (r *ResetAction) Run() (err error) {
|
||||
// Run pre-install stage
|
||||
_ = elementalUtils.RunStage(i.cfg, "kairos-uki-reset.pre")
|
||||
_ = elementalUtils.RunStage(r.cfg, "kairos-uki-reset.pre")
|
||||
_ = events.RunHookScript("/usr/bin/kairos-agent.uki.reset.pre.hook")
|
||||
|
||||
// Get source (from spec?)
|
||||
// Copy the efi file into the proper dir
|
||||
// Remove all boot manager entries?
|
||||
// Create boot manager entry
|
||||
// Set default entry to the one we just created
|
||||
e := elemental.NewElemental(r.cfg)
|
||||
cleanup := utils.NewCleanStack()
|
||||
defer func() { err = cleanup.Cleanup(err) }()
|
||||
|
||||
_ = elementalUtils.RunStage(i.cfg, "kairos-uki-reset.after")
|
||||
// Unmount partitions if any is already mounted before formatting
|
||||
err = e.UnmountPartitions(r.spec.Partitions.PartitionsByMountPoint(true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reformat persistent partition
|
||||
if r.spec.FormatPersistent {
|
||||
persistent := r.spec.Partitions.Persistent
|
||||
if persistent != nil {
|
||||
err = e.FormatPartition(persistent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reformat OEM
|
||||
if r.spec.FormatOEM {
|
||||
oem := r.spec.Partitions.OEM
|
||||
if oem != nil {
|
||||
err = e.FormatPartition(oem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = elementalUtils.RunStage(r.cfg, "kairos-uki-reset.after")
|
||||
_ = events.RunHookScript("/usr/bin/kairos-agent.uki.reset.after.hook") //nolint:errcheck
|
||||
|
||||
return hook.Run(*i.cfg, i.spec, hook.AfterUkiReset...)
|
||||
// Do not reboot/poweroff on cleanup errors
|
||||
err = cleanup.Cleanup(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package uki
|
||||
|
||||
import (
|
||||
"github.com/Masterminds/semver/v3"
|
||||
hook "github.com/kairos-io/kairos-agent/v2/internal/agent/hooks"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/elemental"
|
||||
@ -81,7 +80,7 @@ func (i *UpgradeAction) Run() (err error) {
|
||||
_ = elementalUtils.RunStage(i.cfg, "kairos-uki-upgrade.after")
|
||||
_ = events.RunHookScript("/usr/bin/kairos-agent.uki.upgrade.after.hook") //nolint:errcheck
|
||||
|
||||
return hook.Run(*i.cfg, i.spec, hook.AfterUkiUpgrade...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *UpgradeAction) getEfiFiles() ([]string, error) {
|
||||
|
Loading…
Reference in New Issue
Block a user