Skip to content
Snippets Groups Projects
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
No related branches found
No related tags found
No related merge requests found
...@@ -2,6 +2,7 @@ package cmd ...@@ -2,6 +2,7 @@ package cmd
import ( import (
"fmt" "fmt"
"sort"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
...@@ -44,7 +45,7 @@ var deleteCmd = &cobra.Command{ ...@@ -44,7 +45,7 @@ var deleteCmd = &cobra.Command{
defaultNs, _, err := clientConfig.Namespace() defaultNs, _, err := clientConfig.Namespace()
utils.SortDepLast(objs) sort.Sort(sort.Reverse(utils.DependencyOrder(objs)))
deleteOpts := v1.DeleteOptions{OrphanDependents: &boolFalse} deleteOpts := v1.DeleteOptions{OrphanDependents: &boolFalse}
if gracePeriod >= 0 { 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 ...@@ -3,6 +3,7 @@ package cmd
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"sort"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
...@@ -49,7 +50,7 @@ var updateCmd = &cobra.Command{ ...@@ -49,7 +50,7 @@ var updateCmd = &cobra.Command{
return err return err
} }
utils.SortDepFirst(objs) sort.Sort(utils.DependencyOrder(objs))
for _, obj := range objs { for _, obj := range objs {
desc := fmt.Sprintf("%s/%s", obj.GetKind(), fqName(obj)) desc := fmt.Sprintf("%s/%s", obj.GetKind(), fqName(obj))
......
package utils package utils
import ( import (
"sort"
"k8s.io/client-go/pkg/api/unversioned" "k8s.io/client-go/pkg/api/unversioned"
"k8s.io/client-go/pkg/runtime" "k8s.io/client-go/pkg/runtime"
) )
...@@ -42,23 +40,32 @@ func depTier(o *runtime.Unstructured) int { ...@@ -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 DependencyOrder) Len() int { return len(l) }
func (l dependentObjects) Swap(i, j int) { l[i], l[j] = l[j], l[i] } func (l DependencyOrder) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l dependentObjects) Less(i, j int) bool { func (l DependencyOrder) Less(i, j int) bool {
return depTier(l[i]) < depTier(l[j]) return depTier(l[i]) < depTier(l[j])
} }
// SortDepFirst *best-effort* sorts the objects so that known // AlphabeticalOrder is a `sort.Interface` that sorts the
// dependencies appear earlier in the list. The idea is to prevent // objects by namespace/name/kind alphabetical order
// *some* of the "crash-restart" loops when creating inter-dependent type AlphabeticalOrder []*runtime.Unstructured
// resources.
func SortDepFirst(objs []*runtime.Unstructured) { func (l AlphabeticalOrder) Len() int { return len(l) }
sort.Sort(dependentObjects(objs)) 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. if a.GetNamespace() != b.GetNamespace() {
func SortDepLast(objs []*runtime.Unstructured) { return a.GetNamespace() < b.GetNamespace()
sort.Sort(sort.Reverse(dependentObjects(objs))) }
if a.GetName() != b.GetName() {
return a.GetName() < b.GetName()
}
return a.GetKind() < b.GetKind()
} }
package utils package utils
import ( import (
"reflect"
"sort"
"testing" "testing"
"k8s.io/client-go/pkg/runtime" "k8s.io/client-go/pkg/runtime"
) )
func newObj(apiVersion, kind string) *runtime.Unstructured { var _ sort.Interface = DependencyOrder{}
return &runtime.Unstructured{
Object: map[string]interface{}{ func TestDepSort(t *testing.T) {
"apiVersion": apiVersion, newObj := func(apiVersion, kind string) *runtime.Unstructured {
"kind": kind, return &runtime.Unstructured{
}, Object: map[string]interface{}{
"apiVersion": apiVersion,
"kind": kind,
},
}
} }
}
func TestSort(t *testing.T) {
objs := []*runtime.Unstructured{ objs := []*runtime.Unstructured{
newObj("extensions/v1beta1", "Deployment"), newObj("extensions/v1beta1", "Deployment"),
newObj("v1", "ConfigMap"), newObj("v1", "ConfigMap"),
...@@ -23,7 +27,7 @@ func TestSort(t *testing.T) { ...@@ -23,7 +27,7 @@ func TestSort(t *testing.T) {
newObj("v1", "Service"), newObj("v1", "Service"),
} }
SortDepFirst(objs) sort.Sort(DependencyOrder(objs))
if objs[0].GetKind() != "Namespace" { if objs[0].GetKind() != "Namespace" {
t.Error("Namespace should be sorted first") t.Error("Namespace should be sorted first")
...@@ -32,3 +36,35 @@ func TestSort(t *testing.T) { ...@@ -32,3 +36,35 @@ func TestSort(t *testing.T) {
t.Error("Deployment should be sorted after other objects") 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)
}
}
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