diff --git a/go.mod b/go.mod index d17b1c4b0b..d51db3aff2 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,6 @@ require ( golang.org/x/text v0.19.0 google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.35.1 - gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.31.1 k8s.io/apimachinery v0.31.1 k8s.io/client-go v0.31.1 @@ -92,6 +91,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/cli-runtime v0.31.1 // indirect k8s.io/component-base v0.31.1 // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect diff --git a/pkg/common/file.go b/pkg/common/file.go index 0af620eded..7c2f5dec16 100644 --- a/pkg/common/file.go +++ b/pkg/common/file.go @@ -19,10 +19,11 @@ package common import ( "fmt" "os" - gofilepath "path/filepath" - "strconv" + "path/filepath" + "reflect" - "gopkg.in/yaml.v3" + "github.com/sirupsen/logrus" + "sigs.k8s.io/yaml" ) // Config is the internal representation of the yaml that defines @@ -34,175 +35,123 @@ type Config struct { Debug bool PullImageOnCreate bool DisablePullOnRun bool - yamlData *yaml.Node // YAML representation of config } +const ( + runtimeEndpointKey = "runtime-endpoint" + imageEndpointKey = "image-endpoint" + timeoutKey = "timeout" + debugKey = "debug" + pullImageOnCreateKey = "pull-image-on-create" + disablePullOnRunKey = "disable-pull-on-run" +) + // ReadConfig reads from a file with the given name and returns a config or // an error if the file was unable to be parsed. -func ReadConfig(filepath string) (*Config, error) { - data, err := os.ReadFile(filepath) +func ReadConfig(path string) (*Config, error) { + data, err := os.ReadFile(path) if err != nil { - return nil, err + return nil, fmt.Errorf("read config file path: %w", err) } - yamlConfig := &yaml.Node{} - err = yaml.Unmarshal(data, yamlConfig) - if err != nil { - return nil, err + + m := map[string]any{} + if err := yaml.Unmarshal(data, &m); err != nil { + return nil, fmt.Errorf("unmarshal YAML config: %w", err) } - config, err := getConfigOptions(yamlConfig) - if err != nil { - return nil, err + logrus.Debugf("Unmarshaled config map: %v", m) + + c := &Config{} + + if v, ok := mapKeyValue[string](m, runtimeEndpointKey); ok { + c.RuntimeEndpoint = v } - return config, err -} -// WriteConfig writes config to file -// an error if the file was unable to be written to. -func WriteConfig(c *Config, filepath string) error { - if c == nil { - c = new(Config) - c.yamlData = new(yaml.Node) + if v, ok := mapKeyValue[string](m, imageEndpointKey); ok { + c.ImageEndpoint = v } - setConfigOptions(c) + if v, ok := mapKeyValue[int](m, timeoutKey); ok { + c.Timeout = v + } - data, err := yaml.Marshal(c.yamlData) - if err != nil { - return err + if v, ok := mapKeyValue[bool](m, debugKey); ok { + c.Debug = v } - if err := os.MkdirAll(gofilepath.Dir(filepath), 0o755); err != nil { - return err + if v, ok := mapKeyValue[bool](m, pullImageOnCreateKey); ok { + c.PullImageOnCreate = v } - return os.WriteFile(filepath, data, 0o644) -} -// Extracts config options from the yaml data which is loaded from file. -func getConfigOptions(yamlData *yaml.Node) (*Config, error) { - config := &Config{yamlData: yamlData} - - if len(yamlData.Content) == 0 || - yamlData.Content[0].Content == nil { - return config, nil - } - contentLen := len(yamlData.Content[0].Content) - - // YAML representation contains 2 yaml ScalarNodes per config option. - // One is config option name and other is the value of the option - // These ScalarNodes help preserve comments associated with - // the YAML entry - for indx := 0; indx < contentLen-1; { - configOption := yamlData.Content[0].Content[indx] - name := configOption.Value - value := yamlData.Content[0].Content[indx+1].Value - var err error - switch name { - case "runtime-endpoint": - config.RuntimeEndpoint = value - case "image-endpoint": - config.ImageEndpoint = value - case "timeout": - config.Timeout, err = strconv.Atoi(value) - if err != nil { - return nil, fmt.Errorf("parsing config option '%s': %w", name, err) - } - case "debug": - config.Debug, err = strconv.ParseBool(value) - if err != nil { - return nil, fmt.Errorf("parsing config option '%s': %w", name, err) - } - case "pull-image-on-create": - config.PullImageOnCreate, err = strconv.ParseBool(value) - if err != nil { - return nil, fmt.Errorf("parsing config option '%s': %w", name, err) - } - case "disable-pull-on-run": - config.DisablePullOnRun, err = strconv.ParseBool(value) - if err != nil { - return nil, fmt.Errorf("parsing config option '%s': %w", name, err) - } - default: - return nil, fmt.Errorf("Config option '%s' is not valid", name) - } - indx += 2 + if v, ok := mapKeyValue[bool](m, disablePullOnRunKey); ok { + c.DisablePullOnRun = v } - return config, nil + return c, nil } -// Set config options on yaml data for persistece to file. -func setConfigOptions(config *Config) { - setConfigOption("runtime-endpoint", config.RuntimeEndpoint, config.yamlData) - setConfigOption("image-endpoint", config.ImageEndpoint, config.yamlData) - setConfigOption("timeout", strconv.Itoa(config.Timeout), config.yamlData) - setConfigOption("debug", strconv.FormatBool(config.Debug), config.yamlData) - setConfigOption("pull-image-on-create", strconv.FormatBool(config.PullImageOnCreate), config.yamlData) - setConfigOption("disable-pull-on-run", strconv.FormatBool(config.DisablePullOnRun), config.yamlData) -} +func mapKeyValue[T any](m map[string]any, key string) (ret T, ok bool) { + if value, ok := m[key]; ok { + // Even Integer values will be interpreted as float + if reflect.TypeOf(value).Kind() == reflect.Float64 { + //nolint:forcetypeassert // type assertion done before + value = int(value.(float64)) + } -// Set config option on yaml. -func setConfigOption(configName, configValue string, yamlData *yaml.Node) { - if len(yamlData.Content) == 0 { - yamlData.Kind = yaml.DocumentNode - yamlData.Content = make([]*yaml.Node, 1) - yamlData.Content[0] = &yaml.Node{ - Kind: yaml.MappingNode, - Tag: "!!map", + if typedValue, ok := value.(T); ok { + return typedValue, true + } else { + logrus.Warnf("Invalid value \"%T\" for key %q", value, key) } } - contentLen := 0 - foundOption := false - if yamlData.Content[0].Content != nil { - contentLen = len(yamlData.Content[0].Content) + return ret, false +} + +// WriteConfig writes config to file and return +// an error if the file was unable to be written. +func WriteConfig(c *Config, path string) error { + if c == nil { + c = &Config{} } - // Set value on existing config option - for indx := 0; indx < contentLen-1; { - name := yamlData.Content[0].Content[indx].Value - if name == configName { - yamlData.Content[0].Content[indx+1].Value = configValue - foundOption = true - break - } - indx += 2 - } - - // New config option to set - // YAML representation contains 2 yaml ScalarNodes per config option. - // One is config option name and other is the value of the option - // These ScalarNodes help preserve comments associated with - // the YAML entry - if !foundOption { - const ( - tagPrefix = "!!" - tagStr = tagPrefix + "str" - tagBool = tagPrefix + "bool" - tagInt = tagPrefix + "int" - ) - name := &yaml.Node{ - Kind: yaml.ScalarNode, - Value: configName, - Tag: tagStr, - } - var tagType string - switch configName { - case "timeout": - tagType = tagInt - case "debug": - tagType = tagBool - case "pull-image-on-create": - tagType = tagBool - case "disable-pull-on-run": - tagType = tagBool - default: - tagType = tagStr - } + m := map[string]any{} - value := &yaml.Node{ - Kind: yaml.ScalarNode, - Value: configValue, - Tag: tagType, - } - yamlData.Content[0].Content = append(yamlData.Content[0].Content, name, value) + if c.RuntimeEndpoint != "" { + m[runtimeEndpointKey] = c.RuntimeEndpoint + } + + if c.ImageEndpoint != "" { + m[imageEndpointKey] = c.ImageEndpoint + } + + if c.Timeout != 0 { + m[timeoutKey] = c.Timeout + } + + if c.Debug { + m[debugKey] = c.Debug + } + + if c.PullImageOnCreate { + m[pullImageOnCreateKey] = c.PullImageOnCreate } + + if c.DisablePullOnRun { + m[disablePullOnRunKey] = c.DisablePullOnRun + } + + logrus.Debugf("Marshalling config map: %v", m) + data, err := yaml.Marshal(m) + if err != nil { + return fmt.Errorf("marshal YAML config: %w", err) + } + + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return fmt.Errorf("ensure config path dir: %w", err) + } + + if err := os.WriteFile(path, data, 0o644); err != nil { + return fmt.Errorf("write config file: %w", err) + } + + return nil } diff --git a/pkg/framework/util.go b/pkg/framework/util.go index 15fd542329..8b397a00cb 100644 --- a/pkg/framework/util.go +++ b/pkg/framework/util.go @@ -29,10 +29,10 @@ import ( "github.com/google/uuid" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "gopkg.in/yaml.v3" internalapi "k8s.io/cri-api/pkg/apis" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" remote "k8s.io/cri-client/pkg" + "sigs.k8s.io/yaml" "sigs.k8s.io/cri-tools/pkg/common" )