feature: environment variable based template rendering in sealos create (#4258)

This commit is contained in:
Zihan Li
2023-11-06 19:56:17 +08:00
committed by GitHub
parent f39b23394b
commit faf607b3b0
5 changed files with 112 additions and 67 deletions
+36
View File
@@ -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()
}
+2
View File
@@ -18,6 +18,8 @@ import (
"github.com/containers/storage/pkg/homedir"
)
const TemplateSuffix = ".tmpl"
const (
LvsCareStaticPodName = "kube-sealos-lvscare"
YamlFileSuffix = "yaml"
+5 -49
View File
@@ -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 {
+1 -18
View File
@@ -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) {
+68
View File
@@ -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 == '\'') {