1
2
3
4 package localizer
5
6 import (
7 "io/fs"
8 "log"
9 "os"
10 "path/filepath"
11
12 "sigs.k8s.io/kustomize/api/ifc"
13 "sigs.k8s.io/kustomize/api/internal/generators"
14 "sigs.k8s.io/kustomize/api/internal/loader"
15 "sigs.k8s.io/kustomize/api/internal/target"
16 "sigs.k8s.io/kustomize/api/provider"
17 "sigs.k8s.io/kustomize/api/resmap"
18 "sigs.k8s.io/kustomize/api/types"
19 "sigs.k8s.io/kustomize/kyaml/errors"
20 "sigs.k8s.io/kustomize/kyaml/filesys"
21 "sigs.k8s.io/yaml"
22 )
23
24
25 type localizer struct {
26 fSys filesys.FileSystem
27
28
29 ldr ifc.Loader
30
31
32 root filesys.ConfirmedDir
33
34 rFactory *resmap.Factory
35
36
37 dst string
38 }
39
40
41
42 func Run(target, scope, newDir string, fSys filesys.FileSystem) (string, error) {
43 ldr, args, err := NewLoader(target, scope, newDir, fSys)
44 if err != nil {
45 return "", errors.Wrap(err)
46 }
47 defer func() { _ = ldr.Cleanup() }()
48
49 toDst, err := filepath.Rel(args.Scope.String(), args.Target.String())
50 if err != nil {
51 log.Panicf("cannot find path from %q to child directory %q: %s", args.Scope, args.Target, err)
52 }
53 dst := args.NewDir.Join(toDst)
54 if err = fSys.MkdirAll(dst); err != nil {
55 return "", errors.WrapPrefixf(err, "unable to create directory in localize destination")
56 }
57
58 err = (&localizer{
59 fSys: fSys,
60 ldr: ldr,
61 root: args.Target,
62 rFactory: resmap.NewFactory(provider.NewDepProvider().GetResourceFactory()),
63 dst: dst,
64 }).localize()
65 if err != nil {
66 errCleanup := fSys.RemoveAll(args.NewDir.String())
67 if errCleanup != nil {
68 log.Printf("unable to clean localize destination: %s", errCleanup)
69 }
70 return "", errors.WrapPrefixf(err, "unable to localize target %q", target)
71 }
72 return args.NewDir.String(), nil
73 }
74
75
76 func (lc *localizer) localize() error {
77 kustomization, kustFileName, err := lc.load()
78 if err != nil {
79 return err
80 }
81 err = lc.localizeNativeFields(kustomization)
82 if err != nil {
83 return err
84 }
85 err = lc.localizeBuiltinPlugins(kustomization)
86 if err != nil {
87 return err
88 }
89
90 content, err := yaml.Marshal(kustomization)
91 if err != nil {
92 return errors.WrapPrefixf(err, "unable to serialize localized kustomization file")
93 }
94 if err = lc.fSys.WriteFile(filepath.Join(lc.dst, kustFileName), content); err != nil {
95 return errors.WrapPrefixf(err, "unable to write localized kustomization file")
96 }
97 return nil
98 }
99
100
101 func (lc *localizer) load() (*types.Kustomization, string, error) {
102 content, kustFileName, err := target.LoadKustFile(lc.ldr)
103 if err != nil {
104 return nil, "", errors.Wrap(err)
105 }
106
107 var kust types.Kustomization
108 err = (&kust).Unmarshal(content)
109 if err != nil {
110 return nil, "", errors.Wrap(err)
111 }
112
113
114
115
116
117
118 return &kust, kustFileName, nil
119 }
120
121
122
123 func (lc *localizer) localizeNativeFields(kust *types.Kustomization) error {
124 if path, exists := kust.OpenAPI["path"]; exists {
125 locPath, err := lc.localizeFile(path)
126 if err != nil {
127 return errors.WrapPrefixf(err, "unable to localize openapi path")
128 }
129 kust.OpenAPI["path"] = locPath
130 }
131
132 for fieldName, field := range map[string]struct {
133 paths []string
134 locFn func(string) (string, error)
135 }{
136 "bases": {
137
138
139 kust.Bases,
140 lc.localizeRoot,
141 },
142 "components": {
143 kust.Components,
144 lc.localizeRoot,
145 },
146 "configurations": {
147 kust.Configurations,
148 lc.localizeFile,
149 },
150 "crds": {
151 kust.Crds,
152 lc.localizeFile,
153 },
154 "resources": {
155 kust.Resources,
156 lc.localizeResource,
157 },
158 } {
159 for i, path := range field.paths {
160 locPath, err := field.locFn(path)
161 if err != nil {
162 return errors.WrapPrefixf(err, "unable to localize %s entry", fieldName)
163 }
164 field.paths[i] = locPath
165 }
166 }
167
168 for i := range kust.ConfigMapGenerator {
169 if err := lc.localizeGenerator(&kust.ConfigMapGenerator[i].GeneratorArgs); err != nil {
170 return errors.WrapPrefixf(err, "unable to localize configMapGenerator")
171 }
172 }
173 for i := range kust.SecretGenerator {
174 if err := lc.localizeGenerator(&kust.SecretGenerator[i].GeneratorArgs); err != nil {
175 return errors.WrapPrefixf(err, "unable to localize secretGenerator")
176 }
177 }
178 if err := lc.localizeHelmInflationGenerator(kust); err != nil {
179 return err
180 }
181 if err := lc.localizeHelmCharts(kust); err != nil {
182 return err
183 }
184 if err := lc.localizePatches(kust.Patches); err != nil {
185 return errors.WrapPrefixf(err, "unable to localize patches")
186 }
187
188 if err := lc.localizePatches(kust.PatchesJson6902); err != nil {
189 return errors.WrapPrefixf(err, "unable to localize patchesJson6902")
190 }
191
192 for i, patch := range kust.PatchesStrategicMerge {
193 locPath, err := lc.localizeK8sResource(string(patch))
194 if err != nil {
195 return errors.WrapPrefixf(err, "unable to localize patchesStrategicMerge entry")
196 }
197 kust.PatchesStrategicMerge[i] = types.PatchStrategicMerge(locPath)
198 }
199 for i, replacement := range kust.Replacements {
200 locPath, err := lc.localizeFile(replacement.Path)
201 if err != nil {
202 return errors.WrapPrefixf(err, "unable to localize replacements entry")
203 }
204 kust.Replacements[i].Path = locPath
205 }
206 return nil
207 }
208
209
210 func (lc *localizer) localizeGenerator(generator *types.GeneratorArgs) error {
211 locEnvSrc, err := lc.localizeFile(generator.EnvSource)
212 if err != nil {
213 return errors.WrapPrefixf(err, "unable to localize generator env file")
214 }
215 locEnvs := make([]string, len(generator.EnvSources))
216 for i, env := range generator.EnvSources {
217 locEnvs[i], err = lc.localizeFile(env)
218 if err != nil {
219 return errors.WrapPrefixf(err, "unable to localize generator envs file")
220 }
221 }
222 locFiles := make([]string, len(generator.FileSources))
223 for i, file := range generator.FileSources {
224 locFiles[i], err = lc.localizeFileSource(file)
225 if err != nil {
226 return err
227 }
228 }
229 generator.EnvSource = locEnvSrc
230 generator.EnvSources = locEnvs
231 generator.FileSources = locFiles
232 return nil
233 }
234
235
236
237 func (lc *localizer) localizeFileSource(source string) (string, error) {
238 key, file, err := generators.ParseFileSource(source)
239 if err != nil {
240 return "", errors.Wrap(err)
241 }
242 locFile, err := lc.localizeFile(file)
243 if err != nil {
244 return "", errors.WrapPrefixf(err, "invalid file source %q", source)
245 }
246 var locSource string
247 if source == file {
248 locSource = locFile
249 } else {
250 locSource = key + "=" + locFile
251 }
252 return locSource, nil
253 }
254
255
256
257 func (lc *localizer) localizeHelmInflationGenerator(kust *types.Kustomization) error {
258 for i, chart := range kust.HelmChartInflationGenerator {
259 locFile, err := lc.localizeFile(chart.Values)
260 if err != nil {
261 return errors.WrapPrefixf(err, "unable to localize helmChartInflationGenerator entry %d values", i)
262 }
263 kust.HelmChartInflationGenerator[i].Values = locFile
264
265 locDir, err := lc.copyChartHomeEntry(chart.ChartHome)
266 if err != nil {
267 return errors.WrapPrefixf(err, "unable to copy helmChartInflationGenerator entry %d", i)
268 }
269 kust.HelmChartInflationGenerator[i].ChartHome = locDir
270 }
271 return nil
272 }
273
274
275
276 func (lc *localizer) localizeHelmCharts(kust *types.Kustomization) error {
277 for i, chart := range kust.HelmCharts {
278 locFile, err := lc.localizeFile(chart.ValuesFile)
279 if err != nil {
280 return errors.WrapPrefixf(err, "unable to localize helmCharts entry %d valuesFile", i)
281 }
282 kust.HelmCharts[i].ValuesFile = locFile
283
284 for j, valuesFile := range chart.AdditionalValuesFiles {
285 locFile, err = lc.localizeFile(valuesFile)
286 if err != nil {
287 return errors.WrapPrefixf(err, "unable to localize helmCharts entry %d additionalValuesFiles", i)
288 }
289 kust.HelmCharts[i].AdditionalValuesFiles[j] = locFile
290 }
291 }
292 if kust.HelmGlobals != nil {
293 locDir, err := lc.copyChartHomeEntry(kust.HelmGlobals.ChartHome)
294 if err != nil {
295 return errors.WrapPrefixf(err, "unable to copy helmGlobals")
296 }
297 kust.HelmGlobals.ChartHome = locDir
298 } else if len(kust.HelmCharts) > 0 {
299 _, err := lc.copyChartHomeEntry("")
300 if err != nil {
301 return errors.WrapPrefixf(err, "unable to copy default chart home")
302 }
303 }
304 return nil
305 }
306
307
308 func (lc *localizer) localizePatches(patches []types.Patch) error {
309 for i := range patches {
310 locPath, err := lc.localizeFile(patches[i].Path)
311 if err != nil {
312 return err
313 }
314 patches[i].Path = locPath
315 }
316 return nil
317 }
318
319
320
321 func (lc *localizer) localizeResource(path string) (string, error) {
322 var locPath string
323
324 content, fileErr := lc.ldr.Load(path)
325
326
327
328
329 if fileErr == nil {
330 _, resErr := lc.rFactory.NewResMapFromBytes(content)
331 if resErr != nil {
332 fileErr = errors.WrapPrefixf(resErr, "invalid resource at file %q", path)
333 } else {
334 locPath, fileErr = lc.localizeFileWithContent(path, content)
335 }
336 }
337 if fileErr != nil {
338 var rootErr error
339 locPath, rootErr = lc.localizeRoot(path)
340 if rootErr != nil {
341 err := PathLocalizeError{
342 Path: path,
343 FileError: fileErr,
344 RootError: rootErr,
345 }
346 return "", err
347 }
348 }
349 return locPath, nil
350 }
351
352
353 func (lc *localizer) localizeFile(path string) (string, error) {
354
355
356 if path == "" {
357 return "", nil
358 }
359 content, err := lc.ldr.Load(path)
360 if err != nil {
361 return "", errors.Wrap(err)
362 }
363 return lc.localizeFileWithContent(path, content)
364 }
365
366
367 func (lc *localizer) localizeFileWithContent(path string, content []byte) (string, error) {
368 var locPath string
369 if loader.IsRemoteFile(path) {
370 if lc.fSys.Exists(lc.root.Join(LocalizeDir)) {
371 return "", errors.Errorf("%s already contains %s needed to store file %q", lc.root, LocalizeDir, path)
372 }
373 locPath = locFilePath(path)
374 } else {
375
376
377
378
379
380
381
382
383
384 locPath = cleanFilePath(lc.fSys, lc.root, path)
385 }
386 absPath := filepath.Join(lc.dst, locPath)
387 if err := lc.fSys.MkdirAll(filepath.Dir(absPath)); err != nil {
388 return "", errors.WrapPrefixf(err, "unable to create directories to localize file %q", path)
389 }
390 if err := lc.fSys.WriteFile(absPath, content); err != nil {
391 return "", errors.WrapPrefixf(err, "unable to localize file %q", path)
392 }
393 return locPath, nil
394 }
395
396
397 func (lc *localizer) localizeRoot(path string) (string, error) {
398 if path == "" {
399 return "", nil
400 }
401 ldr, err := lc.ldr.New(path)
402 if err != nil {
403 return "", errors.Wrap(err)
404 }
405 defer func() { _ = ldr.Cleanup() }()
406
407 root, err := filesys.ConfirmDir(lc.fSys, ldr.Root())
408 if err != nil {
409 log.Panicf("unable to establish validated root reference %q: %s", path, err)
410 }
411 var locPath string
412 if repo := ldr.Repo(); repo != "" {
413 if lc.fSys.Exists(lc.root.Join(LocalizeDir)) {
414 return "", errors.Errorf("%s already contains %s needed to store root %q", lc.root, LocalizeDir, path)
415 }
416 locPath, err = locRootPath(path, repo, root, lc.fSys)
417 if err != nil {
418 return "", err
419 }
420 } else {
421 locPath, err = filepath.Rel(lc.root.String(), root.String())
422 if err != nil {
423 log.Panicf("cannot find relative path between scoped localize roots %q and %q: %s", lc.root, root, err)
424 }
425 }
426 newDst := filepath.Join(lc.dst, locPath)
427 if err = lc.fSys.MkdirAll(newDst); err != nil {
428 return "", errors.WrapPrefixf(err, "unable to create root %q in localize destination", path)
429 }
430 err = (&localizer{
431 fSys: lc.fSys,
432 ldr: ldr,
433 root: root,
434 rFactory: lc.rFactory,
435 dst: newDst,
436 }).localize()
437 if err != nil {
438 return "", errors.WrapPrefixf(err, "unable to localize root %q", path)
439 }
440 return locPath, nil
441 }
442
443
444
445
446
447
448
449
450 func (lc *localizer) copyChartHomeEntry(entry string) (string, error) {
451 path := entry
452 if entry == "" {
453 path = types.HelmDefaultHome
454 }
455 if filepath.IsAbs(path) {
456 return "", errors.Errorf("absolute path %q not handled in alpha", path)
457 }
458 isDefault := lc.root.Join(path) == lc.root.Join(types.HelmDefaultHome)
459 locPath, err := lc.copyChartHome(path, !isDefault)
460 if err != nil {
461 return "", errors.WrapPrefixf(err, "unable to copy home %q", entry)
462 }
463 if entry == "" {
464 return "", nil
465 }
466 return locPath, nil
467 }
468
469
470
471
472
473
474 func (lc *localizer) copyChartHome(path string, clean bool) (string, error) {
475 path, err := filepath.Rel(lc.root.String(), lc.root.Join(path))
476 if err != nil {
477 return "", errors.WrapPrefixf(err, "no path to chart home %q", path)
478 }
479
480
481 if !lc.fSys.Exists(lc.root.Join(path)) {
482 return path, nil
483 }
484
485 ldr, err := lc.ldr.New(path)
486 if err != nil {
487 return "", errors.WrapPrefixf(err, "invalid chart home")
488 }
489 cleaned, err := filesys.ConfirmDir(lc.fSys, ldr.Root())
490 if err != nil {
491 log.Panicf("unable to confirm validated directory %q: %s", ldr.Root(), err)
492 }
493 toDst := path
494 if clean {
495 toDst, err = filepath.Rel(lc.root.String(), cleaned.String())
496 if err != nil {
497 log.Panicf("no path between scoped directories %q and %q: %s", lc.root, cleaned, err)
498 }
499 }
500
501 if dst := filepath.Join(lc.dst, toDst); !lc.fSys.Exists(dst) {
502 err = lc.copyDir(cleaned, filepath.Join(lc.dst, toDst))
503 if err != nil {
504 return "", errors.WrapPrefixf(err, "unable to copy chart home %q", path)
505 }
506 }
507 return toDst, nil
508 }
509
510
511 func (lc *localizer) copyDir(src filesys.ConfirmedDir, dst string) error {
512 err := lc.fSys.Walk(src.String(),
513 func(path string, info fs.FileInfo, err error) error {
514 if err != nil {
515 return err
516 }
517 pathToCreate, err := filepath.Rel(src.String(), path)
518 if err != nil {
519 log.Panicf("no path from %q to child file %q: %s", src, path, err)
520 }
521 pathInDst := filepath.Join(dst, pathToCreate)
522 if info.Mode()&os.ModeSymlink == os.ModeSymlink {
523 return nil
524 }
525 if info.IsDir() {
526 err = lc.fSys.MkdirAll(pathInDst)
527 } else {
528 var content []byte
529 content, err = lc.fSys.ReadFile(path)
530 if err != nil {
531 return errors.Wrap(err)
532 }
533 err = lc.fSys.WriteFile(pathInDst, content)
534 }
535 return errors.Wrap(err)
536 })
537 if err != nil {
538 return errors.WrapPrefixf(err, "unable to copy directory %q", src)
539 }
540 return nil
541 }
542
543
544
545
546
547 func (lc *localizer) localizeBuiltinPlugins(kust *types.Kustomization) error {
548 for fieldName, entries := range map[string][]string{
549 "generators": kust.Generators,
550 "transformers": kust.Transformers,
551 "validators": kust.Validators,
552 } {
553 for i, entry := range entries {
554 rm, isPath, err := lc.loadK8sResource(entry)
555 if err != nil {
556 return errors.WrapPrefixf(err, "unable to load %s entry", fieldName)
557 }
558 err = rm.ApplyFilter(&localizeBuiltinPlugins{lc: lc})
559 if err != nil {
560 return errors.Wrap(err)
561 }
562 localizedPlugin, err := rm.AsYaml()
563 if err != nil {
564 return errors.WrapPrefixf(err, "unable to serialize localized %s entry %q", fieldName, entry)
565 }
566 var localizedEntry string
567 if isPath {
568 localizedEntry, err = lc.localizeFileWithContent(entry, localizedPlugin)
569 if err != nil {
570 return errors.WrapPrefixf(err, "unable to localize %s entry", fieldName)
571 }
572 } else {
573 localizedEntry = string(localizedPlugin)
574 }
575 entries[i] = localizedEntry
576 }
577 }
578 return nil
579 }
580
581
582
583
584 func (lc *localizer) localizeK8sResource(resourceEntry string) (string, error) {
585 _, isFile, err := lc.loadK8sResource(resourceEntry)
586 if err != nil {
587 return "", err
588 }
589 if isFile {
590 return lc.localizeFile(resourceEntry)
591 }
592 return resourceEntry, nil
593 }
594
595
596
597
598
599 func (lc *localizer) loadK8sResource(resourceEntry string) (resmap.ResMap, bool, error) {
600 rm, inlineErr := lc.rFactory.NewResMapFromBytes([]byte(resourceEntry))
601 if inlineErr != nil {
602 var fileErr error
603 rm, fileErr = lc.rFactory.FromFile(lc.ldr, resourceEntry)
604 if fileErr != nil {
605 err := ResourceLoadError{
606 InlineError: inlineErr,
607 FileError: fileErr,
608 }
609 return nil, false, errors.WrapPrefixf(err, "unable to load resource entry %q", resourceEntry)
610 }
611 }
612 return rm, inlineErr != nil, nil
613 }
614
View as plain text