Skip to content

Commit

Permalink
Run install off the same command (#196)
Browse files Browse the repository at this point in the history
  • Loading branch information
Itxaka authored Jan 9, 2024
1 parent 6638010 commit 775756f
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 74 deletions.
88 changes: 56 additions & 32 deletions internal/agent/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/kairos-io/kairos-agent/v2/pkg/uki"
internalutils "github.com/kairos-io/kairos-agent/v2/pkg/utils"
"net/url"
"os"
"strings"
Expand All @@ -14,13 +16,10 @@ import (
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
"github.com/sanity-io/litter"

hook "github.com/kairos-io/kairos-agent/v2/internal/agent/hooks"
"github.com/kairos-io/kairos-agent/v2/internal/bus"
"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"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
elementalUtils "github.com/kairos-io/kairos-agent/v2/pkg/utils"
events "github.com/kairos-io/kairos-sdk/bus"
"github.com/kairos-io/kairos-sdk/collector"
"github.com/kairos-io/kairos-sdk/machine"
Expand Down Expand Up @@ -217,41 +216,57 @@ func RunInstall(c *config.Config) error {
c.Install.Device = detectDevice()
}

// Load the installation spec from the Config
installSpec, err := config.ReadInstallSpecFromConfig(c)
if err != nil {
return err
// UKI path. Check if we are on UKI AND if we are running off a cd, otherwise it makes no sense to run the install
// From the installed system
if internalutils.UkiBootMode() == internalutils.UkiRemovableMedia {
return runInstallUki(c)
} else { // Non-uki path
return runInstall(c)
}
}

f, err := fsutils.TempFile(c.Fs, "", "kairos-install-config-xxx.yaml")
// runInstallUki runs the UKI path install
func runInstallUki(c *config.Config) error {
// Load the spec from the config
installSpec, err := config.ReadUkiInstallSpecFromConfig(c)
if err != nil {
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()
// Set our cloud-init to the file we just created
f, err := dumpCCStringToFile(c)
if err == nil {
installSpec.CloudInit = append(installSpec.CloudInit, f)
}

// Check if values are correct
err = installSpec.Sanitize()
if err != nil {
return err
}
err = os.WriteFile(f.Name(), []byte(ccstring), os.ModePerm)

// Add user's cloud-config (to run user defined "before-install" stages)
c.CloudInitPaths = append(c.CloudInitPaths, installSpec.CloudInit...)

installAction := uki.NewInstallAction(c, installSpec)
return installAction.Run()
}

// runInstall runs the non-UKI path install
func runInstall(c *config.Config) error {
// Load the installation spec from the Config
installSpec, err := config.ReadInstallSpecFromConfig(c)
if err != nil {
fmt.Printf("could not write cloud init to %s: %s\n", f.Name(), err.Error())
return err
}

// TODO: This should not be neccessary
installSpec.NoFormat = c.Install.NoFormat

// Set our cloud-init to the file we just created
installSpec.CloudInit = append(installSpec.CloudInit, f.Name())
// Get the source of the installation if we are overriding it
if c.Install.Image != "" {
imgSource, err := v1.NewSrcFromURI(c.Install.Image)
if err != nil {
return err
}
installSpec.Active.Source = imgSource
f, err := dumpCCStringToFile(c)
if err == nil {
installSpec.CloudInit = append(installSpec.CloudInit, f)
}

// Check if values are correct
Expand All @@ -263,20 +278,29 @@ func RunInstall(c *config.Config) error {
// Add user's cloud-config (to run user defined "before-install" stages)
c.CloudInitPaths = append(c.CloudInitPaths, installSpec.CloudInit...)

// Run pre-install stage
_ = elementalUtils.RunStage(c, "kairos-install.pre")
events.RunHookScript("/usr/bin/kairos-agent.install.pre.hook") //nolint:errcheck
// Create the action
installAction := action.NewInstallAction(c, installSpec)
// Run it
if err := installAction.Run(); err != nil {
fmt.Println(err)
os.Exit(1)
return installAction.Run()
}

// dumpCCStringToFile dumps the cloud-init string to a file and returns the path of the file
func dumpCCStringToFile(c *config.Config) (string, error) {
f, err := fsutils.TempFile(c.Fs, "", "kairos-install-config-xxx.yaml")
if err != nil {
c.Logger.Error("Error creating temporary file for install config: %s\n", err.Error())
return "", err
}
_ = elementalUtils.RunStage(c, "kairos-install.after")
events.RunHookScript("/usr/bin/kairos-agent.install.after.hook") //nolint:errcheck
defer os.RemoveAll(f.Name())

return hook.Run(*c, installSpec, hook.AfterInstall...)
ccstring, err := c.String()
if err != nil {
return "", err
}
err = os.WriteFile(f.Name(), []byte(ccstring), os.ModePerm)
if err != nil {
fmt.Printf("could not write cloud init to %s: %s\n", f.Name(), err.Error())
return "", err
}
return f.Name(), nil
}

func ensureDataSourceReady() {
Expand Down
41 changes: 0 additions & 41 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -711,47 +711,6 @@ The validate command expects a configuration file as its only argument. Local fi
// command level: kairos-agent uki --source oci:whatever install
// subcommand level: kairos-agent uki install --source oci:whatever
Subcommands: []*cli.Command{
{
Name: "install",
Usage: "Install to disk",
UsageText: "install [--device DEVICE]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "source",
Usage: "Source for install. Composed of `type:address`. Accepts `file:`,`dir:` or `oci:` for the type of source.\nFor example `file:/var/share/myimage.tar`, `dir:/tmp/extracted` or `oci:repo/image:tag`",
Action: func(c *cli.Context, s string) error {
return validateSource(s)
},
},
&cli.StringFlag{
Name: "device",
},
},
Before: func(c *cli.Context) error {
if c.String("device") == "" {
return fmt.Errorf("on uki, --device flag is required")
}
return nil
},
Action: func(c *cli.Context) error {
config, err := agentConfig.Scan(collector.Directories(configScanDir...), collector.NoLogs)
if err != nil {
return err
}
// Load the spec from the config
installSpec, err := agentConfig.ReadUkiInstallSpecFromConfig(config)
if err != nil {
return err
}

if c.String("device") != "" {
installSpec.Target = c.String("device")
}

installAction := uki.NewInstallAction(config, installSpec)
return installAction.Run()
},
},
{
Name: "upgrade",
Flags: []cli.Flag{
Expand Down
11 changes: 10 additions & 1 deletion pkg/action/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ package action

import (
"fmt"
hook "github.com/kairos-io/kairos-agent/v2/internal/agent/hooks"
"github.com/kairos-io/kairos-agent/v2/pkg/config"
events "github.com/kairos-io/kairos-sdk/bus"
"path/filepath"
"strings"
"time"
Expand Down Expand Up @@ -123,6 +125,10 @@ func (i InstallAction) Run() (err error) {
cleanup := utils.NewCleanStack()
defer func() { err = cleanup.Cleanup(err) }()

// Run pre-install stage
_ = utils.RunStage(i.cfg, "kairos-install.pre")
events.RunHookScript("/usr/bin/kairos-agent.install.pre.hook") //nolint:errcheck

// Set installation sources from a downloaded ISO
if i.spec.Iso != "" {
tmpDir, err := e.GetIso(i.spec.Iso)
Expand Down Expand Up @@ -275,5 +281,8 @@ func (i InstallAction) Run() (err error) {
}
}

return err
_ = utils.RunStage(i.cfg, "kairos-install.after")
_ = events.RunHookScript("/usr/bin/kairos-agent.install.after.hook") //nolint:errcheck

return hook.Run(*i.cfg, i.spec, hook.AfterInstall...)
}
29 changes: 29 additions & 0 deletions pkg/utils/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"crypto/sha256"
"errors"
"fmt"
"github.com/kairos-io/kairos-sdk/state"
"io"
random "math/rand"
"net/url"
Expand Down Expand Up @@ -498,3 +499,31 @@ func FindCommand(defaultPath string, options []string) string {
// Otherwise return default
return defaultPath
}

// IsUki returns true if the system is running in UKI mode. Checks the cmdline as UKI artifacts have the rd.immucore.uki flag
func IsUki() bool {
cmdline, _ := os.ReadFile("/proc/cmdline")
if strings.Contains(string(cmdline), "rd.immucore.uki") {
return true
}
return false
}

const (
UkiHDD state.Boot = "uki_boot_mode"
UkiRemovableMedia state.Boot = "uki_install_mode"
)

// UkiBootMode will return where the system is running from, either HDD or RemovableMedia
// HDD means we are booting from an already installed system
// RemovableMedia means we are booting from a live media like a CD or USB
func UkiBootMode() state.Boot {
if IsUki() {
_, err := os.Stat("/run/cos/uki_boot_mode")
if err != nil {
return UkiHDD
}
return UkiRemovableMedia
}
return state.Unknown
}

0 comments on commit 775756f

Please sign in to comment.