Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: sealos create support for environment variable based template rendering #4222

Merged
merged 9 commits into from Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 {
LZiHaN marked this conversation as resolved.
Show resolved Hide resolved
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