mirror of
https://github.com/labring/sealos.git
synced 2026-08-28 17:22:42 +08:00
feature: environment variable based template rendering in sealos create (#4258)
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
)
|
||||
|
||||
const TemplateSuffix = ".tmpl"
|
||||
|
||||
const (
|
||||
LvsCareStaticPodName = "kube-sealos-lvscare"
|
||||
YamlFileSuffix = "yaml"
|
||||
|
||||
Vendored
+5
-49
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 == '\'') {
|
||||
|
||||
Reference in New Issue
Block a user