// Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 package accumulator import ( "fmt" "log" "strings" "sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig" "sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/resid" ) // ResAccumulator accumulates resources and the rules // used to customize those resources. It's a ResMap // plus stuff needed to modify the ResMap. type ResAccumulator struct { resMap resmap.ResMap tConfig *builtinconfig.TransformerConfig varSet types.VarSet } func MakeEmptyAccumulator() *ResAccumulator { ra := &ResAccumulator{} ra.resMap = resmap.New() ra.tConfig = &builtinconfig.TransformerConfig{} ra.varSet = types.NewVarSet() return ra } // ResMap returns a copy of the internal resMap. func (ra *ResAccumulator) ResMap() resmap.ResMap { return ra.resMap.ShallowCopy() } // Vars returns a copy of underlying vars. func (ra *ResAccumulator) Vars() []types.Var { return ra.varSet.AsSlice() } func (ra *ResAccumulator) AppendAll(resources resmap.ResMap) error { return ra.resMap.AppendAll(resources) } func (ra *ResAccumulator) AbsorbAll(resources resmap.ResMap) error { return ra.resMap.AbsorbAll(resources) } func (ra *ResAccumulator) MergeConfig( tConfig *builtinconfig.TransformerConfig) (err error) { ra.tConfig, err = ra.tConfig.Merge(tConfig) return err } func (ra *ResAccumulator) GetTransformerConfig() *builtinconfig.TransformerConfig { return ra.tConfig } // MergeVars accumulates vars into ResAccumulator. // A Var is a tuple of name, object reference and field reference. // This func takes a list of vars from the current kustomization file and // annotates the accumulated resources with the names of the vars that match // those resources. E.g. if there's a var named "sam" that wants to get // its data from a ConfigMap named "james", and the resource list contains a // ConfigMap named "james", then that ConfigMap will be annotated with the // var name "sam". Later this annotation is used to find the data for "sam" // by digging into a particular fieldpath of "james". func (ra *ResAccumulator) MergeVars(incoming []types.Var) error { for _, v := range incoming { targetId := resid.NewResIdWithNamespace(v.ObjRef.GVK(), v.ObjRef.Name, v.ObjRef.Namespace) idMatcher := targetId.GvknEquals if targetId.Namespace != "" || targetId.IsClusterScoped() { // Preserve backward compatibility. An empty namespace means // wildcard search on the namespace hence we still use GvknEquals idMatcher = targetId.Equals } matched := ra.resMap.GetMatchingResourcesByAnyId(idMatcher) if len(matched) > 1 { return fmt.Errorf( "found %d resId matches for var %s "+ "(unable to disambiguate)", len(matched), v) } if len(matched) == 1 { matched[0].AppendRefVarName(v) } } return ra.varSet.MergeSlice(incoming) } func (ra *ResAccumulator) MergeAccumulator(other *ResAccumulator) (err error) { err = ra.AppendAll(other.resMap) if err != nil { return err } err = ra.MergeConfig(other.tConfig) if err != nil { return err } return ra.varSet.MergeSet(other.varSet) } func (ra *ResAccumulator) findVarValueFromResources(v types.Var) (interface{}, error) { for _, res := range ra.resMap.Resources() { for _, varName := range res.GetRefVarNames() { if varName == v.Name { s, err := res.GetFieldValue(v.FieldRef.FieldPath) if err != nil { return "", fmt.Errorf( "field specified in var '%v' "+ "not found in corresponding resource", v) } return s, nil } } } return "", fmt.Errorf( "var '%v' cannot be mapped to a field "+ "in the set of known resources", v) } // makeVarReplacementMap returns a map of Var names to // their final values. The values are strings intended // for substitution wherever the $(var.Name) occurs. func (ra *ResAccumulator) makeVarReplacementMap() (map[string]interface{}, error) { result := map[string]interface{}{} for _, v := range ra.Vars() { s, err := ra.findVarValueFromResources(v) if err != nil { return nil, err } result[v.Name] = s } return result, nil } func (ra *ResAccumulator) Transform(t resmap.Transformer) error { return t.Transform(ra.resMap) } func (ra *ResAccumulator) ResolveVars() error { replacementMap, err := ra.makeVarReplacementMap() if err != nil { return err } if len(replacementMap) == 0 { return nil } t := newRefVarTransformer( replacementMap, ra.tConfig.VarReference) err = ra.Transform(t) if len(t.UnusedVars()) > 0 { log.Printf( "well-defined vars that were never replaced: %s\n", strings.Join(t.UnusedVars(), ",")) } return err } func (ra *ResAccumulator) FixBackReferences() (err error) { if ra.tConfig.NameReference == nil { return nil } return ra.Transform( newNameReferenceTransformer(ra.tConfig.NameReference)) } // Intersection drops the resources which "other" does not have. func (ra *ResAccumulator) Intersection(other resmap.ResMap) error { otherIds := other.AllIds() for _, curId := range ra.resMap.AllIds() { toDelete := true for _, otherId := range otherIds { if otherId == curId { toDelete = false break } } if toDelete { err := ra.resMap.Remove(curId) if err != nil { return err } } } return nil }