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()
+}