diff --git a/go.mod b/go.mod index 9c3b707..2d8df4a 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ 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.9 + github.com/kairos-io/kairos-sdk v0.0.10 github.com/labstack/echo/v4 v4.10.2 github.com/mitchellh/mapstructure v1.5.0 github.com/mudler/go-nodepair v0.0.0-20221223092639-ba399a66fdfb diff --git a/go.sum b/go.sum index e9088a1..1d651d7 100644 --- a/go.sum +++ b/go.sum @@ -358,6 +358,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.9 h1:DnuvmnCTDBQg0nr3siaXb5pspi+GUsf1rPb0MFOTdTc= github.com/kairos-io/kairos-sdk v0.0.9/go.mod h1:Z+1CLqMZq97bzwX2XSIArr8EoniMth3mMYkOOb8L3QY= +github.com/kairos-io/kairos-sdk v0.0.10 h1:TUgrGSGP6Z1CPfA4gjmbb+cCaJg1OR18c+LD+ZRGqMk= +github.com/kairos-io/kairos-sdk v0.0.10/go.mod h1:AK9poWisuhnzri04diXnTG8L7EkOSUArSeeDn2LYFU0= github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 h1:qq2nCpSrXrmvDGRxW0ruW9BVEV1CN2a9YDOExdt+U0o= github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329/go.mod h1:2VPVQDR4wO7KXHwP+DAypEy67rXf+okUx2zjgpCxZw4= github.com/kendru/darwin/go/depgraph v0.0.0-20221105232959-877d6a81060c h1:eKb4PqwAMhlqwXw0W3atpKaYaPGlXE/Fwh+xpCEYaPk= diff --git a/internal/agent/hooks/hook.go b/internal/agent/hooks/hook.go index 9c3233d..925f469 100644 --- a/internal/agent/hooks/hook.go +++ b/internal/agent/hooks/hook.go @@ -10,7 +10,6 @@ type Interface interface { } var AfterInstall = []Interface{ - &RunStage{}, // Shells out to stages defined from the container image &GrubOptions{}, // Set custom GRUB options &BundleOption{}, &CustomMounts{}, diff --git a/internal/agent/hooks/runstage.go b/internal/agent/hooks/runstage.go deleted file mode 100644 index 8d86265..0000000 --- a/internal/agent/hooks/runstage.go +++ /dev/null @@ -1,22 +0,0 @@ -package hook - -import ( - "github.com/kairos-io/kairos-agent/v2/pkg/config" - "github.com/kairos-io/kairos-agent/v2/pkg/elementalConfig" - v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" - "github.com/kairos-io/kairos-agent/v2/pkg/utils" - - events "github.com/kairos-io/kairos-sdk/bus" -) - -type RunStage struct{} - -func (r RunStage) Run(c config.Config, _ v1.Spec) error { - cfg, err := elementalConfig.ReadConfigRunFromAgentConfig(&c) - if err != nil { - cfg.Logger.Errorf("Error reading config: %s\n", err) - } - _ = utils.RunStage(cfg, "kairos-install.after") - events.RunHookScript("/usr/bin/kairos-agent.install.after.hook") //nolint:errcheck - return nil -} diff --git a/internal/agent/install.go b/internal/agent/install.go index f49c05a..335f22a 100644 --- a/internal/agent/install.go +++ b/internal/agent/install.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" "net/url" "os" "strings" @@ -16,7 +17,6 @@ import ( "github.com/kairos-io/kairos-agent/v2/internal/cmd" "github.com/kairos-io/kairos-agent/v2/pkg/action" "github.com/kairos-io/kairos-agent/v2/pkg/config" - "github.com/kairos-io/kairos-agent/v2/pkg/elementalConfig" 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" @@ -51,17 +51,6 @@ func displayInfo(agentConfig *Config) { } } -func mergeOption(cloudConfig string, r map[string]string) { - c := &config.Config{} - yaml.Unmarshal([]byte(cloudConfig), c) //nolint:errcheck - for k, v := range c.Options { - if k == "cc" { - continue - } - r[k] = v - } -} - func ManualInstall(c, device string, reboot, poweroff, strictValidations bool) error { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -223,22 +212,21 @@ func RunInstall(c *config.Config) error { c.Install.Device = detectDevice() } - // Load the installation Config from the system - installConfig, installSpec, err := elementalConfig.ReadInstallConfigFromAgentConfig(c) + // Load the installation spec from the Config + installSpec, err := config.ReadInstallSpecFromConfig(c) if err != nil { return err } - f, err := elementalUtils.TempFile(installConfig.Fs, "", "kairos-install-config-xxx.yaml") + f, err := fsutils.TempFile(c.Fs, "", "kairos-install-config-xxx.yaml") if err != nil { - installConfig.Logger.Error("Error creating temporal file for install config: %s\n", err.Error()) + c.Logger.Error("Error creating temporary file for install config: %s\n", err.Error()) return err } defer os.RemoveAll(f.Name()) ccstring, err := c.String() if err != nil { - installConfig.Logger.Error("Error creating temporary file for install config: %s\n", err.Error()) return err } err = os.WriteFile(f.Name(), []byte(ccstring), os.ModePerm) @@ -247,6 +235,7 @@ func RunInstall(c *config.Config) error { return err } + // TODO: This should not be neccessary installSpec.NoFormat = c.Install.NoFormat // Set our cloud-init to the file we just created @@ -267,18 +256,20 @@ func RunInstall(c *config.Config) error { } // Add user's cloud-config (to run user defined "before-install" stages) - installConfig.CloudInitPaths = append(installConfig.CloudInitPaths, installSpec.CloudInit...) + c.CloudInitPaths = append(c.CloudInitPaths, installSpec.CloudInit...) // Run pre-install stage - _ = elementalUtils.RunStage(installConfig, "kairos-install.pre") + _ = elementalUtils.RunStage(c, "kairos-install.pre") events.RunHookScript("/usr/bin/kairos-agent.install.pre.hook") //nolint:errcheck // Create the action - installAction := action.NewInstallAction(installConfig, installSpec) + installAction := action.NewInstallAction(c, installSpec) // Run it if err := installAction.Run(); err != nil { fmt.Println(err) os.Exit(1) } + _ = elementalUtils.RunStage(c, "kairos-install.after") + events.RunHookScript("/usr/bin/kairos-agent.install.after.hook") //nolint:errcheck return hook.Run(*c, installSpec, hook.AfterInstall...) } diff --git a/internal/agent/install_test.go b/internal/agent/install_test.go index e981683..a7321f6 100644 --- a/internal/agent/install_test.go +++ b/internal/agent/install_test.go @@ -5,11 +5,11 @@ import ( "fmt" "github.com/jaypipes/ghw/pkg/block" "github.com/kairos-io/kairos-agent/v2/pkg/constants" - "github.com/kairos-io/kairos-agent/v2/pkg/utils" "os" "path/filepath" "github.com/kairos-io/kairos-agent/v2/pkg/config" + "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" @@ -84,10 +84,10 @@ var _ = Describe("RunInstall", func() { fs, cleanup, err = vfst.NewTestFS(map[string]interface{}{"/proc/cmdline": ""}) Expect(err).Should(BeNil()) // Create tmp dir - utils.MkdirAll(fs, "/tmp", constants.DirPerm) + fsutils.MkdirAll(fs, "/tmp", constants.DirPerm) // Create grub confg grubCfg := filepath.Join(constants.ActiveDir, constants.GrubConf) - err = utils.MkdirAll(fs, filepath.Dir(grubCfg), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(grubCfg), constants.DirPerm) Expect(err).To(BeNil()) _, err = fs.Create(grubCfg) Expect(err).To(BeNil()) @@ -133,7 +133,7 @@ var _ = Describe("RunInstall", func() { } device := "/some/device" - err = utils.MkdirAll(fs, filepath.Dir(device), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(device), constants.DirPerm) Expect(err).To(BeNil()) _, err = fs.Create(device) Expect(err).ShouldNot(HaveOccurred()) diff --git a/internal/agent/reset.go b/internal/agent/reset.go index 4500275..56509b6 100644 --- a/internal/agent/reset.go +++ b/internal/agent/reset.go @@ -12,7 +12,6 @@ import ( "github.com/kairos-io/kairos-agent/v2/internal/cmd" "github.com/kairos-io/kairos-agent/v2/pkg/action" "github.com/kairos-io/kairos-agent/v2/pkg/config" - "github.com/kairos-io/kairos-agent/v2/pkg/elementalConfig" sdk "github.com/kairos-io/kairos-sdk/bus" "github.com/kairos-io/kairos-sdk/collector" "github.com/kairos-io/kairos-sdk/machine" @@ -78,7 +77,7 @@ func Reset(dir ...string) error { utils.SetEnv(c.Env) // Load the installation Config from the cloud-config data - resetConfig, resetSpec, err := elementalConfig.ReadResetConfigFromAgentConfig(c) + resetSpec, err := config.ReadResetSpecFromConfig(c) if err != nil { return err } @@ -92,11 +91,11 @@ func Reset(dir ...string) error { resetSpec.FormatOEM = o == "true" } if s := optionsFromEvent["strict"]; s != "" { - resetConfig.Strict = s == "true" + c.Strict = s == "true" } } - resetAction := action.NewResetAction(resetConfig, resetSpec) + resetAction := action.NewResetAction(c, resetSpec) if err := resetAction.Run(); err != nil { fmt.Println(err) os.Exit(1) diff --git a/internal/agent/upgrade.go b/internal/agent/upgrade.go index 916a2e1..b793afd 100644 --- a/internal/agent/upgrade.go +++ b/internal/agent/upgrade.go @@ -11,7 +11,6 @@ import ( "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" - "github.com/kairos-io/kairos-agent/v2/pkg/elementalConfig" "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" @@ -100,7 +99,7 @@ func Upgrade( utils.SetEnv(c.Env) // Load the upgrade Config from the system - upgradeConfig, upgradeSpec, err := elementalConfig.ReadUpgradeConfigFromAgentConfig(c) + upgradeSpec, err := config.ReadUpgradeSpecFromConfig(c) if err != nil { return err } @@ -118,7 +117,7 @@ func Upgrade( return err } - upgradeAction := action.NewUpgradeAction(upgradeConfig, upgradeSpec) + upgradeAction := action.NewUpgradeAction(c, upgradeSpec) err = upgradeAction.Run() if err != nil { diff --git a/main.go b/main.go index 00d6e43..9235b42 100644 --- a/main.go +++ b/main.go @@ -15,8 +15,7 @@ import ( "github.com/kairos-io/kairos-agent/v2/internal/bus" "github.com/kairos-io/kairos-agent/v2/internal/common" "github.com/kairos-io/kairos-agent/v2/internal/webui" - "github.com/kairos-io/kairos-agent/v2/pkg/config" - "github.com/kairos-io/kairos-agent/v2/pkg/elementalConfig" + agentConfig "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-sdk/bundles" "github.com/kairos-io/kairos-sdk/collector" @@ -279,7 +278,7 @@ E.g. kairos-agent install-bundle container:quay.io/kairos/kairos... Description: "Show the runtime configuration of the machine. It will scan the machine for all the configuration and will return the config file processed and found.", Aliases: []string{"s"}, Action: func(c *cli.Context) error { - config, err := config.Scan(collector.Directories(configScanDir...), collector.NoLogs) + config, err := agentConfig.Scan(collector.Directories(configScanDir...), collector.NoLogs) if err != nil { return err } @@ -308,7 +307,7 @@ enabled: true`, Description: "It allows to navigate the YAML config file by searching with 'yq' style keywords as `config get k3s` to retrieve the k3s config block", Aliases: []string{"g"}, Action: func(c *cli.Context) error { - config, err := config.Scan(collector.Directories(configScanDir...), collector.NoLogs, collector.StrictValidation(c.Bool("strict-validation"))) + config, err := agentConfig.Scan(collector.Directories(configScanDir...), collector.NoLogs, collector.StrictValidation(c.Bool("strict-validation"))) if err != nil { return err } @@ -527,24 +526,20 @@ The validate command expects a configuration file as its only argument. Local fi }, Action: func(c *cli.Context) error { stage := c.Args().First() - config, err := config.Scan(collector.Directories(configScanDir...), collector.NoLogs) - if err != nil { - return err - } - cfg, err := elementalConfig.ReadConfigRunFromAgentConfig(config) - cfg.Strict = c.Bool("strict") + config, err := agentConfig.Scan(collector.Directories(configScanDir...), collector.NoLogs) + config.Strict = c.Bool("strict") if len(c.StringSlice("cloud-init-paths")) > 0 { - cfg.CloudInitPaths = append(cfg.CloudInitPaths, c.StringSlice("cloud-init-paths")...) + config.CloudInitPaths = append(config.CloudInitPaths, c.StringSlice("cloud-init-paths")...) } if c.Bool("debug") { - cfg.Logger.SetLevel(logrus.DebugLevel) + config.Logger.SetLevel(logrus.DebugLevel) } if err != nil { - cfg.Logger.Errorf("Error reading config: %s\n", err) + config.Logger.Errorf("Error reading config: %s\n", err) } - return utils.RunStage(cfg, stage) + return utils.RunStage(config, stage) }, }, { @@ -578,24 +573,16 @@ The validate command expects a configuration file as its only argument. Local fi if err != nil { return fmt.Errorf("invalid path %s", destination) } - config, err := config.Scan(collector.Directories(configScanDir...), collector.NoLogs) + config, err := agentConfig.Scan(collector.Directories(configScanDir...), collector.NoLogs) if err != nil { return err } - cfg, err := elementalConfig.ReadConfigRunFromAgentConfig(config) - if err != nil { - return err - } - if c.Bool("debug") { - cfg.Logger.SetLevel(logrus.DebugLevel) - } - - cfg.Logger.Infof("Starting download and extraction for image %s to %s\n", image, destination) + config.Logger.Infof("Starting download and extraction for image %s to %s\n", image, destination) e := v1.OCIImageExtractor{} if err = e.ExtractImage(image, destination, c.String("platform")); err != nil { return err } - cfg.Logger.Infof("Image %s downloaded and extracted to %s correctly\n", image, destination) + config.Logger.Infof("Image %s downloaded and extracted to %s correctly\n", image, destination) return nil }, }, diff --git a/pkg/action/common.go b/pkg/action/common.go index 1ca1d79..7de3fa3 100644 --- a/pkg/action/common.go +++ b/pkg/action/common.go @@ -17,13 +17,13 @@ limitations under the License. package action import ( - v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" + config "github.com/kairos-io/kairos-agent/v2/pkg/config" "github.com/kairos-io/kairos-agent/v2/pkg/utils" ) // Hook is RunStage wrapper that only adds logic to ignore errors // in case v1.Config.Strict is set to false -func Hook(config *v1.Config, hook string) error { +func Hook(config *config.Config, hook string) error { config.Logger.Infof("Running %s hook", hook) err := utils.RunStage(config, hook) if !config.Strict { @@ -33,7 +33,7 @@ func Hook(config *v1.Config, hook string) error { } // ChrootHook executes Hook inside a chroot environment -func ChrootHook(config *v1.Config, hook string, chrootDir string, bindMounts map[string]string) (err error) { +func ChrootHook(config *config.Config, hook string, chrootDir string, bindMounts map[string]string) (err error) { callback := func() error { return Hook(config, hook) } diff --git a/pkg/action/install.go b/pkg/action/install.go index 6850f7d..d653921 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -18,7 +18,9 @@ package action import ( "fmt" + "github.com/kairos-io/kairos-agent/v2/pkg/config" "path/filepath" + "strings" "time" cnst "github.com/kairos-io/kairos-agent/v2/pkg/constants" @@ -107,11 +109,11 @@ func (i *InstallAction) createInstallStateYaml(sysMeta, recMeta interface{}) err } type InstallAction struct { - cfg *v1.Config + cfg *config.Config spec *v1.InstallSpec } -func NewInstallAction(cfg *v1.Config, spec *v1.InstallSpec) *InstallAction { +func NewInstallAction(cfg *config.Config, spec *v1.InstallSpec) *InstallAction { return &InstallAction{cfg: cfg, spec: spec} } @@ -259,7 +261,10 @@ func (i InstallAction) Run() (err error) { } // If we want to eject the cd, create the required executable so the cd is ejected at shutdown - if i.cfg.EjectCD && utils.BootedFrom(i.cfg.Runner, "cdroot") { + out, _ := i.cfg.Runner.Run("cat", "/proc/cmdline") + bootedFromCD := strings.Contains(string(out), "cdroot") + + if i.cfg.EjectCD && bootedFromCD { i.cfg.Logger.Infof("Writing eject script") err = i.cfg.Fs.WriteFile("/usr/lib/systemd/system-shutdown/eject", []byte(cnst.EjectScript), 0744) if err != nil { diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 9c74a31..9aa8b2e 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -20,13 +20,14 @@ import ( "bytes" "errors" "fmt" + agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" "path/filepath" "regexp" "github.com/jaypipes/ghw/pkg/block" "github.com/kairos-io/kairos-agent/v2/pkg/action" "github.com/kairos-io/kairos-agent/v2/pkg/constants" - conf "github.com/kairos-io/kairos-agent/v2/pkg/elementalConfig" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" "github.com/kairos-io/kairos-agent/v2/pkg/utils" v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks" @@ -42,7 +43,7 @@ const partTmpl = ` %d:%ss:%ss:2048s:ext4::type=83;` var _ = Describe("Install action tests", func() { - var config *v1.Config + var config *agentConfig.Config var runner *v1mock.FakeRunner var fs vfs.FS var logger v1.Logger @@ -69,15 +70,15 @@ var _ = Describe("Install action tests", func() { Expect(err).Should(BeNil()) cloudInit = &v1mock.FakeCloudInitRunner{} - config = conf.NewConfig( - conf.WithFs(fs), - conf.WithRunner(runner), - conf.WithLogger(logger), - conf.WithMounter(mounter), - conf.WithSyscall(syscall), - conf.WithClient(client), - conf.WithCloudInitRunner(cloudInit), - conf.WithImageExtractor(extractor), + config = agentConfig.NewConfig( + agentConfig.WithFs(fs), + agentConfig.WithRunner(runner), + agentConfig.WithLogger(logger), + agentConfig.WithMounter(mounter), + agentConfig.WithSyscall(syscall), + agentConfig.WithClient(client), + agentConfig.WithCloudInitRunner(cloudInit), + agentConfig.WithImageExtractor(extractor), ) }) @@ -94,7 +95,7 @@ var _ = Describe("Install action tests", func() { BeforeEach(func() { device = "/some/device" - err = utils.MkdirAll(fs, filepath.Dir(device), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(device), constants.DirPerm) Expect(err).To(BeNil()) _, err = fs.Create(device) Expect(err).ShouldNot(HaveOccurred()) @@ -143,14 +144,14 @@ var _ = Describe("Install action tests", func() { } } // Need to create the IsoBaseTree, like if we are booting from iso - err = utils.MkdirAll(fs, constants.IsoBaseTree, constants.DirPerm) + err = fsutils.MkdirAll(fs, constants.IsoBaseTree, constants.DirPerm) Expect(err).To(BeNil()) - spec = conf.NewInstallSpec(config) + spec = agentConfig.NewInstallSpec(config) spec.Active.Size = 16 grubCfg := filepath.Join(spec.Active.MountPoint, constants.GrubConf) - err = utils.MkdirAll(fs, filepath.Dir(grubCfg), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(grubCfg), constants.DirPerm) Expect(err).To(BeNil()) _, err = fs.Create(grubCfg) Expect(err).To(BeNil()) @@ -215,7 +216,7 @@ var _ = Describe("Install action tests", func() { }) It("Sets the executable /run/cos/ejectcd so systemd can eject the cd on restart", func() { - _ = utils.MkdirAll(fs, "/usr/lib/systemd/system-shutdown", constants.DirPerm) + _ = fsutils.MkdirAll(fs, "/usr/lib/systemd/system-shutdown", constants.DirPerm) _, err := fs.Stat("/usr/lib/systemd/system-shutdown/eject") Expect(err).To(HaveOccurred()) // Override cmdline to return like we are booting from cd @@ -233,7 +234,7 @@ var _ = Describe("Install action tests", func() { }) It("ejectcd does nothing if we are not booting from cd", func() { - _ = utils.MkdirAll(fs, "/usr/lib/systemd/system-shutdown", constants.DirPerm) + _ = fsutils.MkdirAll(fs, "/usr/lib/systemd/system-shutdown", constants.DirPerm) _, err := fs.Stat("/usr/lib/systemd/system-shutdown/eject") Expect(err).To(HaveOccurred()) spec.Target = device @@ -265,7 +266,7 @@ var _ = Describe("Install action tests", func() { It("Successfully installs and adds remote cloud-config", Label("cloud-config"), func() { spec.Target = device spec.CloudInit = []string{"http://my.config.org"} - utils.MkdirAll(fs, constants.OEMDir, constants.DirPerm) + fsutils.MkdirAll(fs, constants.OEMDir, constants.DirPerm) _, err := fs.Create(filepath.Join(constants.OEMDir, "90_custom.yaml")) Expect(err).ShouldNot(HaveOccurred()) Expect(installer.Run()).To(BeNil()) diff --git a/pkg/action/reset.go b/pkg/action/reset.go index e48ec8b..af69501 100644 --- a/pkg/action/reset.go +++ b/pkg/action/reset.go @@ -18,6 +18,7 @@ package action import ( "fmt" + agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config" "path/filepath" "time" @@ -44,11 +45,11 @@ func (r *ResetAction) resetHook(hook string, chroot bool) error { } type ResetAction struct { - cfg *v1.Config + cfg *agentConfig.Config spec *v1.ResetSpec } -func NewResetAction(cfg *v1.Config, spec *v1.ResetSpec) *ResetAction { +func NewResetAction(cfg *agentConfig.Config, spec *v1.ResetSpec) *ResetAction { return &ResetAction{cfg: cfg, spec: spec} } diff --git a/pkg/action/reset_test.go b/pkg/action/reset_test.go index 47045aa..9bb54c5 100644 --- a/pkg/action/reset_test.go +++ b/pkg/action/reset_test.go @@ -20,13 +20,14 @@ import ( "bytes" "errors" "fmt" + agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" "path/filepath" "regexp" "github.com/jaypipes/ghw/pkg/block" "github.com/kairos-io/kairos-agent/v2/pkg/action" "github.com/kairos-io/kairos-agent/v2/pkg/constants" - conf "github.com/kairos-io/kairos-agent/v2/pkg/elementalConfig" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" "github.com/kairos-io/kairos-agent/v2/pkg/utils" v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks" @@ -37,7 +38,7 @@ import ( ) var _ = Describe("Reset action tests", func() { - var config *v1.Config + var config *agentConfig.Config var runner *v1mock.FakeRunner var fs vfs.FS var logger v1.Logger @@ -63,15 +64,15 @@ var _ = Describe("Reset action tests", func() { Expect(err).Should(BeNil()) cloudInit = &v1mock.FakeCloudInitRunner{} - config = conf.NewConfig( - conf.WithFs(fs), - conf.WithRunner(runner), - conf.WithLogger(logger), - conf.WithMounter(mounter), - conf.WithSyscall(syscall), - conf.WithClient(client), - conf.WithCloudInitRunner(cloudInit), - conf.WithImageExtractor(extractor), + config = agentConfig.NewConfig( + agentConfig.WithFs(fs), + agentConfig.WithRunner(runner), + agentConfig.WithLogger(logger), + agentConfig.WithMounter(mounter), + agentConfig.WithSyscall(syscall), + agentConfig.WithClient(client), + agentConfig.WithCloudInitRunner(cloudInit), + agentConfig.WithImageExtractor(extractor), ) }) @@ -87,7 +88,7 @@ var _ = Describe("Reset action tests", func() { Expect(err).ShouldNot(HaveOccurred()) cmdFail = "" recoveryImg := filepath.Join(constants.RunningStateDir, "cOS", constants.RecoveryImgFile) - err = utils.MkdirAll(fs, filepath.Dir(recoveryImg), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(recoveryImg), constants.DirPerm) Expect(err).To(BeNil()) _, err = fs.Create(recoveryImg) Expect(err).To(BeNil()) @@ -140,14 +141,14 @@ var _ = Describe("Reset action tests", func() { } } - spec, err = conf.NewResetSpec(config) + spec, err = agentConfig.NewResetSpec(config) Expect(err).ShouldNot(HaveOccurred()) Expect(spec.Active.Source.IsEmpty()).To(BeFalse()) spec.Active.Size = 16 grubCfg := filepath.Join(spec.Active.MountPoint, spec.GrubConf) - err = utils.MkdirAll(fs, filepath.Dir(grubCfg), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(grubCfg), constants.DirPerm) Expect(err).To(BeNil()) _, err = fs.Create(grubCfg) Expect(err).To(BeNil()) @@ -175,7 +176,7 @@ var _ = Describe("Reset action tests", func() { Expect(reset.Run()).To(BeNil()) }) It("Successfully resets from a squashfs recovery image", Label("channel"), func() { - err := utils.MkdirAll(config.Fs, constants.IsoBaseTree, constants.DirPerm) + err := fsutils.MkdirAll(config.Fs, constants.IsoBaseTree, constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) spec.Active.Source = v1.NewDirSrc(constants.IsoBaseTree) Expect(reset.Run()).To(BeNil()) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 16ba33b..187d3c5 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -18,6 +18,8 @@ package action import ( "fmt" + agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" "path/filepath" "time" @@ -29,11 +31,11 @@ import ( // UpgradeAction represents the struct that will run the upgrade from start to finish type UpgradeAction struct { - config *v1.Config + config *agentConfig.Config spec *v1.UpgradeSpec } -func NewUpgradeAction(config *v1.Config, spec *v1.UpgradeSpec) *UpgradeAction { +func NewUpgradeAction(config *agentConfig.Config, spec *v1.UpgradeSpec) *UpgradeAction { return &UpgradeAction{config: config, spec: spec} } @@ -154,7 +156,7 @@ func (u *UpgradeAction) Run() (err error) { persistentPart := u.spec.Partitions.Persistent if persistentPart != nil { // Create the dir otherwise the check for mounted dir fails - _ = utils.MkdirAll(u.config.Fs, persistentPart.MountPoint, constants.DirPerm) + _ = fsutils.MkdirAll(u.config.Fs, persistentPart.MountPoint, constants.DirPerm) if mnt, err := utils.IsMounted(u.config, persistentPart); !mnt && err == nil { u.Debug("mounting persistent partition") umount, err = e.MountRWPartition(persistentPart) @@ -281,7 +283,7 @@ func (u *UpgradeAction) Run() (err error) { // remove attempts to remove the given path. Does nothing if it doesn't exist func (u *UpgradeAction) remove(path string) error { - if exists, _ := utils.Exists(u.config.Fs, path); exists { + if exists, _ := fsutils.Exists(u.config.Fs, path); exists { u.Debug("[Cleanup] Removing %s", path) return u.config.Fs.RemoveAll(path) } diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 4ab8764..4bdc879 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -19,14 +19,14 @@ 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" "github.com/jaypipes/ghw/pkg/block" "github.com/kairos-io/kairos-agent/v2/pkg/action" "github.com/kairos-io/kairos-agent/v2/pkg/constants" - conf "github.com/kairos-io/kairos-agent/v2/pkg/elementalConfig" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" - "github.com/kairos-io/kairos-agent/v2/pkg/utils" v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -36,7 +36,7 @@ import ( ) var _ = Describe("Runtime Actions", func() { - var config *v1.Config + var config *agentConfig.Config var runner *v1mock.FakeRunner var fs vfs.FS var logger v1.Logger @@ -63,16 +63,16 @@ var _ = Describe("Runtime Actions", func() { Expect(err).Should(BeNil()) cloudInit = &v1mock.FakeCloudInitRunner{} - config = conf.NewConfig( - conf.WithFs(fs), - conf.WithRunner(runner), - conf.WithLogger(logger), - conf.WithMounter(mounter), - conf.WithSyscall(syscall), - conf.WithClient(client), - conf.WithCloudInitRunner(cloudInit), - conf.WithImageExtractor(extractor), - conf.WithPlatform("linux/amd64"), + config = agentConfig.NewConfig( + agentConfig.WithFs(fs), + agentConfig.WithRunner(runner), + agentConfig.WithLogger(logger), + agentConfig.WithMounter(mounter), + agentConfig.WithSyscall(syscall), + agentConfig.WithClient(client), + agentConfig.WithCloudInitRunner(cloudInit), + agentConfig.WithImageExtractor(extractor), + agentConfig.WithPlatform("linux/amd64"), ) }) @@ -98,8 +98,8 @@ var _ = Describe("Runtime Actions", func() { logger.SetLevel(logrus.DebugLevel) // Create paths used by tests - utils.MkdirAll(fs, fmt.Sprintf("%s/cOS", constants.RunningStateDir), constants.DirPerm) - utils.MkdirAll(fs, fmt.Sprintf("%s/cOS", constants.LiveDir), constants.DirPerm) + fsutils.MkdirAll(fs, fmt.Sprintf("%s/cOS", constants.RunningStateDir), constants.DirPerm) + fsutils.MkdirAll(fs, fmt.Sprintf("%s/cOS", constants.LiveDir), constants.DirPerm) mainDisk := block.Disk{ Name: "device", @@ -143,10 +143,10 @@ var _ = Describe("Runtime Actions", func() { Describe(fmt.Sprintf("Booting from %s", constants.ActiveLabel), Label("active_label"), func() { var err error BeforeEach(func() { - spec, err = conf.NewUpgradeSpec(config) + spec, err = agentConfig.NewUpgradeSpec(config) Expect(err).ShouldNot(HaveOccurred()) - err = utils.MkdirAll(config.Fs, filepath.Join(spec.Active.MountPoint, "etc"), constants.DirPerm) + err = fsutils.MkdirAll(config.Fs, filepath.Join(spec.Active.MountPoint, "etc"), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) err = fs.WriteFile( @@ -303,7 +303,7 @@ var _ = Describe("Runtime Actions", func() { Expect(err).To(HaveOccurred()) }) It("Successfully upgrades from directory", Label("directory"), func() { - dirSrc, _ := utils.TempDir(fs, "", "elementalupgrade") + dirSrc, _ := fsutils.TempDir(fs, "", "elementalupgrade") // Create the dir on real os as rsync works on the real os defer fs.RemoveAll(dirSrc) spec.Active.Source = v1.NewDirSrc(dirSrc) @@ -346,10 +346,10 @@ var _ = Describe("Runtime Actions", func() { Describe(fmt.Sprintf("Booting from %s", constants.PassiveLabel), Label("passive_label"), func() { var err error BeforeEach(func() { - spec, err = conf.NewUpgradeSpec(config) + spec, err = agentConfig.NewUpgradeSpec(config) Expect(err).ShouldNot(HaveOccurred()) - err = utils.MkdirAll(config.Fs, filepath.Join(spec.Active.MountPoint, "etc"), constants.DirPerm) + err = fsutils.MkdirAll(config.Fs, filepath.Join(spec.Active.MountPoint, "etc"), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) err = fs.WriteFile( @@ -428,7 +428,7 @@ var _ = Describe("Runtime Actions", func() { err = fs.WriteFile(recoveryImgSquash, []byte("recovery"), constants.FilePerm) Expect(err).ShouldNot(HaveOccurred()) - spec, err = conf.NewUpgradeSpec(config) + spec, err = agentConfig.NewUpgradeSpec(config) Expect(err).ShouldNot(HaveOccurred()) spec.Active.Size = 10 spec.Passive.Size = 10 @@ -484,7 +484,7 @@ var _ = Describe("Runtime Actions", func() { }) It("Successfully upgrades recovery from directory", Label("directory"), func() { - srcDir, _ := utils.TempDir(fs, "", "elemental") + srcDir, _ := fsutils.TempDir(fs, "", "elemental") // create a random file on it _ = fs.WriteFile(fmt.Sprintf("%s/file.file", srcDir), []byte("something"), constants.FilePerm) @@ -513,7 +513,7 @@ var _ = Describe("Runtime Actions", func() { err = fs.WriteFile(recoveryImg, []byte("recovery"), constants.FilePerm) Expect(err).ShouldNot(HaveOccurred()) - spec, err = conf.NewUpgradeSpec(config) + spec, err = agentConfig.NewUpgradeSpec(config) Expect(err).ShouldNot(HaveOccurred()) spec.Active.Size = 10 @@ -569,7 +569,7 @@ var _ = Describe("Runtime Actions", func() { } }) It("Successfully upgrades recovery from directory", Label("directory"), func() { - srcDir, _ := utils.TempDir(fs, "", "elemental") + srcDir, _ := fsutils.TempDir(fs, "", "elemental") // create a random file on it _ = fs.WriteFile(fmt.Sprintf("%s/file.file", srcDir), []byte("something"), constants.FilePerm) diff --git a/pkg/cloudinit/cloudinit_test.go b/pkg/cloudinit/cloudinit_test.go index 2e37bfd..44a3156 100644 --- a/pkg/cloudinit/cloudinit_test.go +++ b/pkg/cloudinit/cloudinit_test.go @@ -20,6 +20,7 @@ import ( "bytes" "errors" "fmt" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" "io/ioutil" "log" "os" @@ -29,7 +30,6 @@ import ( . "github.com/kairos-io/kairos-agent/v2/pkg/cloudinit" "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" v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks" "github.com/twpayne/go-vfs/vfst" @@ -110,9 +110,9 @@ stages: logger = v1.NewBufferLogger(logs) afs, cleanup, _ = vfst.NewTestFS(nil) - err := utils.MkdirAll(afs, "/some/yip", constants.DirPerm) + err := fsutils.MkdirAll(afs, "/some/yip", constants.DirPerm) Expect(err).To(BeNil()) - _ = utils.MkdirAll(afs, "/dev", constants.DirPerm) + _ = fsutils.MkdirAll(afs, "/dev", constants.DirPerm) device = "/dev/device" _, err = afs.Create(device) Expect(err).To(BeNil()) diff --git a/pkg/config/config.go b/pkg/config/config.go index f39ad6c..ac95246 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -2,17 +2,24 @@ package config import ( "fmt" + "github.com/spf13/viper" "os" "path/filepath" + "runtime" "strings" "unicode" + "github.com/kairos-io/kairos-agent/v2/pkg/cloudinit" + "github.com/kairos-io/kairos-agent/v2/pkg/constants" + "github.com/kairos-io/kairos-agent/v2/pkg/http" + v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" "github.com/kairos-io/kairos-sdk/bundles" "github.com/kairos-io/kairos-sdk/collector" "github.com/kairos-io/kairos-sdk/schema" yip "github.com/mudler/yip/pkg/schema" - + "github.com/twpayne/go-vfs" "gopkg.in/yaml.v3" + "k8s.io/mount-utils" ) const ( @@ -36,6 +43,60 @@ type Install struct { BindMounts []string `yaml:"bind_mounts,omitempty"` } +func NewConfig(opts ...GenericOptions) *Config { + log := v1.NewLogger() + + hostPlatform, err := v1.NewPlatformFromArch(runtime.GOARCH) + if err != nil { + log.Errorf("error parsing default platform (%s): %s", runtime.GOARCH, err.Error()) + return nil + } + + arch, err := golangArchToArch(runtime.GOARCH) + if err != nil { + log.Errorf("invalid arch: %s", err.Error()) + return nil + } + + c := &Config{ + Fs: vfs.OSFS, + Logger: log, + Syscall: &v1.RealSyscall{}, + Client: http.NewClient(), + Arch: arch, + Platform: hostPlatform, + SquashFsCompressionConfig: constants.GetDefaultSquashfsCompressionOptions(), + ImageExtractor: v1.OCIImageExtractor{}, + } + for _, o := range opts { + o(c) + } + + // delay runner creation after we have run over the options in case we use WithRunner + if c.Runner == nil { + c.Runner = &v1.RealRunner{Logger: c.Logger} + } + + // Now check if the runner has a logger inside, otherwise point our logger into it + // This can happen if we set the WithRunner option as that doesn't set a logger + if c.Runner.GetLogger() == nil { + c.Runner.SetLogger(c.Logger) + } + + // Delay the yip runner creation, so we set the proper logger instead of blindly setting it to the logger we create + // at the start of NewConfig, as WithLogger can be passed on init, and that would result in 2 different logger + // instances, on the config.Logger and the other on config.CloudInitRunner + if c.CloudInitRunner == nil { + c.CloudInitRunner = cloudinit.NewYipCloudInitRunner(c.Logger, c.Runner, vfs.OSFS) + } + + if c.Mounter == nil { + c.Mounter = mount.New(constants.MountBinary) + } + + return c +} + type Config struct { Install *Install `yaml:"install,omitempty"` collector.Config `yaml:"-"` @@ -46,6 +107,148 @@ type Config struct { Bundles Bundles `yaml:"bundles,omitempty"` GrubOptions map[string]string `yaml:"grub_options,omitempty"` Env []string `yaml:"env,omitempty"` + // From elemental + Debug bool `yaml:"debug,omitempty" mapstructure:"debug"` + Strict bool `yaml:"strict,omitempty" mapstructure:"strict"` + CloudInitPaths []string `yaml:"cloud-init-paths,omitempty" mapstructure:"cloud-init-paths"` + EjectCD bool `yaml:"eject-cd,omitempty" mapstructure:"eject-cd"` + Logger v1.Logger + Fs v1.FS + Mounter mount.Interface + Runner v1.Runner + Syscall v1.SyscallInterface + CloudInitRunner v1.CloudInitRunner + ImageExtractor v1.ImageExtractor + Client v1.HTTPClient + Platform *v1.Platform `yaml:"platform,omitempty" mapstructure:"platform"` + Cosign bool `yaml:"cosign,omitempty" mapstructure:"cosign"` + Verify bool `yaml:"verify,omitempty" mapstructure:"verify"` + CosignPubKey string `yaml:"cosign-key,omitempty" mapstructure:"cosign-key"` + 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"` +} + +// WriteInstallState writes the state.yaml file to the given state and recovery paths +func (c Config) WriteInstallState(i *v1.InstallState, statePath, recoveryPath string) error { + data, err := yaml.Marshal(i) + if err != nil { + return err + } + + data = append([]byte("# Autogenerated file by elemental client, do not edit\n\n"), data...) + + err = c.Fs.WriteFile(statePath, data, constants.FilePerm) + if err != nil { + return err + } + + err = c.Fs.WriteFile(recoveryPath, data, constants.FilePerm) + if err != nil { + return err + } + + return nil +} + +// LoadInstallState loads the state.yaml file and unmarshals it to an InstallState object +func (c Config) LoadInstallState() (*v1.InstallState, error) { + installState := &v1.InstallState{} + data, err := c.Fs.ReadFile(filepath.Join(constants.RunningStateDir, constants.InstallStateFile)) + if err != nil { + return nil, err + } + err = yaml.Unmarshal(data, installState) + if err != nil { + return nil, err + } + return installState, nil +} + +// Sanitize checks the consistency of the struct, returns error +// if unsolvable inconsistencies are found +func (c *Config) Sanitize() error { + // If no squashcompression is set, zero the compression parameters + // By default on NewConfig the SquashFsCompressionConfig is set to the default values, and then override + // on config unmarshall. + if c.SquashFsNoCompression { + c.SquashFsCompressionConfig = []string{} + } + if c.Arch != "" { + p, err := v1.NewPlatformFromArch(c.Arch) + if err != nil { + return err + } + c.Platform = p + } + + if c.Platform == nil { + p, err := v1.NewPlatformFromArch(runtime.GOARCH) + if err != nil { + return err + } + c.Platform = p + } + return nil +} + +type GenericOptions func(a *Config) + +func WithFs(fs v1.FS) func(r *Config) { + return func(r *Config) { + r.Fs = fs + } +} + +func WithLogger(logger v1.Logger) func(r *Config) { + return func(r *Config) { + r.Logger = logger + } +} + +func WithSyscall(syscall v1.SyscallInterface) func(r *Config) { + return func(r *Config) { + r.Syscall = syscall + } +} + +func WithMounter(mounter mount.Interface) func(r *Config) { + return func(r *Config) { + r.Mounter = mounter + } +} + +func WithRunner(runner v1.Runner) func(r *Config) { + return func(r *Config) { + r.Runner = runner + } +} + +func WithClient(client v1.HTTPClient) func(r *Config) { + return func(r *Config) { + r.Client = client + } +} + +func WithCloudInitRunner(ci v1.CloudInitRunner) func(r *Config) { + return func(r *Config) { + r.CloudInitRunner = ci + } +} + +func WithPlatform(platform string) func(r *Config) { + return func(r *Config) { + p, err := v1.ParsePlatform(platform) + if err == nil { + r.Platform = p + } + } +} + +func WithImageExtractor(extractor v1.ImageExtractor) func(r *Config) { + return func(r *Config) { + r.ImageExtractor = extractor + } } type Bundles []Bundle @@ -113,7 +316,8 @@ func FilterKeys(d []byte) ([]byte, error) { } func Scan(opts ...collector.Option) (c *Config, err error) { - result := &Config{} + // Init new config with some default options + result := NewConfig() o := &collector.Options{} if err := o.Apply(opts...); err != nil { @@ -157,6 +361,13 @@ func Scan(opts ...collector.Option) (c *Config, err error) { } } + // If we got debug enabled via cloud config, set it on viper so its available everywhere + if result.Debug { + viper.Set("debug", true) + } + // Config the logger + configLogger(result.Logger, result.Fs) + return result, nil } @@ -207,3 +418,16 @@ func MergeYAML(objs ...interface{}) ([]byte, error) { func AddHeader(header, data string) string { return fmt.Sprintf("%s\n%s", header, data) } + +var errInvalidArch = fmt.Errorf("invalid arch") + +func golangArchToArch(arch string) (string, error) { + switch strings.ToLower(arch) { + case constants.ArchAmd64: + return constants.Archx86, nil + case constants.ArchArm64: + return constants.ArchArm64, nil + default: + return "", errInvalidArch + } +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 20884b1..f6a96b6 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -17,36 +17,22 @@ package config_test import ( "fmt" - "os" + "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" + v1mocks "github.com/kairos-io/kairos-agent/v2/tests/mocks" + "github.com/twpayne/go-vfs" + "github.com/twpayne/go-vfs/vfst" + "path/filepath" "reflect" "strings" - . "github.com/kairos-io/kairos-sdk/schema" . "github.com/kairos-io/kairos-agent/v2/pkg/config" + . "github.com/kairos-io/kairos-sdk/schema" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) -type TConfig struct { - Kairos struct { - OtherKey string `yaml:"other_key"` - NetworkToken string `yaml:"network_token"` - } `yaml:"kairos"` -} - -var _ = Describe("Config", func() { - var d string - BeforeEach(func() { - d, _ = os.MkdirTemp("", "xxxx") - }) - - AfterEach(func() { - if d != "" { - os.RemoveAll(d) - } - }) -}) - func getTagName(s string) string { if len(s) < 1 { return "" @@ -59,7 +45,12 @@ func getTagName(s string) string { f := func(c rune) bool { return c == '"' || c == ',' } - return s[:strings.IndexFunc(s, f)] + index := strings.IndexFunc(s, f) + if index == -1 { + return s + } + + return s[:index] } func structContainsField(f, t string, str interface{}) bool { @@ -118,4 +109,91 @@ var _ = Describe("Schema", func() { structFieldsContainedInOtherStruct(Bundle{}, BundleSchema{}) }) }) + Describe("Write and load installation state", func() { + var config *Config + var runner *v1mocks.FakeRunner + var fs vfs.FS + var mounter *v1mocks.ErrorMounter + var cleanup func() + var err error + var dockerState, channelState *v1.ImageState + var installState *v1.InstallState + var statePath, recoveryPath string + + BeforeEach(func() { + runner = v1mocks.NewFakeRunner() + mounter = v1mocks.NewErrorMounter() + fs, cleanup, err = vfst.NewTestFS(map[string]interface{}{}) + Expect(err).Should(BeNil()) + + config = NewConfig( + WithFs(fs), + WithRunner(runner), + WithMounter(mounter), + ) + dockerState = &v1.ImageState{ + Source: v1.NewDockerSrc("registry.org/my/image:tag"), + Label: "active_label", + FS: "ext2", + SourceMetadata: &v1.DockerImageMeta{ + Digest: "adadgadg", + Size: 23452345, + }, + } + installState = &v1.InstallState{ + Date: "somedate", + Partitions: map[string]*v1.PartitionState{ + "state": { + FSLabel: "state_label", + Images: map[string]*v1.ImageState{ + "active": dockerState, + }, + }, + "recovery": { + FSLabel: "state_label", + Images: map[string]*v1.ImageState{ + "recovery": channelState, + }, + }, + }, + } + + statePath = filepath.Join(constants.RunningStateDir, constants.InstallStateFile) + recoveryPath = "/recoverypart/state.yaml" + err = fsutils.MkdirAll(fs, filepath.Dir(recoveryPath), constants.DirPerm) + Expect(err).ShouldNot(HaveOccurred()) + err = fsutils.MkdirAll(fs, filepath.Dir(statePath), constants.DirPerm) + Expect(err).ShouldNot(HaveOccurred()) + }) + AfterEach(func() { + cleanup() + }) + It("Writes and loads an installation data", func() { + err = config.WriteInstallState(installState, statePath, recoveryPath) + Expect(err).ShouldNot(HaveOccurred()) + loadedInstallState, err := config.LoadInstallState() + Expect(err).ShouldNot(HaveOccurred()) + + Expect(*loadedInstallState).To(Equal(*installState)) + }) + It("Fails writing to state partition", func() { + err = fs.RemoveAll(filepath.Dir(statePath)) + Expect(err).ShouldNot(HaveOccurred()) + err = config.WriteInstallState(installState, statePath, recoveryPath) + Expect(err).Should(HaveOccurred()) + }) + It("Fails writing to recovery partition", func() { + err = fs.RemoveAll(filepath.Dir(statePath)) + Expect(err).ShouldNot(HaveOccurred()) + err = config.WriteInstallState(installState, statePath, recoveryPath) + Expect(err).Should(HaveOccurred()) + }) + It("Fails loading state file", func() { + err = config.WriteInstallState(installState, statePath, recoveryPath) + Expect(err).ShouldNot(HaveOccurred()) + err = fs.RemoveAll(filepath.Dir(statePath)) + _, err = config.LoadInstallState() + Expect(err).Should(HaveOccurred()) + }) + }) }) diff --git a/pkg/elementalConfig/config.go b/pkg/config/spec.go similarity index 64% rename from pkg/elementalConfig/config.go rename to pkg/config/spec.go index 80cad61..9c80aa0 100644 --- a/pkg/elementalConfig/config.go +++ b/pkg/config/spec.go @@ -14,156 +14,38 @@ See the License for the specific language governing permissions and limitations under the License. */ -package elementalConfig +package config import ( "fmt" - "gopkg.in/yaml.v3" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/partitions" "path/filepath" "reflect" - "runtime" "strings" "github.com/kairos-io/kairos-agent/v2/internal/common" - "github.com/kairos-io/kairos-agent/v2/pkg/cloudinit" - agentConfig "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/http" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" - "github.com/kairos-io/kairos-agent/v2/pkg/utils" "github.com/mitchellh/mapstructure" "github.com/sanity-io/litter" "github.com/sirupsen/logrus" "github.com/spf13/viper" - "github.com/twpayne/go-vfs" - "k8s.io/mount-utils" ) -type GenericOptions func(a *v1.Config) - -func WithFs(fs v1.FS) func(r *v1.Config) { - return func(r *v1.Config) { - r.Fs = fs - } -} - -func WithLogger(logger v1.Logger) func(r *v1.Config) { - return func(r *v1.Config) { - r.Logger = logger - } -} - -func WithSyscall(syscall v1.SyscallInterface) func(r *v1.Config) { - return func(r *v1.Config) { - r.Syscall = syscall - } -} - -func WithMounter(mounter mount.Interface) func(r *v1.Config) { - return func(r *v1.Config) { - r.Mounter = mounter - } -} - -func WithRunner(runner v1.Runner) func(r *v1.Config) { - return func(r *v1.Config) { - r.Runner = runner - } -} - -func WithClient(client v1.HTTPClient) func(r *v1.Config) { - return func(r *v1.Config) { - r.Client = client - } -} - -func WithCloudInitRunner(ci v1.CloudInitRunner) func(r *v1.Config) { - return func(r *v1.Config) { - r.CloudInitRunner = ci - } -} - -func WithPlatform(platform string) func(r *v1.Config) { - return func(r *v1.Config) { - p, err := v1.ParsePlatform(platform) - if err == nil { - r.Platform = p - } - } -} - -func WithImageExtractor(extractor v1.ImageExtractor) func(r *v1.Config) { - return func(r *v1.Config) { - r.ImageExtractor = extractor - } -} - -func NewConfig(opts ...GenericOptions) *v1.Config { - log := v1.NewLogger() - - defaultPlatform, err := v1.NewPlatformFromArch(runtime.GOARCH) - if err != nil { - log.Errorf("error parsing default platform (%s): %s", runtime.GOARCH, err.Error()) - return nil - } - - arch, err := utils.GolangArchToArch(runtime.GOARCH) - if err != nil { - log.Errorf("invalid arch: %s", err.Error()) - return nil - } - - c := &v1.Config{ - Fs: vfs.OSFS, - Logger: log, - Syscall: &v1.RealSyscall{}, - Client: http.NewClient(), - Arch: arch, - Platform: defaultPlatform, - SquashFsCompressionConfig: constants.GetDefaultSquashfsCompressionOptions(), - } - for _, o := range opts { - o(c) - } - - // delay runner creation after we have run over the options in case we use WithRunner - if c.Runner == nil { - c.Runner = &v1.RealRunner{Logger: c.Logger} - } - - // Now check if the runner has a logger inside, otherwise point our logger into it - // This can happen if we set the WithRunner option as that doesn't set a logger - if c.Runner.GetLogger() == nil { - c.Runner.SetLogger(c.Logger) - } - - // Delay the yip runner creation, so we set the proper logger instead of blindly setting it to the logger we create - // at the start of NewConfig, as WithLogger can be passed on init, and that would result in 2 different logger - // instances, on the config.Logger and the other on config.CloudInitRunner - if c.CloudInitRunner == nil { - c.CloudInitRunner = cloudinit.NewYipCloudInitRunner(c.Logger, c.Runner, vfs.OSFS) - } - - if c.Mounter == nil { - c.Mounter = mount.New(constants.MountBinary) - } - - return c -} - // NewInstallSpec returns an InstallSpec struct all based on defaults and basic host checks (e.g. EFI vs BIOS) -func NewInstallSpec(cfg *v1.Config) *v1.InstallSpec { +func NewInstallSpec(cfg *Config) *v1.InstallSpec { var firmware string var recoveryImg, activeImg, passiveImg v1.Image recoveryImgFile := filepath.Join(constants.LiveDir, constants.RecoverySquashFile) // Check if current host has EFI firmware - efiExists, _ := utils.Exists(cfg.Fs, constants.EfiDevice) + efiExists, _ := fsutils.Exists(cfg.Fs, constants.EfiDevice) // Check the default ISO installation media is available - isoRootExists, _ := utils.Exists(cfg.Fs, constants.IsoBaseTree) + isoRootExists, _ := fsutils.Exists(cfg.Fs, constants.IsoBaseTree) // Check the default ISO recovery installation media is available) - recoveryExists, _ := utils.Exists(cfg.Fs, recoveryImgFile) + recoveryExists, _ := fsutils.Exists(cfg.Fs, recoveryImgFile) if efiExists { firmware = v1.EFI @@ -206,7 +88,7 @@ func NewInstallSpec(cfg *v1.Config) *v1.InstallSpec { return &v1.InstallSpec{ Firmware: firmware, PartTable: v1.GPT, - Partitions: NewInstallElementalParitions(), + Partitions: NewInstallElementalPartitions(), GrubConf: constants.GrubConf, Tty: constants.DefaultTty, Active: activeImg, @@ -215,9 +97,9 @@ func NewInstallSpec(cfg *v1.Config) *v1.InstallSpec { } } -func NewInstallElementalParitions() v1.ElementalPartitions { - partitions := v1.ElementalPartitions{} - partitions.OEM = &v1.Partition{ +func NewInstallElementalPartitions() v1.ElementalPartitions { + pt := v1.ElementalPartitions{} + pt.OEM = &v1.Partition{ FilesystemLabel: constants.OEMLabel, Size: constants.OEMSize, Name: constants.OEMPartName, @@ -226,7 +108,7 @@ func NewInstallElementalParitions() v1.ElementalPartitions { Flags: []string{}, } - partitions.Recovery = &v1.Partition{ + pt.Recovery = &v1.Partition{ FilesystemLabel: constants.RecoveryLabel, Size: constants.RecoverySize, Name: constants.RecoveryPartName, @@ -235,7 +117,7 @@ func NewInstallElementalParitions() v1.ElementalPartitions { Flags: []string{}, } - partitions.State = &v1.Partition{ + pt.State = &v1.Partition{ FilesystemLabel: constants.StateLabel, Size: constants.StateSize, Name: constants.StatePartName, @@ -244,7 +126,7 @@ func NewInstallElementalParitions() v1.ElementalPartitions { Flags: []string{}, } - partitions.Persistent = &v1.Partition{ + pt.Persistent = &v1.Partition{ FilesystemLabel: constants.PersistentLabel, Size: constants.PersistentSize, Name: constants.PersistentPartName, @@ -252,11 +134,11 @@ func NewInstallElementalParitions() v1.ElementalPartitions { MountPoint: constants.PersistentDir, Flags: []string{}, } - return partitions + return pt } // NewUpgradeSpec returns an UpgradeSpec struct all based on defaults and current host state -func NewUpgradeSpec(cfg *v1.Config) (*v1.UpgradeSpec, error) { +func NewUpgradeSpec(cfg *Config) (*v1.UpgradeSpec, error) { var recLabel, recFs, recMnt string var active, passive, recovery v1.Image @@ -265,7 +147,7 @@ func NewUpgradeSpec(cfg *v1.Config) (*v1.UpgradeSpec, error) { cfg.Logger.Warnf("failed reading installation state: %s", err.Error()) } - parts, err := utils.GetAllPartitions() + parts, err := partitions.GetAllPartitions() if err != nil { return nil, fmt.Errorf("could not read host partitions") } @@ -273,17 +155,17 @@ func NewUpgradeSpec(cfg *v1.Config) (*v1.UpgradeSpec, error) { if ep.Recovery == nil { // We could have recovery in lvm which won't appear in ghw list - ep.Recovery = utils.GetPartitionViaDM(cfg.Fs, constants.RecoveryLabel) + ep.Recovery = partitions.GetPartitionViaDM(cfg.Fs, constants.RecoveryLabel) } if ep.OEM == nil { // We could have OEM in lvm which won't appear in ghw list - ep.OEM = utils.GetPartitionViaDM(cfg.Fs, constants.OEMLabel) + ep.OEM = partitions.GetPartitionViaDM(cfg.Fs, constants.OEMLabel) } if ep.Persistent == nil { // We could have persistent encrypted or in lvm which won't appear in ghw list - ep.Persistent = utils.GetPartitionViaDM(cfg.Fs, constants.PersistentLabel) + ep.Persistent = partitions.GetPartitionViaDM(cfg.Fs, constants.PersistentLabel) } if ep.Recovery != nil { @@ -291,7 +173,7 @@ func NewUpgradeSpec(cfg *v1.Config) (*v1.UpgradeSpec, error) { ep.Recovery.MountPoint = constants.RecoveryDir } - squashedRec, err := utils.HasSquashedRecovery(cfg, ep.Recovery) + squashedRec, err := hasSquashedRecovery(cfg, ep.Recovery) if err != nil { return nil, fmt.Errorf("failed checking for squashed recovery") } @@ -361,23 +243,23 @@ func NewUpgradeSpec(cfg *v1.Config) (*v1.UpgradeSpec, error) { } // NewResetSpec returns a ResetSpec struct all based on defaults and current host state -func NewResetSpec(cfg *v1.Config) (*v1.ResetSpec, error) { +func NewResetSpec(cfg *Config) (*v1.ResetSpec, error) { var imgSource *v1.ImageSource //TODO find a way to pre-load current state values such as labels - if !utils.BootedFrom(cfg.Runner, constants.RecoverySquashFile) && - !utils.BootedFrom(cfg.Runner, constants.SystemLabel) { + if !BootedFrom(cfg.Runner, constants.RecoverySquashFile) && + !BootedFrom(cfg.Runner, constants.SystemLabel) { return nil, fmt.Errorf("reset can only be called from the recovery system") } - efiExists, _ := utils.Exists(cfg.Fs, constants.EfiDevice) + efiExists, _ := fsutils.Exists(cfg.Fs, constants.EfiDevice) installState, err := cfg.LoadInstallState() if err != nil { cfg.Logger.Warnf("failed reading installation state: %s", err.Error()) } - parts, err := utils.GetAllPartitions() + parts, err := partitions.GetAllPartitions() if err != nil { return nil, fmt.Errorf("could not read host partitions") } @@ -403,7 +285,7 @@ func NewResetSpec(cfg *v1.Config) (*v1.ResetSpec, error) { if ep.Recovery == nil { // We could have recovery in lvm which won't appear in ghw list - ep.Recovery = utils.GetPartitionViaDM(cfg.Fs, constants.RecoveryLabel) + ep.Recovery = partitions.GetPartitionViaDM(cfg.Fs, constants.RecoveryLabel) if ep.Recovery == nil { return nil, fmt.Errorf("recovery partition not found") } @@ -422,7 +304,7 @@ func NewResetSpec(cfg *v1.Config) (*v1.ResetSpec, error) { ep.OEM.Name = constants.OEMPartName } else { // We could have oem in lvm which won't appear in ghw list - ep.OEM = utils.GetPartitionViaDM(cfg.Fs, constants.OEMLabel) + ep.OEM = partitions.GetPartitionViaDM(cfg.Fs, constants.OEMLabel) } if ep.OEM == nil { @@ -437,7 +319,7 @@ func NewResetSpec(cfg *v1.Config) (*v1.ResetSpec, error) { ep.Persistent.Name = constants.PersistentPartName } else { // We could have persistent encrypted or in lvm which won't appear in ghw list - ep.Persistent = utils.GetPartitionViaDM(cfg.Fs, constants.PersistentLabel) + ep.Persistent = partitions.GetPartitionViaDM(cfg.Fs, constants.PersistentLabel) } if ep.Persistent == nil { cfg.Logger.Warnf("no Persistent partition found") @@ -446,11 +328,11 @@ func NewResetSpec(cfg *v1.Config) (*v1.ResetSpec, error) { recoveryImg := filepath.Join(constants.RunningStateDir, "cOS", constants.RecoveryImgFile) recoveryImg2 := filepath.Join(constants.RunningRecoveryStateDir, "cOS", constants.RecoveryImgFile) - if exists, _ := utils.Exists(cfg.Fs, recoveryImg); exists { + if exists, _ := fsutils.Exists(cfg.Fs, recoveryImg); exists { imgSource = v1.NewFileSrc(recoveryImg) - } else if exists, _ = utils.Exists(cfg.Fs, recoveryImg2); exists { + } else if exists, _ = fsutils.Exists(cfg.Fs, recoveryImg2); exists { imgSource = v1.NewFileSrc(recoveryImg2) - } else if exists, _ = utils.Exists(cfg.Fs, constants.IsoBaseTree); exists { + } else if exists, _ = fsutils.Exists(cfg.Fs, constants.IsoBaseTree); exists { imgSource = v1.NewDirSrc(constants.IsoBaseTree) } else { imgSource = v1.NewEmptySrc() @@ -484,35 +366,38 @@ func NewResetSpec(cfg *v1.Config) (*v1.ResetSpec, error) { }, nil } -// ReadConfigRunFromAgentConfig reads the configuration directly from a given cloud config string -func ReadConfigRunFromAgentConfig(c *agentConfig.Config) (*v1.Config, error) { - cfg := NewConfig(WithLogger(v1.NewLogger()), WithImageExtractor(v1.OCIImageExtractor{})) - var err error - - ccString, err := c.Config.String() +// ReadResetSpecFromConfig will return a proper v1.ResetSpec based on an agent Config +func ReadResetSpecFromConfig(c *Config) (*v1.ResetSpec, error) { + sp, err := ReadSpecFromCloudConfig(c, "reset") if err != nil { - return nil, err + return &v1.ResetSpec{}, err } + resetSpec := sp.(*v1.ResetSpec) + return resetSpec, nil +} - // Load any cloud-config values that override our default Config - err = yaml.Unmarshal([]byte(ccString), &cfg) +// ReadInstallSpecFromConfig will return a proper v1.InstallSpec based on an agent Config +func ReadInstallSpecFromConfig(c *Config) (*v1.InstallSpec, error) { + sp, err := ReadSpecFromCloudConfig(c, "install") if err != nil { - return nil, err + return &v1.InstallSpec{}, err } - // If we got debug enabled via cloud config, set it on viper so its available everywhere - if cfg.Debug { - viper.Set("debug", true) + installSpec := sp.(*v1.InstallSpec) + return installSpec, nil +} + +// ReadUpgradeSpecFromConfig will return a proper v1.UpgradeSpec based on an agent Config +func ReadUpgradeSpecFromConfig(c *Config) (*v1.UpgradeSpec, error) { + sp, err := ReadSpecFromCloudConfig(c, "reset") + if err != nil { + return &v1.UpgradeSpec{}, err } - configLogger(cfg.Logger, cfg.Fs) - // Store the full cloud-config in here, so we can reuse it afterward for the spec - cfg.FullCloudConfig = ccString - err = cfg.Sanitize() - cfg.Logger.Debugf("Full config loaded: %s", litter.Sdump(cfg)) - return cfg, err + upgradeSpec := sp.(*v1.UpgradeSpec) + return upgradeSpec, nil } // ReadSpecFromCloudConfig returns a v1.Spec for the given spec -func ReadSpecFromCloudConfig(r *v1.Config, spec string) (v1.Spec, error) { +func ReadSpecFromCloudConfig(r *Config, spec string) (v1.Spec, error) { var sp v1.Spec var err error @@ -531,8 +416,12 @@ func ReadSpecFromCloudConfig(r *v1.Config, spec string) (v1.Spec, error) { } // 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(r.FullCloudConfig)) + viper.ReadConfig(strings.NewReader(ccString)) vp := viper.Sub(spec) if vp == nil { vp = viper.New() @@ -546,47 +435,6 @@ func ReadSpecFromCloudConfig(r *v1.Config, spec string) (v1.Spec, error) { return sp, err } -// readConfigAndSpecFromAgentConfig will return the config and spec for the given action based off the agent Config -func readConfigAndSpecFromAgentConfig(c *agentConfig.Config, action string) (*v1.Config, v1.Spec, error) { - config, err := ReadConfigRunFromAgentConfig(c) - if err != nil { - return nil, nil, err - } - spec, err := ReadSpecFromCloudConfig(config, action) - if err != nil { - return nil, nil, err - } - return config, spec, nil -} - -// ReadResetConfigFromAgentConfig will return a proper v1.Config and v1.ResetSpec based on an agent Config -func ReadResetConfigFromAgentConfig(c *agentConfig.Config) (*v1.Config, *v1.ResetSpec, error) { - config, spec, err := readConfigAndSpecFromAgentConfig(c, "reset") - if err != nil { - return nil, nil, err - } - resetSpec := spec.(*v1.ResetSpec) - return config, resetSpec, nil -} - -func ReadInstallConfigFromAgentConfig(c *agentConfig.Config) (*v1.Config, *v1.InstallSpec, error) { - config, spec, err := readConfigAndSpecFromAgentConfig(c, "install") - if err != nil { - return nil, nil, err - } - installSpec := spec.(*v1.InstallSpec) - return config, installSpec, nil -} - -func ReadUpgradeConfigFromAgentConfig(c *agentConfig.Config) (*v1.Config, *v1.UpgradeSpec, error) { - config, spec, err := readConfigAndSpecFromAgentConfig(c, "upgrade") - if err != nil { - return nil, nil, err - } - upgradeSpec := spec.(*v1.UpgradeSpec) - return config, upgradeSpec, nil -} - func configLogger(log v1.Logger, vfs v1.FS) { // Set debug level if viper.GetBool("debug") { @@ -676,3 +524,52 @@ func setDecoder(config *mapstructure.DecoderConfig) { // we got form configs. config.ZeroFields = true } + +// BootedFrom will check if we are booting from the given label +func BootedFrom(runner v1.Runner, label string) bool { + out, _ := runner.Run("cat", "/proc/cmdline") + return strings.Contains(string(out), label) +} + +// HasSquashedRecovery returns true if a squashed recovery image is found in the system +func hasSquashedRecovery(config *Config, recovery *v1.Partition) (squashed bool, err error) { + mountPoint := recovery.MountPoint + if mnt, _ := isMounted(config, recovery); !mnt { + tmpMountDir, err := fsutils.TempDir(config.Fs, "", "elemental") + if err != nil { + config.Logger.Errorf("failed creating temporary dir: %v", err) + return false, err + } + defer config.Fs.RemoveAll(tmpMountDir) // nolint:errcheck + err = config.Mounter.Mount(recovery.Path, tmpMountDir, "auto", []string{}) + if err != nil { + config.Logger.Errorf("failed mounting recovery partition: %v", err) + return false, err + } + mountPoint = tmpMountDir + defer func() { + err = config.Mounter.Unmount(tmpMountDir) + if err != nil { + squashed = false + } + }() + } + return fsutils.Exists(config.Fs, filepath.Join(mountPoint, "cOS", constants.RecoverySquashFile)) +} + +func isMounted(config *Config, part *v1.Partition) (bool, error) { + if part == nil { + return false, fmt.Errorf("nil partition") + } + + if part.MountPoint == "" { + return false, nil + } + // Using IsLikelyNotMountPoint seams to be safe as we are not checking + // for bind mounts here + notMnt, err := config.Mounter.IsLikelyNotMountPoint(part.MountPoint) + if err != nil { + return false, err + } + return !notMnt, nil +} diff --git a/pkg/elementalConfig/config_test.go b/pkg/config/spec_test.go similarity index 80% rename from pkg/elementalConfig/config_test.go rename to pkg/config/spec_test.go index 0d52fea..259a6ee 100644 --- a/pkg/elementalConfig/config_test.go +++ b/pkg/config/spec_test.go @@ -14,11 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package elementalConfig_test +package config_test import ( - "github.com/kairos-io/kairos-agent/v2/pkg/config" + "fmt" + config "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" + "github.com/sanity-io/litter" "github.com/sirupsen/logrus" "k8s.io/mount-utils" "os" @@ -26,9 +29,7 @@ import ( "github.com/jaypipes/ghw/pkg/block" "github.com/kairos-io/kairos-agent/v2/pkg/constants" - "github.com/kairos-io/kairos-agent/v2/pkg/elementalConfig" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" - "github.com/kairos-io/kairos-agent/v2/pkg/utils" v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -46,7 +47,7 @@ var _ = Describe("Types", Label("types", "config"), func() { var sysc *v1mock.FakeSyscall var logger v1.Logger var ci *v1mock.FakeCloudInitRunner - var c *v1.Config + var c *config.Config BeforeEach(func() { fs, cleanup, err = vfst.NewTestFS(nil) Expect(err).ToNot(HaveOccurred()) @@ -56,15 +57,15 @@ var _ = Describe("Types", Label("types", "config"), func() { sysc = &v1mock.FakeSyscall{} logger = v1.NewNullLogger() ci = &v1mock.FakeCloudInitRunner{} - c = elementalConfig.NewConfig( - elementalConfig.WithFs(fs), - elementalConfig.WithMounter(mounter), - elementalConfig.WithRunner(runner), - elementalConfig.WithSyscall(sysc), - elementalConfig.WithLogger(logger), - elementalConfig.WithCloudInitRunner(ci), - elementalConfig.WithClient(client), - elementalConfig.WithPlatform("linux/arm64"), + c = config.NewConfig( + config.WithFs(fs), + config.WithMounter(mounter), + config.WithRunner(runner), + config.WithSyscall(sysc), + config.WithLogger(logger), + config.WithCloudInitRunner(ci), + config.WithClient(client), + config.WithPlatform("linux/arm64"), ) }) AfterEach(func() { @@ -87,24 +88,24 @@ var _ = Describe("Types", Label("types", "config"), func() { fs, cleanup, err := vfst.NewTestFS(nil) defer cleanup() Expect(err).ToNot(HaveOccurred()) - c := elementalConfig.NewConfig( - elementalConfig.WithFs(fs), - elementalConfig.WithMounter(mounter), + c := config.NewConfig( + config.WithFs(fs), + config.WithMounter(mounter), ) Expect(c.Fs).To(Equal(fs)) Expect(c.Mounter).To(Equal(mounter)) Expect(c.Runner).ToNot(BeNil()) }) It("defaults to sane platform if the platform is broken", func() { - c = elementalConfig.NewConfig( - elementalConfig.WithFs(fs), - elementalConfig.WithMounter(mounter), - elementalConfig.WithRunner(runner), - elementalConfig.WithSyscall(sysc), - elementalConfig.WithLogger(logger), - elementalConfig.WithCloudInitRunner(ci), - elementalConfig.WithClient(client), - elementalConfig.WithPlatform("wwwwwww"), + c = config.NewConfig( + config.WithFs(fs), + config.WithMounter(mounter), + config.WithRunner(runner), + config.WithSyscall(sysc), + config.WithLogger(logger), + config.WithCloudInitRunner(ci), + config.WithClient(client), + config.WithPlatform("wwwwwww"), ) Expect(c.Platform.OS).To(Equal("linux")) Expect(c.Platform.Arch).To(Equal("x86_64")) @@ -116,41 +117,41 @@ var _ = Describe("Types", Label("types", "config"), func() { runner := v1mock.NewFakeRunner() sysc := &v1mock.FakeSyscall{} logger := v1.NewNullLogger() - c := elementalConfig.NewConfig( - elementalConfig.WithRunner(runner), - elementalConfig.WithSyscall(sysc), - elementalConfig.WithLogger(logger), + c := config.NewConfig( + config.WithRunner(runner), + config.WithSyscall(sysc), + config.WithLogger(logger), ) Expect(c.Mounter).To(Equal(mount.New(constants.MountBinary))) }) }) Describe("Config", func() { - cfg := elementalConfig.NewConfig(elementalConfig.WithMounter(mounter)) + cfg := config.NewConfig(config.WithMounter(mounter)) Expect(cfg.Mounter).To(Equal(mounter)) Expect(cfg.Runner).NotTo(BeNil()) }) Describe("InstallSpec", func() { It("sets installation defaults from install efi media with recovery", Label("install", "efi"), func() { // Set EFI firmware detection - err = utils.MkdirAll(fs, filepath.Dir(constants.EfiDevice), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(constants.EfiDevice), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create(constants.EfiDevice) Expect(err).ShouldNot(HaveOccurred()) // Set ISO base tree detection - err = utils.MkdirAll(fs, filepath.Dir(constants.IsoBaseTree), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(constants.IsoBaseTree), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create(constants.IsoBaseTree) Expect(err).ShouldNot(HaveOccurred()) // Set recovery image detection detection recoveryImgFile := filepath.Join(constants.LiveDir, constants.RecoverySquashFile) - err = utils.MkdirAll(fs, filepath.Dir(recoveryImgFile), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(recoveryImgFile), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create(recoveryImgFile) Expect(err).ShouldNot(HaveOccurred()) - spec := elementalConfig.NewInstallSpec(c) + spec := config.NewInstallSpec(c) Expect(spec.Firmware).To(Equal(v1.EFI)) Expect(spec.Active.Source.Value()).To(Equal(constants.IsoBaseTree)) Expect(spec.Recovery.Source.Value()).To(Equal(recoveryImgFile)) @@ -166,12 +167,12 @@ var _ = Describe("Types", Label("types", "config"), func() { }) It("sets installation defaults from install bios media without recovery", Label("install", "bios"), func() { // Set ISO base tree detection - err = utils.MkdirAll(fs, filepath.Dir(constants.IsoBaseTree), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(constants.IsoBaseTree), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create(constants.IsoBaseTree) Expect(err).ShouldNot(HaveOccurred()) - spec := elementalConfig.NewInstallSpec(c) + spec := config.NewInstallSpec(c) 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)) @@ -186,7 +187,7 @@ 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 := elementalConfig.NewInstallSpec(c) + spec := config.NewInstallSpec(c) Expect(spec.Firmware).To(Equal(v1.BIOS)) Expect(spec.Active.Source.IsEmpty()).To(BeTrue()) Expect(spec.Recovery.Source.Value()).To(Equal(spec.Active.File)) @@ -245,18 +246,18 @@ var _ = Describe("Types", Label("types", "config"), func() { }) It("sets reset defaults on efi from squashed recovery", func() { // Set EFI firmware detection - err = utils.MkdirAll(fs, filepath.Dir(constants.EfiDevice), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(constants.EfiDevice), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create(constants.EfiDevice) Expect(err).ShouldNot(HaveOccurred()) // Set squashfs detection - err = utils.MkdirAll(fs, filepath.Dir(constants.IsoBaseTree), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(constants.IsoBaseTree), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create(constants.IsoBaseTree) Expect(err).ShouldNot(HaveOccurred()) - spec, err := elementalConfig.NewResetSpec(c) + spec, err := config.NewResetSpec(c) Expect(err).ShouldNot(HaveOccurred()) Expect(spec.Active.Source.Value()).To(Equal(constants.IsoBaseTree)) Expect(spec.Partitions.EFI.MountPoint).To(Equal(constants.EfiDir)) @@ -264,17 +265,17 @@ var _ = Describe("Types", Label("types", "config"), func() { It("sets reset defaults on bios from non-squashed recovery", func() { // Set non-squashfs recovery image detection recoveryImg := filepath.Join(constants.RunningStateDir, "cOS", constants.RecoveryImgFile) - err = utils.MkdirAll(fs, filepath.Dir(recoveryImg), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(recoveryImg), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create(recoveryImg) Expect(err).ShouldNot(HaveOccurred()) - spec, err := elementalConfig.NewResetSpec(c) + spec, err := config.NewResetSpec(c) Expect(err).ShouldNot(HaveOccurred()) Expect(spec.Active.Source.Value()).To(Equal(recoveryImg)) }) It("sets reset defaults on bios from unknown recovery", func() { - spec, err := elementalConfig.NewResetSpec(c) + spec, err := config.NewResetSpec(c) Expect(err).ShouldNot(HaveOccurred()) Expect(spec.Active.Source.IsEmpty()).To(BeTrue()) }) @@ -312,13 +313,13 @@ var _ = Describe("Types", Label("types", "config"), func() { ghwTest.Clean() }) It("fails to set defaults if not booted from recovery", func() { - _, err := elementalConfig.NewResetSpec(c) + _, err := config.NewResetSpec(c) Expect(err).Should(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("reset can only be called from the recovery system")) }) It("fails to set defaults if no recovery partition detected", func() { bootedFrom = constants.SystemLabel - _, err := elementalConfig.NewResetSpec(c) + _, err := config.NewResetSpec(c) Expect(err).Should(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("recovery partition not found")) }) @@ -333,19 +334,19 @@ var _ = Describe("Types", Label("types", "config"), func() { defer ghwTest.Clean() bootedFrom = constants.SystemLabel - _, err := elementalConfig.NewResetSpec(c) + _, err := config.NewResetSpec(c) Expect(err).Should(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("state partition not found")) }) It("fails to set defaults if no efi partition on efi firmware", func() { // Set EFI firmware detection - err = utils.MkdirAll(fs, filepath.Dir(constants.EfiDevice), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(constants.EfiDevice), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create(constants.EfiDevice) Expect(err).ShouldNot(HaveOccurred()) bootedFrom = constants.SystemLabel - _, err := elementalConfig.NewResetSpec(c) + _, err := config.NewResetSpec(c) Expect(err).Should(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("EFI partition not found")) }) @@ -394,12 +395,12 @@ var _ = Describe("Types", Label("types", "config"), func() { ghwTest.Clean() }) It("sets upgrade defaults for active upgrade", func() { - spec, err := elementalConfig.NewUpgradeSpec(c) + spec, err := config.NewUpgradeSpec(c) Expect(err).ShouldNot(HaveOccurred()) Expect(spec.Active.Source.IsEmpty()).To(BeTrue()) }) It("sets upgrade defaults for non-squashed recovery upgrade", func() { - spec, err := elementalConfig.NewUpgradeSpec(c) + spec, err := config.NewUpgradeSpec(c) Expect(err).ShouldNot(HaveOccurred()) Expect(spec.Recovery.Source.IsEmpty()).To(BeTrue()) Expect(spec.Recovery.FS).To(Equal(constants.LinuxImgFs)) @@ -408,12 +409,12 @@ var _ = Describe("Types", Label("types", "config"), func() { //Set squashed recovery detection mounter.Mount("device3", constants.LiveDir, "auto", []string{}) img := filepath.Join(constants.LiveDir, "cOS", constants.RecoverySquashFile) - err = utils.MkdirAll(fs, filepath.Dir(img), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(img), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create(img) Expect(err).ShouldNot(HaveOccurred()) - spec, err := elementalConfig.NewUpgradeSpec(c) + spec, err := config.NewUpgradeSpec(c) Expect(err).ShouldNot(HaveOccurred()) Expect(spec.Recovery.Source.IsEmpty()).To(BeTrue()) Expect(spec.Recovery.FS).To(Equal(constants.SquashFs)) @@ -501,25 +502,25 @@ cloud-init-paths: It("Reads properly the cloud config for install", func() { cfg, err := config.Scan(collector.Directories([]string{dir}...), collector.NoLogs) Expect(err).ToNot(HaveOccurred()) - installConfig, installSpec, err := elementalConfig.ReadInstallConfigFromAgentConfig(cfg) + fmt.Print(litter.Sdump(cfg)) + installSpec, err := config.ReadInstallSpecFromConfig(cfg) Expect(err).ToNot(HaveOccurred()) - Expect(installConfig.Strict).To(BeTrue()) + Expect(cfg.Strict).To(BeTrue()) Expect(installSpec.GrubDefEntry).To(Equal("MyCustomOS")) Expect(installSpec.Active.Size).To(Equal(uint(666))) - Expect(installConfig.CloudInitPaths).To(ContainElement("/what")) + Expect(cfg.CloudInitPaths).To(ContainElement("/what")) }) It("Reads properly the cloud config for reset", func() { bootedFrom = constants.SystemLabel cfg, err := config.Scan(collector.Directories([]string{dir}...), collector.NoLogs) - config, err := elementalConfig.ReadConfigRunFromAgentConfig(cfg) Expect(err).ToNot(HaveOccurred()) // Override the config with our test params - config.Runner = runner - config.Fs = fs - config.Mounter = mounter - config.CloudInitRunner = ci - spec, err := elementalConfig.ReadSpecFromCloudConfig(config, "reset") + cfg.Runner = runner + cfg.Fs = fs + cfg.Mounter = mounter + cfg.CloudInitRunner = ci + spec, err := config.ReadSpecFromCloudConfig(cfg, "reset") Expect(err).ToNot(HaveOccurred()) resetSpec := spec.(*v1.ResetSpec) Expect(resetSpec.FormatPersistent).To(BeTrue()) @@ -528,32 +529,28 @@ cloud-init-paths: }) It("Reads properly the cloud config for upgrade", func() { cfg, err := config.Scan(collector.Directories([]string{dir}...), collector.NoLogs) - config, err := elementalConfig.ReadConfigRunFromAgentConfig(cfg) Expect(err).ToNot(HaveOccurred()) // Override the config with our test params - config.Runner = runner - config.Fs = fs - config.Mounter = mounter - config.CloudInitRunner = ci - spec, err := elementalConfig.ReadSpecFromCloudConfig(config, "upgrade") + cfg.Runner = runner + cfg.Fs = fs + cfg.Mounter = mounter + cfg.CloudInitRunner = ci + spec, err := config.ReadSpecFromCloudConfig(cfg, "upgrade") Expect(err).ToNot(HaveOccurred()) upgradeSpec := spec.(*v1.UpgradeSpec) Expect(upgradeSpec.RecoveryUpgrade).To(BeTrue()) }) It("Fails when a wrong action is read", func() { cfg, err := config.Scan(collector.Directories([]string{dir}...), collector.NoLogs) - config, err := elementalConfig.ReadConfigRunFromAgentConfig(cfg) Expect(err).ToNot(HaveOccurred()) - _, err = elementalConfig.ReadSpecFromCloudConfig(config, "nope") + _, err = config.ReadSpecFromCloudConfig(cfg, "nope") Expect(err).To(HaveOccurred()) }) It("Sets info level if its not on the cloud-config", func() { // Now again but with no config cfg, err := config.Scan(collector.Directories([]string{""}...), collector.NoLogs) Expect(err).ToNot(HaveOccurred()) - installConfig, _, err := elementalConfig.ReadInstallConfigFromAgentConfig(cfg) - Expect(err).ToNot(HaveOccurred()) - Expect(installConfig.Logger.GetLevel()).To(Equal(logrus.InfoLevel)) + Expect(cfg.Logger.GetLevel()).To(Equal(logrus.InfoLevel)) }) It("Sets debug level if its on the cloud-config", func() { ccdata := []byte(`#cloud-config @@ -563,11 +560,19 @@ debug: true Expect(err).ToNot(HaveOccurred()) cfg, err := config.Scan(collector.Directories([]string{dir}...), collector.NoLogs) Expect(err).ToNot(HaveOccurred()) - installConfig, _, err := elementalConfig.ReadInstallConfigFromAgentConfig(cfg) - Expect(err).ToNot(HaveOccurred()) - Expect(installConfig.Logger.GetLevel()).To(Equal(logrus.DebugLevel)) + Expect(cfg.Logger.GetLevel()).To(Equal(logrus.DebugLevel)) }) }) + Describe("TestBootedFrom", Label("BootedFrom"), func() { + It("returns true if we are booting from label FAKELABEL", func() { + runner.ReturnValue = []byte("") + Expect(config.BootedFrom(runner, "FAKELABEL")).To(BeFalse()) + }) + It("returns false if we are not booting from label FAKELABEL", func() { + runner.ReturnValue = []byte("FAKELABEL") + Expect(config.BootedFrom(runner, "FAKELABEL")).To(BeTrue()) + }) + }) }) }) diff --git a/pkg/elemental/elemental.go b/pkg/elemental/elemental.go index 5598415..0997fab 100644 --- a/pkg/elemental/elemental.go +++ b/pkg/elemental/elemental.go @@ -19,21 +19,23 @@ package elemental import ( "errors" "fmt" + v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" "path/filepath" "strings" + agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config" cnst "github.com/kairos-io/kairos-agent/v2/pkg/constants" "github.com/kairos-io/kairos-agent/v2/pkg/partitioner" - v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" "github.com/kairos-io/kairos-agent/v2/pkg/utils" ) // Elemental is the struct meant to self-contain most utils and actions related to Elemental, like installing or applying selinux type Elemental struct { - config *v1.Config + config *agentConfig.Config } -func NewElemental(config *v1.Config) *Elemental { +func NewElemental(config *agentConfig.Config) *Elemental { return &Elemental{ config: config, } @@ -177,7 +179,7 @@ func (e Elemental) MountRWPartition(part *v1.Partition) (umount func() error, er // MountPartition mounts a partition with the given mount options func (e Elemental) MountPartition(part *v1.Partition, opts ...string) error { e.config.Logger.Debugf("Mounting partition %s", part.FilesystemLabel) - err := utils.MkdirAll(e.config.Fs, part.MountPoint, cnst.DirPerm) + err := fsutils.MkdirAll(e.config.Fs, part.MountPoint, cnst.DirPerm) if err != nil { return err } @@ -211,7 +213,7 @@ func (e Elemental) UnmountPartition(part *v1.Partition) error { // MountImage mounts an image with the given mount options func (e Elemental) MountImage(img *v1.Image, opts ...string) error { e.config.Logger.Debugf("Mounting image %s", img.Label) - err := utils.MkdirAll(e.config.Fs, img.MountPoint, cnst.DirPerm) + err := fsutils.MkdirAll(e.config.Fs, img.MountPoint, cnst.DirPerm) if err != nil { return err } @@ -251,7 +253,7 @@ func (e Elemental) UnmountImage(img *v1.Image) error { // CreateFileSystemImage creates the image file for config.target func (e Elemental) CreateFileSystemImage(img *v1.Image) error { e.config.Logger.Infof("Creating file system image %s", img.File) - err := utils.MkdirAll(e.config.Fs, filepath.Dir(img.File), cnst.DirPerm) + err := fsutils.MkdirAll(e.config.Fs, filepath.Dir(img.File), cnst.DirPerm) if err != nil { return err } @@ -298,7 +300,7 @@ func (e *Elemental) DeployImage(img *v1.Image, leaveMounted bool) (info interfac } } else { target = utils.GetTempDir(e.config, "") - err := utils.MkdirAll(e.config.Fs, target, cnst.DirPerm) + err := fsutils.MkdirAll(e.config.Fs, target, cnst.DirPerm) if err != nil { return nil, err } @@ -374,7 +376,7 @@ func (e *Elemental) DumpSource(target string, imgSrc *v1.ImageSource) (info inte return nil, err } } else if imgSrc.IsFile() { - err := utils.MkdirAll(e.config.Fs, filepath.Dir(target), cnst.DirPerm) + err := fsutils.MkdirAll(e.config.Fs, filepath.Dir(target), cnst.DirPerm) if err != nil { return nil, err } @@ -412,7 +414,7 @@ func (e *Elemental) CopyCloudConfig(cloudInit []string) (err error) { func (e *Elemental) SelinuxRelabel(rootDir string, raiseError bool) error { policyFile, err := utils.FindFileWithPrefix(e.config.Fs, filepath.Join(rootDir, cnst.SELinuxTargetedPolicyPath), "policy.") contextFile := filepath.Join(rootDir, cnst.SELinuxTargetedContextFile) - contextExists, _ := utils.Exists(e.config.Fs, contextFile) + contextExists, _ := fsutils.Exists(e.config.Fs, contextFile) if err == nil && contextExists && utils.CommandExists("setfiles") { var out []byte @@ -452,7 +454,7 @@ func (e *Elemental) CheckActiveDeployment(labels []string) bool { // in cnst.DownloadedIsoMnt func (e *Elemental) GetIso(iso string) (tmpDir string, err error) { //TODO support ISO download in persistent storage? - tmpDir, err = utils.TempDir(e.config.Fs, "", "elemental") + tmpDir, err = fsutils.TempDir(e.config.Fs, "", "elemental") if err != nil { return "", err } @@ -470,7 +472,7 @@ func (e *Elemental) GetIso(iso string) (tmpDir string, err error) { if err != nil { return "", err } - err = utils.MkdirAll(e.config.Fs, isoMnt, cnst.DirPerm) + err = fsutils.MkdirAll(e.config.Fs, isoMnt, cnst.DirPerm) if err != nil { return "", err } @@ -486,7 +488,7 @@ func (e *Elemental) GetIso(iso string) (tmpDir string, err error) { }() e.config.Logger.Infof("Mounting squashfs image from iso into %s", rootfsMnt) - err = utils.MkdirAll(e.config.Fs, rootfsMnt, cnst.DirPerm) + err = fsutils.MkdirAll(e.config.Fs, rootfsMnt, cnst.DirPerm) if err != nil { return "", err } @@ -505,7 +507,7 @@ func (e Elemental) UpdateSourcesFormDownloadedISO(workDir string, activeImg *v1. } if recoveryImg != nil { squashedImgSource := filepath.Join(isoMnt, cnst.RecoverySquashFile) - if exists, _ := utils.Exists(e.config.Fs, squashedImgSource); exists { + if exists, _ := fsutils.Exists(e.config.Fs, squashedImgSource); exists { recoveryImg.Source = v1.NewFileSrc(squashedImgSource) recoveryImg.FS = cnst.SquashFs } else if activeImg != nil { diff --git a/pkg/elemental/elemental_test.go b/pkg/elemental/elemental_test.go index e8f3831..c2c9bf7 100644 --- a/pkg/elemental/elemental_test.go +++ b/pkg/elemental/elemental_test.go @@ -19,6 +19,8 @@ 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" @@ -28,7 +30,6 @@ import ( "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" - conf "github.com/kairos-io/kairos-agent/v2/pkg/elementalConfig" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" "github.com/kairos-io/kairos-agent/v2/pkg/utils" v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks" @@ -49,7 +50,7 @@ func TestElementalSuite(t *testing.T) { } var _ = Describe("Elemental", Label("elemental"), func() { - var config *v1.Config + var config *agentConfig.Config var runner *v1mock.FakeRunner var logger v1.Logger var syscall v1.SyscallInterface @@ -67,14 +68,14 @@ var _ = Describe("Elemental", Label("elemental"), func() { logger = v1.NewNullLogger() fs, cleanup, _ = vfst.NewTestFS(nil) extractor = v1mock.NewFakeImageExtractor(logger) - config = conf.NewConfig( - conf.WithFs(fs), - conf.WithRunner(runner), - conf.WithLogger(logger), - conf.WithMounter(mounter), - conf.WithSyscall(syscall), - conf.WithClient(client), - conf.WithImageExtractor(extractor), + config = agentConfig.NewConfig( + agentConfig.WithFs(fs), + agentConfig.WithRunner(runner), + agentConfig.WithLogger(logger), + agentConfig.WithMounter(mounter), + agentConfig.WithSyscall(syscall), + agentConfig.WithClient(client), + agentConfig.WithImageExtractor(extractor), ) }) AfterEach(func() { cleanup() }) @@ -82,9 +83,9 @@ var _ = Describe("Elemental", Label("elemental"), func() { var el *elemental.Elemental var parts v1.ElementalPartitions BeforeEach(func() { - parts = conf.NewInstallElementalParitions() + parts = agentConfig.NewInstallElementalPartitions() - err := utils.MkdirAll(fs, "/some", cnst.DirPerm) + err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm) Expect(err).ToNot(HaveOccurred()) _, err = fs.Create("/some/device") Expect(err).ToNot(HaveOccurred()) @@ -146,9 +147,9 @@ var _ = Describe("Elemental", Label("elemental"), func() { var el *elemental.Elemental var parts v1.ElementalPartitions BeforeEach(func() { - parts = conf.NewInstallElementalParitions() + parts = agentConfig.NewInstallElementalPartitions() - err := utils.MkdirAll(fs, "/some", cnst.DirPerm) + err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm) Expect(err).ToNot(HaveOccurred()) _, err = fs.Create("/some/device") Expect(err).ToNot(HaveOccurred()) @@ -194,9 +195,9 @@ var _ = Describe("Elemental", Label("elemental"), func() { var el *elemental.Elemental var parts v1.ElementalPartitions BeforeEach(func() { - parts = conf.NewInstallElementalParitions() + parts = agentConfig.NewInstallElementalPartitions() - err := utils.MkdirAll(fs, "/some", cnst.DirPerm) + err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm) Expect(err).ToNot(HaveOccurred()) _, err = fs.Create("/some/device") Expect(err).ToNot(HaveOccurred()) @@ -292,7 +293,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { MountPoint: cnst.ActiveDir, Source: v1.NewDirSrc(cnst.IsoBaseTree), } - _ = utils.MkdirAll(fs, cnst.IsoBaseTree, cnst.DirPerm) + _ = fsutils.MkdirAll(fs, cnst.IsoBaseTree, cnst.DirPerm) el = elemental.NewElemental(config) }) @@ -341,10 +342,10 @@ var _ = Describe("Elemental", Label("elemental"), func() { cInit = &v1mock.FakeCloudInitRunner{ExecStages: []string{}, Error: false} config.CloudInitRunner = cInit el = elemental.NewElemental(config) - install = conf.NewInstallSpec(config) + install = agentConfig.NewInstallSpec(config) install.Target = "/some/device" - err := utils.MkdirAll(fs, "/some", cnst.DirPerm) + err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm) Expect(err).ToNot(HaveOccurred()) _, err = fs.Create("/some/device") Expect(err).ToNot(HaveOccurred()) @@ -355,7 +356,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { var efiPartCmds, partCmds, biosPartCmds [][]string BeforeEach(func() { partNum, printOut = 0, printOutput - err := utils.MkdirAll(fs, "/some", cnst.DirPerm) + err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm) Expect(err).To(BeNil()) efiPartCmds = [][]string{ { @@ -435,7 +436,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { Describe("Run with failures", func() { var runFunc func(cmd string, args ...string) ([]byte, error) BeforeEach(func() { - err := utils.MkdirAll(fs, "/some", cnst.DirPerm) + err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm) Expect(err).To(BeNil()) partNum, printOut = 0, printOutput runFunc = func(cmd string, args ...string) ([]byte, error) { @@ -486,9 +487,9 @@ var _ = Describe("Elemental", Label("elemental"), func() { var img *v1.Image var cmdFail string BeforeEach(func() { - sourceDir, err := utils.TempDir(fs, "", "elemental") + sourceDir, err := fsutils.TempDir(fs, "", "elemental") Expect(err).ShouldNot(HaveOccurred()) - destDir, err := utils.TempDir(fs, "", "elemental") + destDir, err := fsutils.TempDir(fs, "", "elemental") Expect(err).ShouldNot(HaveOccurred()) cmdFail = "" el = elemental.NewElemental(config) @@ -532,7 +533,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { sourceImg := "/source.img" _, err := fs.Create(sourceImg) Expect(err).To(BeNil()) - destDir, err := utils.TempDir(fs, "", "elemental") + destDir, err := fsutils.TempDir(fs, "", "elemental") Expect(err).To(BeNil()) img.Source = v1.NewFileSrc(sourceImg) img.MountPoint = destDir @@ -542,7 +543,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { sourceImg := "/source.img" _, err := fs.Create(sourceImg) Expect(err).To(BeNil()) - destDir, err := utils.TempDir(fs, "", "elemental") + destDir, err := fsutils.TempDir(fs, "", "elemental") Expect(err).To(BeNil()) img.Source = v1.NewFileSrc(sourceImg) img.MountPoint = destDir @@ -554,7 +555,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { sourceImg := "/source.img" _, err := fs.Create(sourceImg) Expect(err).To(BeNil()) - destDir, err := utils.TempDir(fs, "", "elemental") + destDir, err := fsutils.TempDir(fs, "", "elemental") Expect(err).To(BeNil()) img.Source = v1.NewFileSrc(sourceImg) img.MountPoint = destDir @@ -596,7 +597,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { BeforeEach(func() { var err error e = elemental.NewElemental(config) - destDir, err = utils.TempDir(fs, "", "elemental") + destDir, err = fsutils.TempDir(fs, "", "elemental") Expect(err).ShouldNot(HaveOccurred()) }) It("Copies files from a directory source", func() { @@ -682,7 +683,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { var relabelCmd []string BeforeEach(func() { // to mock the existance of setfiles command on non selinux hosts - err := utils.MkdirAll(fs, "/usr/sbin", constants.DirPerm) + err := fsutils.MkdirAll(fs, "/usr/sbin", constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) sbin, err := fs.RawPath("/usr/sbin") Expect(err).ShouldNot(HaveOccurred()) @@ -696,11 +697,11 @@ var _ = Describe("Elemental", Label("elemental"), func() { // to mock SELinux policy files policyFile = filepath.Join(constants.SELinuxTargetedPolicyPath, "policy.31") - err = utils.MkdirAll(fs, filepath.Dir(constants.SELinuxTargetedContextFile), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(constants.SELinuxTargetedContextFile), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create(constants.SELinuxTargetedContextFile) Expect(err).ShouldNot(HaveOccurred()) - err = utils.MkdirAll(fs, constants.SELinuxTargetedPolicyPath, constants.DirPerm) + err = fsutils.MkdirAll(fs, constants.SELinuxTargetedPolicyPath, constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create(policyFile) Expect(err).ShouldNot(HaveOccurred()) @@ -749,12 +750,12 @@ var _ = Describe("Elemental", Label("elemental"), func() { }) It("relabels the given root-tree path", func() { contextFile := filepath.Join("/root", constants.SELinuxTargetedContextFile) - err := utils.MkdirAll(fs, filepath.Dir(contextFile), constants.DirPerm) + err := fsutils.MkdirAll(fs, filepath.Dir(contextFile), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create(contextFile) Expect(err).ShouldNot(HaveOccurred()) policyFile = filepath.Join("/root", policyFile) - err = utils.MkdirAll(fs, filepath.Join("/root", constants.SELinuxTargetedPolicyPath), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Join("/root", constants.SELinuxTargetedPolicyPath), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create(policyFile) Expect(err).ShouldNot(HaveOccurred()) @@ -774,7 +775,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { e = elemental.NewElemental(config) }) It("Gets the iso and returns the temporary where it is stored", func() { - tmpDir, err := utils.TempDir(fs, "", "elemental-test") + tmpDir, err := fsutils.TempDir(fs, "", "elemental-test") Expect(err).To(BeNil()) err = fs.WriteFile(fmt.Sprintf("%s/fake.iso", tmpDir), []byte("Hi"), cnst.FilePerm) Expect(err).To(BeNil()) @@ -782,7 +783,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { isoDir, err := e.GetIso(iso) Expect(err).To(BeNil()) // Confirm that the iso is stored in isoDir - utils.Exists(fs, filepath.Join(isoDir, "cOs.iso")) + fsutils.Exists(fs, filepath.Join(isoDir, "cOs.iso")) }) It("Fails if it cant find the iso", func() { iso := "whatever" @@ -792,7 +793,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { }) It("Fails if it cannot mount the iso", func() { mounter.ErrorOnMount = true - tmpDir, err := utils.TempDir(fs, "", "elemental-test") + tmpDir, err := fsutils.TempDir(fs, "", "elemental-test") Expect(err).To(BeNil()) err = fs.WriteFile(fmt.Sprintf("%s/fake.iso", tmpDir), []byte("Hi"), cnst.FilePerm) Expect(err).To(BeNil()) @@ -831,7 +832,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { It("updates recovery only image", func() { recoveryImg = &v1.Image{} isoMnt := "/some/dir/iso" - err := utils.MkdirAll(fs, isoMnt, cnst.DirPerm) + err := fsutils.MkdirAll(fs, isoMnt, cnst.DirPerm) Expect(err).ShouldNot(HaveOccurred()) recoverySquash := filepath.Join(isoMnt, cnst.RecoverySquashFile) _, err = fs.Create(recoverySquash) @@ -883,7 +884,7 @@ 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 := utils.MkdirAll(config.Fs, "/imgMountPoint/etc", constants.DirPerm) + err := fsutils.MkdirAll(config.Fs, "/imgMountPoint/etc", constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) err = config.Fs.WriteFile("/imgMountPoint/etc/os-release", []byte("GRUB_ENTRY_NAME=test"), constants.FilePerm) Expect(err).ShouldNot(HaveOccurred()) @@ -904,7 +905,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { }) Describe("FindKernelInitrd", Label("find"), func() { BeforeEach(func() { - err := utils.MkdirAll(fs, "/path/boot", constants.DirPerm) + err := fsutils.MkdirAll(fs, "/path/boot", constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) }) It("finds kernel and initrd files", func() { diff --git a/pkg/elementalConfig/config_suite_test.go b/pkg/elementalConfig/config_suite_test.go deleted file mode 100644 index ad5194e..0000000 --- a/pkg/elementalConfig/config_suite_test.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright © 2022 SUSE LLC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package elementalConfig_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestTypes(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "config initializer test suite") -} diff --git a/pkg/partitioner/disk.go b/pkg/partitioner/disk.go index 74619f1..6f9ae3b 100644 --- a/pkg/partitioner/disk.go +++ b/pkg/partitioner/disk.go @@ -19,13 +19,14 @@ package partitioner import ( "errors" "fmt" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/partitions" "os" "regexp" "strings" "time" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" - "github.com/kairos-io/kairos-agent/v2/pkg/utils" "github.com/twpayne/go-vfs" ) @@ -327,7 +328,7 @@ func (dev Disk) FindPartitionDevice(partNum int) (string, error) { for tries := 0; tries <= partitionTries; tries++ { dev.logger.Debugf("Trying to find the partition device %d of device %s (try number %d)", partNum, dev, tries+1) _, _ = dev.runner.Run("udevadm", "settle") - if exists, _ := utils.Exists(dev.fs, device); exists { + if exists, _ := fsutils.Exists(dev.fs, device); exists { return device, nil } time.Sleep(1 * time.Second) @@ -401,7 +402,7 @@ func (dev Disk) expandFilesystem(device string) (string, error) { var out []byte var err error - fs, err := utils.GetPartitionFS(device) + fs, err := partitions.GetPartitionFS(device) if err != nil { return fs, err } @@ -419,7 +420,7 @@ func (dev Disk) expandFilesystem(device string) (string, error) { } case "xfs": // to grow an xfs fs it needs to be mounted :/ - tmpDir, err := utils.TempDir(dev.fs, "", "partitioner") + tmpDir, err := fsutils.TempDir(dev.fs, "", "partitioner") defer func(fs v1.FS, path string) { _ = fs.RemoveAll(path) }(dev.fs, tmpDir) diff --git a/pkg/partitioner/partitioner_test.go b/pkg/partitioner/partitioner_test.go index 176fc13..62f91eb 100644 --- a/pkg/partitioner/partitioner_test.go +++ b/pkg/partitioner/partitioner_test.go @@ -18,13 +18,13 @@ package partitioner_test import ( "errors" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" "testing" "github.com/jaypipes/ghw/pkg/block" "github.com/kairos-io/kairos-agent/v2/pkg/constants" part "github.com/kairos-io/kairos-agent/v2/pkg/partitioner" - "github.com/kairos-io/kairos-agent/v2/pkg/utils" mocks "github.com/kairos-io/kairos-agent/v2/tests/mocks" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -211,7 +211,7 @@ var _ = Describe("Partitioner", Label("disk", "partition", "partitioner"), func( BeforeEach(func() { fs, cleanup, _ = vfst.NewTestFS(nil) - err := utils.MkdirAll(fs, "/dev", constants.DirPerm) + err := fsutils.MkdirAll(fs, "/dev", constants.DirPerm) Expect(err).To(BeNil()) _, err = fs.Create("/dev/device") Expect(err).To(BeNil()) diff --git a/pkg/types/v1/config.go b/pkg/types/v1/config.go index ac31128..31a5705 100644 --- a/pkg/types/v1/config.go +++ b/pkg/types/v1/config.go @@ -19,12 +19,10 @@ package v1 import ( "fmt" "path/filepath" - "runtime" "sort" "github.com/kairos-io/kairos-agent/v2/pkg/constants" "gopkg.in/yaml.v3" - "k8s.io/mount-utils" ) const ( @@ -43,94 +41,6 @@ type Spec interface { ShouldShutdown() bool } -// Config is the struct that includes basic and generic configuration of elemental binary runtime. -// It mostly includes the interfaces used around many methods in elemental code -type Config struct { - Debug bool `yaml:"debug,omitempty" mapstructure:"debug"` - Strict bool `yaml:"strict,omitempty" mapstructure:"strict"` - CloudInitPaths []string `yaml:"cloud-init-paths,omitempty" mapstructure:"cloud-init-paths"` - EjectCD bool `yaml:"eject-cd,omitempty" mapstructure:"eject-cd"` - FullCloudConfig string // Stores the full cloud config used to generate the spec afterwards - Logger Logger - Fs FS - Mounter mount.Interface - Runner Runner - Syscall SyscallInterface - CloudInitRunner CloudInitRunner - ImageExtractor ImageExtractor - Client HTTPClient - Platform *Platform `yaml:"platform,omitempty" mapstructure:"platform"` - Cosign bool `yaml:"cosign,omitempty" mapstructure:"cosign"` - Verify bool `yaml:"verify,omitempty" mapstructure:"verify"` - CosignPubKey string `yaml:"cosign-key,omitempty" mapstructure:"cosign-key"` - 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"` -} - -// WriteInstallState writes the state.yaml file to the given state and recovery paths -func (c Config) WriteInstallState(i *InstallState, statePath, recoveryPath string) error { - data, err := yaml.Marshal(i) - if err != nil { - return err - } - - data = append([]byte("# Autogenerated file by elemental client, do not edit\n\n"), data...) - - err = c.Fs.WriteFile(statePath, data, constants.FilePerm) - if err != nil { - return err - } - - err = c.Fs.WriteFile(recoveryPath, data, constants.FilePerm) - if err != nil { - return err - } - - return nil -} - -// LoadInstallState loads the state.yaml file and unmarshals it to an InstallState object -func (c Config) LoadInstallState() (*InstallState, error) { - installState := &InstallState{} - data, err := c.Fs.ReadFile(filepath.Join(constants.RunningStateDir, constants.InstallStateFile)) - if err != nil { - return nil, err - } - err = yaml.Unmarshal(data, installState) - if err != nil { - return nil, err - } - return installState, nil -} - -// Sanitize checks the consistency of the struct, returns error -// if unsolvable inconsistencies are found -func (c *Config) Sanitize() error { - // If no squashcompression is set, zero the compression parameters - // By default on NewConfig the SquashFsCompressionConfig is set to the default values, and then override - // on config unmarshall. - if c.SquashFsNoCompression { - c.SquashFsCompressionConfig = []string{} - } - if c.Arch != "" { - p, err := NewPlatformFromArch(c.Arch) - if err != nil { - return err - } - c.Platform = p - } - - if c.Platform == nil { - p, err := NewPlatformFromArch(runtime.GOARCH) - if err != nil { - return err - } - c.Platform = p - } - return nil -} - // InstallSpec struct represents all the installation action details type InstallSpec struct { Target string `yaml:"device,omitempty" mapstructure:"device"` diff --git a/pkg/types/v1/config_test.go b/pkg/types/v1/config_test.go index 1e8e6b9..f5350fc 100644 --- a/pkg/types/v1/config_test.go +++ b/pkg/types/v1/config_test.go @@ -17,108 +17,13 @@ limitations under the License. package v1_test import ( - "path/filepath" - - "github.com/kairos-io/kairos-agent/v2/pkg/constants" - "github.com/kairos-io/kairos-agent/v2/pkg/elementalConfig" - conf "github.com/kairos-io/kairos-agent/v2/pkg/elementalConfig" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" - "github.com/kairos-io/kairos-agent/v2/pkg/utils" - v1mocks "github.com/kairos-io/kairos-agent/v2/tests/mocks" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/twpayne/go-vfs" - "github.com/twpayne/go-vfs/vfst" ) var _ = Describe("Types", Label("types", "config"), func() { - Describe("Write and load installation state", func() { - var config *v1.Config - var runner *v1mocks.FakeRunner - var fs vfs.FS - var mounter *v1mocks.ErrorMounter - var cleanup func() - var err error - var dockerState, channelState *v1.ImageState - var installState *v1.InstallState - var statePath, recoveryPath string - BeforeEach(func() { - runner = v1mocks.NewFakeRunner() - mounter = v1mocks.NewErrorMounter() - fs, cleanup, err = vfst.NewTestFS(map[string]interface{}{}) - Expect(err).Should(BeNil()) - - config = conf.NewConfig( - conf.WithFs(fs), - conf.WithRunner(runner), - conf.WithMounter(mounter), - ) - dockerState = &v1.ImageState{ - Source: v1.NewDockerSrc("registry.org/my/image:tag"), - Label: "active_label", - FS: "ext2", - SourceMetadata: &v1.DockerImageMeta{ - Digest: "adadgadg", - Size: 23452345, - }, - } - installState = &v1.InstallState{ - Date: "somedate", - Partitions: map[string]*v1.PartitionState{ - "state": { - FSLabel: "state_label", - Images: map[string]*v1.ImageState{ - "active": dockerState, - }, - }, - "recovery": { - FSLabel: "state_label", - Images: map[string]*v1.ImageState{ - "recovery": channelState, - }, - }, - }, - } - - statePath = filepath.Join(constants.RunningStateDir, constants.InstallStateFile) - recoveryPath = "/recoverypart/state.yaml" - err = utils.MkdirAll(fs, filepath.Dir(recoveryPath), constants.DirPerm) - Expect(err).ShouldNot(HaveOccurred()) - err = utils.MkdirAll(fs, filepath.Dir(statePath), constants.DirPerm) - Expect(err).ShouldNot(HaveOccurred()) - }) - AfterEach(func() { - cleanup() - }) - It("Writes and loads an installation data", func() { - err = config.WriteInstallState(installState, statePath, recoveryPath) - Expect(err).ShouldNot(HaveOccurred()) - loadedInstallState, err := config.LoadInstallState() - Expect(err).ShouldNot(HaveOccurred()) - - Expect(*loadedInstallState).To(Equal(*installState)) - }) - It("Fails writing to state partition", func() { - err = fs.RemoveAll(filepath.Dir(statePath)) - Expect(err).ShouldNot(HaveOccurred()) - err = config.WriteInstallState(installState, statePath, recoveryPath) - Expect(err).Should(HaveOccurred()) - }) - It("Fails writing to recovery partition", func() { - err = fs.RemoveAll(filepath.Dir(statePath)) - Expect(err).ShouldNot(HaveOccurred()) - err = config.WriteInstallState(installState, statePath, recoveryPath) - Expect(err).Should(HaveOccurred()) - }) - It("Fails loading state file", func() { - err = config.WriteInstallState(installState, statePath, recoveryPath) - Expect(err).ShouldNot(HaveOccurred()) - err = fs.RemoveAll(filepath.Dir(statePath)) - _, err = config.LoadInstallState() - Expect(err).Should(HaveOccurred()) - }) - }) Describe("ElementalPartitions", func() { var p v1.PartitionList var ep v1.ElementalPartitions @@ -319,151 +224,4 @@ var _ = Describe("Types", Label("types", "config"), func() { Expect(p.GetByName("nonexistent")).To(BeNil()) }) }) - Describe("Config", func() { - It("runs sanitize method", func() { - cfg := elementalConfig.NewConfig(elementalConfig.WithMounter(v1mocks.NewErrorMounter())) - cfg.Verify = true - - err := cfg.Sanitize() - Expect(err).ShouldNot(HaveOccurred()) - }) - }) - Describe("InstallSpec", func() { - var spec *v1.InstallSpec - - BeforeEach(func() { - cfg := elementalConfig.NewConfig(elementalConfig.WithMounter(v1mocks.NewErrorMounter())) - spec = elementalConfig.NewInstallSpec(cfg) - }) - Describe("sanitize", func() { - It("runs method", func() { - Expect(spec.Partitions.EFI).To(BeNil()) - Expect(spec.Active.Source.IsEmpty()).To(BeTrue()) - - // Creates firmware partitions - spec.Active.Source = v1.NewDirSrc("/dir") - spec.Firmware = v1.EFI - err := spec.Sanitize() - Expect(err).ShouldNot(HaveOccurred()) - Expect(spec.Partitions.EFI).NotTo(BeNil()) - - // Sets recovery image file to squashfs file - spec.Recovery.FS = constants.SquashFs - err = spec.Sanitize() - Expect(err).ShouldNot(HaveOccurred()) - Expect(spec.Recovery.File).To(ContainSubstring(constants.RecoverySquashFile)) - - // Sets recovery image file to img file - spec.Recovery.FS = constants.LinuxImgFs - err = spec.Sanitize() - Expect(err).ShouldNot(HaveOccurred()) - Expect(spec.Recovery.File).To(ContainSubstring(constants.RecoveryImgFile)) - - // Fails without state partition - spec.Partitions.State = nil - err = spec.Sanitize() - Expect(err).Should(HaveOccurred()) - - // Fails without an install source - spec.Active.Source = v1.NewEmptySrc() - err = spec.Sanitize() - Expect(err).Should(HaveOccurred()) - }) - Describe("with extra partitions", func() { - BeforeEach(func() { - // Set a source for the install - spec.Active.Source = v1.NewDirSrc("/dir") - }) - It("fails if persistent and an extra partition have size == 0", func() { - spec.ExtraPartitions = append(spec.ExtraPartitions, &v1.Partition{Size: 0}) - err := spec.Sanitize() - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("both persistent partition and extra partitions have size set to 0")) - }) - It("fails if more than one extra partition has size == 0", func() { - spec.Partitions.Persistent.Size = 10 - spec.ExtraPartitions = append(spec.ExtraPartitions, &v1.Partition{Name: "1", Size: 0}) - spec.ExtraPartitions = append(spec.ExtraPartitions, &v1.Partition{Name: "2", Size: 0}) - err := spec.Sanitize() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("more than one extra partition has its size set to 0")) - }) - It("does not fail if persistent size is > 0 and an extra partition has size == 0", func() { - spec.ExtraPartitions = append(spec.ExtraPartitions, &v1.Partition{Size: 0}) - spec.Partitions.Persistent.Size = 10 - err := spec.Sanitize() - Expect(err).ToNot(HaveOccurred()) - }) - }) - }) - }) - Describe("ResetSpec", func() { - It("runs sanitize method", func() { - spec := &v1.ResetSpec{ - Active: v1.Image{ - Source: v1.NewDirSrc("/dir"), - }, - Partitions: v1.ElementalPartitions{ - State: &v1.Partition{ - MountPoint: "mountpoint", - }, - }, - } - err := spec.Sanitize() - Expect(err).ShouldNot(HaveOccurred()) - - //Fails on missing state partition - spec.Partitions.State = nil - err = spec.Sanitize() - Expect(err).Should(HaveOccurred()) - - //Fails on empty source - spec.Active.Source = v1.NewEmptySrc() - err = spec.Sanitize() - Expect(err).Should(HaveOccurred()) - }) - }) - Describe("UpgradeSpec", func() { - It("runs sanitize method", func() { - spec := &v1.UpgradeSpec{ - Active: v1.Image{ - Source: v1.NewDirSrc("/dir"), - }, - Recovery: v1.Image{ - Source: v1.NewDirSrc("/dir"), - }, - Partitions: v1.ElementalPartitions{ - State: &v1.Partition{ - MountPoint: "mountpoint", - }, - Recovery: &v1.Partition{ - MountPoint: "mountpoint", - }, - }, - } - err := spec.Sanitize() - Expect(err).ShouldNot(HaveOccurred()) - - //Fails on empty source for active upgrade - spec.Active.Source = v1.NewEmptySrc() - err = spec.Sanitize() - Expect(err).Should(HaveOccurred()) - - //Fails on missing state partition for active upgrade - spec.Partitions.State = nil - err = spec.Sanitize() - Expect(err).Should(HaveOccurred()) - - //Fails on empty source for recovery upgrade - spec.RecoveryUpgrade = true - spec.Recovery.Source = v1.NewEmptySrc() - err = spec.Sanitize() - Expect(err).Should(HaveOccurred()) - - //Fails on missing recovery partition for recovery upgrade - spec.Partitions.Recovery = nil - err = spec.Sanitize() - Expect(err).Should(HaveOccurred()) - }) - }) }) diff --git a/pkg/utils/chroot.go b/pkg/utils/chroot.go index 84170c5..022cbeb 100644 --- a/pkg/utils/chroot.go +++ b/pkg/utils/chroot.go @@ -19,12 +19,13 @@ package utils import ( "errors" "fmt" + agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" "os" "sort" "strings" "github.com/kairos-io/kairos-agent/v2/pkg/constants" - v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" ) // Chroot represents the struct that will allow us to run commands inside a given chroot @@ -33,10 +34,10 @@ type Chroot struct { defaultMounts []string extraMounts map[string]string activeMounts []string - config *v1.Config + config *agentConfig.Config } -func NewChroot(path string, config *v1.Config) *Chroot { +func NewChroot(path string, config *agentConfig.Config) *Chroot { return &Chroot{ path: path, defaultMounts: []string{"/dev", "/dev/pts", "/proc", "/sys"}, @@ -47,7 +48,7 @@ func NewChroot(path string, config *v1.Config) *Chroot { } // ChrootedCallback runs the given callback in a chroot environment -func ChrootedCallback(cfg *v1.Config, path string, bindMounts map[string]string, callback func() error) error { +func ChrootedCallback(cfg *agentConfig.Config, path string, bindMounts map[string]string, callback func() error) error { chroot := NewChroot(path, cfg) chroot.SetExtraMounts(bindMounts) return chroot.RunCallback(callback) @@ -78,7 +79,7 @@ func (c *Chroot) Prepare() error { for _, mnt := range c.defaultMounts { mountPoint := fmt.Sprintf("%s%s", strings.TrimSuffix(c.path, "/"), mnt) - err = MkdirAll(c.config.Fs, mountPoint, constants.DirPerm) + err = fsutils.MkdirAll(c.config.Fs, mountPoint, constants.DirPerm) if err != nil { return err } @@ -95,7 +96,7 @@ func (c *Chroot) Prepare() error { sort.Strings(keys) for _, k := range keys { mountPoint := fmt.Sprintf("%s%s", strings.TrimSuffix(c.path, "/"), c.extraMounts[k]) - err = MkdirAll(c.config.Fs, mountPoint, constants.DirPerm) + err = fsutils.MkdirAll(c.config.Fs, mountPoint, constants.DirPerm) if err != nil { return err } diff --git a/pkg/utils/common.go b/pkg/utils/common.go index e5349a3..8108079 100644 --- a/pkg/utils/common.go +++ b/pkg/utils/common.go @@ -20,6 +20,9 @@ import ( "crypto/sha256" "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-agent/v2/pkg/utils/partitions" "io" random "math/rand" "net/url" @@ -42,12 +45,6 @@ func CommandExists(command string) bool { return err == nil } -// BootedFrom will check if we are booting from the given label -func BootedFrom(runner v1.Runner, label string) bool { - out, _ := runner.Run("cat", "/proc/cmdline") - return strings.Contains(string(out), label) -} - // GetDeviceByLabel will try to return the device that matches the given label. // attempts value sets the number of attempts to find the device, it // waits a second between attempts. @@ -64,7 +61,7 @@ func GetDeviceByLabel(runner v1.Runner, label string, attempts int) (string, err func GetFullDeviceByLabel(runner v1.Runner, label string, attempts int) (*v1.Partition, error) { for tries := 0; tries < attempts; tries++ { _, _ = runner.Run("udevadm", "settle") - parts, err := GetAllPartitions() + parts, err := partitions.GetAllPartitions() if err != nil { return nil, err } @@ -91,7 +88,7 @@ func ConcatFiles(fs v1.FS, sources []string, target string) (err error) { if len(sources) == 0 { return fmt.Errorf("Empty sources list") } - if dir, _ := IsDir(fs, target); dir { + if dir, _ := fsutils.IsDir(fs, target); dir { target = filepath.Join(target, filepath.Base(sources[0])) } @@ -129,18 +126,18 @@ func ConcatFiles(fs v1.FS, sources []string, target string) (err error) { // Copies source file to target file using Fs interface func CreateDirStructure(fs v1.FS, target string) error { for _, dir := range []string{"/run", "/dev", "/boot", "/usr/local", "/oem"} { - err := MkdirAll(fs, filepath.Join(target, dir), cnst.DirPerm) + err := fsutils.MkdirAll(fs, filepath.Join(target, dir), cnst.DirPerm) if err != nil { return err } } for _, dir := range []string{"/proc", "/sys"} { - err := MkdirAll(fs, filepath.Join(target, dir), cnst.NoWriteDirPerm) + err := fsutils.MkdirAll(fs, filepath.Join(target, dir), cnst.NoWriteDirPerm) if err != nil { return err } } - err := MkdirAll(fs, filepath.Join(target, "/tmp"), cnst.DirPerm) + err := fsutils.MkdirAll(fs, filepath.Join(target, "/tmp"), cnst.DirPerm) if err != nil { return err } @@ -245,7 +242,7 @@ func CosignVerify(fs v1.FS, runner v1.Runner, image string, publicKey string, de args = append(args, image) // Give each cosign its own tuf dir so it doesnt collide with others accessing the same files at the same time - tmpDir, err := TempDir(fs, "", "cosign-tuf-") + tmpDir, err := fsutils.TempDir(fs, "", "cosign-tuf-") if err != nil { return "", err } @@ -301,7 +298,7 @@ func LoadEnvFile(fs v1.FS, file string) (map[string]string, error) { return envMap, err } -func IsMounted(config *v1.Config, part *v1.Partition) (bool, error) { +func IsMounted(config *agentConfig.Config, part *v1.Partition) (bool, error) { if part == nil { return false, fmt.Errorf("nil partition") } @@ -318,37 +315,11 @@ func IsMounted(config *v1.Config, part *v1.Partition) (bool, error) { return !notMnt, nil } -// HasSquashedRecovery returns true if a squashed recovery image is found in the system -func HasSquashedRecovery(config *v1.Config, recovery *v1.Partition) (squashed bool, err error) { - mountPoint := recovery.MountPoint - if mnt, _ := IsMounted(config, recovery); !mnt { - tmpMountDir, err := TempDir(config.Fs, "", "elemental") - if err != nil { - config.Logger.Errorf("failed creating temporary dir: %v", err) - return false, err - } - defer config.Fs.RemoveAll(tmpMountDir) // nolint:errcheck - err = config.Mounter.Mount(recovery.Path, tmpMountDir, "auto", []string{}) - if err != nil { - config.Logger.Errorf("failed mounting recovery partition: %v", err) - return false, err - } - mountPoint = tmpMountDir - defer func() { - err = config.Mounter.Unmount(tmpMountDir) - if err != nil { - squashed = false - } - }() - } - return Exists(config.Fs, filepath.Join(mountPoint, "cOS", cnst.RecoverySquashFile)) -} - // GetTempDir returns the dir for storing related temporal files // It will respect TMPDIR and use that if exists, fallback to try the persistent partition if its mounted // and finally the default /tmp/ dir // suffix is what is appended to the dir name elemental-suffix. If empty it will randomly generate a number -func GetTempDir(config *v1.Config, suffix string) string { +func GetTempDir(config *agentConfig.Config, suffix string) string { // if we got a TMPDIR var, respect and use that if suffix == "" { random.Seed(time.Now().UnixNano()) @@ -360,7 +331,7 @@ func GetTempDir(config *v1.Config, suffix string) string { config.Logger.Debugf("Got tmpdir from TMPDIR var: %s", dir) return filepath.Join(dir, elementalTmpDir) } - parts, err := GetAllPartitions() + parts, err := partitions.GetAllPartitions() if err != nil { config.Logger.Debug("Could not get partitions, defaulting to /tmp") return filepath.Join("/", "tmp", elementalTmpDir) @@ -414,7 +385,7 @@ func IsHTTPURI(uri string) (bool, error) { // GetSource copies given source to destination, if source is a local path it simply // copies files, if source is a remote URL it tries to download URL to destination. -func GetSource(config *v1.Config, source string, destination string) error { +func GetSource(config *agentConfig.Config, source string, destination string) error { local, err := IsLocalURI(source) if err != nil { config.Logger.Errorf("Not a valid url: %s", source) @@ -484,7 +455,7 @@ func FindFileWithPrefix(fs v1.FS, path string, prefixes ...string) (string, erro if !filepath.IsAbs(found) { found = filepath.Join(path, found) } - if exists, _ := Exists(fs, found); exists { + if exists, _ := fsutils.Exists(fs, found); exists { return found, nil } } @@ -497,19 +468,6 @@ func FindFileWithPrefix(fs v1.FS, path string, prefixes ...string) (string, erro return "", fmt.Errorf("No file found with prefixes: %v", prefixes) } -var errInvalidArch = fmt.Errorf("invalid arch") - -func GolangArchToArch(arch string) (string, error) { - switch strings.ToLower(arch) { - case cnst.ArchAmd64: - return cnst.Archx86, nil - case cnst.ArchArm64: - return cnst.ArchArm64, nil - default: - return "", errInvalidArch - } -} - // CalcFileChecksum opens the given file and returns the sha256 checksum of it. func CalcFileChecksum(fs v1.FS, fileName string) (string, error) { f, err := fs.Open(fileName) diff --git a/pkg/utils/fs.go b/pkg/utils/fs/fs.go similarity index 99% rename from pkg/utils/fs.go rename to pkg/utils/fs/fs.go index 052e705..644f07c 100644 --- a/pkg/utils/fs.go +++ b/pkg/utils/fs/fs.go @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package utils +package fsutils import ( "io/fs" diff --git a/pkg/utils/grub.go b/pkg/utils/grub.go index 80a83f9..b35aac5 100644 --- a/pkg/utils/grub.go +++ b/pkg/utils/grub.go @@ -18,21 +18,22 @@ package utils import ( "fmt" + agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" "io/fs" "path/filepath" "strings" "github.com/kairos-io/kairos-agent/v2/pkg/constants" cnst "github.com/kairos-io/kairos-agent/v2/pkg/constants" - v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" ) // Grub is the struct that will allow us to install grub to the target device type Grub struct { - config *v1.Config + config *agentConfig.Config } -func NewGrub(config *v1.Config) *Grub { +func NewGrub(config *agentConfig.Config) *Grub { g := &Grub{ config: config, } @@ -71,7 +72,7 @@ func (g Grub) Install(target, rootDir, bootDir, grubConf, tty string, efi bool, // Select the proper dir for grub - this assumes that we previously run a grub install command, which is not the case in EFI // In the EFI case we default to grub2 - if ok, _ := IsDir(g.config.Fs, filepath.Join(bootDir, "grub")); ok { + if ok, _ := fsutils.IsDir(g.config.Fs, filepath.Join(bootDir, "grub")); ok { systemgrub = "grub" } } @@ -86,7 +87,7 @@ func (g Grub) Install(target, rootDir, bootDir, grubConf, tty string, efi bool, } // Create Needed dir under state partition to store the grub.cfg and any needed modules - err = MkdirAll(g.config.Fs, filepath.Join(bootDir, fmt.Sprintf("%s/%s-efi", systemgrub, g.config.Arch)), cnst.DirPerm) + err = fsutils.MkdirAll(g.config.Fs, filepath.Join(bootDir, fmt.Sprintf("%s/%s-efi", systemgrub, g.config.Arch)), cnst.DirPerm) if err != nil { return fmt.Errorf("error creating grub dir: %s", err) } @@ -108,7 +109,7 @@ func (g Grub) Install(target, rootDir, bootDir, grubConf, tty string, efi bool, } } - ttyExists, _ := Exists(g.config.Fs, fmt.Sprintf("/dev/%s", tty)) + ttyExists, _ := fsutils.Exists(g.config.Fs, fmt.Sprintf("/dev/%s", tty)) if ttyExists && tty != "" && tty != "console" && tty != constants.DefaultTty { // We need to add a tty to the grub file @@ -134,7 +135,7 @@ func (g Grub) Install(target, rootDir, bootDir, grubConf, tty string, efi bool, g.config.Logger.Infof("Generating grub files for efi on %s", target) var foundModules bool for _, m := range []string{"loopback.mod", "squash4.mod", "xzio.mod", "gzio.mod"} { - err = WalkDirFs(g.config.Fs, rootDir, func(path string, d fs.DirEntry, err error) error { + err = fsutils.WalkDirFs(g.config.Fs, rootDir, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } @@ -159,7 +160,7 @@ func (g Grub) Install(target, rootDir, bootDir, grubConf, tty string, efi bool, } } - err = MkdirAll(g.config.Fs, filepath.Join(cnst.EfiDir, "EFI/boot/"), cnst.DirPerm) + err = fsutils.MkdirAll(g.config.Fs, filepath.Join(cnst.EfiDir, "EFI/boot/"), cnst.DirPerm) if err != nil { g.config.Logger.Errorf("Error creating dirs: %s", err) return err diff --git a/pkg/utils/getpartitions.go b/pkg/utils/partitions/getpartitions.go similarity index 99% rename from pkg/utils/getpartitions.go rename to pkg/utils/partitions/getpartitions.go index 1b18248..d43fc52 100644 --- a/pkg/utils/getpartitions.go +++ b/pkg/utils/partitions/getpartitions.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package utils +package partitions import ( "fmt" diff --git a/pkg/utils/runstage.go b/pkg/utils/runstage.go index 9ea2772..c8702dd 100644 --- a/pkg/utils/runstage.go +++ b/pkg/utils/runstage.go @@ -18,11 +18,12 @@ package utils import ( "fmt" + agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" "strings" "github.com/hashicorp/go-multierror" "github.com/kairos-io/kairos-agent/v2/pkg/constants" - v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" "github.com/mudler/yip/pkg/schema" "gopkg.in/yaml.v3" ) @@ -43,7 +44,7 @@ func onlyYAMLPartialErrors(er error) bool { return true } -func checkYAMLError(cfg *v1.Config, allErrors, err error) error { +func checkYAMLError(cfg *agentConfig.Config, allErrors, err error) error { if !onlyYAMLPartialErrors(err) { // here we absorb errors only if are related to YAML unmarshalling // As cmdline is parsed out as a yaml file @@ -57,7 +58,7 @@ func checkYAMLError(cfg *v1.Config, allErrors, err error) error { } // RunStage will run yip -func RunStage(cfg *v1.Config, stage string) error { +func RunStage(cfg *agentConfig.Config, stage string) error { var cmdLineYipURI string var allErrors error var cloudInitPaths []string @@ -67,7 +68,7 @@ func RunStage(cfg *v1.Config, stage string) error { // Make sure cloud init path specified are existing in the system for _, cp := range cloudInitPaths { - err := MkdirAll(cfg.Fs, cp, constants.DirPerm) + err := fsutils.MkdirAll(cfg.Fs, cp, constants.DirPerm) if err != nil { cfg.Logger.Debugf("Failed creating cloud-init config path: %s %s", cp, err.Error()) } diff --git a/pkg/utils/runstage_test.go b/pkg/utils/runstage_test.go index db167cf..322a526 100644 --- a/pkg/utils/runstage_test.go +++ b/pkg/utils/runstage_test.go @@ -19,10 +19,11 @@ package utils_test import ( "bytes" "fmt" + agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" "os" "github.com/kairos-io/kairos-agent/v2/pkg/cloudinit" - conf "github.com/kairos-io/kairos-agent/v2/pkg/elementalConfig" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" "github.com/kairos-io/kairos-agent/v2/pkg/utils" v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks" @@ -41,7 +42,7 @@ func writeCmdline(s string, fs v1.FS) error { } var _ = Describe("run stage", Label("RunStage"), func() { - var config *v1.Config + var config *agentConfig.Config var runner *v1mock.FakeRunner var logger v1.Logger var syscall *v1mock.FakeSyscall @@ -61,13 +62,13 @@ var _ = Describe("run stage", Label("RunStage"), func() { logger.SetLevel(log.DebugLevel) fs, cleanup, _ = vfst.NewTestFS(nil) - config = conf.NewConfig( - conf.WithFs(fs), - conf.WithRunner(runner), - conf.WithLogger(logger), - conf.WithMounter(mounter), - conf.WithSyscall(syscall), - conf.WithClient(client), + config = agentConfig.NewConfig( + agentConfig.WithFs(fs), + agentConfig.WithRunner(runner), + agentConfig.WithLogger(logger), + agentConfig.WithMounter(mounter), + agentConfig.WithSyscall(syscall), + agentConfig.WithClient(client), ) config.CloudInitRunner = cloudinit.NewYipCloudInitRunner(config.Logger, config.Runner, fs) @@ -75,7 +76,7 @@ var _ = Describe("run stage", Label("RunStage"), func() { AfterEach(func() { cleanup() }) It("fails if strict mode is enabled", Label("strict"), func() { - d, err := utils.TempDir(fs, "", "elemental") + d, err := fsutils.TempDir(fs, "", "elemental") Expect(err).ToNot(HaveOccurred()) _ = fs.WriteFile(fmt.Sprintf("%s/test.yaml", d), []byte("stages: [foo,bar]"), os.ModePerm) config.Strict = true @@ -92,7 +93,7 @@ var _ = Describe("run stage", Label("RunStage"), func() { }) It("Goes over extra paths", func() { - d, err := utils.TempDir(fs, "", "elemental") + d, err := fsutils.TempDir(fs, "", "elemental") Expect(err).ToNot(HaveOccurred()) config.Logger.SetLevel(log.DebugLevel) config.CloudInitPaths = []string{d} @@ -105,7 +106,7 @@ var _ = Describe("run stage", Label("RunStage"), func() { }) It("parses cmdline uri", func() { - d, _ := utils.TempDir(fs, "", "elemental") + d, _ := fsutils.TempDir(fs, "", "elemental") _ = fs.WriteFile(fmt.Sprintf("%s/test.yaml", d), []byte{}, os.ModePerm) writeCmdline(fmt.Sprintf("cos.setup=%s/test.yaml", d), fs) diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index f5bb781..29b09e4 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -20,6 +20,8 @@ import ( "bytes" "errors" "fmt" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" + "github.com/kairos-io/kairos-agent/v2/pkg/utils/partitions" "os" "path/filepath" "strings" @@ -27,8 +29,8 @@ import ( "github.com/jaypipes/ghw/pkg/block" + agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config" "github.com/kairos-io/kairos-agent/v2/pkg/constants" - conf "github.com/kairos-io/kairos-agent/v2/pkg/elementalConfig" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" "github.com/kairos-io/kairos-agent/v2/pkg/utils" v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks" @@ -47,7 +49,7 @@ func getNamesFromListFiles(list []os.FileInfo) []string { } var _ = Describe("Utils", Label("utils"), func() { - var config *v1.Config + var config *agentConfig.Config var runner *v1mock.FakeRunner var logger v1.Logger var syscall *v1mock.FakeSyscall @@ -70,13 +72,13 @@ var _ = Describe("Utils", Label("utils"), func() { fs.Mkdir("/run", constants.DirPerm) fs.Mkdir("/etc", constants.DirPerm) - config = conf.NewConfig( - conf.WithFs(fs), - conf.WithRunner(runner), - conf.WithLogger(logger), - conf.WithMounter(mounter), - conf.WithSyscall(syscall), - conf.WithClient(client), + config = agentConfig.NewConfig( + agentConfig.WithFs(fs), + agentConfig.WithRunner(runner), + agentConfig.WithLogger(logger), + agentConfig.WithMounter(mounter), + agentConfig.WithSyscall(syscall), + agentConfig.WithClient(client), ) }) AfterEach(func() { cleanup() }) @@ -176,16 +178,6 @@ var _ = Describe("Utils", Label("utils"), func() { }) }) }) - Describe("TestBootedFrom", Label("BootedFrom"), func() { - It("returns true if we are booting from label FAKELABEL", func() { - runner.ReturnValue = []byte("") - Expect(utils.BootedFrom(runner, "FAKELABEL")).To(BeFalse()) - }) - It("returns false if we are not booting from label FAKELABEL", func() { - runner.ReturnValue = []byte("FAKELABEL") - Expect(utils.BootedFrom(runner, "FAKELABEL")).To(BeTrue()) - }) - }) Describe("GetDeviceByLabel", Label("lsblk", "partitions"), func() { var cmds [][]string BeforeEach(func() { @@ -246,7 +238,7 @@ var _ = Describe("Utils", Label("utils"), func() { ghwTest.Clean() }) It("returns all found partitions", func() { - parts, err := utils.GetAllPartitions() + parts, err := partitions.GetAllPartitions() Expect(err).To(BeNil()) var partNames []string for _, p := range parts { @@ -277,17 +269,17 @@ var _ = Describe("Utils", Label("utils"), func() { ghwTest.Clean() }) It("returns found device with plain partition device", func() { - pFS, err := utils.GetPartitionFS("device1") + pFS, err := partitions.GetPartitionFS("device1") Expect(err).To(BeNil()) Expect(pFS).To(Equal("xfs")) }) It("returns found device with full partition device", func() { - pFS, err := utils.GetPartitionFS("/dev/device1") + pFS, err := partitions.GetPartitionFS("/dev/device1") Expect(err).To(BeNil()) Expect(pFS).To(Equal("xfs")) }) It("fails if no partition is found", func() { - _, err := utils.GetPartitionFS("device2") + _, err := partitions.GetPartitionFS("device2") Expect(err).NotTo(BeNil()) }) }) @@ -377,40 +369,40 @@ var _ = Describe("Utils", Label("utils"), func() { }) Describe("CopyFile", Label("CopyFile"), func() { It("Copies source file to target file", func() { - err := utils.MkdirAll(fs, "/some", constants.DirPerm) + err := fsutils.MkdirAll(fs, "/some", constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create("/some/file") Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Stat("/some/otherfile") Expect(err).Should(HaveOccurred()) Expect(utils.CopyFile(fs, "/some/file", "/some/otherfile")).ShouldNot(HaveOccurred()) - e, err := utils.Exists(fs, "/some/otherfile") + e, err := fsutils.Exists(fs, "/some/otherfile") Expect(err).ShouldNot(HaveOccurred()) Expect(e).To(BeTrue()) }) It("Copies source file to target folder", func() { - err := utils.MkdirAll(fs, "/some", constants.DirPerm) + err := fsutils.MkdirAll(fs, "/some", constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) - err = utils.MkdirAll(fs, "/someotherfolder", constants.DirPerm) + err = fsutils.MkdirAll(fs, "/someotherfolder", constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create("/some/file") Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Stat("/someotherfolder/file") Expect(err).Should(HaveOccurred()) Expect(utils.CopyFile(fs, "/some/file", "/someotherfolder")).ShouldNot(HaveOccurred()) - e, err := utils.Exists(fs, "/someotherfolder/file") + e, err := fsutils.Exists(fs, "/someotherfolder/file") Expect(err).ShouldNot(HaveOccurred()) Expect(e).To(BeTrue()) }) It("Fails to open non existing file", func() { - err := utils.MkdirAll(fs, "/some", constants.DirPerm) + err := fsutils.MkdirAll(fs, "/some", constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) Expect(utils.CopyFile(fs, "/some/file", "/some/otherfile")).NotTo(BeNil()) _, err = fs.Stat("/some/otherfile") Expect(err).NotTo(BeNil()) }) It("Fails to copy on non writable target", func() { - err := utils.MkdirAll(fs, "/some", constants.DirPerm) + err := fsutils.MkdirAll(fs, "/some", constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) fs.Create("/some/file") _, err = fs.Stat("/some/otherfile") @@ -448,13 +440,13 @@ var _ = Describe("Utils", Label("utils"), func() { }) Describe("SyncData", Label("SyncData"), func() { It("Copies all files from source to target", func() { - sourceDir, err := utils.TempDir(fs, "", "elementalsource") + sourceDir, err := fsutils.TempDir(fs, "", "elementalsource") Expect(err).ShouldNot(HaveOccurred()) - destDir, err := utils.TempDir(fs, "", "elementaltarget") + destDir, err := fsutils.TempDir(fs, "", "elementaltarget") Expect(err).ShouldNot(HaveOccurred()) for i := 0; i < 5; i++ { - _, _ = utils.TempFile(fs, sourceDir, "file*") + _, _ = fsutils.TempFile(fs, sourceDir, "file*") } Expect(utils.SyncData(logger, realRunner, fs, sourceDir, destDir)).To(BeNil()) @@ -473,19 +465,19 @@ var _ = Describe("Utils", Label("utils"), func() { }) It("Copies all files from source to target respecting excludes", func() { - sourceDir, err := utils.TempDir(fs, "", "elementalsource") + sourceDir, err := fsutils.TempDir(fs, "", "elementalsource") Expect(err).ShouldNot(HaveOccurred()) - destDir, err := utils.TempDir(fs, "", "elementaltarget") + destDir, err := fsutils.TempDir(fs, "", "elementaltarget") Expect(err).ShouldNot(HaveOccurred()) - utils.MkdirAll(fs, filepath.Join(sourceDir, "host"), constants.DirPerm) - utils.MkdirAll(fs, filepath.Join(sourceDir, "run"), constants.DirPerm) + fsutils.MkdirAll(fs, filepath.Join(sourceDir, "host"), constants.DirPerm) + fsutils.MkdirAll(fs, filepath.Join(sourceDir, "run"), constants.DirPerm) // /tmp/run would be excluded as well, as we define an exclude without the "/" prefix - utils.MkdirAll(fs, filepath.Join(sourceDir, "tmp", "run"), constants.DirPerm) + fsutils.MkdirAll(fs, filepath.Join(sourceDir, "tmp", "run"), constants.DirPerm) for i := 0; i < 5; i++ { - _, _ = utils.TempFile(fs, sourceDir, "file*") + _, _ = fsutils.TempFile(fs, sourceDir, "file*") } Expect(utils.SyncData(logger, realRunner, fs, sourceDir, destDir, "host", "run")).To(BeNil()) @@ -512,19 +504,19 @@ var _ = Describe("Utils", Label("utils"), func() { Expect(destNames).To(Equal(expected)) // /tmp/run is not copied over - Expect(utils.Exists(fs, filepath.Join(destDir, "tmp", "run"))).To(BeFalse()) + Expect(fsutils.Exists(fs, filepath.Join(destDir, "tmp", "run"))).To(BeFalse()) }) It("Copies all files from source to target respecting excludes with '/' prefix", func() { - sourceDir, err := utils.TempDir(fs, "", "elementalsource") + sourceDir, err := fsutils.TempDir(fs, "", "elementalsource") Expect(err).ShouldNot(HaveOccurred()) - destDir, err := utils.TempDir(fs, "", "elementaltarget") + destDir, err := fsutils.TempDir(fs, "", "elementaltarget") Expect(err).ShouldNot(HaveOccurred()) - utils.MkdirAll(fs, filepath.Join(sourceDir, "host"), constants.DirPerm) - utils.MkdirAll(fs, filepath.Join(sourceDir, "run"), constants.DirPerm) - utils.MkdirAll(fs, filepath.Join(sourceDir, "var", "run"), constants.DirPerm) - utils.MkdirAll(fs, filepath.Join(sourceDir, "tmp", "host"), constants.DirPerm) + fsutils.MkdirAll(fs, filepath.Join(sourceDir, "host"), constants.DirPerm) + fsutils.MkdirAll(fs, filepath.Join(sourceDir, "run"), constants.DirPerm) + fsutils.MkdirAll(fs, filepath.Join(sourceDir, "var", "run"), constants.DirPerm) + fsutils.MkdirAll(fs, filepath.Join(sourceDir, "tmp", "host"), constants.DirPerm) Expect(utils.SyncData(logger, realRunner, fs, sourceDir, destDir, "/host", "/run")).To(BeNil()) @@ -541,16 +533,16 @@ var _ = Describe("Utils", Label("utils"), func() { // Shouldn't be the same Expect(destNames).ToNot(Equal(SourceNames)) - Expect(utils.Exists(fs, filepath.Join(destDir, "var", "run"))).To(BeTrue()) - Expect(utils.Exists(fs, filepath.Join(destDir, "tmp", "host"))).To(BeTrue()) - Expect(utils.Exists(fs, filepath.Join(destDir, "host"))).To(BeFalse()) - Expect(utils.Exists(fs, filepath.Join(destDir, "run"))).To(BeFalse()) + Expect(fsutils.Exists(fs, filepath.Join(destDir, "var", "run"))).To(BeTrue()) + Expect(fsutils.Exists(fs, filepath.Join(destDir, "tmp", "host"))).To(BeTrue()) + Expect(fsutils.Exists(fs, filepath.Join(destDir, "host"))).To(BeFalse()) + Expect(fsutils.Exists(fs, filepath.Join(destDir, "run"))).To(BeFalse()) }) It("should not fail if dirs are empty", func() { - sourceDir, err := utils.TempDir(fs, "", "elementalsource") + sourceDir, err := fsutils.TempDir(fs, "", "elementalsource") Expect(err).ShouldNot(HaveOccurred()) - destDir, err := utils.TempDir(fs, "", "elementaltarget") + destDir, err := fsutils.TempDir(fs, "", "elementaltarget") Expect(err).ShouldNot(HaveOccurred()) Expect(utils.SyncData(logger, realRunner, fs, sourceDir, destDir)).To(BeNil()) }) @@ -672,7 +664,7 @@ var _ = Describe("Utils", Label("utils"), func() { }) Describe("DirSize", Label("fs"), func() { BeforeEach(func() { - err := utils.MkdirAll(fs, "/folder/subfolder", constants.DirPerm) + err := fsutils.MkdirAll(fs, "/folder/subfolder", constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) f, err := fs.Create("/folder/file") Expect(err).ShouldNot(HaveOccurred()) @@ -684,14 +676,14 @@ var _ = Describe("Utils", Label("utils"), func() { Expect(err).ShouldNot(HaveOccurred()) }) It("Returns the expected size of a test folder", func() { - size, err := utils.DirSize(fs, "/folder") + size, err := fsutils.DirSize(fs, "/folder") Expect(err).ShouldNot(HaveOccurred()) Expect(size).To(Equal(int64(3072))) }) }) Describe("FindFileWithPrefix", Label("find"), func() { BeforeEach(func() { - err := utils.MkdirAll(fs, "/path/inner", constants.DirPerm) + err := fsutils.MkdirAll(fs, "/path/inner", constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) _, err = fs.Create("/path/onefile") @@ -740,7 +732,7 @@ var _ = Describe("Utils", Label("utils"), func() { Expect(err).Should(HaveOccurred()) }) It("doesn't find any matching file in path", func() { - utils.MkdirAll(fs, "/path", constants.DirPerm) + fsutils.MkdirAll(fs, "/path", constants.DirPerm) _, err := utils.FindFileWithPrefix(fs, "/path", "prefix", "anotherprefix") Expect(err).Should(HaveOccurred()) }) @@ -773,10 +765,10 @@ var _ = Describe("Utils", Label("utils"), func() { logger.SetLevel(v1.DebugLevel()) config.Logger = logger - err := utils.MkdirAll(fs, filepath.Join(bootDir, "grub2"), constants.DirPerm) + err := fsutils.MkdirAll(fs, filepath.Join(bootDir, "grub2"), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) - err = utils.MkdirAll(fs, filepath.Dir(filepath.Join(rootDir, constants.GrubConf)), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Dir(filepath.Join(rootDir, constants.GrubConf)), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) err = fs.WriteFile(filepath.Join(rootDir, constants.GrubConf), []byte("console=tty1"), 0644) @@ -798,17 +790,17 @@ var _ = Describe("Utils", Label("utils"), func() { }) It("installs with efi firmware", Label("efi"), func() { - err := utils.MkdirAll(fs, filepath.Join(rootDir, "/usr/share/efi/x86_64/"), constants.DirPerm) + err := fsutils.MkdirAll(fs, filepath.Join(rootDir, "/usr/share/efi/x86_64/"), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) err = fs.WriteFile(filepath.Join(rootDir, "/usr/share/efi/x86_64/", constants.SignedShim), []byte(""), constants.FilePerm) Expect(err).ShouldNot(HaveOccurred()) err = fs.WriteFile(filepath.Join(rootDir, "/usr/share/efi/x86_64/grub.efi"), []byte(""), constants.FilePerm) Expect(err).ShouldNot(HaveOccurred()) - err = utils.MkdirAll(fs, filepath.Join(rootDir, "/x86_64/"), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Join(rootDir, "/x86_64/"), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) err = fs.WriteFile(filepath.Join(rootDir, "/x86_64/loopback.mod"), []byte(""), constants.FilePerm) Expect(err).ShouldNot(HaveOccurred()) - err = utils.MkdirAll(fs, filepath.Join(rootDir, "/etc/"), constants.DirPerm) + err = fsutils.MkdirAll(fs, filepath.Join(rootDir, "/etc/"), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) err = fs.WriteFile(filepath.Join(rootDir, "/etc/os-release"), []byte("ID=\"suse\""), constants.FilePerm) Expect(err).ShouldNot(HaveOccurred()) @@ -839,7 +831,7 @@ var _ = Describe("Utils", Label("utils"), func() { Expect(err.Error()).To(ContainSubstring("modules")) }) It("fails with efi if no grub files exist", Label("efi"), func() { - err := utils.MkdirAll(fs, filepath.Join(rootDir, "/x86_64/"), constants.DirPerm) + err := fsutils.MkdirAll(fs, filepath.Join(rootDir, "/x86_64/"), constants.DirPerm) Expect(err).ShouldNot(HaveOccurred()) err = fs.WriteFile(filepath.Join(rootDir, "/x86_64/loopback.mod"), []byte(""), constants.FilePerm) Expect(err).ShouldNot(HaveOccurred()) @@ -993,65 +985,6 @@ var _ = Describe("Utils", Label("utils"), func() { Expect(mnt).To(BeFalse()) }) }) - Describe("HasSquashedRecovery", Label("squashedRec"), func() { - var squashedImg string - var part *v1.Partition - BeforeEach(func() { - squashedImg = filepath.Join(constants.LiveDir, "cOS", constants.RecoverySquashFile) - part = &v1.Partition{ - MountPoint: constants.LiveDir, - Path: "/some/device", - } - }) - It("has squashed image from a mounted recovery", func() { - // mount recovery - err := mounter.Mount(part.Path, constants.LiveDir, "auto", []string{}) - Expect(err).ShouldNot(HaveOccurred()) - - // create squashfs - err = utils.MkdirAll(config.Fs, filepath.Dir(squashedImg), constants.DirPerm) - Expect(err).ShouldNot(HaveOccurred()) - _, err = config.Fs.Create(squashedImg) - Expect(err).ShouldNot(HaveOccurred()) - - squash, err := utils.HasSquashedRecovery(config, part) - Expect(err).ShouldNot(HaveOccurred()) - Expect(squash).To(BeTrue()) - }) - It("does not have squashed image from a mounted recovery", func() { - // mount recovery - err := mounter.Mount(part.Path, constants.LiveDir, "auto", []string{}) - Expect(err).ShouldNot(HaveOccurred()) - - squash, err := utils.HasSquashedRecovery(config, part) - Expect(err).ShouldNot(HaveOccurred()) - Expect(squash).To(BeFalse()) - }) - It("has squashed image from a not mounted recovery", func() { - // squashed image on temp dir - squashedImg = filepath.Join("/tmp/elemental", "cOS", constants.RecoverySquashFile) - // create squashfs - err := utils.MkdirAll(config.Fs, filepath.Dir(squashedImg), constants.DirPerm) - Expect(err).ShouldNot(HaveOccurred()) - _, err = config.Fs.Create(squashedImg) - Expect(err).ShouldNot(HaveOccurred()) - - squash, err := utils.HasSquashedRecovery(config, part) - Expect(err).ShouldNot(HaveOccurred()) - Expect(squash).To(BeTrue()) - }) - It("does not have squashed image from a not mounted recovery", func() { - squash, err := utils.HasSquashedRecovery(config, part) - Expect(err).ShouldNot(HaveOccurred()) - Expect(squash).To(BeFalse()) - }) - It("fails to mount recovery", func() { - mounter.ErrorOnMount = true - squash, err := utils.HasSquashedRecovery(config, part) - Expect(err).Should(HaveOccurred()) - Expect(squash).To(BeFalse()) - }) - }) Describe("CleanStack", Label("CleanStack"), func() { var cleaner *utils.CleanStack BeforeEach(func() {