mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-18 05:05:14 +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>
245 lines
6.7 KiB
Go
245 lines
6.7 KiB
Go
// Code generated by pluginator on SortOrderTransformer; DO NOT EDIT.
|
|
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
|
|
package builtins
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"sigs.k8s.io/kustomize/api/resmap"
|
|
"sigs.k8s.io/kustomize/api/resource"
|
|
"sigs.k8s.io/kustomize/api/types"
|
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
|
"sigs.k8s.io/yaml"
|
|
)
|
|
|
|
// Sort the resources using a customizable ordering based of Kind.
|
|
// Defaults to the ordering of the GVK struct, which puts cluster-wide basic
|
|
// resources with no dependencies (like Namespace, StorageClass, etc.) first,
|
|
// and resources with a high number of dependencies
|
|
// (like ValidatingWebhookConfiguration) last.
|
|
type SortOrderTransformerPlugin struct {
|
|
SortOptions *types.SortOptions `json:"sortOptions,omitempty" yaml:"sortOptions,omitempty"`
|
|
}
|
|
|
|
func (p *SortOrderTransformerPlugin) Config(
|
|
_ *resmap.PluginHelpers, c []byte) error {
|
|
return errors.Wrap(yaml.Unmarshal(c, p), "Failed to unmarshal SortOrderTransformer config")
|
|
}
|
|
|
|
func (p *SortOrderTransformerPlugin) applyDefaults() {
|
|
// Default to FIFO sort, aka no-op.
|
|
if p.SortOptions == nil {
|
|
p.SortOptions = &types.SortOptions{
|
|
Order: types.FIFOSortOrder,
|
|
}
|
|
}
|
|
|
|
// If legacy sort is selected and no options are given, default to
|
|
// hardcoded order.
|
|
if p.SortOptions.Order == types.LegacySortOrder && p.SortOptions.LegacySortOptions == nil {
|
|
p.SortOptions.LegacySortOptions = &types.LegacySortOptions{
|
|
OrderFirst: defaultOrderFirst,
|
|
OrderLast: defaultOrderLast,
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *SortOrderTransformerPlugin) validate() error {
|
|
// Check valid values for SortOrder
|
|
if p.SortOptions.Order != types.FIFOSortOrder && p.SortOptions.Order != types.LegacySortOrder {
|
|
return errors.Errorf("the field 'sortOptions.order' must be one of [%s, %s]",
|
|
types.FIFOSortOrder, types.LegacySortOrder)
|
|
}
|
|
|
|
// Validate that the only options set are the ones corresponding to the
|
|
// selected sort order.
|
|
if p.SortOptions.Order == types.FIFOSortOrder &&
|
|
p.SortOptions.LegacySortOptions != nil {
|
|
return errors.Errorf("the field 'sortOptions.legacySortOptions' is"+
|
|
" set but the selected sort order is '%v', not 'legacy'",
|
|
p.SortOptions.Order)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *SortOrderTransformerPlugin) Transform(m resmap.ResMap) (err error) {
|
|
p.applyDefaults()
|
|
err = p.validate()
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
// Sort
|
|
if p.SortOptions.Order == types.LegacySortOrder {
|
|
s := newLegacyIDSorter(m.AllIds(), p.SortOptions.LegacySortOptions)
|
|
sort.Sort(s)
|
|
err = applyOrdering(m, s.resids)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// applyOrdering takes resources (given in ResMap) and a desired ordering given
|
|
// as a sequence of ResIds, and updates the ResMap's resources to match the
|
|
// ordering.
|
|
func applyOrdering(m resmap.ResMap, ordering []resid.ResId) error {
|
|
var err error
|
|
resources := make([]*resource.Resource, m.Size())
|
|
// Clear and refill with the correct order
|
|
for i, id := range ordering {
|
|
resources[i], err = m.GetByCurrentId(id)
|
|
if err != nil {
|
|
return errors.Wrap(err, "expected match for sorting")
|
|
}
|
|
}
|
|
m.Clear()
|
|
for _, r := range resources {
|
|
err = m.Append(r)
|
|
if err != nil {
|
|
return errors.Wrap(err, "SortOrderTransformer: Failed to append to resources")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Code for legacy sorting.
|
|
// Legacy sorting is a "fixed" order sorting maintained for backwards
|
|
// compatibility.
|
|
|
|
// legacyIDSorter sorts resources based on two priority lists:
|
|
// - orderFirst: Resources that should be placed in the start, in the given order.
|
|
// - orderLast: Resources that should be placed in the end, in the given order.
|
|
type legacyIDSorter struct {
|
|
// resids only stores the metadata of the object. This is an optimization as
|
|
// it's expensive to compute these again and again during ordering.
|
|
resids []resid.ResId
|
|
typeOrders map[string]int
|
|
}
|
|
|
|
func newLegacyIDSorter(
|
|
resids []resid.ResId,
|
|
options *types.LegacySortOptions) *legacyIDSorter {
|
|
// Precalculate a resource ranking based on the priority lists.
|
|
var typeOrders = func() map[string]int {
|
|
m := map[string]int{}
|
|
for i, n := range options.OrderFirst {
|
|
m[n] = -len(options.OrderFirst) + i
|
|
}
|
|
for i, n := range options.OrderLast {
|
|
m[n] = 1 + i
|
|
}
|
|
return m
|
|
}()
|
|
return &legacyIDSorter{
|
|
resids: resids,
|
|
typeOrders: typeOrders,
|
|
}
|
|
}
|
|
|
|
var _ sort.Interface = legacyIDSorter{}
|
|
|
|
func (a legacyIDSorter) Len() int { return len(a.resids) }
|
|
func (a legacyIDSorter) Swap(i, j int) {
|
|
a.resids[i], a.resids[j] = a.resids[j], a.resids[i]
|
|
}
|
|
func (a legacyIDSorter) Less(i, j int) bool {
|
|
if !a.resids[i].Gvk.Equals(a.resids[j].Gvk) {
|
|
return gvkLessThan(a.resids[i].Gvk, a.resids[j].Gvk, a.typeOrders)
|
|
}
|
|
return legacyResIDSortString(a.resids[i]) < legacyResIDSortString(a.resids[j])
|
|
}
|
|
|
|
func gvkLessThan(gvk1, gvk2 resid.Gvk, typeOrders map[string]int) bool {
|
|
index1 := typeOrders[gvk1.Kind]
|
|
index2 := typeOrders[gvk2.Kind]
|
|
if index1 != index2 {
|
|
return index1 < index2
|
|
}
|
|
return legacyGVKSortString(gvk1) < legacyGVKSortString(gvk2)
|
|
}
|
|
|
|
// legacyGVKSortString returns a string representation of given GVK used for
|
|
// stable sorting.
|
|
func legacyGVKSortString(x resid.Gvk) string {
|
|
legacyNoGroup := "~G"
|
|
legacyNoVersion := "~V"
|
|
legacyNoKind := "~K"
|
|
legacyFieldSeparator := "_"
|
|
|
|
g := x.Group
|
|
if g == "" {
|
|
g = legacyNoGroup
|
|
}
|
|
v := x.Version
|
|
if v == "" {
|
|
v = legacyNoVersion
|
|
}
|
|
k := x.Kind
|
|
if k == "" {
|
|
k = legacyNoKind
|
|
}
|
|
return strings.Join([]string{g, v, k}, legacyFieldSeparator)
|
|
}
|
|
|
|
// legacyResIDSortString returns a string representation of given ResID used for
|
|
// stable sorting.
|
|
func legacyResIDSortString(id resid.ResId) string {
|
|
legacyNoNamespace := "~X"
|
|
legacyNoName := "~N"
|
|
legacySeparator := "|"
|
|
|
|
ns := id.Namespace
|
|
if ns == "" {
|
|
ns = legacyNoNamespace
|
|
}
|
|
nm := id.Name
|
|
if nm == "" {
|
|
nm = legacyNoName
|
|
}
|
|
return strings.Join(
|
|
[]string{id.Gvk.String(), ns, nm}, legacySeparator)
|
|
}
|
|
|
|
// DO NOT CHANGE!
|
|
// Final legacy ordering provided as a default by kustomize.
|
|
// Originally an attempt to apply resources in the correct order, an effort
|
|
// which later proved impossible as not all types are known beforehand.
|
|
// See: https://github.com/kubernetes-sigs/kustomize/issues/3913
|
|
var defaultOrderFirst = []string{ //nolint:gochecknoglobals
|
|
"Namespace",
|
|
"ResourceQuota",
|
|
"StorageClass",
|
|
"CustomResourceDefinition",
|
|
"ServiceAccount",
|
|
"PodSecurityPolicy",
|
|
"Role",
|
|
"ClusterRole",
|
|
"RoleBinding",
|
|
"ClusterRoleBinding",
|
|
"ConfigMap",
|
|
"Secret",
|
|
"Endpoints",
|
|
"Service",
|
|
"LimitRange",
|
|
"PriorityClass",
|
|
"PersistentVolume",
|
|
"PersistentVolumeClaim",
|
|
"Deployment",
|
|
"StatefulSet",
|
|
"CronJob",
|
|
"PodDisruptionBudget",
|
|
}
|
|
var defaultOrderLast = []string{ //nolint:gochecknoglobals
|
|
"MutatingWebhookConfiguration",
|
|
"ValidatingWebhookConfiguration",
|
|
}
|
|
|
|
func NewSortOrderTransformerPlugin() resmap.TransformerPlugin {
|
|
return &SortOrderTransformerPlugin{}
|
|
}
|