Skip to content

Commit

Permalink
feature: sealos create support for environment variable based templ…
Browse files Browse the repository at this point in the history
…ate rendering (#4222) (#4232)

* feature: environment variable based template rendering in sealos create

* add: goimports tools

* update: goimports tools

* update: goimports tools

* fix golangci lint error

* remove docs

* remove docs

* fix: refactor template rendering logic, abstract common func

* fix ci

Co-authored-by: Zihan Li <eden.zh.li@outlook.com>
  • Loading branch information
sealos-ci-robot and LZiHaN committed Nov 2, 2023
1 parent 286c181 commit e332192
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 65 deletions.
34 changes: 34 additions & 0 deletions pkg/buildah/create.go
Expand Up @@ -15,23 +15,30 @@
package buildah

import (
"context"
"fmt"
"os"
"os/exec"

stringsutil "github.com/labring/sealos/pkg/utils/strings"

"github.com/containers/buildah/pkg/parse"
"github.com/containers/storage/pkg/unshare"
"github.com/labring/sreg/pkg/utils/file"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/sync/errgroup"

"github.com/labring/sealos/pkg/utils/logger"
"github.com/labring/sealos/pkg/utils/maps"
)

type createOptions struct {
name string
platform string
short bool
env []string
}

func newDefaultCreateOptions() *createOptions {
Expand All @@ -45,6 +52,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 {
Expand All @@ -70,11 +78,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
}
Expand Down Expand Up @@ -106,3 +122,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.FromSlice(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 changes: 2 additions & 0 deletions pkg/constants/consts.go
Expand Up @@ -18,6 +18,8 @@ const (
DefaultClusterFileName = "Clusterfile"
)

const TemplateSuffix = ".tmpl"

const (
LvsCareStaticPodName = "kube-sealos-lvscare"
YamlFileSuffix = "yaml"
Expand Down
54 changes: 5 additions & 49 deletions pkg/env/env.go
Expand Up @@ -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:
Expand Down Expand Up @@ -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.Merge(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.Merge(envs, p.getHostEnvInCache(host))
}
return stringsutil.RenderTemplatesWithEnv(dir, data)
}

func (p *processor) getHostEnvInCache(hostIP string) map[string]string {
Expand Down
17 changes: 1 addition & 16 deletions pkg/filesystem/rootfs/rootfs_default.go
Expand Up @@ -193,23 +193,8 @@ 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 = filepath.Join(mountDir, constants.EtcDirName)
renderScripts = filepath.Join(mountDir, constants.ScriptsDirName)
renderManifests = filepath.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) {
Expand Down
68 changes: 68 additions & 0 deletions pkg/utils/strings/strings.go
Expand Up @@ -17,11 +17,18 @@ limitations under the License.
package strings

import (
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"unicode"

"github.com/labring/sealos/pkg/constants"
"github.com/labring/sealos/pkg/template"
"github.com/labring/sealos/pkg/utils/file"

"golang.org/x/exp/slices"

"github.com/labring/sealos/pkg/utils/logger"
Expand Down Expand Up @@ -158,6 +165,67 @@ func RenderTextWithEnv(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 == '\'') {
Expand Down

0 comments on commit e332192

Please sign in to comment.