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 }