cluster/tools/kartongips: init
This forks bitnami/kubecfg into kartongips. The rationale is that we
want to implement hscloud-specific functionality that wouldn't really be
upstreamable into kubecfg (like secret support, mulit-cluster support).
We forked off from github.com/q3k/kubecfg at commit b6817a94492c561ed61a44eeea2d92dcf2e6b8c0.
Change-Id: If5ba513905e0a86f971576fe7061a471c1d8b398
diff --git a/cluster/tools/kartongips/utils/sort.go b/cluster/tools/kartongips/utils/sort.go
new file mode 100644
index 0000000..dc9838c
--- /dev/null
+++ b/cluster/tools/kartongips/utils/sort.go
@@ -0,0 +1,159 @@
+// Copyright 2017 The kubecfg 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 utils
+
+import (
+ "sort"
+
+ log "github.com/sirupsen/logrus"
+ "k8s.io/apimachinery/pkg/api/meta"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/client-go/discovery"
+ "k8s.io/kube-openapi/pkg/util/proto"
+)
+
+var (
+ gkTpr = schema.GroupKind{Group: "extensions", Kind: "ThirdPartyResource"}
+ gkCrd = schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"}
+ gkValidatingWebhook = schema.GroupKind{Group: "admissionregistration.k8s.io", Kind: "ValidatingWebhookConfiguration"}
+ gkMutatingWebhook = schema.GroupKind{Group: "admissionregistration.k8s.io", Kind: "MutatingWebhookConfiguration"}
+)
+
+// a podSpecVisitor traverses a schema tree and records whether the schema
+// contains a PodSpec resource.
+type podSpecVisitor bool
+
+func (v *podSpecVisitor) VisitKind(k *proto.Kind) {
+ if k.GetPath().String() == "io.k8s.api.core.v1.PodSpec" {
+ *v = true
+ return
+ }
+ for _, f := range k.Fields {
+ f.Accept(v)
+ if *v == true {
+ return
+ }
+ }
+}
+
+func (v *podSpecVisitor) VisitReference(s proto.Reference) { s.SubSchema().Accept(v) }
+func (v *podSpecVisitor) VisitArray(s *proto.Array) { s.SubType.Accept(v) }
+func (v *podSpecVisitor) VisitMap(s *proto.Map) { s.SubType.Accept(v) }
+func (v *podSpecVisitor) VisitPrimitive(p *proto.Primitive) {}
+
+var podSpecCache = map[string]podSpecVisitor{}
+
+func containsPodSpec(disco discovery.OpenAPISchemaInterface, gvk schema.GroupVersionKind) bool {
+ result, ok := podSpecCache[gvk.String()]
+ if ok {
+ return bool(result)
+ }
+
+ oapi, err := NewOpenAPISchemaFor(disco, gvk)
+ if err != nil {
+ log.Debugf("error fetching schema for %s: %v", gvk, err)
+ return false
+ }
+
+ oapi.schema.Accept(&result)
+ podSpecCache[gvk.String()] = result
+
+ return bool(result)
+}
+
+// Arbitrary numbers used to do a simple topological sort of resources.
+func depTier(disco discovery.OpenAPISchemaInterface, mapper meta.RESTMapper, o schema.ObjectKind) (int, error) {
+ gvk := o.GroupVersionKind()
+ gk := gvk.GroupKind()
+ if gk == gkTpr || gk == gkCrd {
+ // Special case (first): these create other types
+ return 10, nil
+ } else if gk == gkValidatingWebhook || gk == gkMutatingWebhook {
+ // Special case (last): these require operational services
+ return 200, nil
+ }
+
+ mapping, err := mapper.RESTMapping(gk, gvk.Version)
+ if err != nil {
+ log.Debugf("unable to fetch resource for %s (%v), continuing", gvk, err)
+ return 50, nil
+ }
+
+ if mapping.Scope.Name() == meta.RESTScopeNameRoot {
+ // Place global before namespaced
+ return 20, nil
+ } else if containsPodSpec(disco, gvk) {
+ // (Potentially) starts a pod, so place last
+ return 100, nil
+ } else {
+ // Everything else
+ return 50, nil
+ }
+}
+
+// DependencyOrder is a `sort.Interface` that *best-effort* sorts the
+// objects so that known dependencies appear earlier in the list. The
+// idea is to prevent *some* of the "crash-restart" loops when
+// creating inter-dependent resources.
+func DependencyOrder(disco discovery.OpenAPISchemaInterface, mapper meta.RESTMapper, list []*unstructured.Unstructured) (sort.Interface, error) {
+ sortKeys := make([]int, len(list))
+ for i, item := range list {
+ var err error
+ sortKeys[i], err = depTier(disco, mapper, item.GetObjectKind())
+ if err != nil {
+ return nil, err
+ }
+ }
+ log.Debugf("sortKeys is %v", sortKeys)
+ return &mappedSort{sortKeys: sortKeys, items: list}, nil
+}
+
+type mappedSort struct {
+ sortKeys []int
+ items []*unstructured.Unstructured
+}
+
+func (l *mappedSort) Len() int { return len(l.items) }
+func (l *mappedSort) Swap(i, j int) {
+ l.sortKeys[i], l.sortKeys[j] = l.sortKeys[j], l.sortKeys[i]
+ l.items[i], l.items[j] = l.items[j], l.items[i]
+}
+func (l *mappedSort) Less(i, j int) bool {
+ if l.sortKeys[i] != l.sortKeys[j] {
+ return l.sortKeys[i] < l.sortKeys[j]
+ }
+ // Fall back to alpha sort, to give persistent order
+ return AlphabeticalOrder(l.items).Less(i, j)
+}
+
+// AlphabeticalOrder is a `sort.Interface` that sorts the
+// objects by namespace/name/kind alphabetical order
+type AlphabeticalOrder []*unstructured.Unstructured
+
+func (l AlphabeticalOrder) Len() int { return len(l) }
+func (l AlphabeticalOrder) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+func (l AlphabeticalOrder) Less(i, j int) bool {
+ a, b := l[i], l[j]
+
+ if a.GetNamespace() != b.GetNamespace() {
+ return a.GetNamespace() < b.GetNamespace()
+ }
+ if a.GetName() != b.GetName() {
+ return a.GetName() < b.GetName()
+ }
+ return a.GetKind() < b.GetKind()
+}