Make ordering configurable (#4019)

* 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>
This commit is contained in:
Yannis Zarkadas
2022-12-02 16:59:53 -05:00
committed by GitHub
parent 1e2e7bbc0b
commit a502717460
30 changed files with 1532 additions and 1092 deletions

View File

@@ -16,7 +16,6 @@ type (
IAMPolicyGeneratorPlugin = internal.IAMPolicyGeneratorPlugin
ImageTagTransformerPlugin = internal.ImageTagTransformerPlugin
LabelTransformerPlugin = internal.LabelTransformerPlugin
LegacyOrderTransformerPlugin = internal.LegacyOrderTransformerPlugin
NamespaceTransformerPlugin = internal.NamespaceTransformerPlugin
PatchJson6902TransformerPlugin = internal.PatchJson6902TransformerPlugin
PatchStrategicMergeTransformerPlugin = internal.PatchStrategicMergeTransformerPlugin
@@ -37,7 +36,6 @@ var (
NewIAMPolicyGeneratorPlugin = internal.NewIAMPolicyGeneratorPlugin
NewImageTagTransformerPlugin = internal.NewImageTagTransformerPlugin
NewLabelTransformerPlugin = internal.NewLabelTransformerPlugin
NewLegacyOrderTransformerPlugin = internal.NewLegacyOrderTransformerPlugin
NewNamespaceTransformerPlugin = internal.NewNamespaceTransformerPlugin
NewPatchJson6902TransformerPlugin = internal.NewPatchJson6902TransformerPlugin
NewPatchStrategicMergeTransformerPlugin = internal.NewPatchStrategicMergeTransformerPlugin

View File

@@ -1,46 +0,0 @@
// Code generated by pluginator on LegacyOrderTransformer; DO NOT EDIT.
// pluginator {unknown 1970-01-01T00:00:00Z }
package builtins
import (
"sort"
"github.com/pkg/errors"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
)
// Sort the resources using an ordering defined in the Gvk class.
// This 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 LegacyOrderTransformerPlugin struct{}
// Nothing needed for configuration.
func (p *LegacyOrderTransformerPlugin) Config(
_ *resmap.PluginHelpers, _ []byte) (err error) {
return nil
}
func (p *LegacyOrderTransformerPlugin) Transform(m resmap.ResMap) (err error) {
resources := make([]*resource.Resource, m.Size())
ids := m.AllIds()
sort.Sort(resmap.IdSlice(ids))
for i, id := range ids {
resources[i], err = m.GetByCurrentId(id)
if err != nil {
return errors.Wrap(err, "expected match for sorting")
}
}
m.Clear()
for _, r := range resources {
m.Append(r)
}
return nil
}
func NewLegacyOrderTransformerPlugin() resmap.TransformerPlugin {
return &LegacyOrderTransformerPlugin{}
}

View File

@@ -0,0 +1,244 @@
// 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{}
}

View File

@@ -15,24 +15,23 @@ func _() {
_ = x[HashTransformer-4]
_ = x[ImageTagTransformer-5]
_ = x[LabelTransformer-6]
_ = x[LegacyOrderTransformer-7]
_ = x[NamespaceTransformer-8]
_ = x[PatchJson6902Transformer-9]
_ = x[PatchStrategicMergeTransformer-10]
_ = x[PatchTransformer-11]
_ = x[PrefixSuffixTransformer-12]
_ = x[PrefixTransformer-13]
_ = x[SuffixTransformer-14]
_ = x[ReplicaCountTransformer-15]
_ = x[SecretGenerator-16]
_ = x[ValueAddTransformer-17]
_ = x[HelmChartInflationGenerator-18]
_ = x[ReplacementTransformer-19]
_ = x[NamespaceTransformer-7]
_ = x[PatchJson6902Transformer-8]
_ = x[PatchStrategicMergeTransformer-9]
_ = x[PatchTransformer-10]
_ = x[PrefixSuffixTransformer-11]
_ = x[PrefixTransformer-12]
_ = x[SuffixTransformer-13]
_ = x[ReplicaCountTransformer-14]
_ = x[SecretGenerator-15]
_ = x[ValueAddTransformer-16]
_ = x[HelmChartInflationGenerator-17]
_ = x[ReplacementTransformer-18]
}
const _BuiltinPluginType_name = "UnknownAnnotationsTransformerConfigMapGeneratorIAMPolicyGeneratorHashTransformerImageTagTransformerLabelTransformerLegacyOrderTransformerNamespaceTransformerPatchJson6902TransformerPatchStrategicMergeTransformerPatchTransformerPrefixSuffixTransformerPrefixTransformerSuffixTransformerReplicaCountTransformerSecretGeneratorValueAddTransformerHelmChartInflationGeneratorReplacementTransformer"
const _BuiltinPluginType_name = "UnknownAnnotationsTransformerConfigMapGeneratorIAMPolicyGeneratorHashTransformerImageTagTransformerLabelTransformerNamespaceTransformerPatchJson6902TransformerPatchStrategicMergeTransformerPatchTransformerPrefixSuffixTransformerPrefixTransformerSuffixTransformerReplicaCountTransformerSecretGeneratorValueAddTransformerHelmChartInflationGeneratorReplacementTransformer"
var _BuiltinPluginType_index = [...]uint16{0, 7, 29, 47, 65, 80, 99, 115, 137, 157, 181, 211, 227, 250, 267, 284, 307, 322, 341, 368, 390}
var _BuiltinPluginType_index = [...]uint16{0, 7, 29, 47, 65, 80, 99, 115, 135, 159, 189, 205, 228, 245, 262, 285, 300, 319, 346, 368}
func (i BuiltinPluginType) String() string {
if i < 0 || i >= BuiltinPluginType(len(_BuiltinPluginType_index)-1) {

View File

@@ -19,7 +19,6 @@ const (
HashTransformer
ImageTagTransformer
LabelTransformer
LegacyOrderTransformer
NamespaceTransformer
PatchJson6902Transformer
PatchStrategicMergeTransformer
@@ -100,7 +99,6 @@ var TransformerFactories = map[BuiltinPluginType]func() resmap.TransformerPlugin
HashTransformer: builtins.NewHashTransformerPlugin,
ImageTagTransformer: builtins.NewImageTagTransformerPlugin,
LabelTransformer: builtins.NewLabelTransformerPlugin,
LegacyOrderTransformer: builtins.NewLegacyOrderTransformerPlugin,
NamespaceTransformer: builtins.NewNamespaceTransformerPlugin,
PatchJson6902Transformer: builtins.NewPatchJson6902TransformerPlugin,
PatchStrategicMergeTransformer: builtins.NewPatchStrategicMergeTransformerPlugin,
@@ -111,4 +109,7 @@ var TransformerFactories = map[BuiltinPluginType]func() resmap.TransformerPlugin
ReplacementTransformer: builtins.NewReplacementTransformerPlugin,
ReplicaCountTransformer: builtins.NewReplicaCountTransformerPlugin,
ValueAddTransformer: builtins.NewValueAddTransformerPlugin,
// Do not wired SortOrderTransformer as a builtin plugin.
// We only want it to be available in the top-level kustomization.
// See: https://github.com/kubernetes-sigs/kustomize/issues/3913
}

View File

@@ -5,6 +5,7 @@ package krusty
import (
"fmt"
"log"
"sigs.k8s.io/kustomize/api/internal/builtins"
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
@@ -90,11 +91,9 @@ func (b *Kustomizer) Run(
if err != nil {
return nil, err
}
if b.options.DoLegacyResourceSort {
err = builtins.NewLegacyOrderTransformerPlugin().Transform(m)
if err != nil {
return nil, err
}
err = b.applySortOrder(m, kt)
if err != nil {
return nil, err
}
if b.options.AddManagedbyLabel || utils.StringSliceContains(kt.Kustomization().BuildMetadata, types.ManagedByLabelOption) {
t := builtins.LabelTransformerPlugin{
@@ -126,3 +125,39 @@ func (b *Kustomizer) Run(
}
return m, nil
}
func (b *Kustomizer) applySortOrder(m resmap.ResMap, kt *target.KustTarget) error {
// 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.
if kt.Kustomization().SortOptions != nil {
// If set in CLI flag too, warn the user.
if b.options.Reorder != ReorderOptionUnspecified {
log.Println("Warning: Sorting order is set both in 'kustomization.yaml'" +
" ('sortOptions') and in a CLI flag ('--reorder'). Using the" +
" kustomization file over the CLI flag.")
}
pl := &builtins.SortOrderTransformerPlugin{
SortOptions: kt.Kustomization().SortOptions,
}
err := pl.Transform(m)
if err != nil {
return errors.Wrap(err)
}
} else if b.options.Reorder == ReorderOptionLegacy || b.options.Reorder == ReorderOptionUnspecified {
// Case 2: Sort order set in CLI flag only or not at all.
pl := &builtins.SortOrderTransformerPlugin{
SortOptions: &types.SortOptions{
Order: types.LegacySortOrder,
},
}
return errors.Wrap(pl.Transform(m))
}
return nil
}

View File

@@ -6,6 +6,7 @@ package krusty_test
import (
"testing"
"sigs.k8s.io/kustomize/api/krusty"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
)
@@ -38,7 +39,7 @@ data:
key: value
`)
opts := th.MakeDefaultOptions()
opts.DoLegacyResourceSort = true
opts.Reorder = krusty.ReorderOptionLegacy
m := th.Run(".", opts)
th.AssertActualEqualsExpected(m, `
apiVersion: v1

View File

@@ -8,6 +8,14 @@ import (
"sigs.k8s.io/kustomize/api/types"
)
type ReorderOption string
const (
ReorderOptionLegacy ReorderOption = "legacy"
ReorderOptionNone ReorderOption = "none"
ReorderOptionUnspecified ReorderOption = "unspecified"
)
// Options holds high-level kustomize configuration options,
// e.g. are plugins enabled, should the loader be restricted
// to the kustomization root, etc.
@@ -16,7 +24,15 @@ type Options struct {
// per a particular sort order. When false, don't do the
// sort, and instead respect the depth-first resource input
// order as specified by the kustomization file(s).
DoLegacyResourceSort bool
// Sort the resources before emitting them. Possible values:
// - "legacy": Use a fixed order that kustomize provides for backwards
// compatibility.
// - "none": Respect the depth-first resource input order as specified by the
// kustomization file.
// - "unspecified": The user didn't specify any preference. Kustomize will
// select the appropriate default.
Reorder ReorderOption
// When true, a label
// app.kubernetes.io/managed-by: kustomize-<version>
@@ -37,11 +53,11 @@ type Options struct {
// MakeDefaultOptions returns a default instance of Options.
func MakeDefaultOptions() *Options {
return &Options{
DoLegacyResourceSort: false,
AddManagedbyLabel: false,
LoadRestrictions: types.LoadRestrictionsRootOnly,
DoPrune: false,
PluginConfig: types.DisabledPluginConfig(),
Reorder: ReorderOptionNone,
AddManagedbyLabel: false,
LoadRestrictions: types.LoadRestrictionsRootOnly,
DoPrune: false,
PluginConfig: types.DisabledPluginConfig(),
}
}

View File

@@ -0,0 +1,331 @@
// Copyright 2022 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package krusty_test
import (
"testing"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/krusty"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
)
//nolint:gochecknoglobals
var sortOrderResources = `
apiVersion: v1
kind: Service
metadata:
name: papaya
---
apiVersion: v1
kind: Role
metadata:
name: banana
---
apiVersion: v1
kind: ValidatingWebhookConfiguration
metadata:
name: pomegranate
---
apiVersion: v1
kind: LimitRange
metadata:
name: peach
---
apiVersion: v1
kind: Deployment
metadata:
name: pear
---
apiVersion: v1
kind: Namespace
metadata:
name: apple
---
apiVersion: v1
kind: Secret
metadata:
name: quince
---
apiVersion: v1
kind: Ingress
metadata:
name: durian
---
apiVersion: v1
kind: ConfigMap
metadata:
name: apricot
`
//nolint:gochecknoglobals
var legacyOrderResources = `
apiVersion: v1
kind: Namespace
metadata:
name: apple
---
apiVersion: v1
kind: Role
metadata:
name: banana
---
apiVersion: v1
kind: ConfigMap
metadata:
name: apricot
---
apiVersion: v1
kind: Secret
metadata:
name: quince
---
apiVersion: v1
kind: Service
metadata:
name: papaya
---
apiVersion: v1
kind: LimitRange
metadata:
name: peach
---
apiVersion: v1
kind: Deployment
metadata:
name: pear
---
apiVersion: v1
kind: Ingress
metadata:
name: durian
---
apiVersion: v1
kind: ValidatingWebhookConfiguration
metadata:
name: pomegranate
`
func TestCustomOrdering(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
resources:
- resources.yaml
sortOptions:
order: legacy
legacySortOptions:
orderFirst:
- MutatingWebhookConfiguration
- ValidatingWebhookConfiguration
- ResourceQuota
- StorageClass
- CustomResourceDefinition
- ServiceAccount
- PodSecurityPolicy
- Role
- ClusterRole
- RoleBinding
- ClusterRoleBinding
- ConfigMap
- Secret
- Endpoints
- Service
- LimitRange
- PriorityClass
- PersistentVolume
- PersistentVolumeClaim
- StatefulSet
- CronJob
- PodDisruptionBudget
orderLast:
- Namespace
- Deployment
`)
th.WriteF("base/resources.yaml", sortOrderResources)
th.AssertActualEqualsExpected(
th.Run("base", th.MakeDefaultOptions()),
`
apiVersion: v1
kind: ValidatingWebhookConfiguration
metadata:
name: pomegranate
---
apiVersion: v1
kind: Role
metadata:
name: banana
---
apiVersion: v1
kind: ConfigMap
metadata:
name: apricot
---
apiVersion: v1
kind: Secret
metadata:
name: quince
---
apiVersion: v1
kind: Service
metadata:
name: papaya
---
apiVersion: v1
kind: LimitRange
metadata:
name: peach
---
apiVersion: v1
kind: Ingress
metadata:
name: durian
---
apiVersion: v1
kind: Namespace
metadata:
name: apple
---
apiVersion: v1
kind: Deployment
metadata:
name: pear
`)
}
func TestDefaultLegacyOrdering(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
resources:
- resources.yaml
sortOptions:
order: legacy
`)
th.WriteF("base/resources.yaml", sortOrderResources)
th.AssertActualEqualsExpected(
th.Run("base", th.MakeDefaultOptions()), legacyOrderResources)
}
func TestFIFOOrdering(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
resources:
- resources.yaml
sortOptions:
order: fifo
`)
th.WriteF("base/resources.yaml", sortOrderResources)
th.AssertActualEqualsExpected(
th.Run("base", th.MakeDefaultOptions()), sortOrderResources)
}
func TestInvalidLegacySortOptionsWithoutOrderKey(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
resources:
- resources.yaml
sortOptions:
legacySortOptions: {}
`)
th.WriteF("base/resources.yaml", sortOrderResources)
err := th.RunWithErr("base", th.MakeDefaultOptions())
require.ErrorContains(t, err, "the field 'sortOptions.order' must be one of [fifo, legacy]")
}
func TestInvalidLegacySortOptionsWithFIFOOrder(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
resources:
- resources.yaml
sortOptions:
order: fifo
legacySortOptions: {}
`)
th.WriteF("base/resources.yaml", sortOrderResources)
err := th.RunWithErr("base", th.MakeDefaultOptions())
require.ErrorContains(t, err, "the field 'sortOptions.legacySortOptions' is set but the selected sort order is 'fifo', not 'legacy'")
}
// If the sort order is defined both in a CLI flag and the kustomization file,
// the kustomization file takes precedence.
func TestCLIAndKustomizationSet(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
resources:
- resources.yaml
sortOptions:
order: fifo
`)
th.WriteF("base/resources.yaml", sortOrderResources)
kustOptions := th.MakeDefaultOptions()
kustOptions.Reorder = krusty.ReorderOptionLegacy
th.AssertActualEqualsExpected(th.Run("base", kustOptions), sortOrderResources)
}
// If no sort order is set in the kustomization file, validate that we fallback
// to the default legacy sort ordering.
func TestKustomizationSortOrderNotSet(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
resources:
- resources.yaml
`)
th.WriteF("base/resources.yaml", sortOrderResources)
kustOptions := th.MakeDefaultOptions()
kustOptions.Reorder = krusty.ReorderOptionUnspecified
th.AssertActualEqualsExpected(th.Run("base", kustOptions), legacyOrderResources)
}
func TestChildKustomizationSortOrder(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
resources:
- resources.yaml
sortOptions:
order: legacy
`)
th.WriteF("base/resources.yaml", sortOrderResources)
th.WriteK("overlay", `
resources:
- ../base
sortOptions:
order: fifo
`)
kustOptions := th.MakeDefaultOptions()
// If child kustomization ordering takes effect, the order will be legacy,
// otherwise fifo.
th.AssertActualEqualsExpected(th.Run("overlay", kustOptions), sortOrderResources)
}
func TestSortOrderGivenAsTransformer(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
resources:
- resources.yaml
transformers:
- sort_order.yaml
`)
th.WriteF("base/resources.yaml", sortOrderResources)
th.WriteF("base/sort_order.yaml", `
apiVersion: builtin
kind: SortOrderTransformer
metadata:
name: notImportantHere
sortOptions:
order: fifo`)
kustOptions := th.MakeDefaultOptions()
err := th.RunWithErr("base", kustOptions)
require.ErrorContains(t, err, "unable to load builtin SortOrderTransformer.builtin.[noGrp]")
}

View File

@@ -1,37 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resmap
import (
"sort"
"sigs.k8s.io/kustomize/kyaml/resid"
)
// IdSlice implements the sort interface.
type IdSlice []resid.ResId
var _ sort.Interface = IdSlice{}
func (a IdSlice) Len() int { return len(a) }
func (a IdSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a IdSlice) Less(i, j int) bool {
if !a[i].Gvk.Equals(a[j].Gvk) {
return a[i].Gvk.IsLessThan(a[j].Gvk)
}
return a[i].LegacySortString() < a[j].LegacySortString()
}

View File

@@ -1,52 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resmap
import (
"reflect"
"sort"
"testing"
"sigs.k8s.io/kustomize/kyaml/resid"
)
func TestLess(t *testing.T) {
ids := IdSlice{
resid.NewResIdKindOnly("ConfigMap", "cm"),
resid.NewResIdKindOnly("Pod", "pod"),
resid.NewResIdKindOnly("Namespace", "ns1"),
resid.NewResIdKindOnly("Namespace", "ns2"),
resid.NewResIdKindOnly("Role", "ro"),
resid.NewResIdKindOnly("RoleBinding", "rb"),
resid.NewResIdKindOnly("CustomResourceDefinition", "crd"),
resid.NewResIdKindOnly("ServiceAccount", "sa"),
}
expected := IdSlice{
resid.NewResIdKindOnly("Namespace", "ns1"),
resid.NewResIdKindOnly("Namespace", "ns2"),
resid.NewResIdKindOnly("CustomResourceDefinition", "crd"),
resid.NewResIdKindOnly("ServiceAccount", "sa"),
resid.NewResIdKindOnly("Role", "ro"),
resid.NewResIdKindOnly("RoleBinding", "rb"),
resid.NewResIdKindOnly("ConfigMap", "cm"),
resid.NewResIdKindOnly("Pod", "pod"),
}
sort.Sort(ids)
if !reflect.DeepEqual(ids, expected) {
t.Fatalf("expected %+v but got %+v", expected, ids)
}
}

View File

@@ -101,6 +101,9 @@ type Kustomization struct {
// value of the specified field has been determined.
Vars []Var `json:"vars,omitempty" yaml:"vars,omitempty"`
// SortOptions change the order that kustomize outputs resources.
SortOptions *SortOptions `json:"sortOptions,omitempty" yaml:"sortOptions,omitempty"`
//
// Operands - what kustomize operates on.
//

28
api/types/sortoptions.go Normal file
View File

@@ -0,0 +1,28 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package types
// SortOptions defines the order that kustomize outputs resources.
type SortOptions struct {
// Order selects the ordering strategy.
Order SortOrder `json:"order,omitempty" yaml:"order,omitempty"`
// LegacySortOptions tweaks the sorting for the "legacy" sort ordering
// strategy.
LegacySortOptions *LegacySortOptions `json:"legacySortOptions,omitempty" yaml:"legacySortOptions,omitempty"`
}
// SortOrder defines different ordering strategies.
type SortOrder string
const LegacySortOrder SortOrder = "legacy"
const FIFOSortOrder SortOrder = "fifo"
// LegacySortOptions define various options for tweaking the "legacy" ordering
// strategy.
type LegacySortOptions struct {
// OrderFirst selects the resource kinds to order first.
OrderFirst []string `json:"orderFirst" yaml:"orderFirst"`
// OrderLast selects the resource kinds to order last.
OrderLast []string `json:"orderLast" yaml:"orderLast"`
}