feat: --set contains multi string "=". (#2351)

* fix: --set contains multi string "=".

Signed-off-by: joyceliu <joyceliu@yunify.com>

* fix: --set contains multi string "=".

Signed-off-by: joyceliu <joyceliu@yunify.com>

---------

Signed-off-by: joyceliu <joyceliu@yunify.com>
Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
II 2024-08-09 10:22:59 +08:00 committed by GitHub
parent 4a060a91fe
commit 1aa519d295
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 121 additions and 43 deletions

View File

@ -84,7 +84,7 @@ func (o *CommonOptions) Flags() cliflag.NamedFlagSets {
gfs.StringVar(&o.WorkDir, "work-dir", o.WorkDir, "the base Dir for kubekey. Default current dir. ")
gfs.StringVarP(&o.Artifact, "artifact", "a", "", "Path to a KubeKey artifact")
gfs.StringVarP(&o.ConfigFile, "config", "c", o.ConfigFile, "the config file path. support *.yaml ")
gfs.StringArrayVar(&o.Set, "set", o.Set, "set value in config. format --set key=val")
gfs.StringArrayVar(&o.Set, "set", o.Set, "set value in config. format --set key=val or --set k1=v1,k2=v2")
gfs.StringVarP(&o.InventoryFile, "inventory", "i", o.InventoryFile, "the host list file path. support *.ini")
gfs.BoolVarP(&o.Debug, "debug", "d", o.Debug, "Debug mode, after a successful execution of Pipeline, will retain runtime data, which includes task execution status and parameters.")
gfs.StringVarP(&o.Namespace, "namespace", "n", o.Namespace, "the namespace which pipeline will be executed, all reference resources(pipeline, config, inventory, task) should in the same namespace")
@ -100,34 +100,10 @@ func (o *CommonOptions) completeRef(pipeline *kubekeyv1.Pipeline) (*kubekeyv1.Co
o.WorkDir = filepath.Join(wd, o.WorkDir)
}
config, err := genConfig(o.ConfigFile)
config, err := o.genConfig()
if err != nil {
return nil, nil, fmt.Errorf("generate config error: %w", err)
}
config.Namespace = o.Namespace
if wd, err := config.GetValue("work_dir"); err == nil && wd != nil {
// if work_dir is defined in config, use it. otherwise use current dir.
o.WorkDir = wd.(string)
} else if err := config.SetValue("work_dir", o.WorkDir); err != nil {
return nil, nil, fmt.Errorf("work_dir to config error: %w", err)
}
if o.Artifact != "" {
// override artifact_file in config
if err := config.SetValue("artifact_file", o.Artifact); err != nil {
return nil, nil, fmt.Errorf("artifact file to config error: %w", err)
}
}
for _, s := range o.Set {
s = unescapeString(s)
ss := strings.Split(s, "=")
if len(ss) != 2 {
return nil, nil, fmt.Errorf("--set value should be k=v")
}
if err := setValue(config, ss[0], ss[1]); err != nil {
return nil, nil, fmt.Errorf("--set value to config error: %w", err)
}
}
pipeline.Spec.ConfigRef = &corev1.ObjectReference{
Kind: config.Kind,
Namespace: config.Namespace,
@ -137,11 +113,10 @@ func (o *CommonOptions) completeRef(pipeline *kubekeyv1.Pipeline) (*kubekeyv1.Co
ResourceVersion: config.ResourceVersion,
}
inventory, err := genInventory(o.InventoryFile)
inventory, err := o.genInventory()
if err != nil {
return nil, nil, fmt.Errorf("generate inventory error: %w", err)
}
inventory.Namespace = o.Namespace
pipeline.Spec.InventoryRef = &corev1.ObjectReference{
Kind: inventory.Kind,
Namespace: inventory.Namespace,
@ -154,40 +129,77 @@ func (o *CommonOptions) completeRef(pipeline *kubekeyv1.Pipeline) (*kubekeyv1.Co
return config, inventory, nil
}
func genConfig(configFile string) (*kubekeyv1.Config, error) {
if configFile != "" {
cdata, err := os.ReadFile(configFile)
// genConfig generate config by ConfigFile and set value by command args.
func (o *CommonOptions) genConfig() (*kubekeyv1.Config, error) {
config := defaultConfig.DeepCopy()
if o.ConfigFile != "" {
cdata, err := os.ReadFile(o.ConfigFile)
if err != nil {
return nil, fmt.Errorf("read config file error: %w", err)
}
config := &kubekeyv1.Config{}
config = &kubekeyv1.Config{}
if err := yaml.Unmarshal(cdata, config); err != nil {
return nil, fmt.Errorf("unmarshal config file error: %w", err)
}
return config, nil
}
// set by command args
if o.Namespace != "" {
config.Namespace = o.Namespace
}
if wd, err := config.GetValue("work_dir"); err == nil && wd != nil {
// if work_dir is defined in config, use it. otherwise use current dir.
o.WorkDir = wd.(string)
} else if err := config.SetValue("work_dir", o.WorkDir); err != nil {
return nil, fmt.Errorf("work_dir to config error: %w", err)
}
if o.Artifact != "" {
// override artifact_file in config
if err := config.SetValue("artifact_file", o.Artifact); err != nil {
return nil, fmt.Errorf("artifact file to config error: %w", err)
}
}
for _, s := range o.Set {
for _, setVal := range strings.Split(unescapeString(s), ",") {
i := strings.Index(setVal, "=")
if i == 0 || i == -1 {
return nil, fmt.Errorf("--set value should be k=v")
}
if err := setValue(config, setVal[:i], setVal[i+1:]); err != nil {
return nil, fmt.Errorf("--set value to config error: %w", err)
}
}
}
return defaultConfig, nil
return config, nil
}
func genInventory(inventoryFile string) (*kubekeyv1.Inventory, error) {
if inventoryFile != "" {
cdata, err := os.ReadFile(inventoryFile)
// genConfig generate config by ConfigFile and set value by command args.
func (o *CommonOptions) genInventory() (*kubekeyv1.Inventory, error) {
inventory := defaultInventory.DeepCopy()
if o.InventoryFile != "" {
cdata, err := os.ReadFile(o.InventoryFile)
if err != nil {
klog.V(4).ErrorS(err, "read config file error")
return nil, err
}
inventory := &kubekeyv1.Inventory{}
inventory = &kubekeyv1.Inventory{}
if err := yaml.Unmarshal(cdata, inventory); err != nil {
klog.V(4).ErrorS(err, "unmarshal config file error")
return nil, err
}
return inventory, nil
}
// set by command args
if o.Namespace != "" {
inventory.Namespace = o.Namespace
}
return defaultInventory, nil
return inventory, nil
}
// setValue set key: val in config.
// if val is json string. convert to map or slice
// if val is TRUE,YES,Y. convert to bool type true.
// if val is FALSE,NO,N. convert to bool type false.
func setValue(config *kubekeyv1.Config, key, val string) error {
switch {
case strings.HasPrefix(val, "{") && strings.HasSuffix(val, "{"):
@ -227,8 +239,8 @@ func unescapeString(s string) string {
}
// Iterate over the replacements map and replace escape sequences in the string
for old, new := range replacements {
s = strings.ReplaceAll(s, old, new)
for o, n := range replacements {
s = strings.ReplaceAll(s, o, n)
}
return s

View File

@ -48,6 +48,8 @@ func init() {
SchemeBuilder.Register(&Config{}, &ConfigList{})
}
// SetValue to config
// if key contains "." (a.b), will convert map and set value (a:b:value)
func (c *Config) SetValue(key string, value any) error {
configMap := make(map[string]any)
if c.Spec.Raw != nil {
@ -77,11 +79,25 @@ func (c *Config) SetValue(key string, value any) error {
return nil
}
// GetValue by key
// if key contains "." (a.b), find by the key path (if a:b:value in config.and get value)
func (c *Config) GetValue(key string) (any, error) {
configMap := make(map[string]any)
if err := json.Unmarshal(c.Spec.Raw, &configMap); err != nil {
return nil, err
}
if key == "" {
return configMap, nil
}
// get value
return configMap[key], nil
var result any = configMap
for _, k := range strings.Split(key, ".") {
r, ok := result.(map[string]any)
if !ok {
// cannot find value
return nil, nil
}
result = r[k]
}
return result, nil
}

View File

@ -59,3 +59,53 @@ func TestSetValue(t *testing.T) {
})
}
}
func TestGetValue(t *testing.T) {
testcases := []struct {
name string
key string
config Config
except any
}{
{
name: "all value",
key: "",
config: Config{Spec: runtime.RawExtension{Raw: []byte(`{"a":1}`)}},
except: map[string]interface{}{
"a": int64(1),
},
},
{
name: "none value",
key: "b",
config: Config{Spec: runtime.RawExtension{Raw: []byte(`{"a":1}`)}},
except: nil,
},
{
name: "none multi value",
key: "b.c",
config: Config{Spec: runtime.RawExtension{Raw: []byte(`{"a":1}`)}},
except: nil,
},
{
name: "find one value",
key: "a",
config: Config{Spec: runtime.RawExtension{Raw: []byte(`{"a":1}`)}},
except: int64(1),
},
{
name: "find mulit value",
key: "a.b",
config: Config{Spec: runtime.RawExtension{Raw: []byte(`{"a":{"b":1}}`)}},
except: int64(1),
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
value, err := tc.config.GetValue(tc.key)
assert.NoError(t, err)
assert.Equal(t, tc.except, value)
})
}
}