1// Copyright 2019 The Kubernetes Authors.
2// SPDX-License-Identifier: Apache-2.0
3
4package target
5
6import (
7	"encoding/json"
8	"fmt"
9	"path/filepath"
10	"strings"
11
12	"github.com/pkg/errors"
13	"sigs.k8s.io/kustomize/api/builtins"
14	"sigs.k8s.io/kustomize/api/ifc"
15	"sigs.k8s.io/kustomize/api/internal/accumulator"
16	"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
17	"sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers"
18	"sigs.k8s.io/kustomize/api/internal/plugins/loader"
19	"sigs.k8s.io/kustomize/api/konfig"
20	"sigs.k8s.io/kustomize/api/resmap"
21	"sigs.k8s.io/kustomize/api/types"
22	"sigs.k8s.io/kustomize/kyaml/openapi"
23	"sigs.k8s.io/yaml"
24)
25
26// KustTarget encapsulates the entirety of a kustomization build.
27type KustTarget struct {
28	kustomization *types.Kustomization
29	ldr           ifc.Loader
30	validator     ifc.Validator
31	rFactory      *resmap.Factory
32	pLdr          *loader.Loader
33}
34
35// NewKustTarget returns a new instance of KustTarget.
36func NewKustTarget(
37	ldr ifc.Loader,
38	validator ifc.Validator,
39	rFactory *resmap.Factory,
40	pLdr *loader.Loader) *KustTarget {
41	return &KustTarget{
42		ldr:       ldr,
43		validator: validator,
44		rFactory:  rFactory,
45		pLdr:      pLdr,
46	}
47}
48
49// Load attempts to load the target's kustomization file.
50func (kt *KustTarget) Load() error {
51	content, err := loadKustFile(kt.ldr)
52	if err != nil {
53		return err
54	}
55	content, err = types.FixKustomizationPreUnmarshalling(content)
56	if err != nil {
57		return err
58	}
59	var k types.Kustomization
60	err = k.Unmarshal(content)
61	if err != nil {
62		return err
63	}
64	k.FixKustomizationPostUnmarshalling()
65	errs := k.EnforceFields()
66	if len(errs) > 0 {
67		return fmt.Errorf(
68			"Failed to read kustomization file under %s:\n"+
69				strings.Join(errs, "\n"), kt.ldr.Root())
70	}
71	kt.kustomization = &k
72	return nil
73}
74
75// Kustomization returns a copy of the immutable, internal kustomization object.
76func (kt *KustTarget) Kustomization() types.Kustomization {
77	var result types.Kustomization
78	b, _ := json.Marshal(*kt.kustomization)
79	json.Unmarshal(b, &result)
80	return result
81}
82
83func loadKustFile(ldr ifc.Loader) ([]byte, error) {
84	var content []byte
85	match := 0
86	for _, kf := range konfig.RecognizedKustomizationFileNames() {
87		c, err := ldr.Load(kf)
88		if err == nil {
89			match += 1
90			content = c
91		}
92	}
93	switch match {
94	case 0:
95		return nil, NewErrMissingKustomization(ldr.Root())
96	case 1:
97		return content, nil
98	default:
99		return nil, fmt.Errorf(
100			"Found multiple kustomization files under: %s\n", ldr.Root())
101	}
102}
103
104// MakeCustomizedResMap creates a fully customized ResMap
105// per the instructions contained in its kustomization instance.
106func (kt *KustTarget) MakeCustomizedResMap() (resmap.ResMap, error) {
107	return kt.makeCustomizedResMap()
108}
109
110func (kt *KustTarget) makeCustomizedResMap() (resmap.ResMap, error) {
111	ra, err := kt.AccumulateTarget()
112	if err != nil {
113		return nil, err
114	}
115
116	// The following steps must be done last, not as part of
117	// the recursion implicit in AccumulateTarget.
118
119	err = kt.addHashesToNames(ra)
120	if err != nil {
121		return nil, err
122	}
123
124	// Given that names have changed (prefixs/suffixes added),
125	// fix all the back references to those names.
126	err = ra.FixBackReferences()
127	if err != nil {
128		return nil, err
129	}
130
131	// With all the back references fixed, it's OK to resolve Vars.
132	err = ra.ResolveVars()
133	if err != nil {
134		return nil, err
135	}
136
137	return ra.ResMap(), nil
138}
139
140func (kt *KustTarget) addHashesToNames(
141	ra *accumulator.ResAccumulator) error {
142	p := builtins.NewHashTransformerPlugin()
143	err := kt.configureBuiltinPlugin(p, nil, builtinhelpers.HashTransformer)
144	if err != nil {
145		return err
146	}
147	return ra.Transform(p)
148}
149
150// AccumulateTarget returns a new ResAccumulator,
151// holding customized resources and the data/rules used
152// to do so.  The name back references and vars are
153// not yet fixed.
154func (kt *KustTarget) AccumulateTarget() (
155	ra *accumulator.ResAccumulator, err error) {
156	return kt.accumulateTarget(accumulator.MakeEmptyAccumulator())
157}
158
159// ra should be empty when this KustTarget is a Kustomization, or the ra of the parent if this KustTarget is a Component
160// (or empty if the Component does not have a parent).
161func (kt *KustTarget) accumulateTarget(ra *accumulator.ResAccumulator) (
162	resRa *accumulator.ResAccumulator, err error) {
163	ra, err = kt.accumulateResources(ra, kt.kustomization.Resources)
164	if err != nil {
165		return nil, errors.Wrap(err, "accumulating resources")
166	}
167	ra, err = kt.accumulateComponents(ra, kt.kustomization.Components)
168	if err != nil {
169		return nil, errors.Wrap(err, "accumulating components")
170	}
171	tConfig, err := builtinconfig.MakeTransformerConfig(
172		kt.ldr, kt.kustomization.Configurations)
173	if err != nil {
174		return nil, err
175	}
176	err = ra.MergeConfig(tConfig)
177	if err != nil {
178		return nil, errors.Wrapf(
179			err, "merging config %v", tConfig)
180	}
181	crdTc, err := accumulator.LoadConfigFromCRDs(kt.ldr, kt.kustomization.Crds)
182	if err != nil {
183		return nil, errors.Wrapf(
184			err, "loading CRDs %v", kt.kustomization.Crds)
185	}
186	err = ra.MergeConfig(crdTc)
187	if err != nil {
188		return nil, errors.Wrapf(
189			err, "merging CRDs %v", crdTc)
190	}
191	err = kt.runGenerators(ra)
192	if err != nil {
193		return nil, err
194	}
195	err = kt.runTransformers(ra)
196	if err != nil {
197		return nil, err
198	}
199	err = kt.runValidators(ra)
200	if err != nil {
201		return nil, err
202	}
203	err = ra.MergeVars(kt.kustomization.Vars)
204	if err != nil {
205		return nil, errors.Wrapf(
206			err, "merging vars %v", kt.kustomization.Vars)
207	}
208	return ra, nil
209}
210
211func (kt *KustTarget) runGenerators(
212	ra *accumulator.ResAccumulator) error {
213	var generators []resmap.Generator
214	gs, err := kt.configureBuiltinGenerators()
215	if err != nil {
216		return err
217	}
218	generators = append(generators, gs...)
219	gs, err = kt.configureExternalGenerators()
220	if err != nil {
221		return errors.Wrap(err, "loading generator plugins")
222	}
223	generators = append(generators, gs...)
224	for _, g := range generators {
225		resMap, err := g.Generate()
226		if err != nil {
227			return err
228		}
229		err = ra.AbsorbAll(resMap)
230		if err != nil {
231			return errors.Wrapf(err, "merging from generator %v", g)
232		}
233	}
234	return nil
235}
236
237func (kt *KustTarget) configureExternalGenerators() ([]resmap.Generator, error) {
238	ra := accumulator.MakeEmptyAccumulator()
239	var generatorPaths []string
240	for _, p := range kt.kustomization.Generators {
241		// handle inline generators
242		rm, err := kt.rFactory.NewResMapFromBytes([]byte(p))
243		if err != nil {
244			// not an inline config
245			generatorPaths = append(generatorPaths, p)
246			continue
247		}
248		ra.AppendAll(rm)
249	}
250	ra, err := kt.accumulateResources(ra, generatorPaths)
251	if err != nil {
252		return nil, err
253	}
254	return kt.pLdr.LoadGenerators(kt.ldr, kt.validator, ra.ResMap())
255}
256
257func (kt *KustTarget) runTransformers(ra *accumulator.ResAccumulator) error {
258	var r []resmap.Transformer
259	tConfig := ra.GetTransformerConfig()
260	lts, err := kt.configureBuiltinTransformers(tConfig)
261	if err != nil {
262		return err
263	}
264	r = append(r, lts...)
265	lts, err = kt.configureExternalTransformers(kt.kustomization.Transformers)
266	if err != nil {
267		return err
268	}
269	r = append(r, lts...)
270	return ra.Transform(newMultiTransformer(r))
271}
272
273func (kt *KustTarget) configureExternalTransformers(transformers []string) ([]resmap.Transformer, error) {
274	ra := accumulator.MakeEmptyAccumulator()
275	var transformerPaths []string
276	for _, p := range transformers {
277		// handle inline transformers
278		rm, err := kt.rFactory.NewResMapFromBytes([]byte(p))
279		if err != nil {
280			// not an inline config
281			transformerPaths = append(transformerPaths, p)
282			continue
283		}
284		ra.AppendAll(rm)
285	}
286	ra, err := kt.accumulateResources(ra, transformerPaths)
287
288	if err != nil {
289		return nil, err
290	}
291	return kt.pLdr.LoadTransformers(kt.ldr, kt.validator, ra.ResMap())
292}
293
294func (kt *KustTarget) runValidators(ra *accumulator.ResAccumulator) error {
295	validators, err := kt.configureExternalTransformers(kt.kustomization.Validators)
296	if err != nil {
297		return err
298	}
299	for _, v := range validators {
300		// Validators shouldn't modify the resource map
301		orignal := ra.ResMap().DeepCopy()
302		err = v.Transform(ra.ResMap())
303		if err != nil {
304			return err
305		}
306		newMap := ra.ResMap().DeepCopy()
307		if err = kt.removeValidatedByLabel(newMap); err != nil {
308			return err
309		}
310		if err = orignal.ErrorIfNotEqualSets(newMap); err != nil {
311			return fmt.Errorf("validator shouldn't modify the resource map: %v", err)
312		}
313	}
314	return nil
315}
316
317func (kt *KustTarget) removeValidatedByLabel(rm resmap.ResMap) error {
318	resources := rm.Resources()
319	for _, r := range resources {
320		labels := r.GetLabels()
321		if _, found := labels[konfig.ValidatedByLabelKey]; !found {
322			continue
323		}
324		delete(labels, konfig.ValidatedByLabelKey)
325		if err := r.SetLabels(labels); err != nil {
326			return err
327		}
328	}
329	return nil
330}
331
332// accumulateResources fills the given resourceAccumulator
333// with resources read from the given list of paths.
334func (kt *KustTarget) accumulateResources(
335	ra *accumulator.ResAccumulator, paths []string) (*accumulator.ResAccumulator, error) {
336	for _, path := range paths {
337		// try loading resource as file then as base (directory or git repository)
338		if errF := kt.accumulateFile(ra, path); errF != nil {
339			ldr, err := kt.ldr.New(path)
340			if err != nil {
341				return nil, errors.Wrapf(
342					err, "accumulation err='%s'", errF.Error())
343			}
344			ra, err = kt.accumulateDirectory(ra, ldr, false)
345			if err != nil {
346				return nil, errors.Wrapf(
347					err, "accumulation err='%s'", errF.Error())
348			}
349		}
350	}
351	return ra, nil
352}
353
354// accumulateResources fills the given resourceAccumulator
355// with resources read from the given list of paths.
356func (kt *KustTarget) accumulateComponents(
357	ra *accumulator.ResAccumulator, paths []string) (*accumulator.ResAccumulator, error) {
358	for _, path := range paths {
359		// Components always refer to directories
360		ldr, errL := kt.ldr.New(path)
361		if errL != nil {
362			return nil, fmt.Errorf("loader.New %q", errL)
363		}
364		var errD error
365		ra, errD = kt.accumulateDirectory(ra, ldr, true)
366		if errD != nil {
367			return nil, fmt.Errorf("accumulateDirectory: %q", errD)
368		}
369	}
370	return ra, nil
371}
372
373func (kt *KustTarget) accumulateDirectory(
374	ra *accumulator.ResAccumulator, ldr ifc.Loader, isComponent bool) (*accumulator.ResAccumulator, error) {
375	defer ldr.Cleanup()
376	subKt := NewKustTarget(ldr, kt.validator, kt.rFactory, kt.pLdr)
377	err := subKt.Load()
378	if err != nil {
379		return nil, errors.Wrapf(
380			err, "couldn't make target for path '%s'", ldr.Root())
381	}
382	var bytes []byte
383	path := ldr.Root()
384	if openApiPath, exists := subKt.Kustomization().OpenAPI["path"]; exists {
385		bytes, err = ldr.Load(filepath.Join(path, openApiPath))
386		if err != nil {
387			return nil, err
388		}
389	}
390	err = openapi.SetSchema(subKt.Kustomization().OpenAPI, bytes, false)
391	if err != nil {
392		return nil, err
393	}
394	if isComponent && subKt.kustomization.Kind != types.ComponentKind {
395		return nil, fmt.Errorf(
396			"expected kind '%s' for path '%s' but got '%s'", types.ComponentKind, ldr.Root(), subKt.kustomization.Kind)
397	} else if !isComponent && subKt.kustomization.Kind == types.ComponentKind {
398		return nil, fmt.Errorf(
399			"expected kind != '%s' for path '%s'", types.ComponentKind, ldr.Root())
400	}
401
402	var subRa *accumulator.ResAccumulator
403	if isComponent {
404		// Components don't create a new accumulator: the kustomization directives are added to the current accumulator
405		subRa, err = subKt.accumulateTarget(ra)
406		ra = accumulator.MakeEmptyAccumulator()
407	} else {
408		// Child Kustomizations create a new accumulator which resolves their kustomization directives, which will later
409		// be merged into the current accumulator.
410		subRa, err = subKt.AccumulateTarget()
411	}
412	if err != nil {
413		return nil, errors.Wrapf(
414			err, "recursed accumulation of path '%s'", ldr.Root())
415	}
416	err = ra.MergeAccumulator(subRa)
417	if err != nil {
418		return nil, errors.Wrapf(
419			err, "recursed merging from path '%s'", ldr.Root())
420	}
421	return ra, nil
422}
423
424func (kt *KustTarget) accumulateFile(
425	ra *accumulator.ResAccumulator, path string) error {
426	resources, err := kt.rFactory.FromFile(kt.ldr, path)
427	if err != nil {
428		return errors.Wrapf(err, "accumulating resources from '%s'", path)
429	}
430	err = ra.AppendAll(resources)
431	if err != nil {
432		return errors.Wrapf(err, "merging resources from '%s'", path)
433	}
434	return nil
435}
436
437func (kt *KustTarget) configureBuiltinPlugin(
438	p resmap.Configurable, c interface{}, bpt builtinhelpers.BuiltinPluginType) (err error) {
439	var y []byte
440	if c != nil {
441		y, err = yaml.Marshal(c)
442		if err != nil {
443			return errors.Wrapf(
444				err, "builtin %s marshal", bpt)
445		}
446	}
447	err = p.Config(
448		resmap.NewPluginHelpers(
449			kt.ldr, kt.validator, kt.rFactory, kt.pLdr.Config()),
450		y)
451	if err != nil {
452		return errors.Wrapf(
453			err, "trouble configuring builtin %s with config: `\n%s`", bpt, string(y))
454	}
455	return nil
456}
457