...

Source file src/sigs.k8s.io/kustomize/api/resource/resource.go

Documentation: sigs.k8s.io/kustomize/api/resource

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package resource
     5  
     6  import (
     7  	"fmt"
     8  	"log"
     9  	"strings"
    10  
    11  	"sigs.k8s.io/kustomize/api/filters/patchstrategicmerge"
    12  	"sigs.k8s.io/kustomize/api/ifc"
    13  	"sigs.k8s.io/kustomize/api/internal/utils"
    14  	"sigs.k8s.io/kustomize/api/types"
    15  	"sigs.k8s.io/kustomize/kyaml/kio"
    16  	"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
    17  	"sigs.k8s.io/kustomize/kyaml/resid"
    18  	kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
    19  	"sigs.k8s.io/yaml"
    20  )
    21  
    22  // Resource is an RNode, representing a Kubernetes Resource Model object,
    23  // paired with metadata used by kustomize.
    24  type Resource struct {
    25  	kyaml.RNode
    26  	refVarNames []string
    27  }
    28  
    29  var BuildAnnotations = []string{
    30  	utils.BuildAnnotationPreviousKinds,
    31  	utils.BuildAnnotationPreviousNames,
    32  	utils.BuildAnnotationPrefixes,
    33  	utils.BuildAnnotationSuffixes,
    34  	utils.BuildAnnotationPreviousNamespaces,
    35  	utils.BuildAnnotationAllowNameChange,
    36  	utils.BuildAnnotationAllowKindChange,
    37  	utils.BuildAnnotationsRefBy,
    38  	utils.BuildAnnotationsGenBehavior,
    39  	utils.BuildAnnotationsGenAddHashSuffix,
    40  
    41  	kioutil.PathAnnotation,
    42  	kioutil.IndexAnnotation,
    43  	kioutil.SeqIndentAnnotation,
    44  	kioutil.IdAnnotation,
    45  	kioutil.InternalAnnotationsMigrationResourceIDAnnotation,
    46  
    47  	kioutil.LegacyPathAnnotation,
    48  	kioutil.LegacyIndexAnnotation,
    49  	kioutil.LegacyIdAnnotation,
    50  }
    51  
    52  func (r *Resource) ResetRNode(incoming *Resource) {
    53  	r.RNode = *incoming.Copy()
    54  }
    55  
    56  func (r *Resource) GetGvk() resid.Gvk {
    57  	return resid.GvkFromNode(&r.RNode)
    58  }
    59  
    60  func (r *Resource) Hash(h ifc.KustHasher) (string, error) {
    61  	return h.Hash(&r.RNode)
    62  }
    63  
    64  func (r *Resource) SetGvk(gvk resid.Gvk) {
    65  	r.SetKind(gvk.Kind)
    66  	r.SetApiVersion(gvk.ApiVersion())
    67  }
    68  
    69  func (r *Resource) GetOrigin() (*Origin, error) {
    70  	annotations := r.GetAnnotations()
    71  	originAnnotations, ok := annotations[utils.OriginAnnotationKey]
    72  	if !ok {
    73  		return nil, nil
    74  	}
    75  	var origin Origin
    76  	if err := yaml.Unmarshal([]byte(originAnnotations), &origin); err != nil {
    77  		return nil, err
    78  	}
    79  	return &origin, nil
    80  }
    81  
    82  func (r *Resource) SetOrigin(origin *Origin) error {
    83  	annotations := r.GetAnnotations()
    84  	if origin == nil {
    85  		delete(annotations, utils.OriginAnnotationKey)
    86  	} else {
    87  		originStr, err := origin.String()
    88  		if err != nil {
    89  			return err
    90  		}
    91  		annotations[utils.OriginAnnotationKey] = originStr
    92  	}
    93  	return r.SetAnnotations(annotations)
    94  }
    95  
    96  func (r *Resource) GetTransformations() (Transformations, error) {
    97  	annotations := r.GetAnnotations()
    98  	transformerAnnotations, ok := annotations[utils.TransformerAnnotationKey]
    99  	if !ok {
   100  		return nil, nil
   101  	}
   102  	var transformations Transformations
   103  	if err := yaml.Unmarshal([]byte(transformerAnnotations), &transformations); err != nil {
   104  		return nil, err
   105  	}
   106  	return transformations, nil
   107  }
   108  
   109  func (r *Resource) AddTransformation(origin *Origin) error {
   110  	annotations := r.GetAnnotations()
   111  	transformations, err := r.GetTransformations()
   112  	if err != nil {
   113  		return err
   114  	}
   115  	if transformations == nil {
   116  		transformations = Transformations{}
   117  	}
   118  	transformations = append(transformations, origin)
   119  	transformationStr, err := transformations.String()
   120  	if err != nil {
   121  		return err
   122  	}
   123  	annotations[utils.TransformerAnnotationKey] = transformationStr
   124  	return r.SetAnnotations(annotations)
   125  }
   126  
   127  func (r *Resource) ClearTransformations() error {
   128  	annotations := r.GetAnnotations()
   129  	delete(annotations, utils.TransformerAnnotationKey)
   130  	return r.SetAnnotations(annotations)
   131  }
   132  
   133  // ResCtx is an interface describing the contextual added
   134  // kept kustomize in the context of each Resource object.
   135  // Currently mainly the name prefix and name suffix are added.
   136  type ResCtx interface {
   137  	AddNamePrefix(p string)
   138  	AddNameSuffix(s string)
   139  	GetNamePrefixes() []string
   140  	GetNameSuffixes() []string
   141  }
   142  
   143  // ResCtxMatcher returns true if two Resources are being
   144  // modified in the same kustomize context.
   145  type ResCtxMatcher func(ResCtx) bool
   146  
   147  // DeepCopy returns a new copy of resource
   148  func (r *Resource) DeepCopy() *Resource {
   149  	rc := &Resource{
   150  		RNode: *r.Copy(),
   151  	}
   152  	rc.copyKustomizeSpecificFields(r)
   153  	return rc
   154  }
   155  
   156  // CopyMergeMetaDataFieldsFrom copies everything but the non-metadata in
   157  // the resource.
   158  // TODO: move to RNode, use GetMeta to improve performance.
   159  // TODO: make a version of mergeStringMaps that is build-annotation aware
   160  //   to avoid repeatedly setting refby and genargs annotations
   161  // Must remove the kustomize bit at the end.
   162  func (r *Resource) CopyMergeMetaDataFieldsFrom(other *Resource) error {
   163  	if err := r.SetLabels(
   164  		mergeStringMaps(other.GetLabels(), r.GetLabels())); err != nil {
   165  		return fmt.Errorf("copyMerge cannot set labels - %w", err)
   166  	}
   167  
   168  	ra := r.GetAnnotations()
   169  	_, enableNameSuffixHash := ra[utils.BuildAnnotationsGenAddHashSuffix]
   170  	merged := mergeStringMapsWithBuildAnnotations(other.GetAnnotations(), ra)
   171  	if !enableNameSuffixHash {
   172  		delete(merged, utils.BuildAnnotationsGenAddHashSuffix)
   173  	}
   174  	if err := r.SetAnnotations(merged); err != nil {
   175  		return fmt.Errorf("copyMerge cannot set annotations - %w", err)
   176  	}
   177  
   178  	if err := r.SetName(other.GetName()); err != nil {
   179  		return fmt.Errorf("copyMerge cannot set name - %w", err)
   180  	}
   181  	if err := r.SetNamespace(other.GetNamespace()); err != nil {
   182  		return fmt.Errorf("copyMerge cannot set namespace - %w", err)
   183  	}
   184  	r.copyKustomizeSpecificFields(other)
   185  	return nil
   186  }
   187  
   188  func (r *Resource) copyKustomizeSpecificFields(other *Resource) {
   189  	r.refVarNames = copyStringSlice(other.refVarNames)
   190  }
   191  
   192  func (r *Resource) MergeDataMapFrom(o *Resource) {
   193  	r.SetDataMap(mergeStringMaps(o.GetDataMap(), r.GetDataMap()))
   194  }
   195  
   196  func (r *Resource) MergeBinaryDataMapFrom(o *Resource) {
   197  	r.SetBinaryDataMap(mergeStringMaps(o.GetBinaryDataMap(), r.GetBinaryDataMap()))
   198  }
   199  
   200  func (r *Resource) ErrIfNotEquals(o *Resource) error {
   201  	meYaml, err := r.AsYAML()
   202  	if err != nil {
   203  		return err
   204  	}
   205  	otherYaml, err := o.AsYAML()
   206  	if err != nil {
   207  		return err
   208  	}
   209  	if !r.ReferencesEqual(o) {
   210  		return fmt.Errorf(
   211  			`unequal references - self:
   212  %sreferenced by: %s
   213  --- other:
   214  %sreferenced by: %s
   215  `, meYaml, r.GetRefBy(), otherYaml, o.GetRefBy())
   216  	}
   217  	if string(meYaml) != string(otherYaml) {
   218  		return fmt.Errorf(`---  self:
   219  %s
   220  --- other:
   221  %s
   222  `, meYaml, otherYaml)
   223  	}
   224  	return nil
   225  }
   226  
   227  func (r *Resource) ReferencesEqual(other *Resource) bool {
   228  	setSelf := make(map[resid.ResId]bool)
   229  	setOther := make(map[resid.ResId]bool)
   230  	for _, ref := range other.GetRefBy() {
   231  		setOther[ref] = true
   232  	}
   233  	for _, ref := range r.GetRefBy() {
   234  		if _, ok := setOther[ref]; !ok {
   235  			return false
   236  		}
   237  		setSelf[ref] = true
   238  	}
   239  	return len(setSelf) == len(setOther)
   240  }
   241  
   242  func copyStringSlice(s []string) []string {
   243  	if s == nil {
   244  		return nil
   245  	}
   246  	c := make([]string, len(s))
   247  	copy(c, s)
   248  	return c
   249  }
   250  
   251  // Implements ResCtx AddNamePrefix
   252  func (r *Resource) AddNamePrefix(p string) {
   253  	r.appendCsvAnnotation(utils.BuildAnnotationPrefixes, p)
   254  }
   255  
   256  // Implements ResCtx AddNameSuffix
   257  func (r *Resource) AddNameSuffix(s string) {
   258  	r.appendCsvAnnotation(utils.BuildAnnotationSuffixes, s)
   259  }
   260  
   261  func (r *Resource) appendCsvAnnotation(name, value string) {
   262  	if value == "" {
   263  		return
   264  	}
   265  	currentValue := r.getCsvAnnotation(name)
   266  	newValue := strings.Join(append(currentValue, value), ",")
   267  	if err := r.RNode.PipeE(kyaml.SetAnnotation(name, newValue)); err != nil {
   268  		panic(err)
   269  	}
   270  }
   271  
   272  // Implements ResCtx GetNamePrefixes
   273  func (r *Resource) GetNamePrefixes() []string {
   274  	return r.getCsvAnnotation(utils.BuildAnnotationPrefixes)
   275  }
   276  
   277  // Implements ResCtx GetNameSuffixes
   278  func (r *Resource) GetNameSuffixes() []string {
   279  	return r.getCsvAnnotation(utils.BuildAnnotationSuffixes)
   280  }
   281  
   282  func (r *Resource) getCsvAnnotation(name string) []string {
   283  	annotations := r.GetAnnotations()
   284  	if _, ok := annotations[name]; !ok {
   285  		return nil
   286  	}
   287  	return strings.Split(annotations[name], ",")
   288  }
   289  
   290  // PrefixesSuffixesEquals is conceptually doing the same task as
   291  // OutermostPrefixSuffix but performs a deeper comparison of the suffix and
   292  // prefix slices.
   293  // The allowEmpty flag determines whether an empty prefix/suffix
   294  // should be considered a match on anything.
   295  // This is used when filtering, starting with a coarser pass allowing empty
   296  // matches, before requiring exact matches if there are multiple
   297  // remaining candidates.
   298  func (r *Resource) PrefixesSuffixesEquals(o ResCtx, allowEmpty bool) bool {
   299  	if allowEmpty {
   300  		eitherPrefixEmpty := len(r.GetNamePrefixes()) == 0 || len(o.GetNamePrefixes()) == 0
   301  		eitherSuffixEmpty := len(r.GetNameSuffixes()) == 0 || len(o.GetNameSuffixes()) == 0
   302  
   303  		return (eitherPrefixEmpty || utils.SameEndingSubSlice(r.GetNamePrefixes(), o.GetNamePrefixes())) &&
   304  			(eitherSuffixEmpty || utils.SameEndingSubSlice(r.GetNameSuffixes(), o.GetNameSuffixes()))
   305  	} else {
   306  		return utils.SameEndingSubSlice(r.GetNamePrefixes(), o.GetNamePrefixes()) &&
   307  			utils.SameEndingSubSlice(r.GetNameSuffixes(), o.GetNameSuffixes())
   308  	}
   309  }
   310  
   311  // RemoveBuildAnnotations removes annotations created by the build process.
   312  // These are internal-only to kustomize, added to the data pipeline to
   313  // track name changes so name references can be fixed.
   314  func (r *Resource) RemoveBuildAnnotations() {
   315  	annotations := r.GetAnnotations()
   316  	if len(annotations) == 0 {
   317  		return
   318  	}
   319  	for _, a := range BuildAnnotations {
   320  		delete(annotations, a)
   321  	}
   322  	if err := r.SetAnnotations(annotations); err != nil {
   323  		panic(err)
   324  	}
   325  }
   326  
   327  func (r *Resource) setPreviousId(ns string, n string, k string) *Resource {
   328  	r.appendCsvAnnotation(utils.BuildAnnotationPreviousNames, n)
   329  	r.appendCsvAnnotation(utils.BuildAnnotationPreviousNamespaces, ns)
   330  	r.appendCsvAnnotation(utils.BuildAnnotationPreviousKinds, k)
   331  	return r
   332  }
   333  
   334  // AllowNameChange allows name changes to the resource.
   335  func (r *Resource) AllowNameChange() {
   336  	r.enable(utils.BuildAnnotationAllowNameChange)
   337  }
   338  
   339  // NameChangeAllowed checks if a patch resource is allowed to change another resource's name.
   340  func (r *Resource) NameChangeAllowed() bool {
   341  	return r.isEnabled(utils.BuildAnnotationAllowNameChange)
   342  }
   343  
   344  // AllowKindChange allows kind changes to the resource.
   345  func (r *Resource) AllowKindChange() {
   346  	r.enable(utils.BuildAnnotationAllowKindChange)
   347  }
   348  
   349  // KindChangeAllowed checks if a patch resource is allowed to change another resource's kind.
   350  func (r *Resource) KindChangeAllowed() bool {
   351  	return r.isEnabled(utils.BuildAnnotationAllowKindChange)
   352  }
   353  
   354  func (r *Resource) isEnabled(annoKey string) bool {
   355  	annotations := r.GetAnnotations()
   356  	v, ok := annotations[annoKey]
   357  	return ok && v == utils.Enabled
   358  }
   359  
   360  func (r *Resource) enable(annoKey string) {
   361  	annotations := r.GetAnnotations()
   362  	annotations[annoKey] = utils.Enabled
   363  	if err := r.SetAnnotations(annotations); err != nil {
   364  		panic(err)
   365  	}
   366  }
   367  
   368  // String returns resource as JSON.
   369  func (r *Resource) String() string {
   370  	bs, err := r.MarshalJSON()
   371  	if err != nil {
   372  		return "<" + err.Error() + ">"
   373  	}
   374  	return strings.TrimSpace(string(bs))
   375  }
   376  
   377  // AsYAML returns the resource in Yaml form.
   378  // Easier to read than JSON.
   379  func (r *Resource) AsYAML() ([]byte, error) {
   380  	json, err := r.MarshalJSON()
   381  	if err != nil {
   382  		return nil, err
   383  	}
   384  	return yaml.JSONToYAML(json)
   385  }
   386  
   387  // MustYaml returns YAML or panics.
   388  func (r *Resource) MustYaml() string {
   389  	yml, err := r.AsYAML()
   390  	if err != nil {
   391  		log.Fatal(err)
   392  	}
   393  	return string(yml)
   394  }
   395  
   396  // Behavior returns the behavior for the resource.
   397  func (r *Resource) Behavior() types.GenerationBehavior {
   398  	annotations := r.GetAnnotations()
   399  	if v, ok := annotations[utils.BuildAnnotationsGenBehavior]; ok {
   400  		return types.NewGenerationBehavior(v)
   401  	}
   402  	return types.NewGenerationBehavior("")
   403  }
   404  
   405  // SetBehavior sets the behavior for the resource.
   406  func (r *Resource) SetBehavior(behavior types.GenerationBehavior) {
   407  	annotations := r.GetAnnotations()
   408  	annotations[utils.BuildAnnotationsGenBehavior] = behavior.String()
   409  	if err := r.SetAnnotations(annotations); err != nil {
   410  		panic(err)
   411  	}
   412  }
   413  
   414  // NeedHashSuffix returns true if a resource content
   415  // hash should be appended to the name of the resource.
   416  func (r *Resource) NeedHashSuffix() bool {
   417  	return r.isEnabled(utils.BuildAnnotationsGenAddHashSuffix)
   418  }
   419  
   420  // EnableHashSuffix marks the resource as needing a content
   421  // hash to be appended to the name of the resource.
   422  func (r *Resource) EnableHashSuffix() {
   423  	r.enable(utils.BuildAnnotationsGenAddHashSuffix)
   424  }
   425  
   426  // OrgId returns the original, immutable ResId for the resource.
   427  // This doesn't have to be unique in a ResMap.
   428  func (r *Resource) OrgId() resid.ResId {
   429  	ids := r.PrevIds()
   430  	if len(ids) > 0 {
   431  		return ids[0]
   432  	}
   433  	return r.CurId()
   434  }
   435  
   436  // PrevIds returns a list of ResIds that includes every
   437  // previous ResId the resource has had through all of its
   438  // GVKN transformations, in the order that it had that ID.
   439  // I.e. the oldest ID is first.
   440  // The returned array does not include the resource's current
   441  // ID. If there are no previous IDs, this will return nil.
   442  func (r *Resource) PrevIds() []resid.ResId {
   443  	prevIds, err := utils.PrevIds(&r.RNode)
   444  	if err != nil {
   445  		// this should never happen
   446  		panic(err)
   447  	}
   448  	return prevIds
   449  }
   450  
   451  // StorePreviousId stores the resource's current ID via build annotations.
   452  func (r *Resource) StorePreviousId() {
   453  	id := r.CurId()
   454  	r.setPreviousId(id.EffectiveNamespace(), id.Name, id.Kind)
   455  }
   456  
   457  // CurId returns a ResId for the resource using the
   458  // mutable parts of the resource.
   459  // This should be unique in any ResMap.
   460  func (r *Resource) CurId() resid.ResId {
   461  	return resid.NewResIdWithNamespace(
   462  		r.GetGvk(), r.GetName(), r.GetNamespace())
   463  }
   464  
   465  // GetRefBy returns the ResIds that referred to current resource
   466  func (r *Resource) GetRefBy() []resid.ResId {
   467  	var resIds []resid.ResId
   468  	asStrings := r.getCsvAnnotation(utils.BuildAnnotationsRefBy)
   469  	for _, s := range asStrings {
   470  		resIds = append(resIds, resid.FromString(s))
   471  	}
   472  	return resIds
   473  }
   474  
   475  // AppendRefBy appends a ResId into the refBy list
   476  // Using any type except fmt.Stringer here results in a compilation error
   477  func (r *Resource) AppendRefBy(id fmt.Stringer) {
   478  	r.appendCsvAnnotation(utils.BuildAnnotationsRefBy, id.String())
   479  }
   480  
   481  // GetRefVarNames returns vars that refer to current resource
   482  func (r *Resource) GetRefVarNames() []string {
   483  	return r.refVarNames
   484  }
   485  
   486  // AppendRefVarName appends a name of a var into the refVar list
   487  func (r *Resource) AppendRefVarName(variable types.Var) {
   488  	r.refVarNames = append(r.refVarNames, variable.Name)
   489  }
   490  
   491  // ApplySmPatch applies the provided strategic merge patch.
   492  func (r *Resource) ApplySmPatch(patch *Resource) error {
   493  	n, ns, k := r.GetName(), r.GetNamespace(), r.GetKind()
   494  	if patch.NameChangeAllowed() || patch.KindChangeAllowed() {
   495  		r.StorePreviousId()
   496  	}
   497  	if err := r.ApplyFilter(patchstrategicmerge.Filter{
   498  		Patch: &patch.RNode,
   499  	}); err != nil {
   500  		return err
   501  	}
   502  	if r.IsNilOrEmpty() {
   503  		return nil
   504  	}
   505  	if !patch.KindChangeAllowed() {
   506  		r.SetKind(k)
   507  	}
   508  	if !patch.NameChangeAllowed() {
   509  		r.SetName(n)
   510  	}
   511  	r.SetNamespace(ns)
   512  	return nil
   513  }
   514  
   515  func (r *Resource) ApplyFilter(f kio.Filter) error {
   516  	l, err := f.Filter([]*kyaml.RNode{&r.RNode})
   517  	if len(l) == 0 {
   518  		// The node was deleted, which means the entire resource
   519  		// must be deleted.  Signal that via the following:
   520  		r.SetYNode(nil)
   521  	}
   522  	return err
   523  }
   524  
   525  func mergeStringMaps(maps ...map[string]string) map[string]string {
   526  	result := map[string]string{}
   527  	for _, m := range maps {
   528  		for key, value := range m {
   529  			result[key] = value
   530  		}
   531  	}
   532  	return result
   533  }
   534  
   535  func mergeStringMapsWithBuildAnnotations(maps ...map[string]string) map[string]string {
   536  	result := mergeStringMaps(maps...)
   537  	for i := range BuildAnnotations {
   538  		if len(maps) > 0 {
   539  			if v, ok := maps[0][BuildAnnotations[i]]; ok {
   540  				result[BuildAnnotations[i]] = v
   541  				continue
   542  			}
   543  		}
   544  		delete(result, BuildAnnotations[i])
   545  	}
   546  	return result
   547  }
   548  

View as plain text