Skip to content

Commit

Permalink
UKI improvements (#186)
Browse files Browse the repository at this point in the history
  • Loading branch information
Itxaka authored Dec 18, 2023
1 parent 3254b8a commit b8232ae
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 11 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package config_test

import (
"fmt"
"github.com/kairos-io/kairos-sdk/collector"
"path/filepath"
"reflect"
"strings"
Expand Down Expand Up @@ -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())
Expand Down
39 changes: 34 additions & 5 deletions pkg/config/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}

Expand Down
1 change: 1 addition & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ const (
UkiCdromSource = "/run/install/cdrom"
UkiEfiDir = "/efi"
UkiEfiDiskByLabel = `/dev/disk/by-label/` + EfiLabel
UkiMaxEntries = 3
)

func GetCloudInitPaths() []string {
Expand Down
58 changes: 53 additions & 5 deletions pkg/uki/upgrade.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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 {
Expand All @@ -34,19 +40,61 @@ 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 {
return err
}

_ = 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...)
}

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
}

0 comments on commit b8232ae

Please sign in to comment.