diff --git a/e2e/tests/config/config.go b/e2e/tests/config/config.go index 518339006f..39706af4bc 100644 --- a/e2e/tests/config/config.go +++ b/e2e/tests/config/config.go @@ -56,6 +56,28 @@ var _ = DevSpaceDescribe("config", func() { framework.ExpectEqual(config.Config().Dev["sync-0"].Terminal.Command, "test") }) + ginkgo.It("should patch correctly", func() { + tempDir, err := framework.CopyToTempDir("tests/config/testdata/profile-patches") + framework.ExpectNoError(err) + defer framework.CleanupTempDir(initialDir, tempDir) + + // reload it + config, _, err := framework.LoadConfigWithOptions(f, kubeClient.Client(), "var-patch.yaml", &loader.ConfigOptions{}) + framework.ExpectNoError(err) + + framework.ExpectEqual(len(config.Config().Images), 1) + framework.ExpectEqual(config.Config().Images["test"].Image, "sample") + framework.ExpectEqual(config.Config().Images["test"].Kaniko.Labels, map[string]string{"app": "sample"}) + + // with profile + config, _, err = framework.LoadConfigWithOptions(f, kubeClient.Client(), "var-patch.yaml", &loader.ConfigOptions{Profiles: []string{"alt"}}) + framework.ExpectNoError(err) + + framework.ExpectEqual(len(config.Config().Images), 1) + framework.ExpectEqual(config.Config().Images["test"].Image, "sample-alt") + framework.ExpectEqual(config.Config().Images["test"].Kaniko.Labels, map[string]string{"app": "sample-alt"}) + }) + ginkgo.It("should resolve runtime environment variables correctly", func() { tempDir, err := framework.CopyToTempDir("tests/config/testdata/runtime-variables") framework.ExpectNoError(err) @@ -1737,8 +1759,7 @@ var _ = DevSpaceDescribe("config", func() { framework.ExpectNoError(err) // check if variables were loaded correctly - fmt.Println(config.Variables()) - framework.ExpectEqual(len(config.Variables()), 4+len(variable.AlwaysResolvePredefinedVars)) + framework.ExpectEqual(len(config.Variables()), 3+len(variable.AlwaysResolvePredefinedVars)) framework.ExpectEqual(len(config.LocalCache().ListVars()), 1) test1, _ := config.LocalCache().GetVar("TEST_1") framework.ExpectEqual(test1, "test") @@ -1767,7 +1788,7 @@ var _ = DevSpaceDescribe("config", func() { framework.ExpectNoError(err) // config - framework.ExpectEqual(len(config.Variables()), 4+len(variable.AlwaysResolvePredefinedVars)) + framework.ExpectEqual(len(config.Variables()), 3+len(variable.AlwaysResolvePredefinedVars)) framework.ExpectEqual(len(config.LocalCache().ListVars()), 2) notUsed, _ = config.LocalCache().GetVar("NOT_USED2") framework.ExpectEqual(notUsed, "test") @@ -1792,7 +1813,7 @@ var _ = DevSpaceDescribe("config", func() { framework.ExpectNoError(err) // check if default config variables were loaded correctly - framework.ExpectEqual(len(config.Variables()), 3+len(variable.AlwaysResolvePredefinedVars)) + framework.ExpectEqual(len(config.Variables()), 2+len(variable.AlwaysResolvePredefinedVars)) framework.ExpectEqual(len(config.LocalCache().ListVars()), 1) value, _ := config.LocalCache().GetVar("NAME") framework.ExpectEqual(value, "default") @@ -1808,7 +1829,7 @@ var _ = DevSpaceDescribe("config", func() { framework.ExpectNoError(err) // check if custom config variables were loaded correctly - framework.ExpectEqual(len(customConfig.Variables()), 3+len(variable.AlwaysResolvePredefinedVars)) + framework.ExpectEqual(len(customConfig.Variables()), 2+len(variable.AlwaysResolvePredefinedVars)) framework.ExpectEqual(len(customConfig.LocalCache().ListVars()), 1) value, _ = customConfig.LocalCache().GetVar("NAME") framework.ExpectEqual(value, "custom") diff --git a/e2e/tests/config/testdata/profile-patches/var-patch.yaml b/e2e/tests/config/testdata/profile-patches/var-patch.yaml new file mode 100644 index 0000000000..dd1ff724d3 --- /dev/null +++ b/e2e/tests/config/testdata/profile-patches/var-patch.yaml @@ -0,0 +1,26 @@ +version: v1beta11 + +vars: + - name: LABELS + value: + app: sample + - name: BIN + value: sample + +images: + test: + image: ${BIN} + build: + kaniko: + labels: ${LABELS} + +profiles: + - name: alt + patches: + - op: replace + path: vars.name=LABELS.value + value: + app: sample-alt + - op: replace + path: vars.name=BIN.value + value: sample-alt \ No newline at end of file diff --git a/pkg/devspace/config/loader/variable/resolver.go b/pkg/devspace/config/loader/variable/resolver.go index 8a10be1132..cf60aa8992 100644 --- a/pkg/devspace/config/loader/variable/resolver.go +++ b/pkg/devspace/config/loader/variable/resolver.go @@ -114,7 +114,7 @@ func (r *resolver) replaceString(ctx context.Context, str string) (interface{}, }) } -func (r *resolver) FindVariables(haystack interface{}) ([]*latest.Variable, error) { +func (r *resolver) findVariablesInclude(haystack interface{}, include []*regexp.Regexp) ([]*latest.Variable, error) { // find out what vars are really used varsUsed := map[string]bool{} switch t := haystack.(type) { @@ -124,7 +124,11 @@ func (r *resolver) FindVariables(haystack interface{}) ([]*latest.Variable, erro return "", nil }) case map[string]interface{}: - err := walk.Walk(t, varMatchFn, func(_, value string) (interface{}, error) { + err := walk.Walk(t, varMatchFn, func(path, value string) (interface{}, error) { + if expression.ExcludedPath(path, nil, include) { + return value, nil + } + _, _ = varspkg.ParseString(value, func(v string) (interface{}, error) { varsUsed[v] = true return "", nil @@ -139,7 +143,7 @@ func (r *resolver) FindVariables(haystack interface{}) ([]*latest.Variable, erro // add always resolve variables for _, v := range r.vars { - if v.AlwaysResolve || v.Value != nil { + if v.AlwaysResolve { varsUsed[v.Name] = true } } @@ -154,6 +158,10 @@ func (r *resolver) FindVariables(haystack interface{}) ([]*latest.Variable, erro return r.orderVariables(varsUsed) } +func (r *resolver) FindVariables(haystack interface{}) ([]*latest.Variable, error) { + return r.findVariablesInclude(haystack, nil) +} + func (r *resolver) orderVariables(vars map[string]bool) ([]*latest.Variable, error) { root := graph.NewNode("root", nil) g := graph.NewGraphOf(root, "variable") @@ -295,7 +303,7 @@ func (r *resolver) FillVariables(ctx context.Context, haystack interface{}) (int } func (r *resolver) findAndFillVariables(ctx context.Context, haystack interface{}, exclude, include []*regexp.Regexp) (interface{}, error) { - varsUsed, err := r.FindVariables(haystack) + varsUsed, err := r.findVariablesInclude(haystack, include) if err != nil { return nil, err } diff --git a/pkg/devspace/services/sync/controller.go b/pkg/devspace/services/sync/controller.go index 88b5450db7..106ac467c8 100644 --- a/pkg/devspace/services/sync/controller.go +++ b/pkg/devspace/services/sync/controller.go @@ -1,6 +1,7 @@ package sync import ( + "bufio" "bytes" "context" "fmt" @@ -26,7 +27,6 @@ import ( "github.com/loft-sh/devspace/pkg/devspace/sync" logpkg "github.com/loft-sh/devspace/pkg/util/log" "github.com/loft-sh/devspace/pkg/util/scanner" - "github.com/moby/buildkit/frontend/dockerfile/dockerignore" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" ) @@ -541,7 +541,7 @@ func parseExcludeFile(path string) ([]string, error) { } defer reader.Close() - paths, err := dockerignore.ReadAll(reader) + paths, err := readAll(reader) if err != nil { return nil, errors.Wrap(err, "read exclude file") } @@ -549,6 +549,58 @@ func parseExcludeFile(path string) ([]string, error) { return paths, nil } +// Taken from Dockerignore +// ReadAll reads a .dockerignore file and returns the list of file patterns +// to ignore. Note this will trim whitespace from each line as well +// as use GO's "clean" func to get the shortest/cleanest path for each. +func readAll(reader io.Reader) ([]string, error) { + if reader == nil { + return nil, nil + } + + scanner := bufio.NewScanner(reader) + var excludes []string + currentLine := 0 + + utf8bom := []byte{0xEF, 0xBB, 0xBF} + for scanner.Scan() { + scannedBytes := scanner.Bytes() + // We trim UTF8 BOM + if currentLine == 0 { + scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom) + } + pattern := string(scannedBytes) + currentLine++ + // Lines starting with # (comments) are ignored before processing + if strings.HasPrefix(pattern, "#") { + continue + } + pattern = strings.TrimSpace(pattern) + if pattern == "" { + continue + } + // normalize absolute paths to paths relative to the context + // (taking care of '!' prefix) + invert := pattern[0] == '!' + if invert { + pattern = strings.TrimSpace(pattern[1:]) + } + if len(pattern) > 0 { + pattern = filepath.Clean(pattern) + pattern = filepath.ToSlash(pattern) + } + if invert { + pattern = "!" + pattern + } + + excludes = append(excludes, pattern) + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading .dockerignore: %v", err) + } + return excludes, nil +} + func StartStream(ctx context.Context, client kubectl.Client, pod *v1.Pod, container string, command []string, reader io.Reader, stdoutWriter io.Writer, buffer bool, log logpkg.Logger) error { stderrBuffer := &bytes.Buffer{} stderrReader, stderrWriter := io.Pipe()