diff --git a/cmd/update.go b/cmd/update.go index 8ffc485d425d25142f98e41cc8472833f520e078..655afe59aaec2811d8aa2b56ed72d3553cc0369b 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -10,6 +10,8 @@ import ( "k8s.io/client-go/pkg/api/errors" "k8s.io/client-go/pkg/runtime" "k8s.io/client-go/pkg/util/diff" + + "github.com/ksonnet/kubecfg/utils" ) const ( @@ -47,6 +49,8 @@ var updateCmd = &cobra.Command{ return err } + utils.SortDepFirst(objs) + for _, obj := range objs { desc := fmt.Sprintf("%s/%s", obj.GetKind(), fqName(obj)) glog.Info("Updating ", desc) diff --git a/utils/sort.go b/utils/sort.go new file mode 100644 index 0000000000000000000000000000000000000000..554f4c245113ac77f6bea8077ef0307769e656ff --- /dev/null +++ b/utils/sort.go @@ -0,0 +1,59 @@ +package utils + +import ( + "sort" + + "k8s.io/client-go/pkg/api/unversioned" + "k8s.io/client-go/pkg/runtime" +) + +var ( + gkNamespace = unversioned.GroupKind{Group: "", Kind: "Namespace"} + gkTpr = unversioned.GroupKind{Group: "extensions", Kind: "ThirdPartyResource"} + gkStorageClass = unversioned.GroupKind{Group: "storage.k8s.io", Kind: "StorageClass"} + + gkPod = unversioned.GroupKind{Group: "", Kind: "Pod"} + gkJob = unversioned.GroupKind{Group: "batch", Kind: "Job"} + gkDeployment = unversioned.GroupKind{Group: "extensions", Kind: "Deployment"} + gkDaemonSet = unversioned.GroupKind{Group: "extensions", Kind: "DaemonSet"} + gkStatefulSet = unversioned.GroupKind{Group: "apps", Kind: "StatefulSet"} +) + +// These kinds all start pods. +// TODO: expand this list. +func isPodOrSimilar(gk unversioned.GroupKind) bool { + return gk == gkPod || + gk == gkJob || + gk == gkDeployment || + gk == gkDaemonSet || + gk == gkStatefulSet +} + +// Arbitrary numbers used to do a simple topological sort of resources. +// TODO: expand this list. +func depTier(o *runtime.Unstructured) int { + gk := o.GroupVersionKind().GroupKind() + if gk == gkNamespace || gk == gkTpr || gk == gkStorageClass { + return 10 + } else if isPodOrSimilar(gk) { + return 100 + } else { + return 50 + } +} + +type dependentObjects []*runtime.Unstructured + +func (l dependentObjects) Len() int { return len(l) } +func (l dependentObjects) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l dependentObjects) Less(i, j int) bool { + return depTier(l[i]) < depTier(l[j]) +} + +// SortDepFirst *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 SortDepFirst(objs []*runtime.Unstructured) { + sort.Sort(dependentObjects(objs)) +} diff --git a/utils/sort_test.go b/utils/sort_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c579badc1d220122912ec0a194fb27ef952473c6 --- /dev/null +++ b/utils/sort_test.go @@ -0,0 +1,34 @@ +package utils + +import ( + "testing" + + "k8s.io/client-go/pkg/runtime" +) + +func newObj(apiVersion, kind string) *runtime.Unstructured { + return &runtime.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": apiVersion, + "kind": kind, + }, + } +} + +func TestSort(t *testing.T) { + objs := []*runtime.Unstructured{ + newObj("extensions/v1beta1", "Deployment"), + newObj("v1", "ConfigMap"), + newObj("v1", "Namespace"), + newObj("v1", "Service"), + } + + SortDepFirst(objs) + + if objs[0].GetKind() != "Namespace" { + t.Error("Namespace should be sorted first") + } + if objs[3].GetKind() != "Deployment" { + t.Error("Deployment should be sorted after other objects") + } +}