immucore/internal/utils/common.go

242 lines
6.4 KiB
Go
Raw Normal View History

package utils
import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/avast/retry-go"
"github.com/joho/godotenv"
2023-04-28 07:30:56 +00:00
"github.com/kairos-io/kairos-sdk/state"
)
// BootStateToLabelDevice lets us know the device we need to mount sysroot on based on labels.
func BootStateToLabelDevice() string {
2226 detect boot state (#225) * WIP Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * WIP Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com> Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * WIP add logs everywhere (EOD wip) Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Do the livecd check as late as possible because the herd condition is evaluated too early before the /sys is mounted and thus we don't detect the installed system correctly in UKI mode. Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Wrap NewRuntime to allow passing down a logger so that kairos-sdk logs make it to the immucore.log file Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Add TODOs and remove redundant check in code the livecd check already happens some lines above Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Replace the "replace" with an actual tag Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Remoce "replace" directive and use wrapper method for UnlockAll Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Remove unecessary TODO the log message describes what happened * Re-use the method from kairos-sdk for uki boot detection Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Move messages from Info() to Debug() (PR review request) Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> --------- Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com> Co-authored-by: Mauro Morales <mauro.morales@spectrocloud.com>
2024-02-19 11:42:06 +00:00
runtime, err := state.NewRuntimeWithLogger(Log)
if err != nil {
return ""
}
switch runtime.BootState {
case state.Active:
return filepath.Join("/dev/disk/by-label", "COS_ACTIVE")
case state.Passive:
return filepath.Join("/dev/disk/by-label", "COS_PASSIVE")
case state.Recovery:
return filepath.Join("/dev/disk/by-label", "COS_SYSTEM")
default:
return ""
}
}
// GetRootDir returns the proper dir to mount all the stuff
// Useful if we want to move to a no-pivot boot.
func GetRootDir() string {
cmdline, _ := os.ReadFile(GetHostProcCmdline())
switch {
2226 detect boot state (#225) * WIP Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * WIP Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com> Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * WIP add logs everywhere (EOD wip) Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Do the livecd check as late as possible because the herd condition is evaluated too early before the /sys is mounted and thus we don't detect the installed system correctly in UKI mode. Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Wrap NewRuntime to allow passing down a logger so that kairos-sdk logs make it to the immucore.log file Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Add TODOs and remove redundant check in code the livecd check already happens some lines above Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Replace the "replace" with an actual tag Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Remoce "replace" directive and use wrapper method for UnlockAll Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Remove unecessary TODO the log message describes what happened * Re-use the method from kairos-sdk for uki boot detection Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Move messages from Info() to Debug() (PR review request) Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> --------- Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com> Co-authored-by: Mauro Morales <mauro.morales@spectrocloud.com>
2024-02-19 11:42:06 +00:00
case state.DetectUKIboot(string(cmdline)):
return "/"
default:
// Default is sysroot for normal no-pivot boot
return "/sysroot"
}
}
// UniqueSlice removes duplicated entries from a slice.So dumb. Like really? Why not have a set which enforces uniqueness????
func UniqueSlice(slice []string) []string {
keys := make(map[string]bool)
var list []string
for _, entry := range slice {
if _, value := keys[entry]; !value {
keys[entry] = true
list = append(list, entry)
}
}
return list
}
// ReadEnv will read an env file (key=value) and return a nice map.
func ReadEnv(file string) (map[string]string, error) {
var envMap map[string]string
var err error
f, err := os.Open(file)
if err != nil {
return envMap, err
}
defer func(f *os.File) {
_ = f.Close()
}(f)
envMap, err = godotenv.Parse(f)
if err != nil {
return envMap, err
}
return envMap, err
}
// CreateIfNotExists will check if a path exists and create it if needed.
func CreateIfNotExists(path string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return os.MkdirAll(path, os.ModePerm)
}
return nil
}
// CleanupSlice will clean a slice of strings of empty items
// Typos can be made on writing the cos-layout.env file and that could introduce empty items
// In the lists that we need to go over, which causes bad stuff.
func CleanupSlice(slice []string) []string {
var cleanSlice []string
for _, item := range slice {
if strings.Trim(item, " ") == "" {
continue
}
cleanSlice = append(cleanSlice, item)
}
return cleanSlice
}
// GetTarget gets the target image and device to mount in /sysroot.
func GetTarget(dryRun bool) (string, string, error) {
2226 detect boot state (#225) * WIP Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * WIP Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com> Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * WIP add logs everywhere (EOD wip) Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Do the livecd check as late as possible because the herd condition is evaluated too early before the /sys is mounted and thus we don't detect the installed system correctly in UKI mode. Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Wrap NewRuntime to allow passing down a logger so that kairos-sdk logs make it to the immucore.log file Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Add TODOs and remove redundant check in code the livecd check already happens some lines above Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Replace the "replace" with an actual tag Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Remoce "replace" directive and use wrapper method for UnlockAll Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Remove unecessary TODO the log message describes what happened * Re-use the method from kairos-sdk for uki boot detection Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Move messages from Info() to Debug() (PR review request) Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> --------- Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com> Co-authored-by: Mauro Morales <mauro.morales@spectrocloud.com>
2024-02-19 11:42:06 +00:00
if IsUKI() {
return "", "", nil
}
label := BootStateToLabelDevice()
// If dry run, or we are disabled return whatever values, we won't go much further
if dryRun || DisableImmucore() {
return "fake", label, nil
}
imgs := CleanupSlice(ReadCMDLineArg("cos-img/filename="))
// If no image just panic here, we cannot longer continue
if len(imgs) == 0 {
2226 detect boot state (#225) * WIP Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * WIP Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com> Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * WIP add logs everywhere (EOD wip) Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Do the livecd check as late as possible because the herd condition is evaluated too early before the /sys is mounted and thus we don't detect the installed system correctly in UKI mode. Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Wrap NewRuntime to allow passing down a logger so that kairos-sdk logs make it to the immucore.log file Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Add TODOs and remove redundant check in code the livecd check already happens some lines above Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Replace the "replace" with an actual tag Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Remoce "replace" directive and use wrapper method for UnlockAll Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Remove unecessary TODO the log message describes what happened * Re-use the method from kairos-sdk for uki boot detection Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Move messages from Info() to Debug() (PR review request) Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> --------- Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com> Co-authored-by: Mauro Morales <mauro.morales@spectrocloud.com>
2024-02-19 11:42:06 +00:00
msg := "could not get the image name from cmdline (i.e. cos-img/filename=/cOS/active.img)"
Log.Error().Msg(msg)
return "", "", errors.New(msg)
}
Log.Debug().Str("what", imgs[0]).Msg("Target device")
Log.Debug().Str("what", label).Msg("Target label")
return imgs[0], label, nil
}
// DisableImmucore identifies if we need to be disabled
// We disable if we boot from CD, netboot, squashfs recovery or have the rd.cos.disable stanza in cmdline.
func DisableImmucore() bool {
cmdline, _ := os.ReadFile(GetHostProcCmdline())
cmdlineS := string(cmdline)
return strings.Contains(cmdlineS, "live:LABEL") || strings.Contains(cmdlineS, "live:CDLABEL") ||
strings.Contains(cmdlineS, "netboot") || strings.Contains(cmdlineS, "rd.cos.disable") ||
strings.Contains(cmdlineS, "rd.immucore.disable")
}
// RootRW tells us if the mode to mount root.
func RootRW() string {
if len(ReadCMDLineArg("rd.cos.debugrw")) > 0 || len(ReadCMDLineArg("rd.immucore.debugrw")) > 0 {
Log.Warn().Msg("Mounting root as RW")
return "rw"
}
return "ro"
}
// GetState returns the disk-by-label of the state partition to mount
// This is only valid for either active/passive or normal recovery.
func GetState() string {
var label string
err := retry.Do(
func() error {
2226 detect boot state (#225) * WIP Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * WIP Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com> Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * WIP add logs everywhere (EOD wip) Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Do the livecd check as late as possible because the herd condition is evaluated too early before the /sys is mounted and thus we don't detect the installed system correctly in UKI mode. Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Wrap NewRuntime to allow passing down a logger so that kairos-sdk logs make it to the immucore.log file Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Add TODOs and remove redundant check in code the livecd check already happens some lines above Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Replace the "replace" with an actual tag Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Remoce "replace" directive and use wrapper method for UnlockAll Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Remove unecessary TODO the log message describes what happened * Re-use the method from kairos-sdk for uki boot detection Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Move messages from Info() to Debug() (PR review request) Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> --------- Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com> Co-authored-by: Mauro Morales <mauro.morales@spectrocloud.com>
2024-02-19 11:42:06 +00:00
r, err := state.NewRuntimeWithLogger(Log)
if err != nil {
return err
}
switch r.BootState {
case state.Active, state.Passive:
label = "COS_STATE"
case state.Recovery:
label = "COS_RECOVERY"
default:
return errors.New("could not get label")
}
return nil
},
retry.Delay(1*time.Second),
retry.Attempts(10),
retry.DelayType(retry.FixedDelay),
retry.OnRetry(func(n uint, err error) {
Log.Debug().Uint("try", n).Msg("Cannot get state label, retrying")
}),
)
if err != nil {
Log.Panic().Err(err).Msg("Could not get state label")
}
Log.Debug().Str("what", label).Msg("Get state label")
return filepath.Join("/dev/disk/by-label/", label)
}
func IsUKI() bool {
2226 detect boot state (#225) * WIP Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * WIP Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com> Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * WIP add logs everywhere (EOD wip) Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Do the livecd check as late as possible because the herd condition is evaluated too early before the /sys is mounted and thus we don't detect the installed system correctly in UKI mode. Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Wrap NewRuntime to allow passing down a logger so that kairos-sdk logs make it to the immucore.log file Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Add TODOs and remove redundant check in code the livecd check already happens some lines above Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Replace the "replace" with an actual tag Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Remoce "replace" directive and use wrapper method for UnlockAll Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Remove unecessary TODO the log message describes what happened * Re-use the method from kairos-sdk for uki boot detection Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * Move messages from Info() to Debug() (PR review request) Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> --------- Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com> Co-authored-by: Mauro Morales <mauro.morales@spectrocloud.com>
2024-02-19 11:42:06 +00:00
cmdline, err := os.ReadFile(GetHostProcCmdline())
if err != nil {
Log.Warn().Err(err).Msg("Error reading /proc/cmdline file " + err.Error())
return false
}
return state.DetectUKIboot(string(cmdline))
}
// CommandWithPath runs a command adding the usual PATH to environment
// Useful under UKI as there is nothing setting the PATH.
func CommandWithPath(c string) (string, error) {
cmd := exec.Command("/bin/sh", "-c", c)
cmd.Env = os.Environ()
pathAppend := "/usr/bin:/usr/sbin:/bin:/sbin"
// try to extract any existing path from the environment
for _, env := range cmd.Env {
splitted := strings.Split(env, "=")
if splitted[0] == "PATH" {
pathAppend = fmt.Sprintf("%s:%s", pathAppend, splitted[1])
}
}
cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s", pathAppend))
o, err := cmd.CombinedOutput()
return string(o), err
}
// PrepareCommandWithPath prepares a cmd with the proper env
// For running under yip.
func PrepareCommandWithPath(c string) *exec.Cmd {
cmd := exec.Command("/bin/sh", "-c", c)
cmd.Env = os.Environ()
pathAppend := "/usr/bin:/usr/sbin:/bin:/sbin"
// try to extract any existing path from the environment
for _, env := range cmd.Env {
splitted := strings.Split(env, "=")
if splitted[0] == "PATH" {
pathAppend = fmt.Sprintf("%s:%s", pathAppend, splitted[1])
}
}
cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s", pathAppend))
return cmd
}
// GetHostProcCmdline returns the path to /proc/cmdline
// Mainly used to override the cmdline during testing.
func GetHostProcCmdline() string {
proc := os.Getenv("HOST_PROC_CMDLINE")
if proc == "" {
return "/proc/cmdline"
}
return proc
}