diff --git a/internal/pkg/configmanager/variables.go b/internal/pkg/configmanager/variables.go index dc37f8f04..7aa929daf 100644 --- a/internal/pkg/configmanager/variables.go +++ b/internal/pkg/configmanager/variables.go @@ -10,7 +10,8 @@ func renderConfigWithVariables(fileContent string, variables map[string]interfac str, err := template.New(). FromContent(fileContent). - AddProcessor(template.AddDotForVariablesInConfigProcessor()). + AddDotForVariablesInConfigProcessor(). + AddQuoteForVariablesInConfigProcessor(). SetDefaultRender(fileContent, variables). Render() diff --git a/internal/pkg/configmanager/variables_test.go b/internal/pkg/configmanager/variables_test.go index a5fd7f55f..39d147556 100644 --- a/internal/pkg/configmanager/variables_test.go +++ b/internal/pkg/configmanager/variables_test.go @@ -1,17 +1,20 @@ package configmanager import ( + "fmt" + "os" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("renderConfigWithVariables", func() { - var vars = map[string]interface{}{ - "foo1": "bar1", - "foo2": "bar2", - } When("render a config with variables", func() { It("should works fine", func() { + var vars = map[string]interface{}{ + "foo1": "bar1", + "foo2": "bar2", + } result1, err1 := renderConfigWithVariables("[[ foo1 ]]/[[ foo2]]", vars) result2, err2 := renderConfigWithVariables("a[[ foo1 ]]/[[ foo2]]b", vars) result3, err3 := renderConfigWithVariables(" [[ foo1 ]] [[ foo2]] ", vars) @@ -23,4 +26,34 @@ var _ = Describe("renderConfigWithVariables", func() { Expect(result3).To(Equal([]byte(" bar1 bar2 "))) }) }) + + When("there are env variables", func() { + const ( + envKey1, envKey2 = "GITHUB_TOKEN", "GITLAB_TOKEN" + envVal1, envVal2 = "123456", "abcdef" + ) + BeforeEach(func() { + DeferCleanup(os.Setenv, envKey1, os.Getenv(envKey1)) + DeferCleanup(os.Setenv, envKey2, os.Getenv(envKey2)) + }) + + It("should works fine", func() { + err := os.Setenv(envKey1, envVal1) + Expect(err).NotTo(HaveOccurred()) + err = os.Setenv(envKey2, envVal2) + Expect(err).NotTo(HaveOccurred()) + + content := fmt.Sprintf(` +githubToken: [[env %s]] +gitlabToken: [[ env '%s']] +`, envKey1, envKey2) + expected := fmt.Sprintf(` +githubToken: %s +gitlabToken: %s +`, envVal1, envVal2) + result, err := renderConfigWithVariables(content, nil) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal([]byte(expected))) + }) + }) }) diff --git a/pkg/util/template/processor.go b/pkg/util/template/processor.go index 8ebaa15a8..c0c9a82de 100644 --- a/pkg/util/template/processor.go +++ b/pkg/util/template/processor.go @@ -5,12 +5,10 @@ import "regexp" // 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*` + // add dot if there is only one word in [[ ]] + regex := `\[\[\s*(\w+)\s*\]\]` r := regexp.MustCompile(regex) - return r.ReplaceAllString(s, "[[ .") + return r.ReplaceAllString(s, "[[ .$1 ]]") } func AddDotForVariablesInConfigProcessor() Processor { @@ -19,6 +17,28 @@ func AddDotForVariablesInConfigProcessor() Processor { } } +// When [[ ]] has two words and the first word don't contain quota, add quotes to the second word +// e.g. [[ env GITHUB_TOKEN]] -> [[ env "GITHUB_TOKEN" ]] +// [[ env 'GITHUB_TOKEN' ]] -> do nothing +// [[ env "GITHUB_TOKEN" ]] -> do nothing +// [[ "env" "GITHUB_TOKEN" ]] -> do nothing +// [[ GITHUB_TOKEN ]] -> do nothing +func addQuoteForVariablesInConfig(s string) string { + regex := `\[\[\s*([^'"]\w+)\s+([^'"]\w+)\s*\]\]` + r := regexp.MustCompile(regex) + return r.ReplaceAllString(s, "[[ $1 \"$2\" ]]") +} + +func AddQuoteForVariablesInConfigProcessor() Processor { + return func(bytes []byte) ([]byte, error) { + return []byte(addQuoteForVariablesInConfig(string(bytes))), nil + } +} + func (r *rendererWithGetter) AddDotForVariablesInConfigProcessor() *rendererWithGetter { return r.AddProcessor(AddDotForVariablesInConfigProcessor()) } + +func (r *rendererWithGetter) AddQuoteForVariablesInConfigProcessor() *rendererWithGetter { + return r.AddProcessor(AddQuoteForVariablesInConfigProcessor()) +} diff --git a/pkg/util/template/processor_test.go b/pkg/util/template/processor_test.go index 48833daaa..780a47642 100644 --- a/pkg/util/template/processor_test.go +++ b/pkg/util/template/processor_test.go @@ -1,27 +1,149 @@ package template import ( - "testing" - - "github.com/stretchr/testify/assert" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) -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.") -} +var _ = Describe("addDotForVariablesInConfig", func() { + var ( + origin, gotten, expected string + ) + + JustBeforeEach(func() { + gotten = addDotForVariablesInConfig(origin) + }) + + When("config is normal", func() { + BeforeEach(func() { + origin = "[[varNameA]]" + expected = "[[ .varNameA ]]" + }) + It("should succeed", func() { + Expect(gotten).To(Equal(expected)) + }) + }) + + When("config has spaces", func() { + BeforeEach(func() { + origin = "[[ varNameA ]]" + expected = "[[ .varNameA ]]" + }) + + It("should succeed", func() { + Expect(gotten).To(Equal(expected)) + }) + }) + + When("config has trailing spaces", func() { + BeforeEach(func() { + origin = "[[ varNameA ]]" + expected = "[[ .varNameA ]]" + }) + + It("should succeed", func() { + Expect(gotten).To(Equal(expected)) + }) + }) + + When("config has multiple variables", func() { + BeforeEach(func() { + origin = "[[ varNameA ]]/[[ varNameB ]]/[[ varNameC ]]" + expected = "[[ .varNameA ]]/[[ .varNameB ]]/[[ .varNameC ]]" + }) + + It("should succeed", func() { + Expect(gotten).To(Equal(expected)) + }) + }) + + When("there are more than one words", func() { + BeforeEach(func() { + origin = "[[ func varNameA ]]" + expected = origin + }) + + It("should do nothing", func() { + Expect(gotten).To(Equal(expected)) + }) + }) +}) + +var _ = Describe("addQuoteForVariablesInConfig", func() { + var ( + origin, gotten, expected string + ) + + JustBeforeEach(func() { + gotten = addQuoteForVariablesInConfig(origin) + }) + AfterEach(func() { + Expect(gotten).To(Equal(expected)) + }) + + When("config is normal", func() { + BeforeEach(func() { + origin = `[[env GITHUB_TOKEN]]` + expected = `[[ env "GITHUB_TOKEN" ]]` + }) + + It("should succeed", func() { + Expect(gotten).To(Equal(expected)) + }) + }) + + When("config has single quote", func() { + BeforeEach(func() { + origin = `[[ env 'GITHUB_TOKEN']]` + expected = origin + }) + + It("should do nothing", func() { + Expect(gotten).To(Equal(expected)) + }) + }) + + When("config has quote", func() { + BeforeEach(func() { + origin = `[[ env "GITHUB_TOKEN"]]` + expected = origin + }) + + It("should do nothing", func() { + Expect(gotten).To(Equal(expected)) + }) + }) + + When("config has multiple variables", func() { + BeforeEach(func() { + origin = `[[ env GITHUB_TOKEN]]/[[ env "GITLAB_TOKEN"]]` + expected = `[[ env "GITHUB_TOKEN" ]]/[[ env "GITLAB_TOKEN"]]` + }) + + It("should succeed", func() { + Expect(gotten).To(Equal(expected)) + }) + }) + + When("the first word has quote", func() { + BeforeEach(func() { + origin = `[[ "env" GITHUB_TOKEN]]` + expected = origin + }) + + It("should do nothing", func() { + Expect(gotten).To(Equal(expected)) + }) + }) + + When("there is only one word", func() { + BeforeEach(func() { + origin = `[[GITHUB_TOKEN]]` + expected = origin + }) + + It("should do nothing", func() { + Expect(gotten).To(Equal(expected)) + }) + }) +}) diff --git a/pkg/util/template/render.go b/pkg/util/template/render.go index 527caf4ee..fc9bc75c7 100644 --- a/pkg/util/template/render.go +++ b/pkg/util/template/render.go @@ -5,19 +5,23 @@ import ( "os" "text/template" + "github.com/Masterminds/sprig/v3" + "github.com/devstream-io/devstream/pkg/util/log" ) func Render(name, templateStr string, variable any, funcMaps ...template.FuncMap) (string, error) { t := template.New(name).Option("missingkey=error").Delims("[[", "]]") + // use sprig functions such as "env" + t.Funcs(sprig.FuncMap()) for _, funcMap := range funcMaps { t.Funcs(funcMap) } t, err := t.Parse(templateStr) if err != nil { - log.Warnf("Template parse file failed: %s.", err) + log.Warnf("Template parse file failed, template: %s, err: %s.", templateStr, err) return "", err }