mirror of
https://github.com/kairos-io/kairos-agent.git
synced 2025-06-03 01:44:53 +00:00

This only shows for a given stage what steps would be run and in which order Signed-off-by: Itxaka <itxaka@kairos.io>
164 lines
4.9 KiB
Go
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
|
|
}
|