2023-05-05 16:43:21 +00:00
/ *
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 utils
import (
2024-02-21 09:44:32 +00:00
"bufio"
2023-05-05 16:43:21 +00:00
"crypto/sha256"
"errors"
"fmt"
"io"
2024-03-18 10:57:34 +00:00
"io/fs"
2023-05-05 16:43:21 +00:00
random "math/rand"
"net/url"
"os"
"os/exec"
"path/filepath"
2024-11-27 10:16:56 +00:00
"regexp"
2023-05-05 16:43:21 +00:00
"strconv"
"strings"
"time"
2024-07-26 07:59:10 +00:00
sdkTypes "github.com/kairos-io/kairos-sdk/types"
2024-02-12 17:20:45 +00:00
"github.com/kairos-io/kairos-sdk/state"
2024-09-17 13:27:31 +00:00
"github.com/kairos-io/kairos-sdk/types"
2024-02-12 17:20:45 +00:00
2023-08-04 07:06:08 +00:00
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
"github.com/kairos-io/kairos-agent/v2/pkg/utils/partitions"
2024-07-26 07:59:10 +00:00
"github.com/distribution/reference"
2023-05-05 16:43:21 +00:00
"github.com/joho/godotenv"
2023-07-10 12:39:48 +00:00
cnst "github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
2024-11-06 11:50:14 +00:00
"github.com/twpayne/go-vfs/v5"
2023-05-05 16:43:21 +00:00
)
func CommandExists ( command string ) bool {
_ , err := exec . LookPath ( command )
return err == nil
}
// 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.
2024-09-17 13:27:31 +00:00
func GetDeviceByLabel ( config * agentConfig . Config , label string , attempts int ) ( string , error ) {
2023-05-05 16:43:21 +00:00
for tries := 0 ; tries < attempts ; tries ++ {
2024-09-17 13:27:31 +00:00
_ , _ = config . Runner . Run ( "udevadm" , "trigger" )
_ , _ = config . Runner . Run ( "udevadm" , "settle" )
parts , err := partitions . GetAllPartitions ( & config . Logger )
2023-05-05 16:43:21 +00:00
if err != nil {
2024-09-17 13:27:31 +00:00
return "" , err
2023-05-05 16:43:21 +00:00
}
2024-09-17 13:27:31 +00:00
part := v1 . GetPartitionByNameOrLabel ( "" , label , parts )
2023-05-05 16:43:21 +00:00
if part != nil {
2024-09-17 13:27:31 +00:00
return part . Path , nil
2023-05-05 16:43:21 +00:00
}
time . Sleep ( 1 * time . Second )
}
2024-09-17 13:27:31 +00:00
return "" , errors . New ( "no device found" )
2023-05-05 16:43:21 +00:00
}
// CopyFile Copies source file to target file using Fs interface. If target
// is directory source is copied into that directory using source name file.
func CopyFile ( fs v1 . FS , source string , target string ) ( err error ) {
return ConcatFiles ( fs , [ ] string { source } , target )
}
// ConcatFiles Copies source files to target file using Fs interface.
// Source files are concatenated into target file in the given order.
// If target is a directory source is copied into that directory using
// 1st source name file.
2024-09-27 14:10:49 +00:00
// TODO: Log errors, return errors, whatever but dont ignore them
2023-05-05 16:43:21 +00:00
func ConcatFiles ( fs v1 . FS , sources [ ] string , target string ) ( err error ) {
if len ( sources ) == 0 {
return fmt . Errorf ( "Empty sources list" )
}
2023-07-25 13:21:34 +00:00
if dir , _ := fsutils . IsDir ( fs , target ) ; dir {
2023-05-05 16:43:21 +00:00
target = filepath . Join ( target , filepath . Base ( sources [ 0 ] ) )
}
targetFile , err := fs . Create ( target )
if err != nil {
return err
}
defer func ( ) {
if err == nil {
err = targetFile . Close ( )
} else {
_ = fs . Remove ( target )
}
} ( )
for _ , source := range sources {
2024-03-18 10:57:34 +00:00
sourceFile , err := fs . Open ( source )
2023-05-05 16:43:21 +00:00
if err != nil {
break
}
_ , err = io . Copy ( targetFile , sourceFile )
if err != nil {
break
}
err = sourceFile . Close ( )
if err != nil {
break
}
}
return err
}
// 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" } {
2023-07-25 13:21:34 +00:00
err := fsutils . MkdirAll ( fs , filepath . Join ( target , dir ) , cnst . DirPerm )
2023-05-05 16:43:21 +00:00
if err != nil {
return err
}
}
for _ , dir := range [ ] string { "/proc" , "/sys" } {
2023-07-25 13:21:34 +00:00
err := fsutils . MkdirAll ( fs , filepath . Join ( target , dir ) , cnst . NoWriteDirPerm )
2023-05-05 16:43:21 +00:00
if err != nil {
return err
}
}
2023-07-25 13:21:34 +00:00
err := fsutils . MkdirAll ( fs , filepath . Join ( target , "/tmp" ) , cnst . DirPerm )
2023-05-05 16:43:21 +00:00
if err != nil {
return err
}
// Set /tmp permissions regardless the umask setup
err = fs . Chmod ( filepath . Join ( target , "/tmp" ) , cnst . TempDirPerm )
if err != nil {
return err
}
return nil
}
// SyncData rsync's source folder contents to a target folder content,
// both are expected to exist beforehand.
2024-03-01 11:27:26 +00:00
func SyncData ( log sdkTypes . KairosLogger , runner v1 . Runner , fs v1 . FS , source string , target string , excludes ... string ) error {
2023-05-05 16:43:21 +00:00
if fs != nil {
if s , err := fs . RawPath ( source ) ; err == nil {
source = s
}
if t , err := fs . RawPath ( target ) ; err == nil {
target = t
2024-03-18 10:57:34 +00:00
// create target path if it doesnt exists
if _ , err := os . Stat ( target ) ; err != nil {
err = fsutils . MkdirAll ( fs , target , cnst . DirPerm )
if err != nil {
log . Errorf ( "Error creating target path: %s" , err . Error ( ) )
return err
}
}
2023-05-05 16:43:21 +00:00
}
}
if ! strings . HasSuffix ( source , "/" ) {
source = fmt . Sprintf ( "%s/" , source )
}
if ! strings . HasSuffix ( target , "/" ) {
target = fmt . Sprintf ( "%s/" , target )
}
2023-06-20 10:18:31 +00:00
log . Infof ( "Starting rsync..." )
2025-03-06 09:33:40 +00:00
// TODO: copy xattr if possible or needed for selinux contexts? Or do we just relabel those on first boot?
args := [ ] string {
"--progress" ,
"--partial" ,
"--human-readable" ,
"--archive" , // recursive, symbolic links, permissions, owner, group, modification times, device files, special files
"--acls" , // preserve ACLS and permissions
}
2023-06-20 10:18:31 +00:00
for _ , e := range excludes {
args = append ( args , fmt . Sprintf ( "--exclude=%s" , e ) )
}
args = append ( args , source , target )
done := displayProgress ( log , 5 * time . Second , "Syncing data..." )
2023-08-04 07:06:08 +00:00
out , err := runner . Run ( cnst . Rsync , args ... )
2023-06-20 10:18:31 +00:00
close ( done )
if err != nil {
2023-08-04 07:06:08 +00:00
log . Errorf ( "rsync finished with errors: %s, %s" , err . Error ( ) , string ( out ) )
2023-06-20 10:18:31 +00:00
return err
}
log . Info ( "Finished syncing" )
return nil
}
2024-03-01 11:27:26 +00:00
func displayProgress ( log sdkTypes . KairosLogger , tick time . Duration , message string ) chan bool {
2023-06-20 10:18:31 +00:00
ticker := time . NewTicker ( tick )
done := make ( chan bool )
2023-05-05 16:43:21 +00:00
go func ( ) {
for {
select {
2023-06-20 10:18:31 +00:00
case <- done :
ticker . Stop ( )
2023-05-05 16:43:21 +00:00
return
2023-06-20 10:18:31 +00:00
case <- ticker . C :
log . Debug ( message )
2023-05-05 16:43:21 +00:00
}
}
} ( )
2023-06-20 10:18:31 +00:00
return done
2023-05-05 16:43:21 +00:00
}
// Reboot reboots the system afater the given delay (in seconds) time passed.
func Reboot ( runner v1 . Runner , delay time . Duration ) error {
time . Sleep ( delay * time . Second )
_ , err := runner . Run ( "reboot" , "-f" )
return err
}
// Shutdown halts the system afater the given delay (in seconds) time passed.
func Shutdown ( runner v1 . Runner , delay time . Duration ) error {
time . Sleep ( delay * time . Second )
_ , err := runner . Run ( "poweroff" , "-f" )
return err
}
// CosignVerify runs a cosign validation for the give image and given public key. If no
// key is provided then it attempts a keyless validation (experimental feature).
2024-03-01 11:27:26 +00:00
func CosignVerify ( fs v1 . FS , runner v1 . Runner , image string , publicKey string ) ( string , error ) {
2023-05-05 16:43:21 +00:00
args := [ ] string { }
if publicKey != "" {
args = append ( args , "-key" , publicKey )
} else {
os . Setenv ( "COSIGN_EXPERIMENTAL" , "1" )
defer os . Unsetenv ( "COSIGN_EXPERIMENTAL" )
}
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
2023-07-25 13:21:34 +00:00
tmpDir , err := fsutils . TempDir ( fs , "" , "cosign-tuf-" )
2023-05-05 16:43:21 +00:00
if err != nil {
return "" , err
}
_ = os . Setenv ( "TUF_ROOT" , tmpDir )
defer func ( fs v1 . FS , path string ) {
_ = fs . RemoveAll ( path )
} ( fs , tmpDir )
defer func ( ) {
_ = os . Unsetenv ( "TUF_ROOT" )
} ( )
out , err := runner . Run ( "cosign" , args ... )
return string ( out ) , err
}
// CreateSquashFS creates a squash file at destination from a source, with options
// TODO: Check validity of source maybe?
2024-03-01 11:27:26 +00:00
func CreateSquashFS ( runner v1 . Runner , logger sdkTypes . KairosLogger , source string , destination string , options [ ] string ) error {
2023-05-05 16:43:21 +00:00
// create args
args := [ ] string { source , destination }
// append options passed to args in order to have the correct order
// protect against options passed together in the same string , i.e. "-x add" instead of "-x", "add"
var optionsExpanded [ ] string
for _ , op := range options {
optionsExpanded = append ( optionsExpanded , strings . Split ( op , " " ) ... )
}
args = append ( args , optionsExpanded ... )
out , err := runner . Run ( "mksquashfs" , args ... )
if err != nil {
logger . Debugf ( "Error running squashfs creation, stdout: %s" , out )
logger . Errorf ( "Error while creating squashfs from %s to %s: %s" , source , destination , err )
return err
}
return nil
}
// LoadEnvFile will try to parse the file given and return a map with the kye/values
func LoadEnvFile ( fs v1 . FS , file string ) ( map [ string ] string , error ) {
var envMap map [ string ] string
var err error
f , err := fs . Open ( file )
if err != nil {
return envMap , err
}
defer f . Close ( )
envMap , err = godotenv . Parse ( f )
if err != nil {
return envMap , err
}
return envMap , err
}
2024-09-17 13:27:31 +00:00
func IsMounted ( config * agentConfig . Config , part * types . Partition ) ( bool , error ) {
2023-05-05 16:43:21 +00:00
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
}
// 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
2023-07-25 13:21:34 +00:00
func GetTempDir ( config * agentConfig . Config , suffix string ) string {
2023-05-05 16:43:21 +00:00
// if we got a TMPDIR var, respect and use that
if suffix == "" {
random . Seed ( time . Now ( ) . UnixNano ( ) )
suffix = strconv . Itoa ( int ( random . Uint32 ( ) ) )
}
elementalTmpDir := fmt . Sprintf ( "elemental-%s" , suffix )
dir := os . Getenv ( "TMPDIR" )
if dir != "" {
config . Logger . Debugf ( "Got tmpdir from TMPDIR var: %s" , dir )
return filepath . Join ( dir , elementalTmpDir )
}
2024-09-17 13:27:31 +00:00
parts , err := partitions . GetAllPartitions ( & config . Logger )
2023-05-05 16:43:21 +00:00
if err != nil {
config . Logger . Debug ( "Could not get partitions, defaulting to /tmp" )
return filepath . Join ( "/" , "tmp" , elementalTmpDir )
}
// Check persistent and if its mounted
ep := v1 . NewElementalPartitionsFromList ( parts )
persistent := ep . Persistent
if persistent != nil {
if mnt , _ := IsMounted ( config , persistent ) ; mnt {
config . Logger . Debugf ( "Using tmpdir on persistent volume: %s" , persistent . MountPoint )
return filepath . Join ( persistent . MountPoint , "tmp" , elementalTmpDir )
}
}
config . Logger . Debug ( "Could not get any valid tmpdir, defaulting to /tmp" )
return filepath . Join ( "/" , "tmp" , elementalTmpDir )
}
// IsLocalURI returns true if the uri has "file" scheme or no scheme and URI is
// not prefixed with a domain (container registry style). Returns false otherwise.
// Error is not nil only if the url can't be parsed.
func IsLocalURI ( uri string ) ( bool , error ) {
u , err := url . Parse ( uri )
if err != nil {
return false , err
}
if u . Scheme == "file" {
return true , nil
}
if u . Scheme == "" {
// Check first part of the path is not a domain (e.g. registry.suse.com/elemental)
// reference.ParsedNamed expects a <domain>[:<port>]/<path>[:<tag>] form.
if _ , err = reference . ParseNamed ( uri ) ; err != nil {
return true , nil
}
}
return false , nil
}
// IsHTTPURI returns true if the uri has "http" or "https" scheme, returns false otherwise.
// Error is not nil only if the url can't be parsed.
func IsHTTPURI ( uri string ) ( bool , error ) {
u , err := url . Parse ( uri )
if err != nil {
return false , err
}
if u . Scheme == "http" || u . Scheme == "https" {
return true , nil
}
return false , nil
}
// 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.
2023-07-25 13:21:34 +00:00
func GetSource ( config * agentConfig . Config , source string , destination string ) error {
2023-05-05 16:43:21 +00:00
local , err := IsLocalURI ( source )
if err != nil {
config . Logger . Errorf ( "Not a valid url: %s" , source )
return err
}
err = vfs . MkdirAll ( config . Fs , filepath . Dir ( destination ) , cnst . DirPerm )
if err != nil {
2023-06-20 06:58:01 +00:00
config . Logger . Debugf ( "Failed creating dir %s: %s\n" , filepath . Dir ( destination ) , err . Error ( ) )
2023-05-05 16:43:21 +00:00
return err
}
if local {
u , _ := url . Parse ( source )
2024-03-18 10:57:34 +00:00
_ , err := config . Fs . Stat ( u . Path )
if err != nil {
return fmt . Errorf ( "source %s does not exist" , source )
}
2023-05-05 16:43:21 +00:00
err = CopyFile ( config . Fs , u . Path , destination )
if err != nil {
2023-06-20 06:58:01 +00:00
config . Logger . Debugf ( "error copying source from %s to %s: %s\n" , source , destination , err . Error ( ) )
2023-05-05 16:43:21 +00:00
return err
}
} else {
err = config . Client . GetURL ( config . Logger , source , destination )
if err != nil {
return err
}
}
return nil
}
// ValidContainerReferece returns true if the given string matches
// a container registry reference, false otherwise
func ValidContainerReference ( ref string ) bool {
if _ , err := reference . ParseNormalizedNamed ( ref ) ; err != nil {
return false
}
return true
}
// ValidTaggedContainerReferece returns true if the given string matches
// a container registry reference including a tag, false otherwise.
func ValidTaggedContainerReference ( ref string ) bool {
n , err := reference . ParseNormalizedNamed ( ref )
if err != nil {
return false
}
if reference . IsNameOnly ( n ) {
return false
}
return true
}
// FindFileWithPrefix looks for a file in the given path matching one of the given
// prefixes. Returns the found file path including the given path. It does not
// check subfolders recusively
func FindFileWithPrefix ( fs v1 . FS , path string , prefixes ... string ) ( string , error ) {
files , err := fs . ReadDir ( path )
if err != nil {
return "" , err
}
for _ , f := range files {
if f . IsDir ( ) {
continue
}
for _ , p := range prefixes {
if strings . HasPrefix ( f . Name ( ) , p ) {
2024-03-18 10:57:34 +00:00
info , _ := f . Info ( )
if info . Mode ( ) & os . ModeSymlink == os . ModeSymlink {
2023-05-05 16:43:21 +00:00
found , err := fs . Readlink ( filepath . Join ( path , f . Name ( ) ) )
if err == nil {
if ! filepath . IsAbs ( found ) {
found = filepath . Join ( path , found )
}
2023-07-25 13:21:34 +00:00
if exists , _ := fsutils . Exists ( fs , found ) ; exists {
2023-05-05 16:43:21 +00:00
return found , nil
}
}
} else {
return filepath . Join ( path , f . Name ( ) ) , nil
}
}
}
}
return "" , fmt . Errorf ( "No file found with prefixes: %v" , prefixes )
}
// 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 )
if err != nil {
return "" , err
}
defer f . Close ( )
h := sha256 . New ( )
if _ , err := io . Copy ( h , f ) ; err != nil {
return "" , err
}
return fmt . Sprintf ( "%x" , h . Sum ( nil ) ) , nil
}
// FindCommand will search for the command(s) in the options given to find the current command
// If it cant find it returns the default value give. Useful for the same binaries with different names across OS
2025-04-25 08:43:21 +00:00
func FindCommand ( fs v1 . FS , defaultPath string , options [ ] string ) string {
2023-05-05 16:43:21 +00:00
for _ , p := range options {
2025-04-25 08:43:21 +00:00
// If its a full path, check if it exists directly
if strings . Contains ( p , "/" ) {
d , err := fs . Stat ( p )
if err != nil {
continue
}
if d . IsDir ( ) {
continue
}
m := d . Mode ( )
// Check if its executable
if m & 0111 != 0 {
return p
}
} else {
path , err := exec . LookPath ( p )
if err == nil {
return path
}
2023-05-05 16:43:21 +00:00
}
}
// Otherwise return default
return defaultPath
}
2024-01-09 14:10:04 +00:00
// 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
}
2024-02-21 09:44:32 +00:00
// IsUkiWithFs checks if the system is running in UKI mode
// by checking the kernel command line for the rd.immucore.uki flag
// Uses a v1.Fs interface to allow for testing
func IsUkiWithFs ( fs v1 . FS ) bool {
cmdline , _ := fs . ReadFile ( "/proc/cmdline" )
if strings . Contains ( string ( cmdline ) , "rd.immucore.uki" ) {
return true
}
return false
}
2024-01-09 14:10:04 +00:00
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" )
2024-01-10 09:38:31 +00:00
if err == nil {
2024-01-09 14:10:04 +00:00
return UkiHDD
}
return UkiRemovableMedia
}
return state . Unknown
}
2024-02-21 09:44:32 +00:00
// SystemdBootConfReader reads a systemd-boot conf file and returns a map with the key/value pairs
// TODO: Move this to the sdk with the FS interface
2024-03-18 10:57:34 +00:00
func SystemdBootConfReader ( vfs v1 . FS , filePath string ) ( map [ string ] string , error ) {
file , err := vfs . Open ( filePath )
2024-02-21 09:44:32 +00:00
if err != nil {
return nil , err
}
2024-03-18 10:57:34 +00:00
defer func ( file fs . File ) {
2024-02-21 09:44:32 +00:00
_ = file . Close ( )
} ( file )
result := make ( map [ string ] string )
scanner := bufio . NewScanner ( file )
for scanner . Scan ( ) {
line := scanner . Text ( )
parts := strings . SplitN ( line , " " , 2 )
if len ( parts ) == 2 {
result [ parts [ 0 ] ] = parts [ 1 ]
}
if len ( parts ) == 1 {
result [ parts [ 0 ] ] = ""
}
}
if err := scanner . Err ( ) ; err != nil {
return nil , err
}
return result , nil
}
// SystemdBootConfWriter writes a map to a systemd-boot conf file
// TODO: Move this to the sdk with the FS interface
func SystemdBootConfWriter ( fs v1 . FS , filePath string , conf map [ string ] string ) error {
file , err := fs . Create ( filePath )
if err != nil {
return err
}
defer func ( file * os . File ) {
_ = file . Close ( )
} ( file )
writer := bufio . NewWriter ( file )
for k , v := range conf {
if v == "" {
_ , err = writer . WriteString ( fmt . Sprintf ( "%s \n" , k ) )
} else {
_ , err = writer . WriteString ( fmt . Sprintf ( "%s %s\n" , k , v ) )
}
if err != nil {
return err
}
}
return writer . Flush ( )
}
2024-08-21 07:25:10 +00:00
// CheckFailedInstallation checks if the state file if present, and if it is, it will return true and the error with the file content indicating why we should abort the installation
func CheckFailedInstallation ( stateFile string ) ( bool , error ) {
if _ , err := os . Stat ( stateFile ) ; err == nil {
content , err := os . ReadFile ( stateFile )
if err != nil {
return true , err
}
return true , fmt . Errorf ( "Installation failed: %s" , string ( content ) )
}
return false , nil
}
2024-11-27 10:16:56 +00:00
// AddBootAssessment adds boot assessment to files by appending +3 to the name
// Only for files that dont have it already as those are the ones upgraded
// Existing files that have a boot assessment will be left as is
// This should be called during install, upgrade and reset
// Mainly everything that updates the config files to point to a new artifact we need to reset the boot assessment
// as its a new artifact that needs to be assessed
func AddBootAssessment ( fs v1 . FS , artifactDir string , logger sdkTypes . KairosLogger ) error {
return fsutils . WalkDirFs ( fs , artifactDir , func ( path string , info os . DirEntry , err error ) error {
if err != nil {
return err
}
// Only do files that are conf files but dont match the loader.conf
if ! info . IsDir ( ) && filepath . Ext ( path ) == ".conf" && ! strings . Contains ( info . Name ( ) , "loader.conf" ) {
dir := filepath . Dir ( path )
ext := filepath . Ext ( path )
base := strings . TrimSuffix ( filepath . Base ( path ) , ext )
// Lets check if the file has a boot assessment already. If it does, we dont need to do anything
// If it matches continue
re := regexp . MustCompile ( ` \+\d+(-\d+)?$ ` )
if re . MatchString ( base ) {
logger . Logger . Debug ( ) . Str ( "file" , path ) . Msg ( "Boot assessment already present in file" )
return nil
}
newBase := fmt . Sprintf ( "%s+3%s" , base , ext )
newPath := filepath . Join ( dir , newBase )
logger . Logger . Debug ( ) . Str ( "from" , path ) . Str ( "to" , newPath ) . Msg ( "Enabling boot assessment" )
err = fs . Rename ( path , newPath )
if err != nil {
logger . Logger . Err ( err ) . Str ( "from" , path ) . Str ( "to" , newPath ) . Msg ( "Error renaming file" )
return err
}
}
return nil
} )
}
func ReadAssessmentFromEntry ( fs v1 . FS , entry string , logger sdkTypes . KairosLogger ) ( string , error ) {
// Read current config for boot assessment from current config. We should already have the final config name
// Fix fallback and cos pointing to passive and active
if strings . HasPrefix ( entry , "fallback" ) {
entry = strings . Replace ( entry , "fallback" , "passive" , 1 )
}
if strings . HasPrefix ( entry , "cos" ) {
entry = strings . Replace ( entry , "cos" , "active" , 1 )
}
efiPart , err := partitions . GetEfiPartition ( & logger )
if err != nil {
return "" , err
}
// We only want the ones that match the assessment
currentfile , err := fsutils . GlobFs ( fs , filepath . Join ( efiPart . MountPoint , "loader/entries" , entry + "+*.conf" ) )
if err != nil {
return "" , err
}
if len ( currentfile ) == 0 {
return "" , nil
}
if len ( currentfile ) > 1 {
return "" , fmt . Errorf ( cnst . MultipleEntriesAssessmentError , entry )
}
re := regexp . MustCompile ( ` (\+\d+(-\d+)?)\.conf$ ` )
if ! re . MatchString ( currentfile [ 0 ] ) {
logger . Logger . Debug ( ) . Str ( "file" , currentfile [ 0 ] ) . Msg ( cnst . NoBootAssessmentWarning )
return "" , nil
}
return re . FindStringSubmatch ( currentfile [ 0 ] ) [ 1 ] , nil
}