package utils import ( "bufio" "bytes" "fmt" "os" "github.com/hashicorp/go-multierror" "github.com/kairos-io/immucore/internal/constants" "github.com/mudler/yip/pkg/console" "github.com/mudler/yip/pkg/executor" "github.com/mudler/yip/pkg/logger" "github.com/mudler/yip/pkg/plugins" "github.com/mudler/yip/pkg/schema" "github.com/rs/zerolog" "github.com/twpayne/go-vfs" "gopkg.in/yaml.v3" ) func NewYipExecutor(l logger.Interface) executor.Executor { exec := executor.NewExecutor( executor.WithLogger(l), executor.WithConditionals( plugins.NodeConditional, plugins.IfConditional, ), executor.WithPlugins( // Note, the plugin execution order depends on the order passed here plugins.DNS, plugins.Download, plugins.Git, plugins.Entities, plugins.EnsureDirectories, plugins.EnsureFiles, plugins.Commands, plugins.DeleteEntities, plugins.Hostname, plugins.Sysctl, plugins.User, plugins.SSH, plugins.LoadModules, plugins.Timesyncd, plugins.Systemctl, plugins.Environment, plugins.SystemdFirstboot, plugins.DataSources, plugins.Layout, ), ) return exec } func RunStage(stage string) (bytes.Buffer, error) { var allErrors, err error var cmdLineYipURI string var buffer bytes.Buffer var level zerolog.Level // Specific log here so it writes to a buffer and we can return that as output level = zerolog.InfoLevel // Set debug level debug := len(ReadCMDLineArg("rd.immucore.debug")) > 0 debugFromEnv := os.Getenv("IMMUCORE_DEBUG") != "" if debug || debugFromEnv { level = zerolog.DebugLevel } log := MiddleLog{zerolog.New(bufio.NewWriter(&buffer)).With().Logger().Level(level)} // Set debug logger yip := NewYipExecutor(log) c := ImmucoreConsole{} stageBefore := fmt.Sprintf("%s.before", stage) stageAfter := fmt.Sprintf("%s.after", stage) // Deprecated? Is nowhere on the docs.... cosSetup := ReadCMDLineArg("cos.setup") if len(cosSetup) > 1 { cmdLineYipURI = cosSetup[1] } // Run all stages for each of the default cloud config paths + extra cloud config paths for _, s := range []string{stageBefore, stage, stageAfter} { err = yip.Run(s, vfs.OSFS, c, constants.GetCloudInitPaths()...) 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} { err = yip.Run(s, vfs.OSFS, c, cmdLineArgs...) if err != nil { allErrors = multierror.Append(allErrors, err) } } } // Enable dot notation // This helps to parse the cmdline in dot notation (stage.name.command) from cmdline // IMHO this should be deprecated, plenty of other places to set the stages config yip.Modifier(schema.DotNotationModifier) // Read and parse the cmdline looking for yip config in there cmdLineOut, err := os.ReadFile("/proc/cmdline") if err == nil { for _, s := range []string{stageBefore, stage, stageAfter} { err = yip.Run(s, vfs.OSFS, console.NewStandardConsole(), string(cmdLineOut)) if err != nil { allErrors = checkYAMLError(allErrors, err) } } } // Set back the modifier to nil yip.Modifier(nil) // Not doing anything with the errors yet, need to know which ones are permissible (no metadata, marshall errors, etc..) return buffer, nil } 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(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) } return allErrors }