From faf607b3b04d455c33d39c3abaed6d0556569986 Mon Sep 17 00:00:00 2001 From: Zihan Li Date: Mon, 6 Nov 2023 19:56:17 +0800 Subject: [PATCH] feature: environment variable based template rendering in sealos create (#4258) --- pkg/buildah/create.go | 36 +++++++++++++ pkg/constants/contants.go | 2 + pkg/env/env.go | 54 ++------------------ pkg/filesystem/rootfs/rootfs_default.go | 19 +------ pkg/utils/strings/strings.go | 68 +++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 67 deletions(-) diff --git a/pkg/buildah/create.go b/pkg/buildah/create.go index 359440822..e14aeb733 100644 --- a/pkg/buildah/create.go +++ b/pkg/buildah/create.go @@ -15,10 +15,16 @@ package buildah import ( + "context" "fmt" "os" "os/exec" + "golang.org/x/sync/errgroup" + + "github.com/labring/sealos/pkg/utils/file" + "github.com/labring/sealos/pkg/utils/maps" + "github.com/containers/buildah/pkg/parse" "github.com/containers/storage/pkg/unshare" v1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -26,12 +32,15 @@ import ( "github.com/spf13/pflag" "github.com/labring/sealos/pkg/utils/logger" + + stringsutil "github.com/labring/sealos/pkg/utils/strings" ) type createOptions struct { name string platform string short bool + env []string } func newDefaultCreateOptions() *createOptions { @@ -45,6 +54,7 @@ func (opts *createOptions) RegisterFlags(fs *pflag.FlagSet) { fs.StringVarP(&opts.name, "cluster", "c", opts.name, "name of cluster to be created but not actually run") fs.StringVar(&opts.platform, "platform", opts.platform, "set the OS/ARCH/VARIANT of the image to the provided value instead of the current operating system and architecture of the host (for example `linux/arm`)") fs.BoolVar(&opts.short, "short", false, "if true, print just the mount path.") + fs.StringSliceVarP(&opts.env, "env", "e", opts.env, "set environment variables for template files") } func newCreateCmd() *cobra.Command { @@ -70,11 +80,19 @@ func newCreateCmd() *cobra.Command { if err != nil { return err } + + if len(opts.env) > 0 { + if err := runRender([]string{info.MountPoint}, opts.env); err != nil { + return err + } + } + if !opts.short { logger.Info("Mount point: %s", info.MountPoint) } else { fmt.Println(info.MountPoint) } + if !unshare.IsRootless() { return nil } @@ -106,3 +124,21 @@ func newCreateCmd() *cobra.Command { opts.RegisterFlags(createCmd.Flags()) return createCmd } + +func runRender(mountPoints []string, env []string) error { + eg, _ := errgroup.WithContext(context.Background()) + envs := maps.ListToMap(env) + + for _, mountPoint := range mountPoints { + mp := mountPoint + eg.Go(func() error { + if !file.IsExist(mp) { + logger.Debug("MountPoint %s does not exist, skipping", mp) + return nil + } + return stringsutil.RenderTemplatesWithEnv(mp, envs) + }) + } + + return eg.Wait() +} diff --git a/pkg/constants/contants.go b/pkg/constants/contants.go index 70c9aa064..f30c1c01e 100644 --- a/pkg/constants/contants.go +++ b/pkg/constants/contants.go @@ -18,6 +18,8 @@ import ( "github.com/containers/storage/pkg/homedir" ) +const TemplateSuffix = ".tmpl" + const ( LvsCareStaticPodName = "kube-sealos-lvscare" YamlFileSuffix = "yaml" diff --git a/pkg/env/env.go b/pkg/env/env.go index 722a7e4e7..8cba36d63 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -16,23 +16,15 @@ package env // nosemgrep: go.lang.security.audit.xss.import-text-template.import-text-template import ( - "errors" - "fmt" - "os" - "path/filepath" "strings" "sync" - "github.com/labring/sealos/pkg/template" "github.com/labring/sealos/pkg/types/v1beta1" - fileutil "github.com/labring/sealos/pkg/utils/file" "github.com/labring/sealos/pkg/utils/logger" "github.com/labring/sealos/pkg/utils/maps" stringsutil "github.com/labring/sealos/pkg/utils/strings" ) -const templateSuffix = ".tmpl" - type Interface interface { // WrapShell :If host already set env like DATADISK=/data // This function add env to the shell, like: @@ -73,47 +65,11 @@ func (p *processor) WrapShell(host, shell string) string { } func (p *processor) RenderAll(host, dir string, envs map[string]string) error { - return filepath.Walk(dir, func(path string, info os.FileInfo, errIn error) error { - if errIn != nil { - return errIn - } - if info.IsDir() || !strings.HasSuffix(info.Name(), templateSuffix) { - return nil - } - fileName := strings.TrimSuffix(path, templateSuffix) - if fileutil.IsExist(fileName) { - if err := os.Remove(fileName); err != nil { - logger.Warn(err) - } - } - - writer, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, os.ModePerm) - if err != nil { - return fmt.Errorf("failed to open file [%s] when render env: %v", path, err) - } - - defer writer.Close() - body, err := fileutil.ReadAll(path) - if err != nil { - return err - } - - t, isOk, err := template.TryParse(string(body)) - if isOk { - if err != nil { - return fmt.Errorf("failed to create template: %s %v", path, err) - } - if host != "" { - data := maps.MergeMap(envs, p.getHostEnvInCache(host)) - if err := t.Execute(writer, data); err != nil { - return fmt.Errorf("failed to render env template: %s %v", path, err) - } - } - } else { - return errors.New("parse template failed") - } - return nil - }) + data := envs + if host != "" { + data = maps.MergeMap(envs, p.getHostEnvInCache(host)) + } + return stringsutil.RenderTemplatesWithEnv(dir, data) } func (p *processor) getHostEnvInCache(hostIP string) map[string]string { diff --git a/pkg/filesystem/rootfs/rootfs_default.go b/pkg/filesystem/rootfs/rootfs_default.go index a2e55076d..337f09800 100644 --- a/pkg/filesystem/rootfs/rootfs_default.go +++ b/pkg/filesystem/rootfs/rootfs_default.go @@ -20,7 +20,6 @@ import ( "context" "fmt" "io/fs" - "path" "golang.org/x/sync/errgroup" @@ -152,23 +151,7 @@ func (f *defaultRootfs) unmountRootfs(cluster *v2.Cluster, ipList []string) erro } func renderTemplatesWithEnv(mountDir string, ipList []string, p env.Interface, envs map[string]string) error { - var ( - renderEtc = path.Join(mountDir, constants.EtcDirName) - renderScripts = path.Join(mountDir, constants.ScriptsDirName) - renderManifests = path.Join(mountDir, constants.ManifestsDirName) - ) - - // currently only render once - for _, dir := range []string{renderEtc, renderScripts, renderManifests} { - logger.Debug("render env dir: %s", dir) - if file.IsExist(dir) { - err := p.RenderAll(ipList[0], dir, envs) - if err != nil { - return err - } - } - } - return nil + return p.RenderAll(ipList[0], mountDir, envs) } func newDefaultRootfs(mounts []v2.MountImage) (filesystem.Mounter, error) { diff --git a/pkg/utils/strings/strings.go b/pkg/utils/strings/strings.go index ab9acbe50..113945269 100644 --- a/pkg/utils/strings/strings.go +++ b/pkg/utils/strings/strings.go @@ -18,13 +18,20 @@ package strings import ( "bytes" + "errors" "fmt" "net" + "os" + "path/filepath" "regexp" "sort" "strings" "unicode" + "github.com/labring/sealos/pkg/constants" + "github.com/labring/sealos/pkg/template" + "github.com/labring/sealos/pkg/utils/file" + "github.com/labring/sealos/pkg/utils/logger" ) @@ -223,6 +230,67 @@ func RenderTextFromEnv(text string, envs map[string]string) string { return text } +func RenderTemplatesWithEnv(filePaths string, envs map[string]string) error { + var ( + renderEtc = filepath.Join(filePaths, constants.EtcDirName) + renderScripts = filepath.Join(filePaths, constants.ScriptsDirName) + renderManifests = filepath.Join(filePaths, constants.ManifestsDirName) + ) + + for _, dir := range []string{renderEtc, renderScripts, renderManifests} { + logger.Debug("render env dir: %s", dir) + if !file.IsExist(dir) { + logger.Debug("Directory %s does not exist, skipping", dir) + continue + } + + if err := filepath.Walk(dir, func(path string, info os.FileInfo, errIn error) error { + if errIn != nil { + return errIn + } + if info.IsDir() || !strings.HasSuffix(info.Name(), constants.TemplateSuffix) { + return nil + } + + fileName := strings.TrimSuffix(path, constants.TemplateSuffix) + if file.IsExist(fileName) { + if err := os.Remove(fileName); err != nil { + logger.Warn("failed to remove existing file [%s]: %v", fileName, err) + } + } + + writer, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, os.ModePerm) + if err != nil { + return fmt.Errorf("failed to open file [%s] for rendering: %v", path, err) + } + defer writer.Close() + + body, err := file.ReadAll(path) + if err != nil { + return err + } + + t, isOk, err := template.TryParse(string(body)) + if isOk { + if err != nil { + return fmt.Errorf("failed to create template: %s %v", path, err) + } + if err := t.Execute(writer, envs); err != nil { + return fmt.Errorf("failed to render env template: %s %v", path, err) + } + } else { + return errors.New("parse template failed") + } + + return nil + }); err != nil { + return fmt.Errorf("failed to render templates in directory %s: %v", dir, err) + } + } + + return nil +} + func TrimQuotes(s string) string { if len(s) >= 2 { if c := s[len(s)-1]; s[0] == c && (c == '"' || c == '\'') {