kairos-agent/pkg/utils/runstage.go
Itxaka bd4dce015f
Expose the Analize method of yip (#548)
This only shows for a given stage what steps would be run and in which
order

Signed-off-by: Itxaka <itxaka@kairos.io>
2024-09-20 10:36:09 +02:00

164 lines
4.9 KiB
Go

/*
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"
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"
"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
}
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
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
}
// RunstageAnalyze
func RunStageAnalyze(cfg *agentConfig.Config, stage string) error {
return runstage(cfg, stage, true)
}
// RunStage will run yip
func RunStage(cfg *agentConfig.Config, stage string) error {
return runstage(cfg, stage, false)
}
func runstage(cfg *agentConfig.Config, stage string, analyze bool) error {
var cmdLineYipURI string
var allErrors error
var cloudInitPaths []string
cloudInitPaths = append(constants.GetCloudInitPaths(), cfg.CloudInitPaths...)
cfg.Logger.Debugf("Cloud-init paths set to %v", cloudInitPaths)
if analyze {
cfg.Logger.Info("Analyze mode, showing DAG")
}
// Make sure cloud init path specified are existing in the system
for _, cp := range cloudInitPaths {
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())
}
}
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} {
if analyze {
cfg.CloudInitRunner.Analyze(s, cloudInitPaths...)
} else {
err = cfg.CloudInitRunner.Run(s, cloudInitPaths...)
if err != nil {
allErrors = multierror.Append(allErrors, err)
}
}
}
// Run the stages if cmdline contains the cos.setup stanza
if cmdLineYipURI != "" {
cmdLineArgs := []string{cmdLineYipURI}
for _, s := range []string{stageBefore, stage, stageAfter} {
if analyze {
cfg.CloudInitRunner.Analyze(s, cloudInitPaths...)
} else {
err = cfg.CloudInitRunner.Run(s, cmdLineArgs...)
if err != nil {
allErrors = multierror.Append(allErrors, err)
}
}
}
}
// Run stages encoded from /proc/cmdlines
cfg.CloudInitRunner.SetModifier(schema.DotNotationModifier)
for _, s := range []string{stageBefore, stage, stageAfter} {
if analyze {
cfg.CloudInitRunner.Analyze(s, cloudInitPaths...)
} else {
err = cfg.CloudInitRunner.Run(s, string(cmdLineOut))
if err != nil {
allErrors = checkYAMLError(cfg, allErrors, err)
}
}
}
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.
if allErrors != nil && !cfg.Strict {
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
}