From 34448781a6fb9febd8bdb4543424b9cb2e56f516 Mon Sep 17 00:00:00 2001 From: liujian <54946465+redscholar@users.noreply.github.com> Date: Mon, 31 Mar 2025 10:00:11 +0800 Subject: [PATCH] feat: use unstructured to get or set value for config (#2519) Signed-off-by: joyceliu Co-authored-by: joyceliu --- api/core/v1/config_types.go | 91 +++++--------- api/core/v1/config_types_test.go | 112 ------------------ .../install/cloud-config/tasks/main.yaml | 9 +- cmd/kk/app/options/builtin/create.go | 9 +- cmd/kk/app/options/builtin/init.go | 18 ++- cmd/kk/app/options/option.go | 24 ++-- pkg/connector/ssh_connector.go | 2 +- pkg/const/helper.go | 9 +- .../infrastructure/kkmachine_controller.go | 35 +++--- pkg/project/git.go | 11 +- pkg/variable/variable_get_test.go | 4 + 11 files changed, 98 insertions(+), 226 deletions(-) delete mode 100644 api/core/v1/config_types_test.go diff --git a/api/core/v1/config_types.go b/api/core/v1/config_types.go index a6895c61..e2f20c3b 100644 --- a/api/core/v1/config_types.go +++ b/api/core/v1/config_types.go @@ -17,13 +17,12 @@ limitations under the License. package v1 import ( - "reflect" - "strings" + "encoding/json" "github.com/cockroachdb/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/json" ) // Config store global vars for playbook. @@ -33,70 +32,44 @@ type Config struct { Spec runtime.RawExtension `json:"spec,omitempty"` } -// 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 { - if err := json.Unmarshal(c.Spec.Raw, &configMap); err != nil { - return errors.WithStack(err) - } +// UnmarshalJSON decodes spec.Raw into spec.Object +func (c *Config) UnmarshalJSON(data []byte) error { + type Alias Config + aux := &Alias{} + if err := json.Unmarshal(data, aux); err != nil { + return errors.Wrap(err, "failed to unmarshal config") } - // set value - var f func(input map[string]any, key []string, value any) any - f = func(input map[string]any, key []string, value any) any { - if len(key) == 0 { - return input + *c = Config(*aux) + + // Decode spec.Raw into spec.Object if it's not already set + if len(c.Spec.Raw) > 0 && c.Spec.Object == nil { + var objMap map[string]interface{} + if err := json.Unmarshal(c.Spec.Raw, &objMap); err != nil { + return errors.Wrap(err, "failed to unmarshal spec.Raw") } - - firstKey := key[0] - if len(key) == 1 { - input[firstKey] = value - - return input - } - - // Handle nested maps - if v, ok := input[firstKey]; ok && reflect.TypeOf(v).Kind() == reflect.Map { - if vd, ok := v.(map[string]any); ok { - input[firstKey] = f(vd, key[1:], value) - } - } else { - input[firstKey] = f(make(map[string]any), key[1:], value) - } - - return input + c.Spec.Object = &unstructured.Unstructured{Object: objMap} } - data, err := json.Marshal(f(configMap, strings.Split(key, "."), value)) - if err != nil { - return errors.Wrapf(err, "failed to marshal %q value to json", key) - } - c.Spec.Raw = data 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, errors.WithStack(err) - } - // get all value - if key == "" { - return configMap, nil - } - // get value - var result any = configMap - for _, k := range strings.Split(key, ".") { - r, ok := result.(map[string]any) - if !ok { - // cannot find value - return nil, errors.Errorf("cannot find key: %s", key) +// MarshalJSON ensures spec.Object is converted back to spec.Raw +func (c *Config) MarshalJSON() ([]byte, error) { + // Ensure spec.Object is serialized into spec.Raw + if c.Spec.Object != nil { + raw, err := json.Marshal(c.Spec.Object) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal spec.Object") } - result = r[k] + c.Spec.Raw = raw } - return result, nil + type Alias Config + return json.Marshal((*Alias)(c)) +} + +// Value returns the underlying map[string]any from the Config's unstructured Object. +// This provides direct access to the config values stored in Spec.Object. +func (c *Config) Value() map[string]any { + return c.Spec.Object.(*unstructured.Unstructured).Object } diff --git a/api/core/v1/config_types_test.go b/api/core/v1/config_types_test.go deleted file mode 100644 index 52a4ed44..00000000 --- a/api/core/v1/config_types_test.go +++ /dev/null @@ -1,112 +0,0 @@ -/* -Copyright 2024 The KubeSphere Authors. - -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 v1 - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/runtime" -) - -func TestSetValue(t *testing.T) { - testcases := []struct { - name string - key string - val any - except Config - }{ - { - name: "one level", - key: "a", - val: 2, - except: Config{Spec: runtime.RawExtension{Raw: []byte(`{"a":2}`)}}, - }, - { - name: "two level repeat key", - key: "a.b", - val: 2, - except: Config{Spec: runtime.RawExtension{Raw: []byte(`{"a":{"b":2}}`)}}, - }, - { - name: "two level no-repeat key", - key: "b.c", - val: 2, - except: Config{Spec: runtime.RawExtension{Raw: []byte(`{"a":1,"b":{"c":2}}`)}}, - }, - } - - for _, tc := range testcases { - in := Config{Spec: runtime.RawExtension{Raw: []byte(`{"a":1}`)}} - t.Run(tc.name, func(t *testing.T) { - err := in.SetValue(tc.key, tc.val) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, tc.except, in) - }) - } -} - -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]any{ - "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, _ := tc.config.GetValue(tc.key) - assert.Equal(t, tc.except, value) - }) - } -} diff --git a/builtin/capkk/roles/install/cloud-config/tasks/main.yaml b/builtin/capkk/roles/install/cloud-config/tasks/main.yaml index eae7ef68..8de4c398 100644 --- a/builtin/capkk/roles/install/cloud-config/tasks/main.yaml +++ b/builtin/capkk/roles/install/cloud-config/tasks/main.yaml @@ -140,4 +140,11 @@ loop: "{{ .kubernetes.roles | toJson }}" command: | #!/bin/bash - kubectl label node "{{ .hostname }}" node-role.kubernetes.io/{{ .item }}="" --overwrite \ No newline at end of file + kubectl label node "{{ .hostname }}" node-role.kubernetes.io/{{ .item }}="" --overwrite + +- name: Remove traint if node is worker + when: .kubernetes.roles | has "worker" + ignore_errors: true + command: | + kubectl taint nodes "{{ .hostname }}" node-role.kubernetes.io/master- + kubectl taint nodes "{{ .hostname }}" node-role.kubernetes.io/control-plane- \ No newline at end of file diff --git a/cmd/kk/app/options/builtin/create.go b/cmd/kk/app/options/builtin/create.go index 20d58ef5..4717d76b 100644 --- a/cmd/kk/app/options/builtin/create.go +++ b/cmd/kk/app/options/builtin/create.go @@ -26,6 +26,7 @@ import ( kkcorev1 "github.com/kubesphere/kubekey/api/core/v1" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" cliflag "k8s.io/component-base/cli/flag" "github.com/kubesphere/kubekey/v4/cmd/kk/app/options" @@ -104,13 +105,13 @@ func (o *CreateClusterOptions) Complete(cmd *cobra.Command, args []string) (*kkc func (o *CreateClusterOptions) completeConfig() error { if o.ContainerManager != "" { // override container_manager in config - if err := o.CommonOptions.Config.SetValue("cri.container_manager", o.ContainerManager); err != nil { - return errors.WithStack(err) + if err := unstructured.SetNestedField(o.CommonOptions.Config.Value(), o.ContainerManager, "cri", "container_manager"); err != nil { + return errors.Wrapf(err, "failed to set %q to config", "cri.container_manager") } } - if err := o.CommonOptions.Config.SetValue("kube_version", o.Kubernetes); err != nil { - return errors.WithStack(err) + if err := unstructured.SetNestedField(o.CommonOptions.Config.Value(), o.Kubernetes, "kube_version"); err != nil { + return errors.Wrapf(err, "failed to set %q to config", "kube_version") } return nil diff --git a/cmd/kk/app/options/builtin/init.go b/cmd/kk/app/options/builtin/init.go index 3b08a4c1..9a7a27fb 100644 --- a/cmd/kk/app/options/builtin/init.go +++ b/cmd/kk/app/options/builtin/init.go @@ -26,6 +26,7 @@ import ( kkcorev1 "github.com/kubesphere/kubekey/api/core/v1" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" cliflag "k8s.io/component-base/cli/flag" "github.com/kubesphere/kubekey/v4/cmd/kk/app/options" @@ -85,19 +86,16 @@ func (o *InitOSOptions) Complete(cmd *cobra.Command, args []string) (*kkcorev1.P } func (o *InitOSOptions) complateConfig() error { - if workdir, err := o.CommonOptions.Config.GetValue(_const.Workdir); err != nil { + + if wd, _, err := unstructured.NestedString(o.CommonOptions.Config.Value(), _const.Workdir); err != nil { // workdir should set by CommonOptions - return errors.Wrapf(err, "failed to get value %q in config", _const.Workdir) + return errors.Wrapf(err, "failed to get %q in config", _const.Workdir) } else { - wd, ok := workdir.(string) - if !ok { - return errors.New("workdir should be string value") - } // set binary dir if not set - if _, err := o.CommonOptions.Config.GetValue(_const.BinaryDir); err != nil { - // not found set default - if err := o.CommonOptions.Config.SetValue(_const.BinaryDir, filepath.Join(wd, "kubekey")); err != nil { - return errors.Wrapf(err, "failed to set value %q in config", _const.Workdir) + if _, _, err := unstructured.NestedString(o.CommonOptions.Config.Value(), _const.BinaryDir); err != nil { + // workdir should set by CommonOptions + if err := unstructured.SetNestedField(o.CommonOptions.Config.Value(), filepath.Join(wd, "kubekey"), _const.BinaryDir); err != nil { + return errors.Wrapf(err, "failed to set %q in config", _const.Workdir) } } } diff --git a/cmd/kk/app/options/option.go b/cmd/kk/app/options/option.go index 152d9a12..806c5c8b 100644 --- a/cmd/kk/app/options/option.go +++ b/cmd/kk/app/options/option.go @@ -27,6 +27,7 @@ import ( kkcorev1 "github.com/kubesphere/kubekey/api/core/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/client-go/rest" cliflag "k8s.io/component-base/cli/flag" "k8s.io/klog/v2" @@ -196,14 +197,14 @@ func (o *CommonOptions) Complete(playbook *kkcorev1.Playbook) error { func (o *CommonOptions) completeConfig(config *kkcorev1.Config) error { // set value by command args if o.Workdir != "" { - if err := config.SetValue(_const.Workdir, o.Workdir); err != nil { - return errors.WithStack(err) + if err := unstructured.SetNestedField(o.Config.Value(), o.Workdir, _const.Workdir); err != nil { + return errors.Wrapf(err, "failed to set %q to config", _const.Workdir) } } if o.Artifact != "" { // override artifact_file in config - if err := config.SetValue("artifact_file", o.Artifact); err != nil { - return errors.WithStack(err) + if err := unstructured.SetNestedField(o.Config.Value(), o.Artifact, "artifact_file"); err != nil { + return errors.Wrapf(err, "failed to set %q to config", "artifact_file") } } for _, s := range o.Set { @@ -242,7 +243,8 @@ func setValue(config *kkcorev1.Config, key, val string) error { return errors.Wrapf(err, "failed to unmarshal json for %q", key) } - return config.SetValue(key, value) + return errors.Wrapf(unstructured.SetNestedMap(config.Value(), value, key), + "failed to set %q to config", key) case strings.HasPrefix(val, "[") && strings.HasSuffix(val, "]"): var value []any err := json.Unmarshal([]byte(val), &value) @@ -250,13 +252,17 @@ func setValue(config *kkcorev1.Config, key, val string) error { return errors.Wrapf(err, "failed to unmarshal json for %q", key) } - return config.SetValue(key, value) + return errors.Wrapf(unstructured.SetNestedSlice(config.Value(), value, key), + "failed to set %q to config", key) case strings.EqualFold(val, "TRUE") || strings.EqualFold(val, "YES") || strings.EqualFold(val, "Y"): - return config.SetValue(key, true) + return errors.Wrapf(unstructured.SetNestedField(config.Value(), true, key), + "failed to set %q to config", key) case strings.EqualFold(val, "FALSE") || strings.EqualFold(val, "NO") || strings.EqualFold(val, "N"): - return config.SetValue(key, false) + return errors.Wrapf(unstructured.SetNestedField(config.Value(), false, key), + "failed to set %q to config", key) default: - return config.SetValue(key, val) + return errors.Wrapf(unstructured.SetNestedField(config.Value(), val, key), + "failed to set %q to config", key) } } diff --git a/pkg/connector/ssh_connector.go b/pkg/connector/ssh_connector.go index 28e10fba..cb036501 100644 --- a/pkg/connector/ssh_connector.go +++ b/pkg/connector/ssh_connector.go @@ -223,7 +223,7 @@ func (c *sshConnector) FetchFile(_ context.Context, src string, dst io.Writer) e // ExecuteCommand in remote host func (c *sshConnector) ExecuteCommand(_ context.Context, cmd string) ([]byte, error) { - cmd = fmt.Sprintf("sudo -SE %s << 'KUBEKEY_EOF'\n %s\nKUBEKEY_EOF\n", c.shell, cmd) + cmd = fmt.Sprintf("sudo -SE %s << 'KUBEKEY_EOF'\n%s\nKUBEKEY_EOF\n", c.shell, cmd) klog.V(5).InfoS("exec ssh command", "cmd", cmd, "host", c.Host) // create ssh session session, err := c.client.NewSession() diff --git a/pkg/const/helper.go b/pkg/const/helper.go index b65b772a..90bd07c9 100644 --- a/pkg/const/helper.go +++ b/pkg/const/helper.go @@ -22,6 +22,7 @@ import ( "strings" kkcorev1 "github.com/kubesphere/kubekey/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/klog/v2" "k8s.io/utils/ptr" ) @@ -33,12 +34,8 @@ import ( // If it fails to get the current working directory, it logs another informational message // and returns a default directory path "/opt/kubekey". func GetWorkdirFromConfig(config kkcorev1.Config) string { - workdir, err := config.GetValue(Workdir) - if err == nil { - wd, ok := workdir.(string) - if ok { - return wd - } + if wd, _, err := unstructured.NestedString(config.Value(), Workdir); err == nil { + return wd } klog.Info("work_dir is not set use current dir.") wd, err := os.Getwd() diff --git a/pkg/controllers/infrastructure/kkmachine_controller.go b/pkg/controllers/infrastructure/kkmachine_controller.go index c5470a54..4b3295cf 100644 --- a/pkg/controllers/infrastructure/kkmachine_controller.go +++ b/pkg/controllers/infrastructure/kkmachine_controller.go @@ -12,6 +12,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/rest" "k8s.io/client-go/tools/leaderelection/resourcelock" @@ -378,41 +379,41 @@ func (r *KKMachineReconciler) getConfig(scope *clusterScope, kkmachine *capkkinf klog.InfoS("get default config", "config", config) } - if err := config.SetValue(_const.Workdir, _const.CAPKKWorkdir); err != nil { + if err := unstructured.SetNestedField(config.Value(), _const.CAPKKWorkdir, _const.Workdir); err != nil { return config, errors.Wrapf(err, "failed to set %q in config", _const.Workdir) } - if err := config.SetValue("node_name", _const.ProviderID2Host(scope.Name, kkmachine.Spec.ProviderID)); err != nil { - return config, errors.Wrap(err, "failed to set \"node_name\" in config") + if err := unstructured.SetNestedField(config.Value(), _const.ProviderID2Host(scope.Name, kkmachine.Spec.ProviderID), "node_name"); err != nil { + return config, errors.Wrapf(err, "failed to set %q in config", "node_name") } - if err := config.SetValue("kube_version", kkmachine.Spec.Version); err != nil { - return config, errors.Wrap(err, "failed to set \"kube_version\" in config") + if err := unstructured.SetNestedField(config.Value(), kkmachine.Spec.Version, "kube_version"); err != nil { + return config, errors.Wrapf(err, "failed to set %q in config", "kube_version") } - if err := config.SetValue("kubernetes.cluster_name", scope.Cluster.Name); err != nil { - return config, errors.Wrap(err, "failed to set \"kubernetes.cluster_name\" in config") + if err := unstructured.SetNestedField(config.Value(), scope.Cluster.Name, "kubernetes", "cluster_name"); err != nil { + return config, errors.Wrapf(err, "failed to set %q in config", "kubernetes.cluster_name") } - if err := config.SetValue("kubernetes.roles", kkmachine.Spec.Roles); err != nil { - return config, errors.Wrap(err, "failed to set \"kubernetes.roles\" in config") + if err := unstructured.SetNestedField(config.Value(), kkmachine.Spec.Roles, "kubernetes", "roles"); err != nil { + return config, errors.Wrapf(err, "failed to set %q in config", "kubernetes.roles") } - if err := config.SetValue("cluster_network", scope.Cluster.Spec.ClusterNetwork); err != nil { - return config, errors.Wrap(err, "failed to set \"cluster_network\" in config") + if err := unstructured.SetNestedField(config.Value(), scope.Cluster.Spec.ClusterNetwork, "cluster_network"); err != nil { + return config, errors.Wrapf(err, "failed to set %q in config", "cluster_network") } switch scope.KKCluster.Spec.ControlPlaneEndpointType { case capkkinfrav1beta1.ControlPlaneEndpointTypeVIP: // should set vip addr to config - if err := config.SetValue("kubernetes.control_plane_endpoint.kube_vip.address", scope.Cluster.Spec.ControlPlaneEndpoint.Host); err != nil { - return config, errors.Wrap(err, "failed to set \"kubernetes.control_plane_endpoint.kube_vip.address\" in config") + if err := unstructured.SetNestedField(config.Value(), scope.Cluster.Spec.ControlPlaneEndpoint.Host, "kubernetes", "control_plane_endpoint", "kube_vip", "address"); err != nil { + return config, errors.Wrapf(err, "failed to set %q in config", "kubernetes.control_plane_endpoint.kube_vip.address") } case capkkinfrav1beta1.ControlPlaneEndpointTypeDNS: // do nothing default: return config, errors.New("unsupport ControlPlaneEndpointType") } - if err := config.SetValue("kubernetes.control_plane_endpoint.host", scope.Cluster.Spec.ControlPlaneEndpoint.Host); err != nil { - return config, errors.Wrap(err, "failed to set \"kubernetes.kube_vip.address\" in config") + if err := unstructured.SetNestedField(config.Value(), scope.Cluster.Spec.ControlPlaneEndpoint.Host, "kubernetes", "control_plane_endpoint", "host"); err != nil { + return config, errors.Wrapf(err, "failed to set %q in config", "kubernetes.control_plane_endpoint.host") } - if err := config.SetValue("kubernetes.control_plane_endpoint.type", scope.KKCluster.Spec.ControlPlaneEndpointType); err != nil { - return config, errors.Wrap(err, "failed to set \"kubernetes.kube_vip.enabled\" in config") + if err := unstructured.SetNestedField(config.Value(), scope.KKCluster.Spec.ControlPlaneEndpointType, "kubernetes", "control_plane_endpoint", "type"); err != nil { + return config, errors.Wrapf(err, "failed to set %q in config", "kubernetes.control_plane_endpoint.kube_vip.type") } return config, nil diff --git a/pkg/project/git.go b/pkg/project/git.go index d3f271c1..377a0ae7 100644 --- a/pkg/project/git.go +++ b/pkg/project/git.go @@ -29,6 +29,7 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport/http" kkcorev1 "github.com/kubesphere/kubekey/api/core/v1" kkprojectv1 "github.com/kubesphere/kubekey/api/project/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" _const "github.com/kubesphere/kubekey/v4/pkg/const" ) @@ -43,13 +44,9 @@ func newGitProject(ctx context.Context, playbook kkcorev1.Playbook, update bool) } // get project_dir from playbook - projectDir, err := playbook.Spec.Config.GetValue("project_dir") + projectDir, _, err := unstructured.NestedString(playbook.Spec.Config.Value(), _const.ProjectsDir) if err != nil { - return nil, errors.Wrap(err, "project_dir is not defined") - } - pd, ok := projectDir.(string) - if !ok { - return nil, errors.New("project_dir should be string") + return nil, errors.Wrapf(err, "failed to get %q in config", _const.ProjectsDir) } // git clone to project dir @@ -59,7 +56,7 @@ func newGitProject(ctx context.Context, playbook kkcorev1.Playbook, update bool) p := &gitProject{ Playbook: playbook, - projectDir: filepath.Join(pd, playbook.Spec.Project.Name), + projectDir: filepath.Join(projectDir, playbook.Spec.Project.Name), playbook: playbook.Spec.Playbook, } diff --git a/pkg/variable/variable_get_test.go b/pkg/variable/variable_get_test.go index ce6ced92..be149e62 100644 --- a/pkg/variable/variable_get_test.go +++ b/pkg/variable/variable_get_test.go @@ -21,6 +21,7 @@ import ( kkcorev1 "github.com/kubesphere/kubekey/api/core/v1" "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) @@ -232,6 +233,9 @@ func TestGetWorkdir(t *testing.T) { Config: kkcorev1.Config{ Spec: runtime.RawExtension{ Raw: []byte("{\"work_dir\": \"abc\"}"), + Object: &unstructured.Unstructured{Object: map[string]any{ + "work_dir": "abc", + }}, }, }, },