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 (
"fmt"
2023-07-25 13:21:34 +00:00
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
"github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
2023-05-05 16:43:21 +00:00
"strings"
"github.com/hashicorp/go-multierror"
2023-07-10 12:39:48 +00:00
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
2023-05-05 16:43:21 +00:00
"github.com/mudler/yip/pkg/schema"
"gopkg.in/yaml.v3"
)
func onlyYAMLPartialErrors ( er error ) bool {
if merr , ok := er . ( * multierror . Error ) ; ok {
for _ , e := range merr . Errors {
// Skip partial unmarshalling errors
// TypeError is throwed when it is possible to read the yaml partially
// XXX: Seems errors.Is and errors.As are not working as expected here.
// Even if the underlying type is yaml.TypeError.
var d * yaml . TypeError
if fmt . Sprintf ( "%T" , e ) != fmt . Sprintf ( "%T" , d ) {
return false
}
}
}
return true
}
2023-07-25 13:21:34 +00:00
func checkYAMLError ( cfg * agentConfig . Config , allErrors , err error ) error {
2023-05-05 16:43:21 +00:00
if ! onlyYAMLPartialErrors ( err ) {
// here we absorb errors only if are related to YAML unmarshalling
// As cmdline is parsed out as a yaml file
allErrors = multierror . Append ( allErrors , err )
} else {
cfg . Logger . Debug ( "/proc/cmdline parsing returned errors while unmarshalling. Ignoring as /proc/cmdline fields are turned to a YAML document, and partial failures are valid" )
cfg . Logger . Debug ( err )
}
return allErrors
}
2024-09-20 08:36:09 +00:00
// RunstageAnalyze
func RunStageAnalyze ( cfg * agentConfig . Config , stage string ) error {
return runstage ( cfg , stage , true )
}
2023-05-05 16:43:21 +00:00
// RunStage will run yip
2023-07-25 13:21:34 +00:00
func RunStage ( cfg * agentConfig . Config , stage string ) error {
2024-09-20 08:36:09 +00:00
return runstage ( cfg , stage , false )
}
func runstage ( cfg * agentConfig . Config , stage string , analyze bool ) error {
2023-05-05 16:43:21 +00:00
var cmdLineYipURI string
var allErrors error
2023-07-25 09:08:27 +00:00
var cloudInitPaths [ ] string
2023-05-05 16:43:21 +00:00
2023-07-25 09:08:27 +00:00
cloudInitPaths = append ( constants . GetCloudInitPaths ( ) , cfg . CloudInitPaths ... )
2023-05-05 16:43:21 +00:00
cfg . Logger . Debugf ( "Cloud-init paths set to %v" , cloudInitPaths )
2024-09-20 08:36:09 +00:00
if analyze {
cfg . Logger . Info ( "Analyze mode, showing DAG" )
}
2023-05-05 16:43:21 +00:00
// Make sure cloud init path specified are existing in the system
for _ , cp := range cloudInitPaths {
2023-07-25 13:21:34 +00:00
err := fsutils . MkdirAll ( cfg . Fs , cp , constants . DirPerm )
2023-05-05 16:43:21 +00:00
if err != nil {
cfg . Logger . Debugf ( "Failed creating cloud-init config path: %s %s" , cp , err . Error ( ) )
}
}
stageBefore := fmt . Sprintf ( "%s.before" , stage )
stageAfter := fmt . Sprintf ( "%s.after" , stage )
// Check if the cmdline has the cos.setup key and extract its value to run yip on that given uri
cmdLineOut , err := cfg . Fs . ReadFile ( "/proc/cmdline" )
if err != nil {
allErrors = multierror . Append ( allErrors , err )
}
cmdLine := strings . Split ( string ( cmdLineOut ) , " " )
for _ , line := range cmdLine {
if strings . Contains ( line , "=" ) {
lineSplit := strings . Split ( line , "=" )
if lineSplit [ 0 ] == "cos.setup" {
cmdLineYipURI = lineSplit [ 1 ]
cfg . Logger . Debugf ( "Found cos.setup stanza on cmdline with value %s" , cmdLineYipURI )
}
}
}
// Run all stages for each of the default cloud config paths + extra cloud config paths
for _ , s := range [ ] string { stageBefore , stage , stageAfter } {
2024-09-20 08:36:09 +00:00
if analyze {
cfg . CloudInitRunner . Analyze ( s , cloudInitPaths ... )
} else {
err = cfg . CloudInitRunner . Run ( s , cloudInitPaths ... )
if err != nil {
allErrors = multierror . Append ( allErrors , err )
}
2023-05-05 16:43:21 +00:00
}
}
// Run the stages if cmdline contains the cos.setup stanza
if cmdLineYipURI != "" {
cmdLineArgs := [ ] string { cmdLineYipURI }
for _ , s := range [ ] string { stageBefore , stage , stageAfter } {
2024-09-20 08:36:09 +00:00
if analyze {
cfg . CloudInitRunner . Analyze ( s , cloudInitPaths ... )
} else {
err = cfg . CloudInitRunner . Run ( s , cmdLineArgs ... )
if err != nil {
allErrors = multierror . Append ( allErrors , err )
}
2023-05-05 16:43:21 +00:00
}
}
}
// Run stages encoded from /proc/cmdlines
cfg . CloudInitRunner . SetModifier ( schema . DotNotationModifier )
for _ , s := range [ ] string { stageBefore , stage , stageAfter } {
2024-09-20 08:36:09 +00:00
if analyze {
cfg . CloudInitRunner . Analyze ( s , cloudInitPaths ... )
} else {
err = cfg . CloudInitRunner . Run ( s , string ( cmdLineOut ) )
if err != nil {
allErrors = checkYAMLError ( cfg , allErrors , err )
}
2023-05-05 16:43:21 +00:00
}
2024-09-20 08:36:09 +00:00
2023-05-05 16:43:21 +00:00
}
cfg . CloudInitRunner . SetModifier ( nil )
// We return error here only if we have been running in strict mode.
// Cloud configs are being loaded and executed on a best-effort, so every step/config
// gets a chance to be executed and error is being appended and reported.
2023-07-25 09:08:27 +00:00
if allErrors != nil && ! cfg . Strict {
2023-05-05 16:43:21 +00:00
cfg . Logger . Info ( "Some errors found but were ignored. Enable --strict mode to fail on those or --debug to see them in the log" )
cfg . Logger . Warn ( allErrors )
return nil
}
return allErrors
}