mirror of
https://github.com/kairos-io/kairos-agent.git
synced 2025-09-03 01:54:29 +00:00
UKI improvements (#186)
This commit is contained in:
2
go.mod
2
go.mod
@@ -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.21-0.20231218143909-a99f8bb48751
|
||||
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
|
||||
|
2
go.sum
2
go.sum
@@ -367,6 +367,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kairos-io/kairos-sdk v0.0.20 h1:iadV3ylhQELgWUFe/fETfs2qFhPtKZwnDN55okZZVgs=
|
||||
github.com/kairos-io/kairos-sdk v0.0.20/go.mod h1:17dpFG2d3Q/TcT86DlLK5nNXEjlSrkYl7bsvO2cpYGE=
|
||||
github.com/kairos-io/kairos-sdk v0.0.21-0.20231218143909-a99f8bb48751 h1:kyW/RlMT0yujMYR0HATHM1q0Cwb7TNT8j+huykrjzIk=
|
||||
github.com/kairos-io/kairos-sdk v0.0.21-0.20231218143909-a99f8bb48751/go.mod h1:17dpFG2d3Q/TcT86DlLK5nNXEjlSrkYl7bsvO2cpYGE=
|
||||
github.com/kairos-io/kcrypt v0.7.1-0.20231206231913-12a8d5d33cf0 h1:bInWIHqP+8GNOO0b6mtvZn6HxEQuhMgr5h9QBuarR38=
|
||||
github.com/kairos-io/kcrypt v0.7.1-0.20231206231913-12a8d5d33cf0/go.mod h1:sP+kdJ6WyPPWlzZuDNfkV2wmnCDPWCGpC5nF7KhHX3Q=
|
||||
github.com/kairos-io/kcrypt v0.8.0 h1:uA5GVF74hzqNOgVvvuue585vAWKXbjMQ93mBJuhKuTE=
|
||||
|
@@ -70,6 +70,7 @@ func NewConfig(opts ...GenericOptions) *Config {
|
||||
ImageExtractor: v1.OCIImageExtractor{},
|
||||
SquashFsNoCompression: true,
|
||||
Install: &Install{},
|
||||
UkiMaxEntries: constants.UkiMaxEntries,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(c)
|
||||
@@ -134,6 +135,7 @@ type Config struct {
|
||||
Arch string `yaml:"arch,omitempty" mapstructure:"arch"`
|
||||
SquashFsCompressionConfig []string `yaml:"squash-compression,omitempty" mapstructure:"squash-compression"`
|
||||
SquashFsNoCompression bool `yaml:"squash-no-compression,omitempty" mapstructure:"squash-no-compression"`
|
||||
UkiMaxEntries int `yaml:"uki-max-entries,omitempty" mapstructure:"uki-max-entries"`
|
||||
}
|
||||
|
||||
// WriteInstallState writes the state.yaml file to the given state and recovery paths
|
||||
|
@@ -17,6 +17,7 @@ package config_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kairos-io/kairos-sdk/collector"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -169,6 +170,11 @@ var _ = Describe("Schema", func() {
|
||||
AfterEach(func() {
|
||||
cleanup()
|
||||
})
|
||||
It("Scan can override options", func() {
|
||||
c, err := Scan(collector.Readers(strings.NewReader(`uki-max-entries: 34`)), collector.NoLogs)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(c.UkiMaxEntries).To(Equal(34))
|
||||
})
|
||||
It("Writes and loads an installation data", func() {
|
||||
err = config.WriteInstallState(installState, statePath, recoveryPath)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
@@ -18,6 +18,8 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/google/go-containerregistry/pkg/crane"
|
||||
"golang.org/x/sys/unix"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -538,6 +540,18 @@ func ReadUkiInstallSpecFromConfig(c *Config) (*v1.InstallUkiSpec, error) {
|
||||
func NewUkiUpgradeSpec(cfg *Config) (*v1.UpgradeUkiSpec, error) {
|
||||
spec := &v1.UpgradeUkiSpec{}
|
||||
err := unmarshallFullSpec(cfg, "upgrade", spec)
|
||||
// TODO: Use this everywhere?
|
||||
cfg.Logger.Infof("Checking if OCI image %s exists", spec.Active.Source.Value())
|
||||
if spec.Active.Source.IsDocker() {
|
||||
_, err := crane.Manifest(spec.Active.Source.Value())
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "MANIFEST_UNKNOWN") {
|
||||
return nil, fmt.Errorf("oci image %s does not exist", spec.Active.Source.Value())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the actual source size to calculate the image size and partitions size
|
||||
size, err := GetSourceSize(cfg, spec.Active.Source)
|
||||
if err != nil {
|
||||
@@ -548,12 +562,27 @@ func NewUkiUpgradeSpec(cfg *Config) (*v1.UpgradeUkiSpec, error) {
|
||||
spec.Active.Size = uint(size)
|
||||
}
|
||||
|
||||
spec.EfiPartition = &v1.Partition{
|
||||
FilesystemLabel: constants.EfiLabel,
|
||||
FS: constants.EfiFs,
|
||||
Path: constants.UkiEfiDiskByLabel,
|
||||
MountPoint: constants.UkiEfiDir,
|
||||
// Get EFI partition
|
||||
parts, err := partitions.GetAllPartitions()
|
||||
if err != nil {
|
||||
return spec, fmt.Errorf("could not read host partitions")
|
||||
}
|
||||
for _, p := range parts {
|
||||
if p.FilesystemLabel == constants.EfiLabel {
|
||||
spec.EfiPartition = p
|
||||
break
|
||||
}
|
||||
}
|
||||
// Get free size of partition
|
||||
var stat unix.Statfs_t
|
||||
_ = unix.Statfs(spec.EfiPartition.MountPoint, &stat)
|
||||
freeSize := stat.Bfree * uint64(stat.Bsize) / 1000 / 1000
|
||||
cfg.Logger.Debugf("Partition on mountpoint %s has %dMb free", spec.EfiPartition.MountPoint, freeSize)
|
||||
// Check if the source is over the free size
|
||||
if spec.Active.Size > uint(freeSize) {
|
||||
return spec, fmt.Errorf("source size(%d) is bigger than the free space(%d) on the EFI partition(%s)", spec.Active.Size, freeSize, spec.EfiPartition.MountPoint)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
|
@@ -112,6 +112,7 @@ const (
|
||||
UkiCdromSource = "/run/install/cdrom"
|
||||
UkiEfiDir = "/efi"
|
||||
UkiEfiDiskByLabel = `/dev/disk/by-label/` + EfiLabel
|
||||
UkiMaxEntries = 3
|
||||
)
|
||||
|
||||
func GetCloudInitPaths() []string {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
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"
|
||||
@@ -9,6 +10,11 @@ import (
|
||||
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"
|
||||
"github.com/sanity-io/litter"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type UpgradeAction struct {
|
||||
@@ -34,11 +40,38 @@ func (i *UpgradeAction) Run() (err error) {
|
||||
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
|
||||
efiFiles, err := i.getEfiFiles()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.cfg.Logger.Infof("Found %d UKI files", len(efiFiles))
|
||||
if len(efiFiles) > i.cfg.UkiMaxEntries && i.cfg.UkiMaxEntries > 0 {
|
||||
i.cfg.Logger.Infof("Found %d UKI files, which is over max entries allowed(%d) removing the oldest one", len(efiFiles), i.cfg.UkiMaxEntries)
|
||||
versionList := semver.Collection{}
|
||||
for _, f := range efiFiles {
|
||||
versionList = append(versionList, semver.MustParse(f))
|
||||
}
|
||||
// Sort it so the oldest one is first
|
||||
sort.Sort(versionList)
|
||||
i.cfg.Logger.Debugf("All versions found: %s", litter.Sdump(versionList))
|
||||
// Remove the oldest one
|
||||
i.cfg.Logger.Infof("Removing: %s", filepath.Join(i.spec.EfiPartition.MountPoint, "EFI", "kairos", versionList[0].Original()))
|
||||
err = i.cfg.Fs.Remove(filepath.Join(i.spec.EfiPartition.MountPoint, "EFI", "kairos", versionList[0].Original()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Remove the conf file as well
|
||||
i.cfg.Logger.Infof("Removing: %s", filepath.Join(i.spec.EfiPartition.MountPoint, "loader", "entries", versionList[0].String()+".conf"))
|
||||
// Don't care about errors here, systemd-boot will ignore any configs if it cant find the efi file mentioned in it
|
||||
e := i.cfg.Fs.Remove(filepath.Join(i.spec.EfiPartition.MountPoint, "loader", "entries", versionList[0].String()+".conf"))
|
||||
if e != nil {
|
||||
i.cfg.Logger.Warnf("Failed to remove conf file: %s", e)
|
||||
}
|
||||
} else {
|
||||
i.cfg.Logger.Infof("Found %d UKI files, which is under max entries allowed(%d) not removing any", len(efiFiles), i.cfg.UkiMaxEntries)
|
||||
}
|
||||
|
||||
// Dump artifact to efi dir
|
||||
_, err = e.DumpSource(constants.UkiEfiDir, i.spec.Active.Source)
|
||||
if err != nil {
|
||||
@@ -50,3 +83,18 @@ func (i *UpgradeAction) Run() (err error) {
|
||||
|
||||
return hook.Run(*i.cfg, i.spec, hook.AfterUkiUpgrade...)
|
||||
}
|
||||
|
||||
func (i *UpgradeAction) getEfiFiles() ([]string, error) {
|
||||
var efiFiles []string
|
||||
files, err := os.ReadDir(filepath.Join(i.spec.EfiPartition.MountPoint, "EFI", "kairos"))
|
||||
if err != nil {
|
||||
return efiFiles, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if !file.IsDir() && strings.HasSuffix(file.Name(), ".efi") {
|
||||
efiFiles = append(efiFiles, file.Name())
|
||||
}
|
||||
}
|
||||
return efiFiles, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user