From a8e533e60885ef61e47ac0c5e493beb5b3b02eb2 Mon Sep 17 00:00:00 2001 From: zuoxuesong-worker Date: Fri, 22 Aug 2025 09:25:53 +0800 Subject: [PATCH] feature: support task include vars (#2717) feature: support task include vars feature: support task include vars feature: support task include vars feature: support task include vars feature: support task include vars feature: support task include vars feature: support task include vars feature: support task include vars feature: support task include vars feature: support task include vars feature: support task include vars feature: support task include vars Signed-off-by: xuesongzuo@yunify.com --- docs/zh/modules/include_vars.md | 18 ++++++ pkg/const/workdir.go | 3 + pkg/modules/copy.go | 2 +- pkg/modules/include_vars.go | 96 ++++++++++++++++++++++++++++++++ pkg/modules/include_vars_test.go | 70 +++++++++++++++++++++++ pkg/modules/module.go | 2 + 6 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 docs/zh/modules/include_vars.md create mode 100644 pkg/modules/include_vars.go create mode 100644 pkg/modules/include_vars_test.go diff --git a/docs/zh/modules/include_vars.md b/docs/zh/modules/include_vars.md new file mode 100644 index 00000000..ed0ebba7 --- /dev/null +++ b/docs/zh/modules/include_vars.md @@ -0,0 +1,18 @@ +# include_vars 模块 + +include_vars模块允许用户将变量设置到指定的主机中生效。 + +## 参数 + +| 参数 | 说明 | 类型 | 必填 | 默认值 | +|------|------|------|------|-------| +| include_vars | 引用的文件地址,类型必须是yaml/yml | 字符串 | 是 | - | + +## 使用示例 + +1. 设置字符串参数 +```yaml +- name: set other var file + include_vars: "{{ .os.architecture }}/var.yaml" +``` + diff --git a/pkg/const/workdir.go b/pkg/const/workdir.go index c225fe93..fbc1bdda 100644 --- a/pkg/const/workdir.go +++ b/pkg/const/workdir.go @@ -138,3 +138,6 @@ const RuntimePlaybookVariableDir = "variable" // KubernetesDir represents the remote host directory for each Kubernetes connection created during playbook execution. const KubernetesDir = "kubernetes" + +// VarsDir is a directory name for vars +const VarsDir = "vars" diff --git a/pkg/modules/copy.go b/pkg/modules/copy.go index d2b77828..04e04ea0 100644 --- a/pkg/modules/copy.go +++ b/pkg/modules/copy.go @@ -182,7 +182,7 @@ func (ca copyArgs) handleAbsolutePath(ctx context.Context, conn connector.Connec func (ca copyArgs) handleRelativePath(ctx context.Context, options ExecOptions, conn connector.Connector) (string, string, error) { pj, err := project.New(ctx, options.Playbook, false) if err != nil { - return StdoutFailed, "failed to get playbook", err + return StdoutFailed, StderrGetPlaybook, err } relPath := filepath.Join(options.Task.Annotations[kkcorev1alpha1.TaskAnnotationRelativePath], _const.ProjectRolesFilesDir, ca.src) diff --git a/pkg/modules/include_vars.go b/pkg/modules/include_vars.go new file mode 100644 index 00000000..6d31c542 --- /dev/null +++ b/pkg/modules/include_vars.go @@ -0,0 +1,96 @@ +package modules + +import ( + "context" + "os" + "path/filepath" + + "github.com/cockroachdb/errors" + kkcorev1alpha1 "github.com/kubesphere/kubekey/api/core/v1alpha1" + "gopkg.in/yaml.v3" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + _const "github.com/kubesphere/kubekey/v4/pkg/const" + "github.com/kubesphere/kubekey/v4/pkg/project" + "github.com/kubesphere/kubekey/v4/pkg/utils" + "github.com/kubesphere/kubekey/v4/pkg/variable" +) + +/* +Module: include_vars + +Description: +- Adds or updates host variables for one or more hosts. + +Example Usage in Playbook Task: + - name: Add custom variables to hosts + include_vars: path/file.yaml + +Return Values: +- On success: returns empty stdout and stderr. +- On failure: returns error message in stderr. +*/ + +type includeVarsArgs struct { + includeVars string +} + +// ModuleIncludeVars handle the "include_vars" module ,add other var files into playbook +func ModuleIncludeVars(ctx context.Context, options ExecOptions) (string, string, error) { + // get host variable + vd, err := options.getAllVariables() + if err != nil { + return StdoutFailed, StderrGetHostVariable, err + } + // check args + includeVarsByte, err := variable.Extension2String(vd, options.Args) + if err != nil { + return StdoutFailed, StderrParseArgument, err + } + if len(includeVarsByte) == 0 { + return StdoutFailed, "input file path wrong", errors.New("input value can not be empty") + } + arg := includeVarsArgs{ + includeVars: string(includeVarsByte), + } + if !filepath.IsLocal(arg.includeVars) { + return StdoutFailed, "can not read remote file", errors.New("can not read remote file") + } + if !utils.HasSuffixIn(arg.includeVars, []string{"yaml", "yml"}) { + return StdoutFailed, "input file type wrong", errors.New("input file type wrong") + } + + var includeVarsFileContent []byte + if filepath.IsAbs(arg.includeVars) { + includeVarsFileContent, err = os.ReadFile(arg.includeVars) + if err != nil { + return StdoutFailed, "failed to read var file", errors.Wrap(err, "failed to read include variables file") + } + } else { + pj, err := project.New(ctx, options.Playbook, false) + if err != nil { + return StdoutFailed, StderrGetPlaybook, err + } + fileReadPath := filepath.Join(options.Task.Annotations[kkcorev1alpha1.TaskAnnotationRelativePath], _const.VarsDir, arg.includeVars) + includeVarsFileContent, err = pj.ReadFile(fileReadPath) + if err != nil { + return StdoutFailed, "failed to read var file", err + } + } + + var node yaml.Node + // Unmarshal the YAML document into a root node. + if err := yaml.Unmarshal(includeVarsFileContent, &node); err != nil { + return StdoutFailed, StderrParseArgument, errors.Wrap(err, "failed to failed to unmarshal YAML") + } + + if err := options.Variable.Merge(variable.MergeRuntimeVariable([]yaml.Node{node}, options.Host)); err != nil { + return StdoutFailed, StderrParseArgument, errors.Wrap(err, "failed to merge runtime variables") + } + + return StdoutSuccess, "", nil +} + +func init() { + utilruntime.Must(RegisterModule("include_vars", ModuleIncludeVars)) +} diff --git a/pkg/modules/include_vars_test.go b/pkg/modules/include_vars_test.go new file mode 100644 index 00000000..4a4621f7 --- /dev/null +++ b/pkg/modules/include_vars_test.go @@ -0,0 +1,70 @@ +package modules + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestModuleIncludeVars(t *testing.T) { + testcases := []struct { + name string + opt ExecOptions + exceptStdout string + }{ + { + name: "include remote path", + opt: ExecOptions{ + Args: runtime.RawExtension{ + Raw: []byte(`{ +"include_vars": "http://127.0.0.1:8080/include_vars", +}`), + }, + Variable: newTestVariable(nil, nil), + }, + exceptStdout: StdoutFailed, + }, { + name: "include empty path", + opt: ExecOptions{ + Args: runtime.RawExtension{ + Raw: []byte(`{ +"include_vars": "", +}`), + }, + Variable: newTestVariable(nil, nil), + }, + exceptStdout: StdoutFailed, + }, { + name: "include path not exist", + opt: ExecOptions{ + Args: runtime.RawExtension{ + Raw: []byte(`{ +"include_vars": "/path/not/exist/not_exist.yaml", +}`), + }, + Variable: newTestVariable(nil, nil), + }, + exceptStdout: StdoutFailed, + }, { + name: "include path not yaml", + opt: ExecOptions{ + Args: runtime.RawExtension{ + Raw: []byte(`{ +"include_vars": "/path/some/exist/exist.yyy", +}`), + }, + Variable: newTestVariable(nil, nil), + }, + exceptStdout: StdoutFailed, + }, + } + + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + stdout, _, _ := ModuleIncludeVars(context.Background(), testcase.opt) + assert.Equal(t, testcase.exceptStdout, stdout) + }) + } +} diff --git a/pkg/modules/module.go b/pkg/modules/module.go index 8d43dcb7..8531583d 100644 --- a/pkg/modules/module.go +++ b/pkg/modules/module.go @@ -46,6 +46,8 @@ const ( StderrParseArgument = "failed to parse argument" // StderrUnsupportArgs is returned when the provided arguments are not supported. StderrUnsupportArgs = "unsupport args" + // StderrGetPlaybook is returned when get playbook error + StderrGetPlaybook = "failed to get playbook" ) // ModuleExecFunc defines the function signature for executing a module.