diff --git a/cmd/delete.go b/cmd/delete.go index 2975cfb298d2aa786dfb81a1a26caf694f5e5361..7e84c22b316305abd74ff7749b2e53be1f74e8c3 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "sort" "github.com/golang/glog" "github.com/spf13/cobra" @@ -44,7 +45,7 @@ var deleteCmd = &cobra.Command{ defaultNs, _, err := clientConfig.Namespace() - utils.SortDepLast(objs) + sort.Sort(sort.Reverse(utils.DependencyOrder(objs))) deleteOpts := v1.DeleteOptions{OrphanDependents: &boolFalse} if gracePeriod >= 0 { diff --git a/cmd/diff.go b/cmd/diff.go new file mode 100644 index 0000000000000000000000000000000000000000..61128acadb804bb2d1db2972917c46e83625f6aa --- /dev/null +++ b/cmd/diff.go @@ -0,0 +1,96 @@ +package cmd + +import ( + "fmt" + "io" + "os" + "sort" + + "github.com/golang/glog" + "github.com/mattn/go-isatty" + "github.com/spf13/cobra" + "github.com/yudai/gojsondiff" + "github.com/yudai/gojsondiff/formatter" + "k8s.io/client-go/pkg/api/errors" + + "github.com/ksonnet/kubecfg/utils" +) + +func init() { + RootCmd.AddCommand(diffCmd) +} + +var diffCmd = &cobra.Command{ + Use: "diff", + Short: "Display differences between server and local config", + RunE: func(cmd *cobra.Command, args []string) error { + out := cmd.OutOrStdout() + + objs, err := readObjs(cmd, args) + if err != nil { + return err + } + + clientpool, disco, err := restClientPool(cmd) + if err != nil { + return err + } + + defaultNs, _, err := clientConfig.Namespace() + if err != nil { + return err + } + + sort.Sort(utils.AlphabeticalOrder(objs)) + + for _, obj := range objs { + desc := fmt.Sprintf("%s/%s", obj.GetKind(), fqName(obj)) + glog.V(2).Info("Fetching ", desc) + + c, err := clientForResource(clientpool, disco, obj, defaultNs) + if err != nil { + return err + } + + liveObj, err := c.Get(obj.GetName()) + if err != nil && errors.IsNotFound(err) { + glog.V(2).Infof("%s doesn't exist on the server", desc) + liveObj = nil + } else if err != nil { + return fmt.Errorf("Error fetching %s: %v", desc, err) + } + + fmt.Fprintln(out, "---") + fmt.Fprintf(out, "- live %s\n+ config %s", desc, desc) + if liveObj == nil { + fmt.Fprintf(out, "%s doesn't exist on server\n", desc) + continue + } + + diff := gojsondiff.New().CompareObjects(liveObj.Object, obj.Object) + + if diff.Modified() { + fcfg := formatter.AsciiFormatterConfig{ + Coloring: istty(out), + } + formatter := formatter.NewAsciiFormatter(liveObj.Object, fcfg) + text, err := formatter.Format(diff) + if err != nil { + return err + } + fmt.Fprintf(out, "%s", text) + } else { + fmt.Fprintf(out, "%s unchanged\n", desc) + } + } + + return nil + }, +} + +func istty(w io.Writer) bool { + if f, ok := w.(*os.File); ok { + return isatty.IsTerminal(f.Fd()) + } + return false +} diff --git a/cmd/update.go b/cmd/update.go index 655afe59aaec2811d8aa2b56ed72d3553cc0369b..a2ade46c24963eea14fde0706842ea8ab1c8b137 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -3,6 +3,7 @@ package cmd import ( "encoding/json" "fmt" + "sort" "github.com/golang/glog" "github.com/spf13/cobra" @@ -49,7 +50,7 @@ var updateCmd = &cobra.Command{ return err } - utils.SortDepFirst(objs) + sort.Sort(utils.DependencyOrder(objs)) for _, obj := range objs { desc := fmt.Sprintf("%s/%s", obj.GetKind(), fqName(obj)) diff --git a/utils/sort.go b/utils/sort.go index 3f92be0720a5a28740152029b2f047c3758b710b..2fd88276afb44a9de8773ad1dcf0677500288407 100644 --- a/utils/sort.go +++ b/utils/sort.go @@ -1,8 +1,6 @@ package utils import ( - "sort" - "k8s.io/client-go/pkg/api/unversioned" "k8s.io/client-go/pkg/runtime" ) @@ -42,23 +40,32 @@ func depTier(o *runtime.Unstructured) int { } } -type dependentObjects []*runtime.Unstructured +// 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. +type DependencyOrder []*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 { +func (l DependencyOrder) Len() int { return len(l) } +func (l DependencyOrder) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l DependencyOrder) 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)) -} +// AlphabeticalOrder is a `sort.Interface` that sorts the +// objects by namespace/name/kind alphabetical order +type AlphabeticalOrder []*runtime.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] -// SortDepLast is the reverse order of SortDepFirst. -func SortDepLast(objs []*runtime.Unstructured) { - sort.Sort(sort.Reverse(dependentObjects(objs))) + 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() } diff --git a/utils/sort_test.go b/utils/sort_test.go index c579badc1d220122912ec0a194fb27ef952473c6..0a8df52de984d0ab8f22de5d1684856681df9174 100644 --- a/utils/sort_test.go +++ b/utils/sort_test.go @@ -1,21 +1,25 @@ package utils import ( + "reflect" + "sort" "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, - }, +var _ sort.Interface = DependencyOrder{} + +func TestDepSort(t *testing.T) { + newObj := func(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"), @@ -23,7 +27,7 @@ func TestSort(t *testing.T) { newObj("v1", "Service"), } - SortDepFirst(objs) + sort.Sort(DependencyOrder(objs)) if objs[0].GetKind() != "Namespace" { t.Error("Namespace should be sorted first") @@ -32,3 +36,35 @@ func TestSort(t *testing.T) { t.Error("Deployment should be sorted after other objects") } } + +func TestAlphaSort(t *testing.T) { + newObj := func(ns, name, kind string) *runtime.Unstructured { + o := runtime.Unstructured{} + o.SetNamespace(ns) + o.SetName(name) + o.SetKind(kind) + return &o + } + + objs := []*runtime.Unstructured{ + newObj("default", "mysvc", "Deployment"), + newObj("", "default", "StorageClass"), + newObj("", "default", "ClusterRole"), + newObj("default", "mydeploy", "Deployment"), + newObj("default", "mysvc", "Secret"), + } + + expected := []*runtime.Unstructured{ + objs[2], + objs[1], + objs[3], + objs[0], + objs[4], + } + + sort.Sort(AlphabeticalOrder(objs)) + + if !reflect.DeepEqual(objs, expected) { + t.Errorf("actual != expected: %v != %v", objs, expected) + } +}