// Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 package resid import ( "strings" "sigs.k8s.io/kustomize/kyaml/openapi" "sigs.k8s.io/kustomize/kyaml/yaml" ) // Gvk identifies a Kubernetes API type. // https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type Gvk struct { Group string `json:"group,omitempty" yaml:"group,omitempty"` Version string `json:"version,omitempty" yaml:"version,omitempty"` Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` // isClusterScoped is true if the object is known, per the openapi // data in use, to be cluster scoped, and false otherwise. isClusterScoped bool } func NewGvk(g, v, k string) Gvk { result := Gvk{Group: g, Version: v, Kind: k} result.isClusterScoped = openapi.IsCertainlyClusterScoped(result.AsTypeMeta()) return result } func GvkFromNode(r *yaml.RNode) Gvk { g, v := ParseGroupVersion(r.GetApiVersion()) return NewGvk(g, v, r.GetKind()) } // FromKind makes a Gvk with only the kind specified. func FromKind(k string) Gvk { return NewGvk("", "", k) } // ParseGroupVersion parses a KRM metadata apiVersion field. func ParseGroupVersion(apiVersion string) (group, version string) { if i := strings.Index(apiVersion, "/"); i > -1 { return apiVersion[:i], apiVersion[i+1:] } return "", apiVersion } // GvkFromString makes a Gvk from the output of Gvk.String(). func GvkFromString(s string) Gvk { values := strings.Split(s, fieldSep) if len(values) != 3 { // ...then the string didn't come from Gvk.String(). return Gvk{ Group: noGroup, Version: noVersion, Kind: noKind, } } g := values[0] if g == noGroup { g = "" } v := values[1] if v == noVersion { v = "" } k := values[2] if k == noKind { k = "" } return NewGvk(g, v, k) } // Values that are brief but meaningful in logs. const ( noGroup = "~G" noVersion = "~V" noKind = "~K" fieldSep = "_" ) // String returns a string representation of the GVK. func (x Gvk) String() string { g := x.Group if g == "" { g = noGroup } v := x.Version if v == "" { v = noVersion } k := x.Kind if k == "" { k = noKind } return strings.Join([]string{g, v, k}, fieldSep) } // ApiVersion returns the combination of Group and Version func (x Gvk) ApiVersion() string { var sb strings.Builder if x.Group != "" { sb.WriteString(x.Group) sb.WriteString("/") } sb.WriteString(x.Version) return sb.String() } // StringWoEmptyField returns a string representation of the GVK. Non-exist // fields will be omitted. func (x Gvk) StringWoEmptyField() string { var s []string if x.Group != "" { s = append(s, x.Group) } if x.Version != "" { s = append(s, x.Version) } if x.Kind != "" { s = append(s, x.Kind) } return strings.Join(s, fieldSep) } // Equals returns true if the Gvk's have equal fields. func (x Gvk) Equals(o Gvk) bool { return x.Group == o.Group && x.Version == o.Version && x.Kind == o.Kind } // An attempt to order things to help k8s, e.g. // a Service should come before things that refer to it. // Namespace should be first. // In some cases order just specified to provide determinism. var orderFirst = []string{ "Namespace", "ResourceQuota", "StorageClass", "CustomResourceDefinition", "ServiceAccount", "PodSecurityPolicy", "Role", "ClusterRole", "RoleBinding", "ClusterRoleBinding", "ConfigMap", "Secret", "Endpoints", "Service", "LimitRange", "PriorityClass", "PersistentVolume", "PersistentVolumeClaim", "Deployment", "StatefulSet", "CronJob", "PodDisruptionBudget", } var orderLast = []string{ "MutatingWebhookConfiguration", "ValidatingWebhookConfiguration", } var typeOrders = func() map[string]int { m := map[string]int{} for i, n := range orderFirst { m[n] = -len(orderFirst) + i } for i, n := range orderLast { m[n] = 1 + i } return m }() // IsLessThan returns true if self is less than the argument. func (x Gvk) IsLessThan(o Gvk) bool { indexI := typeOrders[x.Kind] indexJ := typeOrders[o.Kind] if indexI != indexJ { return indexI < indexJ } return x.String() < o.String() } // IsSelected returns true if `selector` selects `x`; otherwise, false. // If `selector` and `x` are the same, return true. // If `selector` is nil, it is considered a wildcard match, returning true. // If selector fields are empty, they are considered wildcards matching // anything in the corresponding fields, e.g. // // this item: // // // is selected by // // // but rejected by // // func (x Gvk) IsSelected(selector *Gvk) bool { if selector == nil { return true } if len(selector.Group) > 0 { if x.Group != selector.Group { return false } } if len(selector.Version) > 0 { if x.Version != selector.Version { return false } } if len(selector.Kind) > 0 { if x.Kind != selector.Kind { return false } } return true } // AsTypeMeta returns a yaml.TypeMeta from x's information. func (x Gvk) AsTypeMeta() yaml.TypeMeta { return yaml.TypeMeta{ APIVersion: x.ApiVersion(), Kind: x.Kind, } } // IsClusterScoped returns true if the Gvk is certainly cluster scoped // with respect to the available openapi data. func (x Gvk) IsClusterScoped() bool { return x.isClusterScoped }