diff --git a/api/internal/localizer/localizer.go b/api/internal/localizer/localizer.go new file mode 100644 index 00000000000..6c3fac348b3 --- /dev/null +++ b/api/internal/localizer/localizer.go @@ -0,0 +1,17 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package localizer + +import ( + "log" + + "sigs.k8s.io/kustomize/kyaml/errors" + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +func cleanDst(newDir string, fSys filesys.FileSystem) { + if err := fSys.RemoveAll(newDir); err != nil { + log.Printf("%s", errors.WrapPrefixf(err, "unable to clean localize destination").Error()) + } +} diff --git a/api/internal/localizer/locloader.go b/api/internal/localizer/locloader.go index 6f761053c2b..d67b8275816 100644 --- a/api/internal/localizer/locloader.go +++ b/api/internal/localizer/locloader.go @@ -6,6 +6,7 @@ package localizer import ( "fmt" "log" + "net/url" "path/filepath" "strings" @@ -18,11 +19,15 @@ import ( const urlSeparator = "/" const replacement = "-" + const dstPrefix = "localized" +const localizeDir = dstPrefix + replacement + "files" const fatalPrefix = "should never happen" -const errRemoteTarget = "invalid remote localize target '%s'" +const errAlpha = "alpha unsupported localize feature" +const errReference = "invalid localize reference location '%s'" -var errRootOutsideScope = errors.Errorf("local root not within scope") +var errDstRef = errors.Errorf("reference into localize destination") +var errRootOutsideScope = errors.Errorf("local root not within localize scope") // locArgs stores localize arguments type locArgs struct { @@ -31,38 +36,95 @@ type locArgs struct { scope filesys.ConfirmedDir // localize destination - newDir string + newDir filesys.ConfirmedDir } -// locLoader is the Loader for kustomize localize. +// LocLoader is the Loader for kustomize localize. // -// Each locLoader is created and Loads under localize constraints. For each input path, locLoader +// Each LocLoader is created and Loads under localize constraints. For each input path, LocLoader // also outputs the corresponding path to the localized copy. -type locLoader struct { +type LocLoader struct { fSys filesys.FileSystem args *locArgs - // loader at locLoader's current directory + // loader at LocLoader's current directory ldr ifc.Loader - // whether locLoader and all its ancestors are the result of local references + // whether LocLoader and all its ancestors are the result of local references local bool // directory in newDir that mirrors ldr root dst string } +// LocPath represents the localized counterpart of a target reference +type LocPath struct { + Remote bool + + Path string +} + +// ValidateLocArgs validates the arguments to kustomize localize. +// On success, ValidateLocArgs returns a LocLoader at targetArg and the localize destination directory; otherwise, an +// error. +func ValidateLocArgs(targetArg string, scopeArg string, newDirArg string, fSys filesys.FileSystem) (*LocLoader, filesys.ConfirmedDir, error) { + // for security, should enforce load restrictions + ldr, err := loader.NewLoader(loader.RestrictionRootOnly, targetArg, fSys) + if err != nil { + return nil, "", errors.WrapPrefixf(err, "unable to establish localize target '%s'", targetArg) + } + var spec *git.RepoSpec + if _, remote := ldr.Repo(); remote { + spec, err = parseLocRootURL(targetArg) + if err != nil { + cleanLdr(ldr) + return nil, "", errors.WrapPrefixf(err, "invalid remote localize target '%s'", targetArg) + } + } + + scope, err := validateScope(scopeArg, targetArg, ldr, fSys) + if err != nil { + cleanLdr(ldr) + return nil, "", errors.WrapPrefixf(err, "invalid localize scope '%s'", scopeArg) + } + + newDir, err := validateNewDir(newDirArg, ldr, spec, fSys) + if err != nil { + cleanLdr(ldr) + return nil, "", errors.WrapPrefixf(err, "invalid localize destination '%s'", newDirArg) + } + + var realScope string + if repo, remote := ldr.Repo(); remote { + realScope = repo + } else { + realScope = scope.String() + } + toDst := pathSuffix(realScope, ldr.Root()) + + return &LocLoader{ + fSys: fSys, + args: &locArgs{ + scope: scope, + newDir: newDir, + }, + ldr: ldr, + local: scope != "", + dst: newDir.Join(toDst), + }, newDir, nil +} + // validateScope returns the scope given localize arguments and targetLdr that corresponds to targetArg func validateScope(scopeArg string, targetArg string, targetLdr ifc.Loader, fSys filesys.FileSystem) (filesys.ConfirmedDir, error) { var scope filesys.ConfirmedDir - errBadScopeForTarget := fmt.Sprintf("invalid scope '%s' for target '%s'", scopeArg, targetArg) + errBadScopeForTarget := fmt.Sprintf("invalid localize scope '%s' for target '%s'", scopeArg, targetArg) switch _, remote := targetLdr.Repo(); { case remote: if scopeArg != "" { return "", errors.WrapPrefixf( - errors.Errorf("cannot specify scope for remote target"), + errors.Errorf("cannot specify scope for remote localize target"), errBadScopeForTarget) } case scopeArg == "": @@ -71,7 +133,7 @@ func validateScope(scopeArg string, targetArg string, targetLdr ifc.Loader, fSys var err error scope, err = filesys.ConfirmDir(fSys, scopeArg) if err != nil { - return "", errors.WrapPrefixf(err, "unable to establish scope '%s'", scopeArg) + return "", errors.WrapPrefixf(err, "unable to establish localize scope") } if !filesys.ConfirmedDir(targetLdr.Root()).HasPrefix(scope) { return scope, errors.WrapPrefixf(errRootOutsideScope, errBadScopeForTarget) @@ -90,7 +152,9 @@ func parseLocRootURL(rootURL string) (*git.RepoSpec, error) { "%s: cannot parse validated remote root '%s'", fatalPrefix, rootURL).Error()) } if spec.Ref == "" { - return nil, errors.Errorf("localize remote root missing ref query string parameter") + return nil, errors.WrapPrefixf( + errors.Errorf("localize remote root missing ref query string parameter"), + "invalid localize remote root '%s'", rootURL) } return spec, nil } @@ -105,93 +169,182 @@ func urlBase(url string) string { return cleaned[i+1:] } -// defaultNewDir calculates the path of the default localize destination directory from targetLdr -// at the localize target and spec of target, which is nil if target is local -func defaultNewDir(targetLdr ifc.Loader, spec *git.RepoSpec, fSys filesys.FileSystem) (string, error) { - wd, err := filesys.ConfirmDir(fSys, filesys.SelfDir) - if err != nil { - return "", errors.WrapPrefixf(err, "unable to establish working directory") - } - - var newDir string +// defaultNewDir calculates the default localize destination directory name from targetLdr at the localize target +// and spec of target, which is nil if target is local +func defaultNewDir(targetLdr ifc.Loader, spec *git.RepoSpec) string { targetDir := filepath.Base(targetLdr.Root()) - if repo, remote := targetLdr.Repo(); remote { + switch repo, remote := targetLdr.Repo(); { + case remote: if repo == targetLdr.Root() { targetDir = urlBase(spec.OrgRepo) } - newDir = strings.Join( + return strings.Join( []string{dstPrefix, targetDir, strings.ReplaceAll(spec.Ref, urlSeparator, replacement)}, replacement) - } else { - newDir = strings.Join([]string{dstPrefix, targetDir}, replacement) + case targetDir == string(filepath.Separator): + return dstPrefix + default: + return strings.Join([]string{dstPrefix, targetDir}, replacement) } - return wd.Join(newDir), nil } -// NewLocLoader is the factory method for locLoader. -// NewLocLoader accepts the arguments to kustomize localize and returns a locLoader at targetArg. -func NewLocLoader(targetArg string, scopeArg string, newDirArg string, fSys filesys.FileSystem) (*locLoader, error) { - // for security, should enforce load restrictions - ldr, err := loader.NewLoader(loader.RestrictionRootOnly, targetArg, fSys) +// validateNewDir returns the localize destination directory or error. Note that spec is nil if targetLdr is at local +// target. +func validateNewDir(newDirArg string, targetLdr ifc.Loader, spec *git.RepoSpec, fSys filesys.FileSystem) (filesys.ConfirmedDir, error) { + var newDirPath string + if newDirArg == "" { + newDirPath = defaultNewDir(targetLdr, spec) + } else { + newDirPath = newDirArg + } + if fSys.Exists(newDirPath) { + return "", errors.WrapPrefixf( + errors.Errorf("localize destination already exists"), + "invalid localize destination path '%s'", newDirPath) + } + if err := fSys.Mkdir(newDirPath); err != nil { + return "", errors.WrapPrefixf(err, "unable to create localize destination directory") + } + newDir, err := filesys.ConfirmDir(fSys, newDirPath) if err != nil { - return nil, errors.WrapPrefixf(err, "unable to establish localize target '%s'", targetArg) + cleanDst(newDirPath, fSys) + return "", errors.WrapPrefixf(err, "unable to establish localize destination") } - var spec *git.RepoSpec - if _, remote := ldr.Repo(); remote { - spec, err = parseLocRootURL(targetArg) - if err != nil { - return nil, errors.WrapPrefixf(err, errRemoteTarget, targetArg) - } + + return newDir, nil +} + +// pathSuffix returns path relative to prefix, where both are cleaned absolute paths +// and prefix is a directory known to contain path +func pathSuffix(prefix string, path string) string { + if prefix == string(filepath.Separator) || prefix == path { + return path[len(prefix):] } + return path[len(prefix)+1:] +} - scope, err := validateScope(scopeArg, targetArg, ldr, fSys) +func (ll *LocLoader) Root() string { + return ll.ldr.Root() +} + +func (ll *LocLoader) Dst() string { + return ll.dst +} + +func toFSPath(urlPath string) string { + return filepath.Join(strings.Split(urlPath, urlSeparator)...) +} + +func locFilePath(u *url.URL) string { + // raw github urls are the only type of file urls kustomize accepts; + // path consists of org, repo, version, path in repo + return filepath.Join(localizeDir, u.Hostname(), toFSPath(u.Path)) +} + +// Load returns the contents of path and its corresponding localize destination path information +// if path is a file that can be localized. Otherwise, Load returns an error. +func (ll *LocLoader) Load(path string) ([]byte, *LocPath, error) { + // checks in root, and thus in scope + content, err := ll.ldr.Load(path) if err != nil { - clean(ldr) - return nil, err + return nil, nil, errors.WrapPrefixf(err, "invalid file reference") } - var newDir string - if newDirArg == "" { - newDir, err = defaultNewDir(ldr, spec, fSys) + var dst string + var remote bool + switch { + case loader.HasRemoteFileScheme(path): + remote = true + + u, err := url.Parse(path) if err != nil { - clean(ldr) - return nil, err + log.Fatalf(errors.WrapPrefixf( + err, + "%s: cannot parse validated file url '%s'", fatalPrefix, path).Error()) + } + dst = locFilePath(u) + case !filepath.IsAbs(path): + abs := filepath.Join(ll.ldr.Root(), path) + // avoid symlinks; only write file corresponding to actual location in root + // avoid path that Load() shows to be in root, but may traverse outside + // temporarily; for example, ../root/config; problematic for rename and + // relocation + dir, f, err := ll.fSys.CleanedAbs(abs) + if err != nil { + log.Fatalf(errors.WrapPrefixf( + err, + "%s: cannot clean validated file path '%s'", fatalPrefix, abs).Error()) } - } else { - newDir = newDirArg - } - var realScope string - if repo, remote := ldr.Repo(); remote { - realScope = repo - } else { - realScope = scope.String() + cleanPath := dir.Join(f) + // target cannot reference newDir, as this load would've failed prior to localize; + // not a problem if remote because then reference could only be in newDir if repo copy, + // which will be cleaned, is inside newDir + if ll.local && dir.HasPrefix(ll.args.newDir) { + return nil, nil, errors.WrapPrefixf(errDstRef, errReference, cleanPath) + } + dst = pathSuffix(ll.ldr.Root(), cleanPath) + default: + return nil, nil, errors.WrapPrefixf(errors.Errorf(errAlpha), "path '%s' is absolute", path) } - toDst, err := filepath.Rel(realScope, ldr.Root()) + + return content, &LocPath{ + remote, + dst, + }, nil +} + +// New returns a LocLoader at path and whether path is remote if path is a root that can be localized. +// Otherwise, New returns an error. +func (ll *LocLoader) New(path string) (*LocLoader, *LocPath, error) { + ldr, err := ll.ldr.New(path) if err != nil { - log.Fatalf(errors.WrapPrefixf( - err, - "%s: scope '%s' contains target '%s'", fatalPrefix, realScope, ldr.Root()).Error()) + return nil, nil, errors.WrapPrefixf(err, "invalid root reference") } - return &locLoader{ - fSys: fSys, - args: &locArgs{ - scope: scope, - newDir: newDir, - }, - ldr: ldr, - local: scope == "", - dst: filepath.Join(newDir, toDst), - }, nil + var repo, inDst string + var remote bool + switch repo, remote = ldr.Repo(); { + case remote: + spec, err := parseLocRootURL(path) + if err != nil { + return nil, nil, err + } + inRepo := pathSuffix(repo, ldr.Root()) + inDst = filepath.Join(localizeDir, spec.Domain, toFSPath(spec.OrgRepo), toFSPath(spec.Ref), inRepo) + case ll.local && !filesys.ConfirmedDir(ldr.Root()).HasPrefix(ll.args.scope): + return nil, nil, errors.WrapPrefixf( + errRootOutsideScope, + "invalid root location '%s' for scope '%s'", ldr.Root(), ll.args.scope) + case ll.local && filesys.ConfirmedDir(ldr.Root()).HasPrefix(ll.args.newDir): + return nil, nil, errors.WrapPrefixf(errDstRef, errReference, ldr.Root()) + default: + inDst, err = filepath.Rel(ll.ldr.Root(), ldr.Root()) + if err != nil { + log.Fatalf(errors.WrapPrefixf(err, + "%s: cannot find relative path from root '%s' to its valid root reference '%s'", + fatalPrefix, ll.ldr.Root(), ldr.Root()).Error()) + } + } + + return &LocLoader{ + fSys: ll.fSys, + args: ll.args, + ldr: ldr, + local: ll.local && !remote, + dst: filepath.Join(ll.dst, inDst), + }, &LocPath{ + remote, + inDst, + }, nil } // Cleanup tries to clean the by-products of localize and logs if unsuccessful -func (ll *locLoader) Cleanup() { - clean(ll.ldr) +func (ll *LocLoader) Cleanup() { + cleanLdr(ll.ldr) } -func clean(ldr ifc.Loader) { +func cleanLdr(ldr ifc.Loader) { if err := ldr.Cleanup(); err != nil { log.Printf("%s", errors.WrapPrefixf(err, "unable to clean by-products of loading target").Error()) } diff --git a/api/internal/localizer/locloader_test.go b/api/internal/localizer/locloader_test.go index 7a4cf8bc3c8..19a1eeec474 100644 --- a/api/internal/localizer/locloader_test.go +++ b/api/internal/localizer/locloader_test.go @@ -6,7 +6,6 @@ package localizer_test import ( "bytes" "log" - "path/filepath" "testing" "github.com/stretchr/testify/require" @@ -14,45 +13,74 @@ import ( "sigs.k8s.io/kustomize/kyaml/filesys" ) +const dstPrefix = "localized" + func makeMemoryFs(t *testing.T) filesys.FileSystem { t.Helper() req := require.New(t) fSys := filesys.MakeFsInMemory() - req.NoError(fSys.Mkdir("/a")) - req.NoError(fSys.WriteFile("/a/kustomization.yaml", []byte("file"))) - req.NoError(fSys.MkdirAll("/alpha/beta/gamma")) + req.NoError(fSys.MkdirAll("/a/b")) + req.NoError(fSys.WriteFile("/a/kustomization.yaml", []byte("/a"))) + + dirChain := "/alpha/beta/gamma/delta" + req.NoError(fSys.MkdirAll(dirChain)) + req.NoError(fSys.WriteFile(dirChain+"/kustomization.yaml", []byte(dirChain))) + req.NoError(fSys.Mkdir("/alpha/beta/c")) return fSys } -func TestNewLocLoaderLocalTarget(t *testing.T) { - cases := map[string]string{ - "absolute": "/a", - "relative": "a", - } - for name, target := range cases { - target := target - t.Run(name, func(t *testing.T) { - req := require.New(t) +func checkLocLoader(req *require.Assertions, ll *lclzr.LocLoader, root string, dst string) { + req.Equal(root, ll.Root()) + req.Equal(dst, ll.Dst()) +} - fSys := makeMemoryFs(t) +func checkConfirmedDir(req *require.Assertions, dir filesys.ConfirmedDir, path string, fSys filesys.FileSystem) { + req.Equal(path, dir.String()) + req.True(fSys.Exists(path)) +} - var buf bytes.Buffer - log.SetOutput(&buf) - locLdr, err := lclzr.NewLocLoader(target, "/", "/newDir", fSys) - req.NoError(err) +func checkLocPath(req *require.Assertions, locPath *lclzr.LocPath, remote bool, path string) { + req.Equal(remote, locPath.Remote) + req.Equal(path, locPath.Path) +} - fSysCopy := makeMemoryFs(t) - req.Equal(fSysCopy, fSys) +func TestLocalLoadNewAndCleanup(t *testing.T) { + req := require.New(t) + fSys := makeMemoryFs(t) - locLdr.Cleanup() - req.Equal(fSysCopy, fSys) - req.Empty(buf.String()) - }) - } + var buf bytes.Buffer + log.SetOutput(&buf) + // typical setup + ll, dst, err := lclzr.ValidateLocArgs("a", "/", "/newDir", fSys) + req.NoError(err) + checkLocLoader(req, ll, "/a", "/newDir/a") + + req.Equal("/newDir", dst.String()) + fSysCopy := makeMemoryFs(t) + req.NoError(fSysCopy.Mkdir("/newDir")) + req.Equal(fSysCopy, fSys) + + // easy load directly in root + content, locPath, err := ll.Load("kustomization.yaml") + req.NoError(err) + req.Equal([]byte("/a"), content) + checkLocPath(req, locPath, false, "kustomization.yaml") + + // typical sibling root reference + sibLL, locPath, err := ll.New("../alpha") + req.NoError(err) + checkLocLoader(req, sibLL, "/alpha", "/newDir/alpha") + checkLocPath(req, locPath, false, "../alpha") + + // only need to test once, since don't need to call Cleanup() on local target + ll.Cleanup() + // file system checks are also one-time + req.Equal(fSysCopy, fSys) + req.Empty(buf.String()) } -func TestNewLocLoaderDefaultScope(t *testing.T) { +func TestValidateLocArgsDefaultForRootTarget(t *testing.T) { cases := map[string]struct { target string scope string @@ -70,18 +98,56 @@ func TestNewLocLoaderDefaultScope(t *testing.T) { params := params t.Run(name, func(t *testing.T) { req := require.New(t) - fSys := makeMemoryFs(t) - _, err := lclzr.NewLocLoader(params.target, params.scope, "/a/newDir", fSys) + ll, dst, err := lclzr.ValidateLocArgs(params.target, params.scope, "", fSys) + req.NoError(err) + checkLocLoader(req, ll, "/", "/"+dstPrefix) + checkConfirmedDir(req, dst, "/"+dstPrefix, fSys) + + // file in root, but nested + content, locPath, err := ll.Load("a/kustomization.yaml") + req.NoError(err) + req.Equal([]byte("/a"), content) + checkLocPath(req, locPath, false, "a/kustomization.yaml") + + childLL, locPath, err := ll.New("a") + req.NoError(err) + checkLocLoader(req, childLL, "/a", "/"+dstPrefix+"/a") + checkLocPath(req, locPath, false, "a") + + // messy, uncleaned path + content, locPath, err = childLL.Load("./../a/kustomization.yaml") req.NoError(err) + req.Equal([]byte("/a"), content) + checkLocPath(req, locPath, false, "kustomization.yaml") }) } } -func TestNewLocLoaderDefaultDst(t *testing.T) { - _, err := lclzr.NewLocLoader("/alpha/beta", "/alpha", "", makeMemoryFs(t)) - require.NoError(t, err) +func TestNewMultiple(t *testing.T) { + req := require.New(t) + fSys := makeMemoryFs(t) + + // default destination for non-file system root target + // destination outside of scope + ll, dst, err := lclzr.ValidateLocArgs("/alpha/beta", "/alpha", "", fSys) + req.NoError(err) + newDir := "/" + dstPrefix + "-beta" + checkLocLoader(req, ll, "/alpha/beta", newDir+"/beta") + checkConfirmedDir(req, dst, newDir, fSys) + + // nested child root that isn't cleaned + descLL, locPath, err := ll.New("../beta/gamma/delta") + req.NoError(err) + checkLocLoader(req, descLL, "/alpha/beta/gamma/delta", newDir+"/beta/gamma/delta") + checkLocPath(req, locPath, false, "gamma/delta") + + // upwards traversal + higherLL, locPath, err := descLL.New("../../c") + req.NoError(err) + checkLocLoader(req, higherLL, "/alpha/beta/c", newDir+"/beta/c") + checkLocPath(req, locPath, false, "../../c") } func makeWdFs(t *testing.T) map[string]filesys.FileSystem { @@ -102,13 +168,14 @@ func makeWdFs(t *testing.T) map[string]filesys.FileSystem { } } -func TestNewLocLoaderCwdNotRoot(t *testing.T) { +func TestValidateLocArgsCwdNotRoot(t *testing.T) { cases := map[string]struct { wd string target string scope string - dest string + newDir string }{ + // target not immediate child of scope "outer dir": { "a", "b/c/d/e", @@ -127,17 +194,21 @@ func TestNewLocLoaderCwdNotRoot(t *testing.T) { test := test t.Run(name, func(t *testing.T) { req := require.New(t) - fSys := makeWdFs(t)[test.wd] - dest := filepath.Join(test.wd, test.dest) - _, err := lclzr.NewLocLoader(test.target, test.scope, dest, fSys) + ll, dst, err := lclzr.ValidateLocArgs(test.target, test.scope, test.newDir, fSys) req.NoError(err) + newDir := test.wd + "/" + test.newDir + checkLocLoader(req, ll, "a/b/c/d/e", newDir+"/d/e") + + req.Equal(newDir, dst.String()) + // memory file system can only find paths rooted at current node + req.True(fSys.Exists(test.newDir)) }) } } -func TestNewLocLoaderFails(t *testing.T) { +func TestValidateLocArgsFails(t *testing.T) { cases := map[string]struct { target string scope string @@ -163,14 +234,82 @@ func TestNewLocLoaderFails(t *testing.T) { "/a", "/newDir", }, + "existing dst": { + "/alpha", + "/", + "/a", + }, } for name, params := range cases { params := params + t.Run(name, func(t *testing.T) { + _, _, err := lclzr.ValidateLocArgs(params.target, params.scope, params.dest, makeMemoryFs(t)) + require.Error(t, err) + }) + } +} + +func TestNewFails(t *testing.T) { + t.Run("ValidateLocArgs", func(t *testing.T) { + req := require.New(t) + fSys := makeMemoryFs(t) + + ll, dst, err := lclzr.ValidateLocArgs("/alpha/beta/gamma", "alpha", "alpha/beta/gamma/newDir", fSys) + req.NoError(err) + checkLocLoader(req, ll, "/alpha/beta/gamma", "/alpha/beta/gamma/newDir/beta/gamma") + checkConfirmedDir(req, dst, "/alpha/beta/gamma/newDir", fSys) + }) + cases := map[string]string{ + "outside scope": "../../../a", + "at dst": "newDir", + "ancestor": "../../beta", + "non-existent root": "delt", + "file": "delta/kustomization.yaml", + } + for name, root := range cases { + root := root t.Run(name, func(t *testing.T) { fSys := makeMemoryFs(t) - _, err := lclzr.NewLocLoader(params.target, params.scope, params.dest, fSys) + ll, _, err := lclzr.ValidateLocArgs("/alpha/beta/gamma", "alpha", "alpha/beta/gamma/newDir", fSys) + require.NoError(t, err) + + _, _, err = ll.New(root) require.Error(t, err) }) } } + +func TestLoadFails(t *testing.T) { + t.Run("ValidateLocArgs", func(t *testing.T) { + req := require.New(t) + fSys := makeMemoryFs(t) + + ll, dst, err := lclzr.ValidateLocArgs("a", "", "/a/b/newDir", fSys) + req.NoError(err) + checkLocLoader(req, ll, "/a", "/a/b/newDir") + checkConfirmedDir(req, dst, "/a/b/newDir", fSys) + }) + cases := map[string]string{ + "absolute path": "/a/kustomization.yaml", + "directory": "b", + "non-existent file": "kubectl.yaml", + "file outside root": "../alpha/beta/gamma/delta/kustomization.yaml", + "inside dst": "newDir/kustomization.yaml", + } + for name, file := range cases { + file := file + t.Run(name, func(t *testing.T) { + req := require.New(t) + fSys := makeMemoryFs(t) + + ll, _, err := lclzr.ValidateLocArgs("./a/../a", "/a/../a", "/a/newDir", fSys) + req.NoError(err) + + req.NoError(fSys.WriteFile("/a/newDir/kustomization.yaml", []byte("/a/newDir"))) + + _, _, err = ll.Load(file) + req.Error(err) + }) + } +}