...

Source file src/sigs.k8s.io/kustomize/kyaml/resid/gvk.go

Documentation: sigs.k8s.io/kustomize/kyaml/resid

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package resid
     5  
     6  import (
     7  	"strings"
     8  
     9  	"sigs.k8s.io/kustomize/kyaml/openapi"
    10  	"sigs.k8s.io/kustomize/kyaml/yaml"
    11  )
    12  
    13  // Gvk identifies a Kubernetes API type.
    14  // https://git.k8s.io/design-proposals-archive/api-machinery/api-group.md
    15  type Gvk struct {
    16  	Group   string `json:"group,omitempty" yaml:"group,omitempty"`
    17  	Version string `json:"version,omitempty" yaml:"version,omitempty"`
    18  	Kind    string `json:"kind,omitempty" yaml:"kind,omitempty"`
    19  	// isClusterScoped is true if the object is known, per the openapi
    20  	// data in use, to be cluster scoped, and false otherwise.
    21  	isClusterScoped bool
    22  }
    23  
    24  func NewGvk(g, v, k string) Gvk {
    25  	result := Gvk{Group: g, Version: v, Kind: k}
    26  	result.isClusterScoped =
    27  		openapi.IsCertainlyClusterScoped(result.AsTypeMeta())
    28  	return result
    29  }
    30  
    31  func GvkFromNode(r *yaml.RNode) Gvk {
    32  	g, v := ParseGroupVersion(r.GetApiVersion())
    33  	return NewGvk(g, v, r.GetKind())
    34  }
    35  
    36  // FromKind makes a Gvk with only the kind specified.
    37  func FromKind(k string) Gvk {
    38  	return NewGvk("", "", k)
    39  }
    40  
    41  // ParseGroupVersion parses a KRM metadata apiVersion field.
    42  func ParseGroupVersion(apiVersion string) (group, version string) {
    43  	if i := strings.Index(apiVersion, "/"); i > -1 {
    44  		return apiVersion[:i], apiVersion[i+1:]
    45  	}
    46  	return "", apiVersion
    47  }
    48  
    49  // GvkFromString makes a Gvk from the output of Gvk.String().
    50  func GvkFromString(s string) Gvk {
    51  	values := strings.Split(s, fieldSep)
    52  	if len(values) < 3 {
    53  		// ...then the string didn't come from Gvk.String().
    54  		return Gvk{
    55  			Group:   noGroup,
    56  			Version: noVersion,
    57  			Kind:    noKind,
    58  		}
    59  	}
    60  	k := values[0]
    61  	if k == noKind {
    62  		k = ""
    63  	}
    64  	v := values[1]
    65  	if v == noVersion {
    66  		v = ""
    67  	}
    68  	g := strings.Join(values[2:], fieldSep)
    69  	if g == noGroup {
    70  		g = ""
    71  	}
    72  	return NewGvk(g, v, k)
    73  }
    74  
    75  // Values that are brief but meaningful in logs.
    76  const (
    77  	noGroup   = "[noGrp]"
    78  	noVersion = "[noVer]"
    79  	noKind    = "[noKind]"
    80  	fieldSep  = "."
    81  )
    82  
    83  // String returns a string representation of the GVK.
    84  func (x Gvk) String() string {
    85  	g := x.Group
    86  	if g == "" {
    87  		g = noGroup
    88  	}
    89  	v := x.Version
    90  	if v == "" {
    91  		v = noVersion
    92  	}
    93  	k := x.Kind
    94  	if k == "" {
    95  		k = noKind
    96  	}
    97  	return strings.Join([]string{k, v, g}, fieldSep)
    98  }
    99  
   100  // stableSortString returns a GVK representation that ensures determinism and
   101  // backwards-compatibility in testing, logging, ...
   102  func (x Gvk) stableSortString() string {
   103  	stableNoGroup := "~G"
   104  	stableNoVersion := "~V"
   105  	stableNoKind := "~K"
   106  	stableFieldSeparator := "_"
   107  
   108  	g := x.Group
   109  	if g == "" {
   110  		g = stableNoGroup
   111  	}
   112  	v := x.Version
   113  	if v == "" {
   114  		v = stableNoVersion
   115  	}
   116  	k := x.Kind
   117  	if k == "" {
   118  		k = stableNoKind
   119  	}
   120  	return strings.Join([]string{g, v, k}, stableFieldSeparator)
   121  }
   122  
   123  // ApiVersion returns the combination of Group and Version
   124  func (x Gvk) ApiVersion() string {
   125  	if x.Group != "" {
   126  		return x.Group + "/" + x.Version
   127  	}
   128  	return x.Version
   129  }
   130  
   131  // StringWoEmptyField returns a string representation of the GVK. Non-exist
   132  // fields will be omitted. This is called when generating a filename for the
   133  // resource.
   134  func (x Gvk) StringWoEmptyField() string {
   135  	var s []string
   136  	if x.Group != "" {
   137  		s = append(s, x.Group)
   138  	}
   139  	if x.Version != "" {
   140  		s = append(s, x.Version)
   141  	}
   142  	if x.Kind != "" {
   143  		s = append(s, x.Kind)
   144  	}
   145  	return strings.Join(s, "_")
   146  }
   147  
   148  // Equals returns true if the Gvk's have equal fields.
   149  func (x Gvk) Equals(o Gvk) bool {
   150  	return x.Group == o.Group && x.Version == o.Version && x.Kind == o.Kind
   151  }
   152  
   153  // An attempt to order things to help k8s, e.g.
   154  // a Service should come before things that refer to it.
   155  // Namespace should be first.
   156  // In some cases order just specified to provide determinism.
   157  var orderFirst = []string{
   158  	"Namespace",
   159  	"ResourceQuota",
   160  	"StorageClass",
   161  	"CustomResourceDefinition",
   162  	"ServiceAccount",
   163  	"PodSecurityPolicy",
   164  	"Role",
   165  	"ClusterRole",
   166  	"RoleBinding",
   167  	"ClusterRoleBinding",
   168  	"ConfigMap",
   169  	"Secret",
   170  	"Endpoints",
   171  	"Service",
   172  	"LimitRange",
   173  	"PriorityClass",
   174  	"PersistentVolume",
   175  	"PersistentVolumeClaim",
   176  	"Deployment",
   177  	"StatefulSet",
   178  	"CronJob",
   179  	"PodDisruptionBudget",
   180  }
   181  var orderLast = []string{
   182  	"MutatingWebhookConfiguration",
   183  	"ValidatingWebhookConfiguration",
   184  }
   185  var typeOrders = func() map[string]int {
   186  	m := map[string]int{}
   187  	for i, n := range orderFirst {
   188  		m[n] = -len(orderFirst) + i
   189  	}
   190  	for i, n := range orderLast {
   191  		m[n] = 1 + i
   192  	}
   193  	return m
   194  }()
   195  
   196  // IsLessThan returns true if self is less than the argument.
   197  func (x Gvk) IsLessThan(o Gvk) bool {
   198  	indexI := typeOrders[x.Kind]
   199  	indexJ := typeOrders[o.Kind]
   200  	if indexI != indexJ {
   201  		return indexI < indexJ
   202  	}
   203  	return x.stableSortString() < o.stableSortString()
   204  }
   205  
   206  // IsSelected returns true if `selector` selects `x`; otherwise, false.
   207  // If `selector` and `x` are the same, return true.
   208  // If `selector` is nil, it is considered a wildcard match, returning true.
   209  // If selector fields are empty, they are considered wildcards matching
   210  // anything in the corresponding fields, e.g.
   211  //
   212  // this item:
   213  //       <Group: "extensions", Version: "v1beta1", Kind: "Deployment">
   214  //
   215  // is selected by
   216  //       <Group: "",           Version: "",        Kind: "Deployment">
   217  //
   218  // but rejected by
   219  //       <Group: "apps",       Version: "",        Kind: "Deployment">
   220  //
   221  func (x Gvk) IsSelected(selector *Gvk) bool {
   222  	if selector == nil {
   223  		return true
   224  	}
   225  	if len(selector.Group) > 0 {
   226  		if x.Group != selector.Group {
   227  			return false
   228  		}
   229  	}
   230  	if len(selector.Version) > 0 {
   231  		if x.Version != selector.Version {
   232  			return false
   233  		}
   234  	}
   235  	if len(selector.Kind) > 0 {
   236  		if x.Kind != selector.Kind {
   237  			return false
   238  		}
   239  	}
   240  	return true
   241  }
   242  
   243  // AsTypeMeta returns a yaml.TypeMeta from x's information.
   244  func (x Gvk) AsTypeMeta() yaml.TypeMeta {
   245  	return yaml.TypeMeta{
   246  		APIVersion: x.ApiVersion(),
   247  		Kind:       x.Kind,
   248  	}
   249  }
   250  
   251  // IsClusterScoped returns true if the Gvk is certainly cluster scoped
   252  // with respect to the available openapi data.
   253  func (x Gvk) IsClusterScoped() bool {
   254  	return x.isClusterScoped
   255  }
   256  

View as plain text