Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ docs/build
# YAML for testings
config*.yaml*
app*.yaml
variables*.yaml
variables*.yaml.bak

# IDE
.idea/
Expand Down
2 changes: 1 addition & 1 deletion cmd/devstream/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ DevStream will generate and execute a new plan based on the config file and the

func applyCMDFunc(cmd *cobra.Command, args []string) {
log.Info("Apply started.")
if err := pluginengine.Apply(configFile, continueDirectly); err != nil {
if err := pluginengine.Apply(configFile, varFile, continueDirectly); err != nil {
log.Errorf("Apply failed => %s.", err)
os.Exit(1)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/devstream/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ DevStream will delete everything defined in the config file, regardless of the s
func deleteCMDFunc(cmd *cobra.Command, args []string) {
log.Info("Delete started.")

if err := pluginengine.Remove(configFile, continueDirectly, isForceDelete); err != nil {
if err := pluginengine.Remove(configFile, varFile, continueDirectly, isForceDelete); err != nil {
log.Errorf("Delete error: %s.", err)
os.Exit(1)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/devstream/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var initCMD = &cobra.Command{
}

func initCMDFunc(cmd *cobra.Command, args []string) {
cfg := configloader.LoadConf(configFile)
cfg := configloader.LoadConf(configFile, varFile)
if cfg == nil {
log.Fatal("Failed to load the config file.")
}
Expand Down
2 changes: 2 additions & 0 deletions cmd/devstream/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

var (
configFile string
varFile string
pluginDir string
continueDirectly bool
isDebug bool
Expand Down Expand Up @@ -41,6 +42,7 @@ func init() {
cobra.OnInitialize(initConfig)

rootCMD.PersistentFlags().StringVarP(&configFile, "config-file", "f", "config.yaml", "config file")
rootCMD.PersistentFlags().StringVarP(&varFile, "var-file", "", "variables.yaml", "variables file")
rootCMD.PersistentFlags().StringVarP(&pluginDir, "plugin-dir", "d", pluginengine.DefaultPluginDir, "plugins directory")
rootCMD.PersistentFlags().BoolVarP(&continueDirectly, "yes", "y", false, "apply/delete directly without confirmation")
rootCMD.PersistentFlags().BoolVarP(&isDebug, "debug", "", false, "debug level log")
Expand Down
2 changes: 1 addition & 1 deletion cmd/devstream/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var verifyCMD = &cobra.Command{
func verifyCMDFunc(cmd *cobra.Command, args []string) {
log.Info("Verify started.")

if pluginengine.Verify(configFile) {
if pluginengine.Verify(configFile, varFile) {
log.Success("Verify succeeded.")
} else {
log.Info("Verify finished.")
Expand Down
17 changes: 13 additions & 4 deletions internal/pkg/configloader/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,28 @@ func (t *Tool) DeepCopy() *Tool {
}

// LoadConf reads an input file as a Config struct.
func LoadConf(fname string) *Config {
fileBytes, err := ioutil.ReadFile(fname)
func LoadConf(configFileName, varFileName string) *Config {
configFileBytes, err := ioutil.ReadFile(configFileName)
if err != nil {
log.Error(err)
log.Info("Maybe the default file doesn't exist or you forgot to pass your config file to the \"-f\" option?")
log.Info("See \"dtm help\" for more information.")
return nil
}

log.Debugf("Config file: \n%s\n", string(fileBytes))
log.Debugf("Original config: \n%s\n", string(configFileBytes))

// handle variables in the config file
configFileBytesWithVarsRendered, err := renderVariables(varFileName, configFileBytes)
if err != nil {
log.Error(err)
return nil
}

log.Debugf("Config file after rendering with variables: \n%s\n", string(configFileBytesWithVarsRendered))

var config Config
err = yaml.Unmarshal(fileBytes, &config)
err = yaml.Unmarshal(configFileBytesWithVarsRendered, &config)
if err != nil {
log.Error("Please verify the format of your config file.")
log.Errorf("Reading config file failed. %s.", err)
Expand Down
89 changes: 89 additions & 0 deletions internal/pkg/configloader/variables.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package configloader

import (
"bytes"
"errors"
"html/template"
"io/ioutil"
"os"
"regexp"

"gopkg.in/yaml.v3"

"github.com/devstream-io/devstream/pkg/util/log"
)

const defaultVarFileName = "variables.yaml"

func renderVariables(varFileName string, configFileBytes []byte) ([]byte, error) {
// if the var file is default (user didn't overwrite the value with --var-file option)
// and the default var file doesn't exist, do nothing
// it's OK to not use a var file
if defaultVarFileName == varFileName {
if _, err := os.Stat(defaultVarFileName); errors.Is(err, os.ErrNotExist) {
return configFileBytes, nil
}
}

// load variables file
variables, err := loadVariablesFilesIntoMap(varFileName)
if err != nil {
log.Error(err)
return nil, err
}

// handle variables format
configFileContentString := addDotForVariablesInConfig(string(configFileBytes))

// render config with variables
result, err := renderConfigWithVariables(configFileContentString, variables)
if err != nil {
log.Error(err)
return nil, err
}

return result, nil
}

func loadVariablesFilesIntoMap(varFileName string) (map[string]interface{}, error) {
fileBytes, err := ioutil.ReadFile(varFileName)
if err != nil {
return nil, err
}

log.Debugf("Variables file: \n%s\n", string(fileBytes))

variables := make(map[string]interface{})
err = yaml.Unmarshal(fileBytes, &variables)
if err != nil {
return nil, err
}

return variables, nil
}

// this is because our variables syntax is [[ varName ]]
// while Go's template is [[ .varName ]]
func addDotForVariablesInConfig(s string) string {
// regex := `\[\[\s*(.*)\s*\]\]`
// r := regexp.MustCompile(regex)
// return r.ReplaceAllString(s, "[[ .$1 ]]")
regex := `\[\[\s*`
r := regexp.MustCompile(regex)
return r.ReplaceAllString(s, "[[ .")
}

func renderConfigWithVariables(fileContent string, variables map[string]interface{}) ([]byte, error) {
tpl, err := template.New("configfile").Delims("[[", "]]").Parse(fileContent)
if err != nil {
return nil, err
}

var results bytes.Buffer
err = tpl.Execute(&results, variables)
if err != nil {
return nil, err
}

return results.Bytes(), err
}
37 changes: 37 additions & 0 deletions internal/pkg/configloader/variables_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package configloader

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestAddDotForVariablesInConfigNormal(t *testing.T) {
res := addDotForVariablesInConfig("[[varNameA]]")
assert.Equal(t, "[[ .varNameA]]", res, "Adding dot for variable names passed.")
}

func TestAddDotForVariablesInConfigWithSpaces(t *testing.T) {
res := addDotForVariablesInConfig("[[ varNameA]]")
assert.Equal(t, "[[ .varNameA]]", res, "Adding dot for variable names passed.")
}

func TestAddDotForVariablesInConfigWithTrailingSpaces(t *testing.T) {
res := addDotForVariablesInConfig("[[ varNameA ]]")
assert.Equal(t, "[[ .varNameA ]]", res, "Adding dot for variable names passed.")
}

func TestAddDotForVariablesInConfigMultipleVars(t *testing.T) {
res := addDotForVariablesInConfig("[[ varNameA ]]/[[ varNameB ]]/[[ varNameC ]]")
assert.Equal(t, "[[ .varNameA ]]/[[ .varNameB ]]/[[ .varNameC ]]", res, "Adding dot for variable names passed.")
}

func TestRenderConfigWithVariables(t *testing.T) {
variables := map[string]interface{}{
"varNameA": "A",
"varNameB": "B",
}
result, err := renderConfigWithVariables("[[ .varNameA ]]/[[ .varNameB]]", variables)
assert.Equal(t, err, nil)
assert.Equal(t, string(result), "A/B")
}
4 changes: 2 additions & 2 deletions internal/pkg/pluginengine/cmd_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (
"github.com/devstream-io/devstream/pkg/util/log"
)

func Apply(configFile string, continueDirectly bool) error {
cfg := configloader.LoadConf(configFile)
func Apply(configFile, varFile string, continueDirectly bool) error {
cfg := configloader.LoadConf(configFile, varFile)
if cfg == nil {
return fmt.Errorf("failed to load the config file")
}
Expand Down
4 changes: 2 additions & 2 deletions internal/pkg/pluginengine/cmd_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (
"github.com/devstream-io/devstream/pkg/util/log"
)

func Remove(configFile string, continueDirectly bool, isForceDelete bool) error {
cfg := configloader.LoadConf(configFile)
func Remove(configFile, varFile string, continueDirectly bool, isForceDelete bool) error {
cfg := configloader.LoadConf(configFile, varFile)
if cfg == nil {
return fmt.Errorf("failed to load the config file")
}
Expand Down
4 changes: 2 additions & 2 deletions internal/pkg/pluginengine/cmd_verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (
)

// Verify returns true if all the comments in this function are met
func Verify(configFile string) bool {
func Verify(configFile, varFile string) bool {
// 1. loading config file succeeded
cfg := configloader.LoadConf(configFile)
cfg := configloader.LoadConf(configFile, varFile)
if cfg == nil {
return false
}
Expand Down