Serge Bazanski | be538db | 2020-11-12 00:22:42 +0100 | [diff] [blame] | 1 | // Copyright 2017 The kubecfg authors |
| 2 | // |
| 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 | package utils |
| 17 | |
| 18 | import ( |
| 19 | "sort" |
| 20 | |
| 21 | log "github.com/sirupsen/logrus" |
| 22 | "k8s.io/apimachinery/pkg/api/meta" |
| 23 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" |
| 24 | "k8s.io/apimachinery/pkg/runtime/schema" |
| 25 | "k8s.io/client-go/discovery" |
| 26 | "k8s.io/kube-openapi/pkg/util/proto" |
| 27 | ) |
| 28 | |
| 29 | var ( |
| 30 | gkTpr = schema.GroupKind{Group: "extensions", Kind: "ThirdPartyResource"} |
| 31 | gkCrd = schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"} |
| 32 | gkValidatingWebhook = schema.GroupKind{Group: "admissionregistration.k8s.io", Kind: "ValidatingWebhookConfiguration"} |
| 33 | gkMutatingWebhook = schema.GroupKind{Group: "admissionregistration.k8s.io", Kind: "MutatingWebhookConfiguration"} |
| 34 | ) |
| 35 | |
| 36 | // a podSpecVisitor traverses a schema tree and records whether the schema |
| 37 | // contains a PodSpec resource. |
| 38 | type podSpecVisitor bool |
| 39 | |
| 40 | func (v *podSpecVisitor) VisitKind(k *proto.Kind) { |
| 41 | if k.GetPath().String() == "io.k8s.api.core.v1.PodSpec" { |
| 42 | *v = true |
| 43 | return |
| 44 | } |
| 45 | for _, f := range k.Fields { |
| 46 | f.Accept(v) |
| 47 | if *v == true { |
| 48 | return |
| 49 | } |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | func (v *podSpecVisitor) VisitReference(s proto.Reference) { s.SubSchema().Accept(v) } |
| 54 | func (v *podSpecVisitor) VisitArray(s *proto.Array) { s.SubType.Accept(v) } |
| 55 | func (v *podSpecVisitor) VisitMap(s *proto.Map) { s.SubType.Accept(v) } |
| 56 | func (v *podSpecVisitor) VisitPrimitive(p *proto.Primitive) {} |
| 57 | |
| 58 | var podSpecCache = map[string]podSpecVisitor{} |
| 59 | |
| 60 | func containsPodSpec(disco discovery.OpenAPISchemaInterface, gvk schema.GroupVersionKind) bool { |
| 61 | result, ok := podSpecCache[gvk.String()] |
| 62 | if ok { |
| 63 | return bool(result) |
| 64 | } |
| 65 | |
| 66 | oapi, err := NewOpenAPISchemaFor(disco, gvk) |
| 67 | if err != nil { |
| 68 | log.Debugf("error fetching schema for %s: %v", gvk, err) |
| 69 | return false |
| 70 | } |
| 71 | |
| 72 | oapi.schema.Accept(&result) |
| 73 | podSpecCache[gvk.String()] = result |
| 74 | |
| 75 | return bool(result) |
| 76 | } |
| 77 | |
| 78 | // Arbitrary numbers used to do a simple topological sort of resources. |
| 79 | func depTier(disco discovery.OpenAPISchemaInterface, mapper meta.RESTMapper, o schema.ObjectKind) (int, error) { |
| 80 | gvk := o.GroupVersionKind() |
| 81 | gk := gvk.GroupKind() |
| 82 | if gk == gkTpr || gk == gkCrd { |
| 83 | // Special case (first): these create other types |
| 84 | return 10, nil |
| 85 | } else if gk == gkValidatingWebhook || gk == gkMutatingWebhook { |
| 86 | // Special case (last): these require operational services |
| 87 | return 200, nil |
| 88 | } |
| 89 | |
| 90 | mapping, err := mapper.RESTMapping(gk, gvk.Version) |
| 91 | if err != nil { |
| 92 | log.Debugf("unable to fetch resource for %s (%v), continuing", gvk, err) |
| 93 | return 50, nil |
| 94 | } |
| 95 | |
| 96 | if mapping.Scope.Name() == meta.RESTScopeNameRoot { |
| 97 | // Place global before namespaced |
| 98 | return 20, nil |
| 99 | } else if containsPodSpec(disco, gvk) { |
| 100 | // (Potentially) starts a pod, so place last |
| 101 | return 100, nil |
| 102 | } else { |
| 103 | // Everything else |
| 104 | return 50, nil |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | // DependencyOrder is a `sort.Interface` that *best-effort* sorts the |
| 109 | // objects so that known dependencies appear earlier in the list. The |
| 110 | // idea is to prevent *some* of the "crash-restart" loops when |
| 111 | // creating inter-dependent resources. |
| 112 | func DependencyOrder(disco discovery.OpenAPISchemaInterface, mapper meta.RESTMapper, list []*unstructured.Unstructured) (sort.Interface, error) { |
| 113 | sortKeys := make([]int, len(list)) |
| 114 | for i, item := range list { |
| 115 | var err error |
| 116 | sortKeys[i], err = depTier(disco, mapper, item.GetObjectKind()) |
| 117 | if err != nil { |
| 118 | return nil, err |
| 119 | } |
| 120 | } |
| 121 | log.Debugf("sortKeys is %v", sortKeys) |
| 122 | return &mappedSort{sortKeys: sortKeys, items: list}, nil |
| 123 | } |
| 124 | |
| 125 | type mappedSort struct { |
| 126 | sortKeys []int |
| 127 | items []*unstructured.Unstructured |
| 128 | } |
| 129 | |
| 130 | func (l *mappedSort) Len() int { return len(l.items) } |
| 131 | func (l *mappedSort) Swap(i, j int) { |
| 132 | l.sortKeys[i], l.sortKeys[j] = l.sortKeys[j], l.sortKeys[i] |
| 133 | l.items[i], l.items[j] = l.items[j], l.items[i] |
| 134 | } |
| 135 | func (l *mappedSort) Less(i, j int) bool { |
| 136 | if l.sortKeys[i] != l.sortKeys[j] { |
| 137 | return l.sortKeys[i] < l.sortKeys[j] |
| 138 | } |
| 139 | // Fall back to alpha sort, to give persistent order |
| 140 | return AlphabeticalOrder(l.items).Less(i, j) |
| 141 | } |
| 142 | |
| 143 | // AlphabeticalOrder is a `sort.Interface` that sorts the |
| 144 | // objects by namespace/name/kind alphabetical order |
| 145 | type AlphabeticalOrder []*unstructured.Unstructured |
| 146 | |
| 147 | func (l AlphabeticalOrder) Len() int { return len(l) } |
| 148 | func (l AlphabeticalOrder) Swap(i, j int) { l[i], l[j] = l[j], l[i] } |
| 149 | func (l AlphabeticalOrder) Less(i, j int) bool { |
| 150 | a, b := l[i], l[j] |
| 151 | |
| 152 | if a.GetNamespace() != b.GetNamespace() { |
| 153 | return a.GetNamespace() < b.GetNamespace() |
| 154 | } |
| 155 | if a.GetName() != b.GetName() { |
| 156 | return a.GetName() < b.GetName() |
| 157 | } |
| 158 | return a.GetKind() < b.GetKind() |
| 159 | } |