blob: dc9838c583d9f6ec8763234fbaa9074db18eb170 [file] [log] [blame]
Serge Bazanskibe538db2020-11-12 00:22:42 +01001// 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
16package utils
17
18import (
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
29var (
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.
38type podSpecVisitor bool
39
40func (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
53func (v *podSpecVisitor) VisitReference(s proto.Reference) { s.SubSchema().Accept(v) }
54func (v *podSpecVisitor) VisitArray(s *proto.Array) { s.SubType.Accept(v) }
55func (v *podSpecVisitor) VisitMap(s *proto.Map) { s.SubType.Accept(v) }
56func (v *podSpecVisitor) VisitPrimitive(p *proto.Primitive) {}
57
58var podSpecCache = map[string]podSpecVisitor{}
59
60func 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.
79func 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.
112func 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
125type mappedSort struct {
126 sortKeys []int
127 items []*unstructured.Unstructured
128}
129
130func (l *mappedSort) Len() int { return len(l.items) }
131func (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}
135func (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
145type AlphabeticalOrder []*unstructured.Unstructured
146
147func (l AlphabeticalOrder) Len() int { return len(l) }
148func (l AlphabeticalOrder) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
149func (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}