// Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 package types import ( "fmt" "reflect" "sort" "strings" "sigs.k8s.io/kustomize/api/resid" ) const defaultFieldPath = "metadata.name" // Var represents a variable whose value will be sourced // from a field in a Kubernetes object. type Var struct { // Value of identifier name e.g. FOO used in container args, annotations // Appears in pod template as $(FOO) Name string `json:"name" yaml:"name"` // ObjRef must refer to a Kubernetes resource under the // purview of this kustomization. ObjRef should use the // raw name of the object (the name specified in its YAML, // before addition of a namePrefix and a nameSuffix). ObjRef Target `json:"objref" yaml:"objref"` // FieldRef refers to the field of the object referred to by // ObjRef whose value will be extracted for use in // replacing $(FOO). // If unspecified, this defaults to fieldPath: $defaultFieldPath FieldRef FieldSelector `json:"fieldref,omitempty" yaml:"fieldref,omitempty"` } // Target refers to a kubernetes object by Group, Version, Kind and Name // gvk.Gvk contains Group, Version and Kind // APIVersion is added to keep the backward compatibility of using ObjectReference // for Var.ObjRef type Target struct { APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` resid.Gvk `json:",inline,omitempty" yaml:",inline,omitempty"` Name string `json:"name" yaml:"name"` Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` } // GVK returns the Gvk object in Target func (t *Target) GVK() resid.Gvk { if t.APIVersion == "" { return t.Gvk } versions := strings.Split(t.APIVersion, "/") if len(versions) == 2 { t.Group = versions[0] t.Version = versions[1] } if len(versions) == 1 { t.Version = versions[0] } return t.Gvk } // FieldSelector contains the fieldPath to an object field. // This struct is added to keep the backward compatibility of using ObjectFieldSelector // for Var.FieldRef type FieldSelector struct { FieldPath string `json:"fieldPath,omitempty" yaml:"fieldPath,omitempty"` } // defaulting sets reference to field used by default. func (v *Var) Defaulting() { if v.FieldRef.FieldPath == "" { v.FieldRef.FieldPath = defaultFieldPath } v.ObjRef.GVK() } // DeepEqual returns true if var a and b are Equals. // Note 1: The objects are unchanged by the VarEqual // Note 2: Should be normalize be FieldPath before doing // the DeepEqual. spec.a[b] is supposed to be the same // as spec.a.b func (v Var) DeepEqual(other Var) bool { v.Defaulting() other.Defaulting() return reflect.DeepEqual(v, other) } // VarSet is a set of Vars where no var.Name is repeated. type VarSet struct { set map[string]Var } // NewVarSet returns an initialized VarSet func NewVarSet() VarSet { return VarSet{set: map[string]Var{}} } // AsSlice returns the vars as a slice. func (vs *VarSet) AsSlice() []Var { s := make([]Var, len(vs.set)) i := 0 for _, v := range vs.set { s[i] = v i++ } sort.Sort(byName(s)) return s } // Copy returns a copy of the var set. func (vs *VarSet) Copy() VarSet { newSet := make(map[string]Var, len(vs.set)) for k, v := range vs.set { newSet[k] = v } return VarSet{set: newSet} } // MergeSet absorbs other vars with error on name collision. func (vs *VarSet) MergeSet(incoming VarSet) error { for _, incomingVar := range incoming.set { if err := vs.Merge(incomingVar); err != nil { return err } } return nil } // MergeSlice absorbs a Var slice with error on name collision. // Empty fields in incoming vars are defaulted. func (vs *VarSet) MergeSlice(incoming []Var) error { for _, v := range incoming { if err := vs.Merge(v); err != nil { return err } } return nil } // Merge absorbs another Var with error on name collision. // Empty fields in incoming Var is defaulted. func (vs *VarSet) Merge(v Var) error { if vs.Contains(v) { return fmt.Errorf( "var '%s' already encountered", v.Name) } v.Defaulting() vs.set[v.Name] = v return nil } // AbsorbSet absorbs other vars with error on (name,value) collision. func (vs *VarSet) AbsorbSet(incoming VarSet) error { for _, v := range incoming.set { if err := vs.Absorb(v); err != nil { return err } } return nil } // AbsorbSlice absorbs a Var slice with error on (name,value) collision. // Empty fields in incoming vars are defaulted. func (vs *VarSet) AbsorbSlice(incoming []Var) error { for _, v := range incoming { if err := vs.Absorb(v); err != nil { return err } } return nil } // Absorb absorbs another Var with error on (name,value) collision. // Empty fields in incoming Var is defaulted. func (vs *VarSet) Absorb(v Var) error { conflicting := vs.Get(v.Name) if conflicting == nil { // no conflict. The var is valid. v.Defaulting() vs.set[v.Name] = v return nil } if !reflect.DeepEqual(v, *conflicting) { // two vars with the same name are pointing at two // different resources. return fmt.Errorf( "var '%s' already encountered", v.Name) } return nil } // Contains is true if the set has the other var. func (vs *VarSet) Contains(other Var) bool { return vs.Get(other.Name) != nil } // Get returns the var with the given name, else nil. func (vs *VarSet) Get(name string) *Var { if v, found := vs.set[name]; found { return &v } return nil } // byName is a sort interface which sorts Vars by name alphabetically type byName []Var func (v byName) Len() int { return len(v) } func (v byName) Swap(i, j int) { v[i], v[j] = v[j], v[i] } func (v byName) Less(i, j int) bool { return v[i].Name < v[j].Name }