Uki upgrade (#182)

This commit is contained in:
Itxaka 2023-12-18 11:38:26 +01:00 committed by GitHub
parent 2be11b827e
commit 3254b8a36e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 152 additions and 33 deletions

3
go.mod
View File

@ -13,8 +13,8 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/jaypipes/ghw v0.12.0
github.com/joho/godotenv v1.5.1
github.com/kairos-io/kairos-sdk v0.0.20
github.com/kairos-io/kcrypt v0.8.0
github.com/kairos-io/kairos-sdk v0.0.20
github.com/labstack/echo/v4 v4.11.1
github.com/mitchellh/mapstructure v1.5.0
github.com/mudler/go-nodepair v0.0.0-20221223092639-ba399a66fdfb
@ -161,6 +161,7 @@ require (
github.com/swaggest/refl v1.3.0 // indirect
github.com/tredoe/osutil/v2 v2.0.0-rc.16 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/urfave/cli v1.22.14 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vbatts/tar-split v0.11.3 // indirect

4
go.sum
View File

@ -46,6 +46,7 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
@ -621,6 +622,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ=
@ -639,6 +641,8 @@ github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI=
github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=

View File

@ -9,12 +9,37 @@ import (
"github.com/kairos-io/kairos-sdk/machine"
"github.com/kairos-io/kairos-sdk/utils"
kcrypt "github.com/kairos-io/kcrypt/pkg/lib"
"strconv"
"strings"
"time"
)
type KcryptUKI struct{}
func (k KcryptUKI) Run(c config.Config, _ v1.Spec) error {
// pre-check for systemd version, we need something higher or equal to 252
run, err := utils.SH("systemctl --version | head -1 | awk '{ print $2}'")
systemdVersion := strings.TrimSpace(string(run))
if err != nil {
c.Logger.Errorf("could not get systemd version: %s", err)
c.Logger.Errorf("could not get systemd version: %s", run)
return err
}
if systemdVersion == "" {
c.Logger.Errorf("could not get systemd version: %s", err)
return err
}
// Change systemdVersion to int value
systemdVersionInt, err := strconv.Atoi(systemdVersion)
if err != nil {
c.Logger.Errorf("could not convert systemd version to int: %s", err)
return err
}
// If systemd version is less than 252 return
if systemdVersionInt < 252 {
c.Logger.Infof("systemd version is %s, we need 252 or higher for encrypting partitions", systemdVersion)
return nil
}
// We always encrypt OEM and PERSISTENT under UKI
// If mounted, unmount it
@ -22,7 +47,7 @@ func (k KcryptUKI) Run(c config.Config, _ v1.Spec) error {
_ = machine.Umount(constants.PersistentDir) //nolint:errcheck
// Backup oem as we already copied files on there and on luksify it will be wiped
err := machine.Mount("COS_OEM", constants.OEMDir)
err = machine.Mount("COS_OEM", constants.OEMDir)
if err != nil {
return err
}
@ -60,7 +85,6 @@ func (k KcryptUKI) Run(c config.Config, _ v1.Spec) error {
c.Logger.Infof("Done encrypting %s", p)
}
// Restore OEM
err = kcrypt.UnlockAll(true)
if err != nil {
return err

View File

@ -12,6 +12,7 @@ import (
"github.com/kairos-io/kairos-agent/v2/pkg/action"
config "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"
events "github.com/kairos-io/kairos-sdk/bus"
"github.com/kairos-io/kairos-sdk/collector"
"github.com/kairos-io/kairos-sdk/utils"
@ -185,3 +186,49 @@ func getReleasesFromProvider(includePrereleases bool) ([]string, error) {
return result, nil
}
func UkiUpgrade(source string, dirs []string, strictValidations bool) error {
bus.Manager.Initialize()
cliConf, err := generateUpgradeConfForCLIArgs(source, false)
if err != nil {
return err
}
c, err := config.Scan(collector.Directories(dirs...),
collector.Readers(strings.NewReader(cliConf)),
collector.StrictValidation(strictValidations))
if err != nil {
return err
}
utils.SetEnv(c.Env)
// Load the upgrade Config from the system
upgradeSpec, err := config.ReadUkiUpgradeFromConfig(c)
if err != nil {
return err
}
err = upgradeSpec.Sanitize()
if err != nil {
return err
}
upgradeAction := uki.NewUpgradeAction(c, upgradeSpec)
err = upgradeAction.Run()
if err != nil {
return err
}
if upgradeSpec.Reboot {
utils.Reboot()
}
if upgradeSpec.PowerOff {
utils.PowerOFF()
}
return hook.Run(*c, upgradeSpec, hook.AfterUpgrade...)
}

30
main.go
View File

@ -27,6 +27,7 @@ import (
"github.com/kairos-io/kairos-sdk/schema"
"github.com/kairos-io/kairos-sdk/state"
"github.com/kairos-io/kairos-sdk/versioneer"
"github.com/sanity-io/litter"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
@ -726,6 +727,12 @@ The validate command expects a configuration file as its only argument. Local fi
Name: "device",
},
},
Before: func(c *cli.Context) error {
if c.String("device") == "" {
return fmt.Errorf("on uki, --device flag is required")
}
return nil
},
Action: func(c *cli.Context) error {
config, err := agentConfig.Scan(collector.Directories(configScanDir...), collector.NoLogs)
if err != nil {
@ -757,22 +764,14 @@ The validate command expects a configuration file as its only argument. Local fi
},
},
Before: func(c *cli.Context) error {
return fmt.Errorf("not implemented")
if c.String("source") == "" {
return fmt.Errorf("On uki, source is required")
}
return nil
},
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()
source := c.String("source")
return agent.UkiUpgrade(source, configScanDir, c.Bool("strict-validation"))
},
},
{
@ -847,6 +846,9 @@ The kairos agent is a component to abstract away node ops, providing a common fe
Before: func(c *cli.Context) error {
// Set debug from here already, so it's loaded by the ReadConfigRun
viper.Set("debug", c.Bool("debug"))
if c.Bool("debug") {
litter.Config.HidePrivateFields = false
}
return nil
},
Commands: cmds,

View File

@ -496,7 +496,7 @@ func NewUkiInstallSpec(cfg *Config) (*v1.InstallUkiSpec, error) {
// 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
Size: constants.ImgSize * 2, // TODO: Fix this and set proper size based on the source size
Name: constants.EfiPartName,
FS: constants.EfiFs,
MountPoint: constants.EfiDir,
@ -521,7 +521,7 @@ func NewUkiInstallSpec(cfg *Config) (*v1.InstallUkiSpec, error) {
// 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
return spec, err
}
@ -535,6 +535,28 @@ func ReadUkiInstallSpecFromConfig(c *Config) (*v1.InstallUkiSpec, error) {
return installSpec, nil
}
func NewUkiUpgradeSpec(cfg *Config) (*v1.UpgradeUkiSpec, error) {
spec := &v1.UpgradeUkiSpec{}
err := unmarshallFullSpec(cfg, "upgrade", spec)
// Get the actual source size to calculate the image size and partitions size
size, err := GetSourceSize(cfg, spec.Active.Source)
if err != nil {
cfg.Logger.Warnf("Failed to infer size for images: %s", err.Error())
spec.Active.Size = constants.ImgSize
} else {
cfg.Logger.Infof("Setting image size to %dMb", size)
spec.Active.Size = uint(size)
}
spec.EfiPartition = &v1.Partition{
FilesystemLabel: constants.EfiLabel,
FS: constants.EfiFs,
Path: constants.UkiEfiDiskByLabel,
MountPoint: constants.UkiEfiDir,
}
return spec, err
}
// 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")
@ -653,8 +675,7 @@ func ReadSpecFromCloudConfig(r *Config, spec string) (v1.Spec, error) {
// TODO: Fill with proper defaults
sp = &v1.ResetUkiSpec{}
case "upgrade-uki":
// TODO: Fill with proper defaults
sp = &v1.UpgradeUkiSpec{}
sp, err = NewUkiUpgradeSpec(r)
default:
return nil, fmt.Errorf("spec not valid: %s", spec)
}

View File

@ -108,8 +108,10 @@ const (
Rsync = "rsync"
UkiSource = "/run/install/uki"
UkiCdromSource = "/run/install/cdrom"
UkiSource = "/run/install/uki"
UkiCdromSource = "/run/install/cdrom"
UkiEfiDir = "/efi"
UkiEfiDiskByLabel = `/dev/disk/by-label/` + EfiLabel
)
func GetCloudInitPaths() []string {

View File

@ -35,7 +35,7 @@ const (
// ImageSource represents the source from where an image is created for easy identification
type ImageSource struct {
source string
source string `yaml:"source"`
srcType string
}

View File

@ -202,7 +202,6 @@ func (u *UpgradeSpec) Sanitize() error {
}
return nil
}
func (u *UpgradeSpec) ShouldReboot() bool { return u.Reboot }
func (u *UpgradeSpec) ShouldShutdown() bool { return u.PowerOff }
@ -526,8 +525,10 @@ func (i *InstallUkiSpec) GetPartitions() ElementalPartitions { return i.Partitio
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"`
Active Image `yaml:"system,omitempty" mapstructure:"system"`
Reboot bool `yaml:"reboot,omitempty" mapstructure:"reboot"`
PowerOff bool `yaml:"poweroff,omitempty" mapstructure:"poweroff"`
EfiPartition *Partition `yaml:"efi-partition,omitempty" mapstructure:"efi-partition"`
}
func (i *UpgradeUkiSpec) Sanitize() error {

View File

@ -3,9 +3,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/constants"
"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 UpgradeAction struct {
@ -18,16 +21,30 @@ func NewUpgradeAction(cfg *config.Config, spec *v1.UpgradeUkiSpec) *UpgradeActio
}
func (i *UpgradeAction) Run() (err error) {
e := elemental.NewElemental(i.cfg)
cleanup := utils.NewCleanStack()
defer func() { err = cleanup.Cleanup(err) }()
// Run pre-install stage
_ = elementalUtils.RunStage(i.cfg, "kairos-uki-upgrade.pre")
_ = events.RunHookScript("/usr/bin/kairos-agent.uki.upgrade.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
// REMOUNT /efi as RW (its RO by default)
umount, err := e.MountRWPartition(i.spec.EfiPartition)
if err != nil {
return err
}
cleanup.Push(umount)
// TODO: Check size of EFI partition to see if we can upgrade
// TODO: Check size of source to see if we can upgrade
// TODO: Check number of existing UKI files
// TODO: Load them, order them via semver
// TODO: Remove the latest one if its over the max number of entries
// Dump artifact to efi dir
_, err = e.DumpSource(constants.UkiEfiDir, i.spec.Active.Source)
if err != nil {
return err
}
_ = elementalUtils.RunStage(i.cfg, "kairos-uki-upgrade.after")
_ = events.RunHookScript("/usr/bin/kairos-agent.uki.upgrade.after.hook") //nolint:errcheck