diff --git a/api/internal/localizer/localizer.go b/api/internal/localizer/localizer.go index 67728c1b2..2d80225d5 100644 --- a/api/internal/localizer/localizer.go +++ b/api/internal/localizer/localizer.go @@ -112,6 +112,13 @@ func (lc *localizer) localize() error { // localizeNativeFields localizes paths on kustomize-native fields, like configMapGenerator, that kustomize has a // built-in understanding of. This excludes helm-related fields, such as `helmGlobals` and `helmCharts`. func (lc *localizer) localizeNativeFields(kust *types.Kustomization) error { + for i, path := range kust.Components { + newPath, err := lc.localizeDir(path) + if err != nil { + return errors.WrapPrefixf(err, "unable to localize components field") + } + kust.Components[i] = newPath + } for i := range kust.Patches { if kust.Patches[i].Path != "" { newPath, err := lc.localizeFile(kust.Patches[i].Path) @@ -121,7 +128,7 @@ func (lc *localizer) localizeNativeFields(kust *types.Kustomization) error { kust.Patches[i].Path = newPath } } - // TODO(annasong): localize all other kustomization fields: resources, bases, components, crds, configurations, + // TODO(annasong): localize all other kustomization fields: resources, bases, crds, configurations, // openapi, patchesJson6902, patchesStrategicMerge, replacements, configMapGenerators, secretGenerators return nil } @@ -160,6 +167,48 @@ func (lc *localizer) localizeFile(path string) (string, error) { return locPath, nil } +// localizeDir localizes root path and returns the localized path +func (lc *localizer) localizeDir(path string) (string, error) { + ldr, err := lc.ldr.New(path) + if err != nil { + return "", errors.Wrap(err) + } + defer func() { _ = ldr.Cleanup() }() + + root, err := filesys.ConfirmDir(lc.fSys, ldr.Root()) + if err != nil { + log.Panicf("unable to establish validated root reference %q: %s", path, err) + } + var locPath string + if repo := ldr.Repo(); repo != "" { + // TODO(annasong): You need to check if you can add a localize directory here to store + // the remote file content. There may be a directory that shares the localize directory name. + locPath = locRootPath(path, repo, root) + } else { + locPath, err = filepath.Rel(lc.root.String(), root.String()) + if err != nil { + log.Panicf("cannot find relative path between scoped localize roots %q and %q: %s", lc.root, root, err) + } + } + newDst := filepath.Join(lc.dst, locPath) + if err = lc.fSys.MkdirAll(newDst); err != nil { + return "", errors.WrapPrefixf(err, "unable to create root %q in localize destination", path) + } + err = (&localizer{ + fSys: lc.fSys, + validator: lc.validator, + rFactory: lc.rFactory, + pLdr: lc.pLdr, + ldr: ldr, + root: root, + dst: newDst, + }).localize() + if err != nil { + return "", errors.WrapPrefixf(err, "unable to localize root %q", path) + } + return locPath, nil +} + // localizeBuiltinPlugins localizes built-in plugins on kust that can contain file paths. The built-in plugins // can be inline or in a file. This excludes the HelmChartInflationGenerator. // diff --git a/api/internal/localizer/localizer_test.go b/api/internal/localizer/localizer_test.go index f050d536e..6d0d3ec26 100644 --- a/api/internal/localizer/localizer_test.go +++ b/api/internal/localizer/localizer_test.go @@ -488,3 +488,170 @@ when parsing as filepath received error: %s`, test.errPrefix, test.inlineErrMsg, }) } } + +func TestLocalizeDirInTarget(t *testing.T) { + type testCase struct { + name string + files map[string]string + } + for _, tc := range []testCase{ + { + name: "multi_nested_child", + files: map[string]string{ + "kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1beta1 +components: +- delta/epsilon +kind: Kustomization +`, + "delta/epsilon/kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +namespace: kustomize-namespace +`, + }, + }, + { + name: "recursive", + files: map[string]string{ + "kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1beta1 +components: +- delta +kind: Kustomization +`, + "delta/kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1alpha1 +components: +- epsilon +kind: Component +`, + "delta/epsilon/kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +namespace: kustomize-namespace +`, + }, + }, + { + name: "file_in_dir", + files: map[string]string{ + "kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1beta1 +components: +- delta +kind: Kustomization +`, + "delta/kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +patches: +- path: patch.yaml +`, + "delta/patch.yaml": podConfiguration, + }, + }, + { + name: "multiple_calls", + files: map[string]string{ + "kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1beta1 +components: +- delta +- delta/epsilon +kind: Kustomization +`, + "delta/kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +namespace: kustomize-namespace +`, + "delta/epsilon/kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1alpha1 +buildMetadata: +- managedByLabel +kind: Component +`, + }, + }, + { + name: "localize_directory_name_when_no_remote", + files: map[string]string{ + "kustomization.yaml": fmt.Sprintf(`apiVersion: kustomize.config.k8s.io/v1beta1 +components: +- %s +kind: Kustomization +`, LocalizeDir), + fmt.Sprintf("%s/kustomization.yaml", LocalizeDir): `apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +namespace: kustomize-namespace +`, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + fSys := makeMemoryFs(t) + addFiles(t, fSys, "/alpha/beta/gamma", tc.files) + + err := Run("/alpha/beta/gamma", "/alpha/beta", "/dst", fSys) + require.NoError(t, err) + + fSysExpected := makeMemoryFs(t) + addFiles(t, fSysExpected, "/alpha/beta/gamma", tc.files) + addFiles(t, fSysExpected, "/dst/gamma", tc.files) + checkFSys(t, fSysExpected, fSys) + }) + } +} + +func TestLocalizeDirCleanedSibling(t *testing.T) { + fSys := makeMemoryFs(t) + kustAndComponents := map[string]string{ + // This test checks that winding paths that might traverse through directories + // outside of scope, which will not be present at destination, are cleaned. + "beta/gamma/kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1beta1 +components: +- delta/../../../../a/b/../../alpha/beta/sibling +kind: Kustomization`, + "beta/sibling/kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +namespace: kustomize-namespace +`, + } + addFiles(t, fSys, "/alpha", kustAndComponents) + + err := Run("/alpha/beta/gamma", "/alpha", "/alpha/beta/dst", fSys) + require.NoError(t, err) + + fSysExpected := makeMemoryFs(t) + addFiles(t, fSysExpected, "/alpha", kustAndComponents) + cleanedFiles := map[string]string{ + "beta/gamma/kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1beta1 +components: +- ../sibling +kind: Kustomization +`, + "beta/sibling/kustomization.yaml": kustAndComponents["beta/sibling/kustomization.yaml"], + } + addFiles(t, fSysExpected, "/alpha/beta/dst", cleanedFiles) + checkFSys(t, fSysExpected, fSys) +} + +func TestLocalizeComponents(t *testing.T) { + fSys := makeMemoryFs(t) + kustAndComponents := map[string]string{ + "kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1beta1 +components: +- a +- alpha +kind: Kustomization +`, + "a/kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +namePrefix: my- +`, + "alpha/kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +nameSuffix: -test +`, + } + addFiles(t, fSys, "/", kustAndComponents) + + err := Run("/", "", "", fSys) + require.NoError(t, err) + + fSysExpected := makeMemoryFs(t) + addFiles(t, fSysExpected, "/", kustAndComponents) + addFiles(t, fSysExpected, "/localized", kustAndComponents) + checkFSys(t, fSysExpected, fSys) +} diff --git a/api/internal/localizer/util.go b/api/internal/localizer/util.go index 58820796a..e435eaf2e 100644 --- a/api/internal/localizer/util.go +++ b/api/internal/localizer/util.go @@ -145,3 +145,12 @@ func locFilePath(fileURL string) string { // so we can use it as is. return filepath.Join(LocalizeDir, u.Hostname(), path) } + +// locRootPath returns the relative localized path of the validated root url rootURL, where the local copy of its repo +// is at repoDir and the copy of its root is at rootDir +// TODO(annasong): implement +func locRootPath(rootURL string, repoDir string, rootDir filesys.ConfirmedDir) string { + _ = rootURL + _, _ = repoDir, rootDir + return "" +}