Commit 01e5f51b authored by Angus Lees's avatar Angus Lees
Browse files

Implement diff subcommand

Uses github.com/yudai/gojsondiff for the heavy lifting.
parent 5f4a09c5
......@@ -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 {
......
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
}
......@@ -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))
......
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()
}
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)
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment