mirror of
https://github.com/kairos-io/kairos-agent.git
synced 2025-06-03 01:44:53 +00:00
Merge pull request #152 from kairos-io/1837-add-source-to-commands
1837 Add `--source` to various commands
This commit is contained in:
commit
300cc9290a
@ -53,16 +53,21 @@ func displayInfo(agentConfig *Config) {
|
||||
}
|
||||
}
|
||||
|
||||
func ManualInstall(c, device string, reboot, poweroff, strictValidations bool) error {
|
||||
func ManualInstall(c, sourceImgURL, device string, reboot, poweroff, strictValidations bool) error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
source, err := prepareConfiguration(ctx, c)
|
||||
configSource, err := prepareConfiguration(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cc, err := config.Scan(collector.Directories(source), collector.MergeBootLine, collector.StrictValidation(strictValidations), collector.NoLogs)
|
||||
cliConf := generateInstallConfForCLIArgs(sourceImgURL)
|
||||
|
||||
cc, err := config.Scan(collector.Directories(configSource),
|
||||
collector.Readers(strings.NewReader(cliConf)),
|
||||
collector.MergeBootLine,
|
||||
collector.StrictValidation(strictValidations), collector.NoLogs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -83,7 +88,7 @@ func ManualInstall(c, device string, reboot, poweroff, strictValidations bool) e
|
||||
return RunInstall(cc)
|
||||
}
|
||||
|
||||
func Install(dir ...string) error {
|
||||
func Install(sourceImgURL string, dir ...string) error {
|
||||
var cc *config.Config
|
||||
var err error
|
||||
|
||||
@ -116,9 +121,12 @@ func Install(dir ...string) error {
|
||||
|
||||
ensureDataSourceReady()
|
||||
|
||||
// Reads config, and if present and offline is defined,
|
||||
// runs the installation
|
||||
cc, err = config.Scan(collector.Directories(dir...), collector.MergeBootLine)
|
||||
cliConf := generateInstallConfForCLIArgs(sourceImgURL)
|
||||
|
||||
// Reads config, and if present and offline is defined, runs the installation
|
||||
cc, err = config.Scan(collector.Directories(dir...),
|
||||
collector.Readers(strings.NewReader(cliConf)),
|
||||
collector.MergeBootLine)
|
||||
if err == nil && cc.Install != nil && cc.Install.Auto {
|
||||
err = RunInstall(cc)
|
||||
if err != nil {
|
||||
@ -331,3 +339,14 @@ func prepareConfiguration(ctx context.Context, source string) (string, error) {
|
||||
|
||||
return f.Name(), nil
|
||||
}
|
||||
|
||||
func generateInstallConfForCLIArgs(sourceImageURL string) string {
|
||||
if sourceImageURL == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`install:
|
||||
system:
|
||||
uri: %s
|
||||
`, sourceImageURL)
|
||||
}
|
||||
|
@ -3,13 +3,14 @@ package agent
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/jaypipes/ghw/pkg/block"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/jaypipes/ghw/pkg/block"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
||||
"github.com/twpayne/go-vfs"
|
||||
"github.com/twpayne/go-vfs/vfst"
|
||||
|
@ -129,7 +129,7 @@ func detectDevice() string {
|
||||
return preferedDevice
|
||||
}
|
||||
|
||||
func InteractiveInstall(debug, spawnShell bool) error {
|
||||
func InteractiveInstall(debug, spawnShell bool, sourceImgURL string) error {
|
||||
var sshUsers []string
|
||||
bus.Manager.Initialize()
|
||||
|
||||
@ -229,7 +229,7 @@ func InteractiveInstall(debug, spawnShell bool) error {
|
||||
}
|
||||
|
||||
if !isYes(allGood) {
|
||||
return InteractiveInstall(debug, spawnShell)
|
||||
return InteractiveInstall(debug, spawnShell, sourceImgURL)
|
||||
}
|
||||
|
||||
usersToSet := map[string]schema.User{}
|
||||
@ -282,8 +282,11 @@ func InteractiveInstall(debug, spawnShell bool) error {
|
||||
if err != nil {
|
||||
fmt.Printf("could not write event cloud init: %s\n", err.Error())
|
||||
}
|
||||
// override cc with our new config object from the scan, so it's updated for the RunInstall function
|
||||
cc, _ = config.Scan(collector.Directories(tmpdir), collector.MergeBootLine, collector.NoLogs)
|
||||
|
||||
cliConf := generateInstallConfForCLIArgs(sourceImgURL)
|
||||
cc, _ = config.Scan(collector.Directories(tmpdir),
|
||||
collector.Readers(strings.NewReader(cliConf)),
|
||||
collector.MergeBootLine, collector.NoLogs)
|
||||
}
|
||||
|
||||
pterm.Info.Println("Starting installation")
|
||||
|
@ -102,6 +102,11 @@ func Reset(reboot, unattended bool, dir ...string) error {
|
||||
resetSpec.Reboot = reboot
|
||||
}
|
||||
|
||||
err = resetSpec.Sanitize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resetAction := action.NewResetAction(c, resetSpec)
|
||||
if err := resetAction.Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
|
@ -5,16 +5,16 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"strings"
|
||||
|
||||
hook "github.com/kairos-io/kairos-agent/v2/internal/agent/hooks"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/kairos-io/kairos-agent/v2/internal/bus"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/action"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
config "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/github"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
events "github.com/kairos-io/kairos-sdk/bus"
|
||||
"github.com/kairos-io/kairos-sdk/collector"
|
||||
"github.com/kairos-io/kairos-sdk/utils"
|
||||
@ -55,64 +55,11 @@ func Upgrade(
|
||||
version, source string, force, strictValidations bool, dirs []string, preReleases, upgradeRecovery bool) error {
|
||||
bus.Manager.Initialize()
|
||||
|
||||
if version == "" && source == "" {
|
||||
fmt.Println("Searching for releases")
|
||||
if preReleases {
|
||||
fmt.Println("Including pre-releases")
|
||||
}
|
||||
releases := ListReleases(preReleases)
|
||||
|
||||
if len(releases) == 0 {
|
||||
return fmt.Errorf("no releases found")
|
||||
}
|
||||
|
||||
// Using Original here because the parsing removes the v as its a semver. But it stores the original full version there
|
||||
version = releases[0].Original()
|
||||
|
||||
if utils.Version() == version && !force {
|
||||
fmt.Printf("version %s already installed. use --force to force upgrade\n", version)
|
||||
return nil
|
||||
}
|
||||
msg := fmt.Sprintf("Latest release is %s\nAre you sure you want to upgrade to this release? (y/n)", version)
|
||||
reply, err := promptBool(events.YAMLPrompt{Prompt: msg, Default: "y"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reply == "false" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
img := source
|
||||
var err error
|
||||
if img == "" {
|
||||
img, err = determineUpgradeImage(version)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Set this here with viper help so we can use it while creating the upgrade spec
|
||||
// And its properly set since creation without having to modify it later
|
||||
// This should be binded somehow but the current cli doesnt allow us to bind flags to values
|
||||
viper.Set("upgradeSource", img)
|
||||
viper.Set("upgradeRecovery", upgradeRecovery)
|
||||
|
||||
c, err := config.Scan(collector.Directories(dirs...), collector.StrictValidation(strictValidations))
|
||||
upgradeSpec, c, err := generateUpgradeSpec(version, source, force, strictValidations, dirs, preReleases, upgradeRecovery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
utils.SetEnv(c.Env)
|
||||
|
||||
// Load the upgrade Config from the system
|
||||
upgradeSpec, err := config.ReadUpgradeSpecFromConfig(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sanitize
|
||||
err = upgradeSpec.Sanitize()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -138,7 +85,7 @@ func Upgrade(
|
||||
|
||||
// determineUpgradeImage asks the provider plugin for an image or constructs
|
||||
// it using version and data from /etc/os-release
|
||||
func determineUpgradeImage(version string) (string, error) {
|
||||
func determineUpgradeImage(version string) (*v1.ImageSource, error) {
|
||||
var img string
|
||||
bus.Manager.Response(events.EventVersionImage, func(p *pluggable.Plugin, r *pluggable.EventResponse) {
|
||||
img = r.Data
|
||||
@ -148,17 +95,133 @@ func determineUpgradeImage(version string) (string, error) {
|
||||
Version: version,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if img != "" {
|
||||
return img, nil
|
||||
return v1.NewSrcFromURI(img)
|
||||
}
|
||||
|
||||
registry, err := utils.OSRelease("IMAGE_REPO")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't find IMAGE_REPO key under /etc/os-release %w", err)
|
||||
return nil, fmt.Errorf("can't find IMAGE_REPO key under /etc/os-release %w", err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%s", registry, version), nil
|
||||
return v1.NewSrcFromURI(fmt.Sprintf("%s:%s", registry, version))
|
||||
}
|
||||
|
||||
// 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": {},
|
||||
}
|
||||
|
||||
if upgradeRecovery {
|
||||
upgrade["upgrade"]["recovery"] = "true"
|
||||
}
|
||||
|
||||
// Set uri both for active and recovery because we don't know what we are
|
||||
// actually upgrading. The "upgradeRecovery" is just the command line argument.
|
||||
// The user might have set it to "true" in the kairos config. Since we don't
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
d, err := json.Marshal(upgrade)
|
||||
|
||||
return string(d), err
|
||||
}
|
||||
|
||||
func handleEmptySource(spec *v1.UpgradeSpec, version string, preReleases, force bool) error {
|
||||
var err error
|
||||
if spec.RecoveryUpgrade {
|
||||
if spec.Recovery.Source.IsEmpty() {
|
||||
spec.Recovery.Source, err = getLatestOrConstructSource(version, preReleases, force)
|
||||
}
|
||||
} else {
|
||||
if spec.Active.Source.IsEmpty() {
|
||||
spec.Active.Source, err = getLatestOrConstructSource(version, preReleases, force)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func getLatestOrConstructSource(version string, preReleases, force bool) (*v1.ImageSource, error) {
|
||||
var err error
|
||||
if version == "" {
|
||||
version, err = findLatestVersion(preReleases, force)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return determineUpgradeImage(version)
|
||||
}
|
||||
|
||||
func findLatestVersion(preReleases, force bool) (string, error) {
|
||||
fmt.Println("Searching for releases")
|
||||
if preReleases {
|
||||
fmt.Println("Including pre-releases")
|
||||
}
|
||||
releases := ListReleases(preReleases)
|
||||
|
||||
if len(releases) == 0 {
|
||||
return "", fmt.Errorf("no releases found")
|
||||
}
|
||||
|
||||
// Using Original here because the parsing removes the v as its a semver. But it stores the original full version there
|
||||
version := releases[0].Original()
|
||||
|
||||
if utils.Version() == version && !force {
|
||||
return "", fmt.Errorf("version %s already installed. use --force to force upgrade", version)
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("Latest release is %s\nAre you sure you want to upgrade to this release? (y/n)", version)
|
||||
reply, err := promptBool(events.YAMLPrompt{Prompt: msg, Default: "y"})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if reply == "false" {
|
||||
return "", fmt.Errorf("cancelled by the user")
|
||||
}
|
||||
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func generateUpgradeSpec(version, sourceImageURL string, force, strictValidations bool, dirs []string, preReleases, upgradeRecovery bool) (*v1.UpgradeSpec, *config.Config, error) {
|
||||
cliConf, err := generateUpgradeConfForCLIArgs(sourceImageURL, upgradeRecovery)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c, err := config.Scan(collector.Directories(dirs...),
|
||||
collector.Readers(strings.NewReader(cliConf)),
|
||||
collector.StrictValidation(strictValidations))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
utils.SetEnv(c.Env)
|
||||
|
||||
// Load the upgrade Config from the system
|
||||
upgradeSpec, err := config.ReadUpgradeSpecFromConfig(c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = handleEmptySource(upgradeSpec, version, preReleases, force)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return upgradeSpec, c, nil
|
||||
}
|
||||
|
66
main.go
66
main.go
@ -54,6 +54,11 @@ func ReleasesToOutput(rels semver.Collection, output string) []string {
|
||||
}
|
||||
}
|
||||
|
||||
var sourceFlag = cli.StringFlag{
|
||||
Name: "source",
|
||||
Usage: "Source for upgrade. Composed of `type:address`. Accepts `file:`,`dir:` or `oci:` for the type of source.\nFor example `file:/var/share/myimage.tar`, `dir:/tmp/extracted` or `oci:repo/image:tag`",
|
||||
}
|
||||
|
||||
var cmds = []*cli.Command{
|
||||
{
|
||||
Name: "upgrade",
|
||||
@ -66,10 +71,7 @@ var cmds = []*cli.Command{
|
||||
Name: "image",
|
||||
Usage: "[DEPRECATED] Specify a full image reference, e.g.: quay.io/some/image:tag",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "source",
|
||||
Usage: "Source for upgrade. Composed of `type:address`. Accepts `file:`,`dir:` or `oci:` for the type of source.\nFor example `file:/var/share/myimage.tar`, `dir:/tmp/extracted` or `oci:repo/image:tag`",
|
||||
},
|
||||
&sourceFlag,
|
||||
&cli.BoolFlag{Name: "pre", Usage: "Include pre-releases (rc, beta, alpha)"},
|
||||
&cli.BoolFlag{Name: "recovery", Usage: "Upgrade recovery"},
|
||||
},
|
||||
@ -110,15 +112,8 @@ See https://kairos.io/docs/upgrade/manual/ for documentation.
|
||||
},
|
||||
},
|
||||
Before: func(c *cli.Context) error {
|
||||
source := c.String("source")
|
||||
if source != "" {
|
||||
r, err := regexp.Compile(`^oci:|dir:|file:`)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if !r.MatchString(source) {
|
||||
return fmt.Errorf("source %s does not match any of oci:, dir: or file: ", source)
|
||||
}
|
||||
if err := validateSource(c.String("source")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return checkRoot()
|
||||
@ -385,13 +380,20 @@ This command is meant to be used from the boot GRUB menu, but can be also starte
|
||||
&cli.BoolFlag{
|
||||
Name: "shell",
|
||||
},
|
||||
&sourceFlag,
|
||||
},
|
||||
Usage: "Starts interactive installation",
|
||||
Before: func(c *cli.Context) error {
|
||||
if err := validateSource(c.String("source")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return checkRoot()
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return agent.InteractiveInstall(c.Bool("debug"), c.Bool("shell"))
|
||||
source := c.String("source")
|
||||
|
||||
return agent.InteractiveInstall(c.Bool("debug"), c.Bool("shell"), source)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -410,8 +412,13 @@ This command is meant to be used from the boot GRUB menu, but can be also starte
|
||||
&cli.BoolFlag{
|
||||
Name: "reboot",
|
||||
},
|
||||
&sourceFlag,
|
||||
},
|
||||
Before: func(c *cli.Context) error {
|
||||
if err := validateSource(c.String("source")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return checkRoot()
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
@ -420,7 +427,9 @@ This command is meant to be used from the boot GRUB menu, but can be also starte
|
||||
}
|
||||
config := c.Args().First()
|
||||
|
||||
return agent.ManualInstall(config, c.String("device"), c.Bool("reboot"), c.Bool("poweroff"), c.Bool("strict-validation"))
|
||||
source := c.String("source")
|
||||
|
||||
return agent.ManualInstall(config, source, c.String("device"), c.Bool("reboot"), c.Bool("poweroff"), c.Bool("strict-validation"))
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -436,10 +445,19 @@ See also https://kairos.io/docs/installation/qrcode/ for documentation.
|
||||
This command is meant to be used from the boot GRUB menu, but can be started manually`,
|
||||
Aliases: []string{"i"},
|
||||
Before: func(c *cli.Context) error {
|
||||
if err := validateSource(c.String("source")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return checkRoot()
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
&sourceFlag,
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return agent.Install(configScanDir...)
|
||||
source := c.String("source")
|
||||
|
||||
return agent.Install(source, configScanDir...)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -686,3 +704,19 @@ func checkRoot() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSource(source string) error {
|
||||
if source == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
r, err := regexp.Compile(`^oci:|dir:|file:`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !r.MatchString(source) {
|
||||
return fmt.Errorf("source %s does not match any of oci:, dir: or file: ", source)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -18,14 +18,14 @@ package action_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
"github.com/kairos-io/kairos-sdk/collector"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
"github.com/kairos-io/kairos-sdk/collector"
|
||||
|
||||
"github.com/jaypipes/ghw/pkg/block"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/action"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
@ -111,7 +111,7 @@ var _ = Describe("Install action tests", func() {
|
||||
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
|
||||
regexCmd := regexp.MustCompile(cmdFail)
|
||||
if cmdFail != "" && regexCmd.MatchString(cmd) {
|
||||
return []byte{}, errors.New(fmt.Sprintf("failed on %s", cmd))
|
||||
return []byte{}, fmt.Errorf("failed on %s", cmd)
|
||||
}
|
||||
switch cmd {
|
||||
case "parted":
|
||||
@ -152,7 +152,8 @@ var _ = Describe("Install action tests", func() {
|
||||
err = fsutils.MkdirAll(fs, constants.IsoBaseTree, constants.DirPerm)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
spec = agentConfig.NewInstallSpec(config)
|
||||
spec, err = agentConfig.NewInstallSpec(config)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
spec.Active.Size = 16
|
||||
|
||||
grubCfg := filepath.Join(spec.Active.MountPoint, constants.GrubConf)
|
||||
|
@ -19,10 +19,11 @@ package action_test
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
"path/filepath"
|
||||
|
||||
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
|
||||
"github.com/jaypipes/ghw/pkg/block"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/action"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
|
@ -27,7 +27,7 @@ import (
|
||||
"github.com/kairos-io/kairos-agent/v2/internal/common"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils/partitions"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/sanity-io/litter"
|
||||
@ -36,7 +36,7 @@ import (
|
||||
)
|
||||
|
||||
// NewInstallSpec returns an InstallSpec struct all based on defaults and basic host checks (e.g. EFI vs BIOS)
|
||||
func NewInstallSpec(cfg *Config) *v1.InstallSpec {
|
||||
func NewInstallSpec(cfg *Config) (*v1.InstallSpec, error) {
|
||||
var firmware string
|
||||
var recoveryImg, activeImg, passiveImg v1.Image
|
||||
|
||||
@ -60,6 +60,7 @@ func NewInstallSpec(cfg *Config) *v1.InstallSpec {
|
||||
activeImg.File = filepath.Join(constants.StateDir, "cOS", constants.ActiveImgFile)
|
||||
activeImg.FS = constants.LinuxImgFs
|
||||
activeImg.MountPoint = constants.ActiveDir
|
||||
|
||||
if isoRootExists {
|
||||
activeImg.Source = v1.NewDirSrc(constants.IsoBaseTree)
|
||||
} else {
|
||||
@ -109,10 +110,15 @@ func NewInstallSpec(cfg *Config) *v1.InstallSpec {
|
||||
spec.Recovery.Size = uint(size)
|
||||
}
|
||||
|
||||
err = unmarshallFullSpec(cfg, "install", spec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed unmarshalling the full spec: %w", err)
|
||||
}
|
||||
|
||||
// Calculate the partitions afterwards so they use the image sizes for the final partition sizes
|
||||
spec.Partitions = NewInstallElementalPartitions(spec)
|
||||
|
||||
return spec
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
func NewInstallElementalPartitions(spec *v1.InstallSpec) v1.ElementalPartitions {
|
||||
@ -199,7 +205,7 @@ func NewUpgradeSpec(cfg *Config) (*v1.UpgradeSpec, error) {
|
||||
|
||||
squashedRec, err := hasSquashedRecovery(cfg, ep.Recovery)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed checking for squashed recovery")
|
||||
return nil, fmt.Errorf("failed checking for squashed recovery: %w", err)
|
||||
}
|
||||
|
||||
if squashedRec {
|
||||
@ -265,35 +271,42 @@ func NewUpgradeSpec(cfg *Config) (*v1.UpgradeSpec, error) {
|
||||
State: installState,
|
||||
}
|
||||
|
||||
source := viper.GetString("upgradeSource")
|
||||
recoveryUpgrade := viper.GetBool("upgradeRecovery")
|
||||
if source != "" {
|
||||
imgSource, err := v1.NewSrcFromURI(source)
|
||||
if err == nil {
|
||||
if recoveryUpgrade {
|
||||
spec.RecoveryUpgrade = recoveryUpgrade
|
||||
spec.Recovery.Source = imgSource
|
||||
} else {
|
||||
spec.Active.Source = imgSource
|
||||
}
|
||||
size, err := GetSourceSize(cfg, imgSource)
|
||||
if err != nil {
|
||||
cfg.Logger.Warnf("Failed to infer size for images: %s", err.Error())
|
||||
} else {
|
||||
cfg.Logger.Infof("Setting image size to %dMb", size)
|
||||
// On upgrade only the active or recovery will be upgraded, so we dont need to override passive
|
||||
if recoveryUpgrade {
|
||||
spec.Recovery.Size = uint(size)
|
||||
} else {
|
||||
spec.Active.Size = uint(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
setUpgradeSourceSize(cfg, spec)
|
||||
|
||||
err = unmarshallFullSpec(cfg, "upgrade", spec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed unmarshalling the full spec: %w", err)
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
func setUpgradeSourceSize(cfg *Config, spec *v1.UpgradeSpec) error {
|
||||
var size int64
|
||||
var err error
|
||||
|
||||
var targetSpec *v1.Image
|
||||
if spec.RecoveryUpgrade {
|
||||
targetSpec = &(spec.Recovery)
|
||||
} else {
|
||||
targetSpec = &(spec.Active)
|
||||
}
|
||||
|
||||
if targetSpec.Source.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
size, err = GetSourceSize(cfg, targetSpec.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.Logger.Infof("Setting image size to %dMb", size)
|
||||
targetSpec.Size = uint(size)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewResetSpec returns a ResetSpec struct all based on defaults and current host state
|
||||
func NewResetSpec(cfg *Config) (*v1.ResetSpec, error) {
|
||||
var imgSource *v1.ImageSource
|
||||
@ -427,6 +440,11 @@ func NewResetSpec(cfg *Config) (*v1.ResetSpec, error) {
|
||||
spec.Passive.Size = uint(size)
|
||||
}
|
||||
|
||||
err = unmarshallFullSpec(cfg, "reset", spec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed unmarshalling the full spec: %w", err)
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
@ -548,7 +566,7 @@ func ReadSpecFromCloudConfig(r *Config, spec string) (v1.Spec, error) {
|
||||
|
||||
switch spec {
|
||||
case "install":
|
||||
sp = NewInstallSpec(r)
|
||||
sp, err = NewInstallSpec(r)
|
||||
case "upgrade":
|
||||
sp, err = NewUpgradeSpec(r)
|
||||
case "reset":
|
||||
@ -560,26 +578,11 @@ func ReadSpecFromCloudConfig(r *Config, spec string) (v1.Spec, error) {
|
||||
return nil, fmt.Errorf("failed initializing spec: %v", err)
|
||||
}
|
||||
|
||||
// Load the config into viper from the raw cloud config string
|
||||
ccString, err := r.String()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed initializing spec: %v", err)
|
||||
}
|
||||
viper.SetConfigType("yaml")
|
||||
viper.ReadConfig(strings.NewReader(ccString))
|
||||
vp := viper.Sub(spec)
|
||||
if vp == nil {
|
||||
vp = viper.New()
|
||||
}
|
||||
|
||||
err = vp.Unmarshal(sp, setDecoder, decodeHook)
|
||||
if err != nil {
|
||||
r.Logger.Warnf("error unmarshalling %s Spec: %s", spec, err)
|
||||
}
|
||||
err = sp.Sanitize()
|
||||
if err != nil {
|
||||
r.Logger.Warnf("Error sanitizing the % spec: %s", spec, err)
|
||||
return sp, fmt.Errorf("sanitizing the %s spec: %w", spec, err)
|
||||
}
|
||||
|
||||
r.Logger.Debugf("Loaded %s spec: %s", spec, litter.Sdump(sp))
|
||||
return sp, nil
|
||||
}
|
||||
@ -722,3 +725,24 @@ func isMounted(config *Config, part *v1.Partition) (bool, error) {
|
||||
}
|
||||
return !notMnt, nil
|
||||
}
|
||||
|
||||
func unmarshallFullSpec(r *Config, subkey string, sp v1.Spec) error {
|
||||
// Load the config into viper from the raw cloud config string
|
||||
ccString, err := r.String()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed initializing spec: %w", err)
|
||||
}
|
||||
viper.SetConfigType("yaml")
|
||||
viper.ReadConfig(strings.NewReader(ccString))
|
||||
vp := viper.Sub(subkey)
|
||||
if vp == nil {
|
||||
vp = viper.New()
|
||||
}
|
||||
|
||||
err = vp.Unmarshal(sp, setDecoder, decodeHook)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error unmarshalling %s Spec: %w", subkey, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -18,11 +18,14 @@ package config_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/jaypipes/ghw/pkg/block"
|
||||
config "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
||||
"github.com/kairos-io/kairos-sdk/collector"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
@ -31,8 +34,6 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/twpayne/go-vfs/vfst"
|
||||
"k8s.io/mount-utils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var _ = Describe("Types", Label("types", "config"), func() {
|
||||
@ -154,7 +155,8 @@ var _ = Describe("Types", Label("types", "config"), func() {
|
||||
_, err = fs.Create(recoveryImgFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
spec := config.NewInstallSpec(c)
|
||||
spec, err := config.NewInstallSpec(c)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(spec.Firmware).To(Equal(v1.EFI))
|
||||
Expect(spec.Active.Source.Value()).To(Equal(constants.IsoBaseTree))
|
||||
Expect(spec.Recovery.Source.Value()).To(Equal(recoveryImgFile))
|
||||
@ -175,7 +177,8 @@ var _ = Describe("Types", Label("types", "config"), func() {
|
||||
_, err = fs.Create(constants.IsoBaseTree)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
spec := config.NewInstallSpec(c)
|
||||
spec, err := config.NewInstallSpec(c)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(spec.Firmware).To(Equal(v1.BIOS))
|
||||
Expect(spec.Active.Source.Value()).To(Equal(constants.IsoBaseTree))
|
||||
Expect(spec.Recovery.Source.Value()).To(Equal(spec.Active.File))
|
||||
@ -190,7 +193,8 @@ var _ = Describe("Types", Label("types", "config"), func() {
|
||||
Expect(spec.Partitions.BIOS).NotTo(BeNil())
|
||||
})
|
||||
It("sets installation defaults without being on installation media", Label("install"), func() {
|
||||
spec := config.NewInstallSpec(c)
|
||||
spec, err := config.NewInstallSpec(c)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(spec.Firmware).To(Equal(v1.BIOS))
|
||||
Expect(spec.Active.Source.IsEmpty()).To(BeTrue())
|
||||
Expect(spec.Recovery.Source.Value()).To(Equal(spec.Active.File))
|
||||
@ -459,6 +463,8 @@ upgrade:
|
||||
recovery: true
|
||||
system:
|
||||
uri: docker:test/image:latest
|
||||
recovery-system:
|
||||
uri: docker:test/image:latest
|
||||
cloud-init-paths:
|
||||
- /what
|
||||
`)
|
||||
@ -498,6 +504,12 @@ cloud-init-paths:
|
||||
ghwTest = v1mock.GhwMock{}
|
||||
ghwTest.AddDisk(mainDisk)
|
||||
ghwTest.CreateDevices()
|
||||
|
||||
fs, cleanup, err = vfst.NewTestFS(nil)
|
||||
err = fsutils.MkdirAll(fs, filepath.Dir(constants.IsoBaseTree), constants.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = fs.Create(constants.IsoBaseTree)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
@ -505,7 +517,11 @@ cloud-init-paths:
|
||||
ghwTest.Clean()
|
||||
})
|
||||
It("Reads properly the cloud config for install", func() {
|
||||
cfg, err := config.Scan(collector.Directories([]string{dir}...), collector.NoLogs)
|
||||
cfg, err := config.Scan(collector.Directories([]string{dir}...),
|
||||
collector.NoLogs,
|
||||
)
|
||||
cfg.Fs = fs
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
// Once we got the cfg override the fs to our test fs
|
||||
cfg.Runner = runner
|
||||
|
@ -19,15 +19,15 @@ package elemental_test
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
|
||||
"github.com/jaypipes/ghw/pkg/block"
|
||||
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
cnst "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"
|
||||
@ -340,13 +340,15 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
var printOut string
|
||||
var failPart bool
|
||||
var install *v1.InstallSpec
|
||||
var err error
|
||||
|
||||
BeforeEach(func() {
|
||||
cInit = &v1mock.FakeCloudInitRunner{ExecStages: []string{}, Error: false}
|
||||
config.CloudInitRunner = cInit
|
||||
config.Install.Device = "/some/device"
|
||||
el = elemental.NewElemental(config)
|
||||
install = agentConfig.NewInstallSpec(config)
|
||||
install, err = agentConfig.NewInstallSpec(config)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
install.Target = "/some/device"
|
||||
|
||||
err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm)
|
||||
@ -498,7 +500,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
cmdFail = ""
|
||||
el = elemental.NewElemental(config)
|
||||
img = &v1.Image{
|
||||
FS: constants.LinuxImgFs,
|
||||
FS: cnst.LinuxImgFs,
|
||||
Size: 16,
|
||||
Source: v1.NewDirSrc(sourceDir),
|
||||
MountPoint: destDir,
|
||||
@ -524,7 +526,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
Expect(el.DeployImage(img, false)).To(BeNil())
|
||||
})
|
||||
It("Deploys an squashfs image from a directory", func() {
|
||||
img.FS = constants.SquashFs
|
||||
img.FS = cnst.SquashFs
|
||||
Expect(el.DeployImage(img, true)).To(BeNil())
|
||||
Expect(runner.MatchMilestones([][]string{
|
||||
{
|
||||
@ -569,7 +571,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
})
|
||||
It("Fails creating the squashfs filesystem", func() {
|
||||
cmdFail = "mksquashfs"
|
||||
img.FS = constants.SquashFs
|
||||
img.FS = cnst.SquashFs
|
||||
_, err := el.DeployImage(img, true)
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(runner.MatchMilestones([][]string{
|
||||
@ -609,7 +611,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
src := ""
|
||||
dest := ""
|
||||
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
|
||||
if cmd == constants.Rsync {
|
||||
if cmd == cnst.Rsync {
|
||||
rsyncCount += 1
|
||||
src = args[len(args)-2]
|
||||
dest = args[len(args)-1]
|
||||
@ -647,7 +649,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
Expect(err).To(BeNil())
|
||||
_, err = e.DumpSource(destFile, v1.NewFileSrc(sourceImg))
|
||||
Expect(err).To(BeNil())
|
||||
Expect(runner.IncludesCmds([][]string{{constants.Rsync}}))
|
||||
Expect(runner.IncludesCmds([][]string{{cnst.Rsync}}))
|
||||
})
|
||||
It("Fails to copy, source file is not present", func() {
|
||||
_, err := e.DumpSource("whatever", v1.NewFileSrc("/source.img"))
|
||||
@ -687,7 +689,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
var relabelCmd []string
|
||||
BeforeEach(func() {
|
||||
// to mock the existance of setfiles command on non selinux hosts
|
||||
err := fsutils.MkdirAll(fs, "/usr/sbin", constants.DirPerm)
|
||||
err := fsutils.MkdirAll(fs, "/usr/sbin", cnst.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
sbin, err := fs.RawPath("/usr/sbin")
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
@ -700,23 +702,23 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
// to mock SELinux policy files
|
||||
policyFile = filepath.Join(constants.SELinuxTargetedPolicyPath, "policy.31")
|
||||
err = fsutils.MkdirAll(fs, filepath.Dir(constants.SELinuxTargetedContextFile), constants.DirPerm)
|
||||
policyFile = filepath.Join(cnst.SELinuxTargetedPolicyPath, "policy.31")
|
||||
err = fsutils.MkdirAll(fs, filepath.Dir(cnst.SELinuxTargetedContextFile), cnst.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = fs.Create(constants.SELinuxTargetedContextFile)
|
||||
_, err = fs.Create(cnst.SELinuxTargetedContextFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
err = fsutils.MkdirAll(fs, constants.SELinuxTargetedPolicyPath, constants.DirPerm)
|
||||
err = fsutils.MkdirAll(fs, cnst.SELinuxTargetedPolicyPath, cnst.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = fs.Create(policyFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
relabelCmd = []string{
|
||||
"setfiles", "-c", policyFile, "-e", "/dev", "-e", "/proc", "-e", "/sys",
|
||||
"-F", constants.SELinuxTargetedContextFile, "/",
|
||||
"-F", cnst.SELinuxTargetedContextFile, "/",
|
||||
}
|
||||
})
|
||||
It("does nothing if the context file is not found", func() {
|
||||
err := fs.Remove(constants.SELinuxTargetedContextFile)
|
||||
err := fs.Remove(cnst.SELinuxTargetedContextFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
c := elemental.NewElemental(config)
|
||||
@ -753,13 +755,13 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
Expect(runner.CmdsMatch([][]string{relabelCmd})).To(BeNil())
|
||||
})
|
||||
It("relabels the given root-tree path", func() {
|
||||
contextFile := filepath.Join("/root", constants.SELinuxTargetedContextFile)
|
||||
err := fsutils.MkdirAll(fs, filepath.Dir(contextFile), constants.DirPerm)
|
||||
contextFile := filepath.Join("/root", cnst.SELinuxTargetedContextFile)
|
||||
err := fsutils.MkdirAll(fs, filepath.Dir(contextFile), cnst.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = fs.Create(contextFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
policyFile = filepath.Join("/root", policyFile)
|
||||
err = fsutils.MkdirAll(fs, filepath.Join("/root", constants.SELinuxTargetedPolicyPath), constants.DirPerm)
|
||||
err = fsutils.MkdirAll(fs, filepath.Join("/root", cnst.SELinuxTargetedPolicyPath), cnst.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = fs.Create(policyFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
@ -888,9 +890,9 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
Expect(runner.CmdsMatch([][]string{{"grub2-editenv"}})).NotTo(BeNil())
|
||||
})
|
||||
It("loads /etc/os-release on empty default entry", func() {
|
||||
err := fsutils.MkdirAll(config.Fs, "/imgMountPoint/etc", constants.DirPerm)
|
||||
err := fsutils.MkdirAll(config.Fs, "/imgMountPoint/etc", cnst.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
err = config.Fs.WriteFile("/imgMountPoint/etc/os-release", []byte("GRUB_ENTRY_NAME=test"), constants.FilePerm)
|
||||
err = config.Fs.WriteFile("/imgMountPoint/etc/os-release", []byte("GRUB_ENTRY_NAME=test"), cnst.FilePerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
el := elemental.NewElemental(config)
|
||||
@ -909,7 +911,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
})
|
||||
Describe("FindKernelInitrd", Label("find"), func() {
|
||||
BeforeEach(func() {
|
||||
err := fsutils.MkdirAll(fs, "/path/boot", constants.DirPerm)
|
||||
err := fsutils.MkdirAll(fs, "/path/boot", cnst.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("finds kernel and initrd files", func() {
|
||||
|
Loading…
Reference in New Issue
Block a user