github.com/splunk/dan1-qbec@v0.7.3/internal/objsort/sort.go (about)

     1  /*
     2     Copyright 2019 Splunk Inc.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  // Package objsort allows sorting of K8s objects in the order in which they should be applied to the cluster.
    18  package objsort
    19  
    20  import (
    21  	"sort"
    22  
    23  	"github.com/splunk/qbec/internal/model"
    24  	"k8s.io/apimachinery/pkg/runtime/schema"
    25  )
    26  
    27  // OrderingProvider provides a positive order for the supplied item if it wants
    28  // to influence its apply order or 0 if it does not care.
    29  type OrderingProvider func(item model.K8sQbecMeta) int
    30  
    31  // Namespaced returns true if the supplied gvk is a namespaced resource.
    32  type Namespaced func(gvk schema.GroupVersionKind) (namespaced bool, err error)
    33  
    34  // Config is the sort configuration. The ordering provider may be nil if no custom
    35  // ordering is required.
    36  type Config struct {
    37  	OrderingProvider    OrderingProvider // custom ordering provider
    38  	NamespacedIndicator Namespaced       // indicator to determine if resource sis namespaced
    39  }
    40  
    41  // ordering for specific classes of objects
    42  const (
    43  	GenericClusterObjectOrder = 30  // any cluster-level object that does not have an assigned order
    44  	GenericNamespacedOrder    = 80  // any namespaced object that does not have an assigned order
    45  	GenericPodOrder           = 100 // any object that results in pod creation
    46  	GenericLast               = 120 // any object for which server metadata was not found
    47  )
    48  
    49  // SpecifiedOrdering defines ordering for a set of known kubernetes objects.
    50  var SpecifiedOrdering = map[schema.GroupKind]int{
    51  	{Group: "extensions", Kind: "PodSecurityPolicy"}:                                10,
    52  	{Group: "extensions", Kind: "ThirdPartyResource"}:                               20,
    53  	{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"}:               20,
    54  	{Group: "", Kind: "Namespace"}:                                                  GenericNamespacedOrder - 30,
    55  	{Group: "", Kind: "LimitRange"}:                                                 GenericNamespacedOrder - 20,
    56  	{Group: "", Kind: "ServiceAccount"}:                                             GenericNamespacedOrder - 20,
    57  	{Group: "", Kind: "ConfigMap"}:                                                  GenericNamespacedOrder - 10,
    58  	{Group: "", Kind: "Secret"}:                                                     GenericNamespacedOrder - 10,
    59  	{Group: "extensions", Kind: "DaemonSet"}:                                        GenericPodOrder,
    60  	{Group: "extensions", Kind: "Deployment"}:                                       GenericPodOrder,
    61  	{Group: "extensions", Kind: "ReplicaSet"}:                                       GenericPodOrder,
    62  	{Group: "extensions", Kind: "StatefulSet"}:                                      GenericPodOrder,
    63  	{Group: "apps", Kind: "DaemonSet"}:                                              GenericPodOrder,
    64  	{Group: "apps", Kind: "Deployment"}:                                             GenericPodOrder,
    65  	{Group: "apps", Kind: "ReplicaSet"}:                                             GenericPodOrder,
    66  	{Group: "apps", Kind: "StatefulSet"}:                                            GenericPodOrder,
    67  	{Group: "batch", Kind: "Job"}:                                                   GenericPodOrder,
    68  	{Group: "batch", Kind: "CronJob"}:                                               GenericPodOrder,
    69  	{Group: "", Kind: "Service"}:                                                    GenericPodOrder + 10,
    70  	{Group: "admissionregistration.k8s.io", Kind: "ValidatingWebhookConfiguration"}: GenericPodOrder + 20,
    71  	{Group: "admissionregistration.k8s.io", Kind: "MutatingWebhookConfiguration"}:   GenericPodOrder + 20,
    72  }
    73  
    74  func getOrder(ob model.K8sQbecMeta, config Config) int {
    75  	order := config.OrderingProvider(ob)
    76  	if order > 0 {
    77  		return order
    78  	}
    79  	gvk := ob.GroupVersionKind()
    80  	gk := gvk.GroupKind()
    81  	if order, ok := SpecifiedOrdering[gk]; ok {
    82  		return order
    83  	}
    84  	namespaced, err := config.NamespacedIndicator(gvk)
    85  	if err != nil {
    86  		return GenericLast
    87  	}
    88  	if namespaced {
    89  		return GenericNamespacedOrder
    90  	}
    91  	return GenericClusterObjectOrder
    92  }
    93  
    94  type sortInput struct {
    95  	item      interface{}
    96  	kind      string
    97  	component string
    98  	ns        string
    99  	name      string
   100  	order     int
   101  }
   102  
   103  type sorter struct {
   104  	inputs []sortInput
   105  	config Config
   106  }
   107  
   108  func newSorter(config Config) *sorter {
   109  	if config.OrderingProvider == nil {
   110  		config.OrderingProvider = func(ob model.K8sQbecMeta) int { return 0 }
   111  	}
   112  	return &sorter{
   113  		config: config,
   114  	}
   115  }
   116  
   117  func (s *sorter) add(o model.K8sQbecMeta, item interface{}) {
   118  	s.inputs = append(s.inputs, sortInput{
   119  		item:      item,
   120  		kind:      o.GetKind(),
   121  		component: o.Component(),
   122  		ns:        o.GetNamespace(),
   123  		name:      o.GetName(),
   124  		order:     getOrder(o, s.config),
   125  	})
   126  }
   127  
   128  func (s *sorter) sort() {
   129  	items := s.inputs
   130  	sort.Slice(items, func(i, j int) bool {
   131  		left := items[i]
   132  		right := items[j]
   133  		if left.order != right.order {
   134  			return left.order < right.order
   135  		}
   136  		if left.kind != right.kind {
   137  			return left.kind < right.kind
   138  		}
   139  		if left.component != right.component {
   140  			return left.component < right.component
   141  		}
   142  		if left.ns != right.ns {
   143  			return left.ns < right.ns
   144  		}
   145  		return left.name < right.name
   146  	})
   147  }
   148  
   149  // SortMeta sorts the supplied meta objects based on the config.
   150  func SortMeta(inputs []model.K8sQbecMeta, config Config) []model.K8sQbecMeta {
   151  	sorter := newSorter(config)
   152  	for _, obj := range inputs {
   153  		sorter.add(obj, obj)
   154  	}
   155  	sorter.sort()
   156  	ret := make([]model.K8sQbecMeta, 0, len(inputs))
   157  	for _, o := range sorter.inputs {
   158  		ret = append(ret, o.item.(model.K8sQbecMeta))
   159  	}
   160  	return ret
   161  }
   162  
   163  // Sort sorts the supplied local objects based on the supplied configuration.
   164  func Sort(inputs []model.K8sLocalObject, config Config) []model.K8sLocalObject {
   165  	sorter := newSorter(config)
   166  	for _, obj := range inputs {
   167  		sorter.add(obj, obj)
   168  	}
   169  	sorter.sort()
   170  	ret := make([]model.K8sLocalObject, 0, len(inputs))
   171  	for _, o := range sorter.inputs {
   172  		ret = append(ret, o.item.(model.K8sLocalObject))
   173  	}
   174  	return ret
   175  }