mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-17 18:25:26 +00:00
* api: Add new types for customizeable resource ordering Signed-off-by: Yannis Zarkadas <yanniszark@arrikto.com> * plugins: Implement SortOrderTransformer plugin Implement the SortOrderTransformer plugin. This plugin allows the user to customize the order that kustomize will output resources in. The API for the plugin is the following: sortOptions: order: legacy | fifo legacySortOptions: orderFirst: - {GVK} orderLast: - {GVK} Signed-off-by: Yannis Zarkadas <yanniszark@arrikto.com> * plugins: Add boilerplate and generate code for new SortOrderTransformer Signed-off-by: Yannis Zarkadas <yanniszark@arrikto.com> * build: Add option to denote if the reorder flag was set by the user We want to take different actions if the reorder flag was set by the user or filled by the default value. Thus, we propagate this information from build to the krusty options. Signed-off-by: Yannis Zarkadas <yanniszark@gmail.com> * api/krusty: Ensure sort ordering works with CLI flag and kustomization Sort order can be defined in two places: - (new) kustomization file - (old) CLI flag We want the kustomization file to take precedence over the CLI flag. Eventually, we may want to move away from having a CLI flag altogether: https://github.com/kubernetes-sigs/kustomize/issues/3947 Case 1: Sort order set in kustomization file AND in CLI flag. Print a warning and let the kustomization file take precedence. Case 2: Sort order set in CLI flag only or not at all. Follow the CLI flag (defaults to legacy) and reorder at the end. Case 3: Sort order set in kustomization file only. Simply build the kustomization. Signed-off-by: Yannis Zarkadas <yanniszark@gmail.com> * krusty: Add e2e test for SortOrderTransformer Signed-off-by: Yannis Zarkadas <yanniszark@arrikto.com> * plugins: Purge LegacyOrderTransformer Signed-off-by: Yannis Zarkadas <yanniszark@gmail.com> * Update go.work.sum Signed-off-by: Yannis Zarkadas <yanniszark@gmail.com> * review: Make review changes Signed-off-by: Yannis Zarkadas <yanniszark@gmail.com> Signed-off-by: Yannis Zarkadas <yanniszark@arrikto.com> Signed-off-by: Yannis Zarkadas <yanniszark@gmail.com>
259 lines
5.9 KiB
Go
259 lines
5.9 KiB
Go
// 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://git.k8s.io/design-proposals-archive/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,
|
|
}
|
|
}
|
|
k := values[0]
|
|
if k == noKind {
|
|
k = ""
|
|
}
|
|
v := values[1]
|
|
if v == noVersion {
|
|
v = ""
|
|
}
|
|
g := strings.Join(values[2:], fieldSep)
|
|
if g == noGroup {
|
|
g = ""
|
|
}
|
|
return NewGvk(g, v, k)
|
|
}
|
|
|
|
// Values that are brief but meaningful in logs.
|
|
const (
|
|
noGroup = "[noGrp]"
|
|
noVersion = "[noVer]"
|
|
noKind = "[noKind]"
|
|
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{k, v, g}, fieldSep)
|
|
}
|
|
|
|
// stableSortString returns a GVK representation that ensures determinism and
|
|
// backwards-compatibility in testing, logging, ...
|
|
func (x Gvk) stableSortString() string {
|
|
stableNoGroup := "~G"
|
|
stableNoVersion := "~V"
|
|
stableNoKind := "~K"
|
|
stableFieldSeparator := "_"
|
|
|
|
g := x.Group
|
|
if g == "" {
|
|
g = stableNoGroup
|
|
}
|
|
v := x.Version
|
|
if v == "" {
|
|
v = stableNoVersion
|
|
}
|
|
k := x.Kind
|
|
if k == "" {
|
|
k = stableNoKind
|
|
}
|
|
return strings.Join([]string{g, v, k}, stableFieldSeparator)
|
|
}
|
|
|
|
// 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. This is called when generating a filename for the
|
|
// resource.
|
|
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, "_")
|
|
}
|
|
|
|
// 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.stableSortString() < o.stableSortString()
|
|
}
|
|
|
|
// 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:
|
|
// <Group: "extensions", Version: "v1beta1", Kind: "Deployment">
|
|
//
|
|
// is selected by
|
|
// <Group: "", Version: "", Kind: "Deployment">
|
|
//
|
|
// but rejected by
|
|
// <Group: "apps", Version: "", Kind: "Deployment">
|
|
//
|
|
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
|
|
}
|