immucore/internal/utils/cloudinit.go

147 lines
4.0 KiB
Go

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
}