1// Copyright 2019 The Kubernetes Authors. 2// SPDX-License-Identifier: Apache-2.0 3 4package resid 5 6import ( 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://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md 15type 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 24func 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 31func 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. 37func FromKind(k string) Gvk { 38 return NewGvk("", "", k) 39} 40 41// ParseGroupVersion parses a KRM metadata apiVersion field. 42func 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(). 50func 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 g := values[0] 61 if g == noGroup { 62 g = "" 63 } 64 v := values[1] 65 if v == noVersion { 66 v = "" 67 } 68 k := values[2] 69 if k == noKind { 70 k = "" 71 } 72 return NewGvk(g, v, k) 73} 74 75// Values that are brief but meaningful in logs. 76const ( 77 noGroup = "~G" 78 noVersion = "~V" 79 noKind = "~K" 80 fieldSep = "_" 81) 82 83// String returns a string representation of the GVK. 84func (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{g, v, k}, fieldSep) 98} 99 100// ApiVersion returns the combination of Group and Version 101func (x Gvk) ApiVersion() string { 102 var sb strings.Builder 103 if x.Group != "" { 104 sb.WriteString(x.Group) 105 sb.WriteString("/") 106 } 107 sb.WriteString(x.Version) 108 return sb.String() 109} 110 111// StringWoEmptyField returns a string representation of the GVK. Non-exist 112// fields will be omitted. 113func (x Gvk) StringWoEmptyField() string { 114 var s []string 115 if x.Group != "" { 116 s = append(s, x.Group) 117 } 118 if x.Version != "" { 119 s = append(s, x.Version) 120 } 121 if x.Kind != "" { 122 s = append(s, x.Kind) 123 } 124 return strings.Join(s, fieldSep) 125} 126 127// Equals returns true if the Gvk's have equal fields. 128func (x Gvk) Equals(o Gvk) bool { 129 return x.Group == o.Group && x.Version == o.Version && x.Kind == o.Kind 130} 131 132// An attempt to order things to help k8s, e.g. 133// a Service should come before things that refer to it. 134// Namespace should be first. 135// In some cases order just specified to provide determinism. 136var orderFirst = []string{ 137 "Namespace", 138 "ResourceQuota", 139 "StorageClass", 140 "CustomResourceDefinition", 141 "ServiceAccount", 142 "PodSecurityPolicy", 143 "Role", 144 "ClusterRole", 145 "RoleBinding", 146 "ClusterRoleBinding", 147 "ConfigMap", 148 "Secret", 149 "Endpoints", 150 "Service", 151 "LimitRange", 152 "PriorityClass", 153 "PersistentVolume", 154 "PersistentVolumeClaim", 155 "Deployment", 156 "StatefulSet", 157 "CronJob", 158 "PodDisruptionBudget", 159} 160var orderLast = []string{ 161 "MutatingWebhookConfiguration", 162 "ValidatingWebhookConfiguration", 163} 164var typeOrders = func() map[string]int { 165 m := map[string]int{} 166 for i, n := range orderFirst { 167 m[n] = -len(orderFirst) + i 168 } 169 for i, n := range orderLast { 170 m[n] = 1 + i 171 } 172 return m 173}() 174 175// IsLessThan returns true if self is less than the argument. 176func (x Gvk) IsLessThan(o Gvk) bool { 177 indexI := typeOrders[x.Kind] 178 indexJ := typeOrders[o.Kind] 179 if indexI != indexJ { 180 return indexI < indexJ 181 } 182 return x.String() < o.String() 183} 184 185// IsSelected returns true if `selector` selects `x`; otherwise, false. 186// If `selector` and `x` are the same, return true. 187// If `selector` is nil, it is considered a wildcard match, returning true. 188// If selector fields are empty, they are considered wildcards matching 189// anything in the corresponding fields, e.g. 190// 191// this item: 192// <Group: "extensions", Version: "v1beta1", Kind: "Deployment"> 193// 194// is selected by 195// <Group: "", Version: "", Kind: "Deployment"> 196// 197// but rejected by 198// <Group: "apps", Version: "", Kind: "Deployment"> 199// 200func (x Gvk) IsSelected(selector *Gvk) bool { 201 if selector == nil { 202 return true 203 } 204 if len(selector.Group) > 0 { 205 if x.Group != selector.Group { 206 return false 207 } 208 } 209 if len(selector.Version) > 0 { 210 if x.Version != selector.Version { 211 return false 212 } 213 } 214 if len(selector.Kind) > 0 { 215 if x.Kind != selector.Kind { 216 return false 217 } 218 } 219 return true 220} 221 222// AsTypeMeta returns a yaml.TypeMeta from x's information. 223func (x Gvk) AsTypeMeta() yaml.TypeMeta { 224 return yaml.TypeMeta{ 225 APIVersion: x.ApiVersion(), 226 Kind: x.Kind, 227 } 228} 229 230// IsClusterScoped returns true if the Gvk is certainly cluster scoped 231// with respect to the available openapi data. 232func (x Gvk) IsClusterScoped() bool { 233 return x.isClusterScoped 234} 235