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