diff --git a/cmd/diff.go b/cmd/diff.go
index f719bfb119630cd0c0b63f3b41beac78771d39ff..bddf771ec2d81156809aac52ea7fb3215dac71d2 100644
--- a/cmd/diff.go
+++ b/cmd/diff.go
@@ -18,8 +18,14 @@ package cmd
 import (
 	"fmt"
 	"os"
+	"strings"
+
+	"k8s.io/client-go/discovery"
+	"k8s.io/client-go/dynamic"
 
 	"github.com/spf13/cobra"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/client-go/tools/clientcmd"
 
 	"github.com/ksonnet/ksonnet/metadata"
 	"github.com/ksonnet/ksonnet/pkg/kubecfg"
@@ -29,29 +35,20 @@ const flagDiffStrategy = "diff-strategy"
 
 func init() {
 	addEnvCmdFlags(diffCmd)
-	bindClientGoFlags(diffCmd)
 	bindJsonnetFlags(diffCmd)
 	diffCmd.PersistentFlags().String(flagDiffStrategy, "all", "Diff strategy, all or subset.")
 	RootCmd.AddCommand(diffCmd)
 }
 
 var diffCmd = &cobra.Command{
-	Use:   "diff [env-name] [-f <file-or-dir>]",
-	Short: "Display differences between server and local config",
+	Use:   "diff [<env1> [<env2>]] [-f <file-or-dir>]",
+	Short: "Display differences between server and local config, or server and server config",
 	RunE: func(cmd *cobra.Command, args []string) error {
-		if len(args) > 1 {
-			return fmt.Errorf("'diff' takes at most a single argument, that is the name of the environment")
+		if len(args) > 2 {
+			return fmt.Errorf("'diff' takes at most two arguments, that are the name of the environments")
 		}
 
 		flags := cmd.Flags()
-		var err error
-
-		c := kubecfg.DiffCmd{}
-
-		c.DiffStrategy, err = flags.GetString(flagDiffStrategy)
-		if err != nil {
-			return err
-		}
 
 		cwd, err := os.Getwd()
 		if err != nil {
@@ -59,36 +56,55 @@ var diffCmd = &cobra.Command{
 		}
 		wd := metadata.AbsPath(cwd)
 
-		envSpec, err := parseEnvCmd(cmd, args)
+		files, err := flags.GetStringArray(flagFile)
 		if err != nil {
 			return err
 		}
 
-		c.ClientPool, c.Discovery, err = restClientPool(cmd, envSpec.env)
-		if err != nil {
-			return err
+		var env1 *string
+		if len(args) > 0 {
+			env1 = &args[0]
+		}
+
+		var env2 *string
+		if len(args) > 1 {
+			env2 = &args[1]
 		}
 
-		c.Namespace, err = namespace()
+		diffStrategy, err := flags.GetString(flagDiffStrategy)
 		if err != nil {
 			return err
 		}
 
-		objs, err := expandEnvCmdObjs(cmd, envSpec, wd)
+		c, err := initDiffCmd(cmd, wd, env1, env2, files, diffStrategy)
 		if err != nil {
 			return err
 		}
 
-		return c.Run(objs, cmd.OutOrStdout())
+		return c.Run(cmd.OutOrStdout())
 	},
-	Long: `Display differences between server and local configuration.
+	Long: `Display differences between server and local configuration, or server and server
+configurations.
 
 ksonnet applications are accepted, as well as normal JSON, YAML, and Jsonnet
 files.`,
-	Example: `  # Show diff between resources described in a local ksonnet application and
-  # the cluster referenced by the 'dev' environment. Can be used in any
-  # subdirectory of the application.
-  ks diff dev
+	Example: `  # Show diff between resources described in a the local 'dev' environment
+  # specified by the ksonnet application and the remote cluster referenced by
+  # the same 'dev' environment. Can be used in any subdirectory of the application.
+  ksonnet diff dev
+
+  # Show diff between resources at remote clusters. This requires ksonnet
+  # application defined environments. Diff between the cluster defined at the
+  # 'us-west/dev' environment, and the cluster defined at the 'us-west/prod'
+  # environment. Can be used in any subdirectory of the application.
+  ksonnet diff remote:us-west/dev remote:us-west/prod
+
+  # Show diff between resources at a remote and a local cluster. This requires
+  # ksonnet application defined environments. Diff between the cluster defined
+  # at the 'us-west/dev' environment, and the cluster defined at the
+  # 'us-west/prod' environment. Can be used in any subdirectory of the
+  # application.
+  ksonnet diff local:us-west/dev remote:us-west/prod
 
   # Show diff between resources described in a YAML file and the cluster
   # referenced in '$KUBECONFIG'.
@@ -102,3 +118,193 @@ files.`,
   # referred to by './kubeconfig'.
   ks diff --kubeconfig=./kubeconfig -f ./pod.yaml`,
 }
+
+func initDiffCmd(cmd *cobra.Command, wd metadata.AbsPath, envFq1, envFq2 *string, files []string, diffStrategy string) (kubecfg.DiffCmd, error) {
+	const (
+		remote = "remote"
+		local  = "local"
+	)
+
+	if envFq2 == nil {
+		return initDiffSingleEnv(*envFq1, diffStrategy, files, cmd, wd)
+	}
+
+	// expect envs to be of the format local:myenv or remote:myenv
+	env1 := strings.SplitN(*envFq1, ":", 2)
+	env2 := strings.SplitN(*envFq2, ":", 2)
+
+	// validation
+	if len(env1) < 2 || len(env2) < 2 || (env1[0] != local && env1[0] != remote) || (env2[0] != local && env2[0] != remote) {
+		return nil, fmt.Errorf("<env> must be prefaced by %s: or %s:, ex: %s:us-west/prod", local, remote, remote)
+	}
+	if len(files) > 0 {
+		return nil, fmt.Errorf("'-f' is not currently supported for multiple environments")
+	}
+
+	manager, err := metadata.Find(wd)
+	if err != nil {
+		return nil, err
+	}
+	componentPaths, err := manager.ComponentPaths()
+	if err != nil {
+		return nil, err
+	}
+	baseObj := constructBaseObj(componentPaths)
+
+	if env1[0] == local && env2[0] == local {
+		return initDiffLocalCmd(env1[1], env2[1], diffStrategy, baseObj, cmd, manager)
+	}
+
+	if env1[0] == remote && env2[0] == remote {
+		return initDiffRemotesCmd(env1[1], env2[1], diffStrategy, baseObj, cmd, manager)
+	}
+
+	localEnv := env1[1]
+	remoteEnv := env2[1]
+	if env1[0] == remote {
+		localEnv = env2[1]
+		remoteEnv = env1[1]
+	}
+	return initDiffRemoteCmd(localEnv, remoteEnv, diffStrategy, baseObj, cmd, manager)
+}
+
+// initDiffSingleEnv sets up configurations for diffing using one environment
+func initDiffSingleEnv(env, diffStrategy string, files []string, cmd *cobra.Command, wd metadata.AbsPath) (kubecfg.DiffCmd, error) {
+	c := kubecfg.DiffRemoteCmd{}
+	c.DiffStrategy = diffStrategy
+	c.Client = &kubecfg.Client{}
+	var err error
+
+	if strings.HasPrefix(env, "remote:") || strings.HasPrefix(env, "local:") {
+		return nil, fmt.Errorf("single <env> argument with prefix 'local:' or 'remote:' not allowed")
+	}
+
+	envSpec := &envSpec{env: &env, files: files}
+	c.Client.APIObjects, err = expandEnvCmdObjs(cmd, envSpec, wd)
+	if err != nil {
+		return nil, err
+	}
+
+	c.Client.ClientPool, c.Client.Discovery, err = restClientPool(cmd, envSpec.env)
+	if err != nil {
+		return nil, err
+	}
+
+	c.Client.Namespace, err = namespace()
+	if err != nil {
+		return nil, err
+	}
+
+	return &c, nil
+}
+
+// initDiffLocalCmd sets up configurations for diffing between two sets of expanded Kubernetes objects locally
+func initDiffLocalCmd(env1, env2, diffStrategy, baseObj string, cmd *cobra.Command, m metadata.Manager) (kubecfg.DiffCmd, error) {
+	c := kubecfg.DiffLocalCmd{}
+	c.DiffStrategy = diffStrategy
+	var err error
+
+	c.Env1 = &kubecfg.LocalEnv{}
+	c.Env1.Name = env1
+	c.Env1.APIObjects, err = expandEnvObjs(cmd, c.Env1.Name, baseObj, m)
+	if err != nil {
+		return nil, err
+	}
+
+	c.Env2 = &kubecfg.LocalEnv{}
+	c.Env2.Name = env2
+	c.Env2.APIObjects, err = expandEnvObjs(cmd, c.Env2.Name, baseObj, m)
+	if err != nil {
+		return nil, err
+	}
+
+	return &c, nil
+}
+
+// initDiffRemotesCmd sets up configurations for diffing between objects on two remote clusters
+func initDiffRemotesCmd(env1, env2, diffStrategy, baseObj string, cmd *cobra.Command, m metadata.Manager) (kubecfg.DiffCmd, error) {
+	c := kubecfg.DiffRemotesCmd{}
+	c.DiffStrategy = diffStrategy
+
+	c.ClientA = &kubecfg.Client{}
+	c.ClientB = &kubecfg.Client{}
+
+	c.ClientA.Name = env1
+	c.ClientB.Name = env2
+
+	var err error
+	c.ClientA.APIObjects, err = expandEnvObjs(cmd, c.ClientA.Name, baseObj, m)
+	if err != nil {
+		return nil, err
+	}
+	c.ClientB.APIObjects, err = expandEnvObjs(cmd, c.ClientB.Name, baseObj, m)
+	if err != nil {
+		return nil, err
+	}
+
+	c.ClientA.ClientPool, c.ClientA.Discovery, c.ClientA.Namespace, err = setupClientConfig(&c.ClientA.Name, cmd)
+	if err != nil {
+		return nil, err
+	}
+	c.ClientB.ClientPool, c.ClientB.Discovery, c.ClientB.Namespace, err = setupClientConfig(&c.ClientB.Name, cmd)
+	if err != nil {
+		return nil, err
+	}
+
+	return &c, nil
+}
+
+// initDiffRemoteCmd sets up configurations for diffing between local objects and objects on a remote cluster
+func initDiffRemoteCmd(localEnv, remoteEnv, diffStrategy, baseObj string, cmd *cobra.Command, m metadata.Manager) (kubecfg.DiffCmd, error) {
+	c := kubecfg.DiffRemoteCmd{}
+	c.DiffStrategy = diffStrategy
+	c.Client = &kubecfg.Client{}
+
+	var err error
+	c.Client.APIObjects, err = expandEnvObjs(cmd, localEnv, baseObj, m)
+	if err != nil {
+		return nil, err
+	}
+
+	c.Client.ClientPool, c.Client.Discovery, c.Client.Namespace, err = setupClientConfig(&remoteEnv, cmd)
+	if err != nil {
+		return nil, err
+	}
+
+	return &c, nil
+}
+
+func setupClientConfig(env *string, cmd *cobra.Command) (dynamic.ClientPool, discovery.DiscoveryInterface, string, error) {
+	overrides := clientcmd.ConfigOverrides{}
+	loadingRules := *clientcmd.NewDefaultClientConfigLoadingRules()
+	loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
+	config := clientcmd.NewInteractiveDeferredLoadingClientConfig(&loadingRules, &overrides, os.Stdin)
+
+	clientPool, discovery, err := restClient(cmd, env, config, overrides)
+	if err != nil {
+		return nil, nil, "", err
+	}
+
+	namespace, err := namespaceFor(config, overrides)
+	if err != nil {
+		return nil, nil, "", err
+	}
+
+	return clientPool, discovery, namespace, nil
+}
+
+// expandEnvObjs finds and expands templates for an environment
+func expandEnvObjs(cmd *cobra.Command, env, baseObj string, manager metadata.Manager) ([]*unstructured.Unstructured, error) {
+	expander, err := newExpander(cmd)
+	if err != nil {
+		return nil, err
+	}
+
+	libPath, envLibPath, envComponentPath := manager.LibPaths(env)
+	expander.FlagJpath = append([]string{string(libPath), string(envLibPath)}, expander.FlagJpath...)
+	expander.ExtCodes = append([]string{baseObj}, expander.ExtCodes...)
+
+	envFiles := []string{string(envComponentPath)}
+
+	return expander.Expand(envFiles)
+}
diff --git a/cmd/root.go b/cmd/root.go
index 7cea5a3c56bbf4b781b74a1e7dd85c4d4deb6c83..e738adc3a93f7cc60d3bcc56d0e7608cf6501443 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -121,6 +121,10 @@ var RootCmd = &cobra.Command{
 // clientConfig.Namespace() is broken in client-go 3.0:
 // namespace in config erroneously overrides explicit --namespace
 func namespace() (string, error) {
+	return namespaceFor(clientConfig, overrides)
+}
+
+func namespaceFor(c clientcmd.ClientConfig, overrides clientcmd.ConfigOverrides) (string, error) {
 	if overrides.Context.Namespace != "" {
 		return overrides.Context.Namespace, nil
 	}
@@ -256,15 +260,15 @@ func dumpJSON(v interface{}) string {
 	return string(buf.Bytes())
 }
 
-func restClientPool(cmd *cobra.Command, envName *string) (dynamic.ClientPool, discovery.DiscoveryInterface, error) {
+func restClient(cmd *cobra.Command, envName *string, config clientcmd.ClientConfig, overrides clientcmd.ConfigOverrides) (dynamic.ClientPool, discovery.DiscoveryInterface, error) {
 	if envName != nil {
-		err := overrideCluster(*envName)
+		err := overrideCluster(*envName, config, overrides)
 		if err != nil {
 			return nil, nil, err
 		}
 	}
 
-	conf, err := clientConfig.ClientConfig()
+	conf, err := config.ClientConfig()
 	if err != nil {
 		return nil, nil, err
 	}
@@ -282,6 +286,10 @@ func restClientPool(cmd *cobra.Command, envName *string) (dynamic.ClientPool, di
 	return pool, discoCache, nil
 }
 
+func restClientPool(cmd *cobra.Command, envName *string) (dynamic.ClientPool, discovery.DiscoveryInterface, error) {
+	return restClient(cmd, envName, clientConfig, overrides)
+}
+
 type envSpec struct {
 	env   *string
 	files []string
@@ -317,9 +325,8 @@ func parseEnvCmd(cmd *cobra.Command, args []string) (*envSpec, error) {
 //
 // If the environment URI the user is attempting to deploy to is not the current
 // kubeconfig context, we must manually override the client-go --cluster flag
-// to ensure we are deploying to the correct cluster. The same logic applies
-// for overwriting the --namespace flag.
-func overrideCluster(envName string) error {
+// to ensure we are deploying to the correct cluster.
+func overrideCluster(envName string, clientConfig clientcmd.ClientConfig, overrides clientcmd.ConfigOverrides) error {
 	cwd, err := os.Getwd()
 	if err != nil {
 		return err
@@ -361,7 +368,7 @@ func overrideCluster(envName string) error {
 		return nil
 	}
 
-	return fmt.Errorf("Attempting to deploy to environment '%s' at %s, but there are no clusters with that URI", envName, env.URI)
+	return fmt.Errorf("Attempting to operate on environment '%s' at %s, but there are no clusters with that URI", envName, env.URI)
 }
 
 // expandEnvCmdObjs finds and expands templates for the family of commands of
@@ -403,8 +410,9 @@ func expandEnvCmdObjs(cmd *cobra.Command, envSpec *envSpec, cwd metadata.AbsPath
 			if err != nil {
 				return nil, err
 			}
-			baseObjExtCode := fmt.Sprintf("%s=%s", metadata.ComponentsExtCodeKey, constructBaseObj(componentPaths))
-			expander.ExtCodes = append([]string{baseObjExtCode}, expander.ExtCodes...)
+
+			baseObj := constructBaseObj(componentPaths)
+			expander.ExtCodes = append([]string{baseObj}, expander.ExtCodes...)
 			fileNames = []string{string(envComponentPath)}
 		}
 	}
@@ -435,5 +443,5 @@ func constructBaseObj(paths []string) string {
 		fmt.Fprintf(&obj, "  %s: import \"%s\",\n", name, p)
 	}
 	obj.WriteString("}\n")
-	return obj.String()
+	return fmt.Sprintf("%s=%s", metadata.ComponentsExtCodeKey, obj.String())
 }
diff --git a/cmd/root_test.go b/cmd/root_test.go
index 3ee755b64e9abaaccd6181f13499570b2fecdaab..2bab864d54004a5615573529b4335a3fb993488e 100644
--- a/cmd/root_test.go
+++ b/cmd/root_test.go
@@ -16,6 +16,7 @@
 package cmd
 
 import (
+	"fmt"
 	"testing"
 )
 
@@ -68,7 +69,7 @@ func TestConstructBaseObj(t *testing.T) {
 
 	for _, s := range tests {
 		res := constructBaseObj(s.inputPaths)
-		if res != s.expected {
+		if res != fmt.Sprintf("__ksonnet/components=%s", s.expected) {
 			t.Errorf("Wrong object constructed\n  expected: %v\n  got: %v", s.expected, res)
 		}
 	}
diff --git a/pkg/kubecfg/apply.go b/pkg/kubecfg/apply.go
index 4f39eedb59629020ffa9d1fa1b4917e94191da2d..0fb8a6596ddf63843ed68007b8b1f9b97c0a429a 100644
--- a/pkg/kubecfg/apply.go
+++ b/pkg/kubecfg/apply.go
@@ -13,7 +13,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/apimachinery/pkg/util/diff"
+	kdiff "k8s.io/apimachinery/pkg/util/diff"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/discovery"
 	"k8s.io/client-go/dynamic"
@@ -100,7 +100,7 @@ func (c ApplyCmd) Run(apiObjects []*unstructured.Unstructured, wd metadata.AbsPa
 			return fmt.Errorf("Error updating %s: %s", desc, err)
 		}
 
-		log.Debug("Updated object: ", diff.ObjectDiff(obj, newobj))
+		log.Debug("Updated object: ", kdiff.ObjectDiff(obj, newobj))
 
 		// Some objects appear under multiple kinds
 		// (eg: Deployment is both extensions/v1beta1
diff --git a/pkg/kubecfg/diff.go b/pkg/kubecfg/diff.go
index 59ebb2b8bec7ee5eae1e430d8a6169b2c38c6960..e7b2c119fb5bdf7904089a770cdefceea5723e60 100644
--- a/pkg/kubecfg/diff.go
+++ b/pkg/kubecfg/diff.go
@@ -35,70 +35,200 @@ import (
 
 var ErrDiffFound = fmt.Errorf("Differences found.")
 
-// DiffCmd represents the diff subcommand
-type DiffCmd struct {
+// DiffCmd is an interface containing a set of functions that allow diffing
+// between two sets of data containing Kubernetes resouces.
+type DiffCmd interface {
+	Run(out io.Writer) error
+}
+
+// Diff is a base representation of the `diff` functionality.
+type Diff struct {
+	DiffStrategy string
+}
+
+// Client holds the necessary information to connect with a remote Kubernetes
+// cluster.
+type Client struct {
 	ClientPool dynamic.ClientPool
 	Discovery  discovery.DiscoveryInterface
 	Namespace  string
+	// Name of the remote client to identify changes against. This field is Optional.
+	Name string
+	// APIObjects are the Kubernetes objects being diffed against
+	APIObjects []*unstructured.Unstructured
+}
 
-	DiffStrategy string
+// LocalEnv holds the local Kubernetes objects for an environment and relevant
+// environment details, such as, environment name
+type LocalEnv struct {
+	Name       string
+	APIObjects []*unstructured.Unstructured
+}
+
+// ---------------------------------------------------------------------------
+
+// DiffRemoteCmd extends DiffCmd and is meant to represent diffing between some
+// set of Kubernetes objects and the Kubernetes objects located on a remote
+// client.
+type DiffRemoteCmd struct {
+	Diff
+	Client *Client
+}
+
+func (c *DiffRemoteCmd) Run(out io.Writer) error {
+	const (
+		local  = "config"
+		remote = "live"
+	)
+
+	_, liveObjs, err := getLiveObjs(c.Client)
+	if err != nil {
+		return err
+	}
+
+	return diffAll(c.Client.APIObjects, liveObjs, local, remote, c.DiffStrategy, &c.Client.Discovery, true, out)
+}
+
+// ---------------------------------------------------------------------------
+
+// DiffLocalCmd extends DiffCmd and is meant to represent diffing between two
+// sets of Kubernetes objects.
+type DiffLocalCmd struct {
+	Diff
+	Env1 *LocalEnv
+	Env2 *LocalEnv
 }
 
-func (c DiffCmd) Run(apiObjects []*unstructured.Unstructured, out io.Writer) error {
-	sort.Sort(utils.AlphabeticalOrder(apiObjects))
+func (c *DiffLocalCmd) Run(out io.Writer) error {
+	m := map[string]*unstructured.Unstructured{}
+	for _, b := range c.Env2.APIObjects {
+		m[hash(nil, b, true)] = b
+	}
+
+	return diffAll(c.Env1.APIObjects, m, c.Env1.Name, c.Env2.Name, c.DiffStrategy, nil, true, out)
+}
+
+// ---------------------------------------------------------------------------
+
+// DiffRemotesCmd extends DiffCmd and is meant to represent diffing between the
+// Kubernetes objects on two remote clients.
+type DiffRemotesCmd struct {
+	Diff
+	ClientA *Client
+	ClientB *Client
+}
+
+func (c *DiffRemotesCmd) Run(out io.Writer) error {
+	liveObjsA, _, err := getLiveObjs(c.ClientA)
+	if err != nil {
+		return err
+	}
+
+	_, liveObjsB, err := getLiveObjs(c.ClientB)
+	if err != nil {
+		return err
+	}
+
+	return diffAll(liveObjsA, liveObjsB, c.ClientA.Name, c.ClientB.Name, c.DiffStrategy, &c.ClientA.Discovery, false, out)
+}
+
+// ---------------------------------------------------------------------------
+
+func diffAll(a []*unstructured.Unstructured, b map[string]*unstructured.Unstructured, aName, bName, strategy string,
+	discovery *discovery.DiscoveryInterface, fqName bool, out io.Writer) error {
+
+	sort.Sort(utils.AlphabeticalOrder(a))
 
 	diffFound := false
-	for _, obj := range apiObjects {
-		desc := fmt.Sprintf("%s %s", utils.ResourceNameFor(c.Discovery, obj), utils.FqName(obj))
-		log.Debugf("Fetching ", desc)
+	for _, o := range a {
+		desc := hash(discovery, o, fqName)
+		var bObj map[string]interface{}
+		if b[desc] != nil {
+			bObj = b[desc].Object
+		}
 
-		client, err := utils.ClientForResource(c.ClientPool, c.Discovery, obj, c.Namespace)
+		var err error
+		log.Debugf("Diffing %s\nA: %s\nB: %s\n", desc, o.Object, bObj)
+		diffFound, err = diff(desc, aName, bName, strategy, o.Object, bObj, out)
 		if err != nil {
 			return err
 		}
+	}
+
+	if diffFound {
+		return ErrDiffFound
+	}
+	return nil
+}
+
+func diff(desc, aName, bName, strategy string, aObj, bObj map[string]interface{}, out io.Writer) (diffFound bool, err error) {
+	fmt.Fprintln(out, "---")
+	fmt.Fprintf(out, "- %s %s\n+ %s %s\n", bName, desc, aName, desc)
+	if bObj == nil {
+		fmt.Fprintf(out, "%s doesn't exist on %s\n", desc, bName)
+		return true, nil
+	}
+
+	if strategy == "subset" {
+		bObj = removeMapFields(aObj, bObj)
+	}
+	diff := gojsondiff.New().CompareObjects(bObj, aObj)
+
+	if diff.Modified() {
+		fcfg := formatter.AsciiFormatterConfig{
+			Coloring: istty(out),
+		}
+		formatter := formatter.NewAsciiFormatter(bObj, fcfg)
+		text, err := formatter.Format(diff)
+		if err != nil {
+			return true, err
+		}
+		fmt.Fprintf(out, "%s", text)
+		return true, nil
+	}
+
+	fmt.Fprintf(out, "%s unchanged\n", desc)
+	return false, nil
+}
+
+// hash serves as an identifier for the Kubernetes resource.
+func hash(discovery *discovery.DiscoveryInterface, obj *unstructured.Unstructured, fqName bool) string {
+	name := obj.GetName()
+	if fqName {
+		name = utils.FqName(obj)
+	}
+	if discovery == nil {
+		return fmt.Sprintf("%s %s", utils.GroupVersionKindFor(obj), name)
+	}
+	return fmt.Sprintf("%s %s", utils.ResourceNameFor(*discovery, obj), name)
+}
+
+func getLiveObjs(client *Client) ([]*unstructured.Unstructured, map[string]*unstructured.Unstructured, error) {
+	var liveObjs []*unstructured.Unstructured
+	liveObjsMap := map[string]*unstructured.Unstructured{}
+
+	for _, obj := range client.APIObjects {
+		desc := hash(&client.Discovery, obj, true)
+		log.Debugf("Fetching %s", desc)
+
+		client, err := utils.ClientForResource(client.ClientPool, client.Discovery, obj, client.Namespace)
+		if err != nil {
+			return nil, nil, err
+		}
 
 		liveObj, err := client.Get(obj.GetName())
 		if err != nil && errors.IsNotFound(err) {
 			log.Debugf("%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\n", desc, desc)
-		if liveObj == nil {
-			fmt.Fprintf(out, "%s doesn't exist on server\n", desc)
-			diffFound = true
 			continue
+		} else if err != nil {
+			return nil, nil, fmt.Errorf("Error fetching %s: %v", desc, err)
 		}
 
-		liveObjObject := liveObj.Object
-		if c.DiffStrategy == "subset" {
-			liveObjObject = removeMapFields(obj.Object, liveObjObject)
-		}
-		diff := gojsondiff.New().CompareObjects(liveObjObject, obj.Object)
-
-		if diff.Modified() {
-			diffFound = true
-			fcfg := formatter.AsciiFormatterConfig{
-				Coloring: istty(out),
-			}
-			formatter := formatter.NewAsciiFormatter(liveObjObject, fcfg)
-			text, err := formatter.Format(diff)
-			if err != nil {
-				return err
-			}
-			fmt.Fprintf(out, "%s", text)
-		} else {
-			fmt.Fprintf(out, "%s unchanged\n", desc)
-		}
+		liveObjs = append(liveObjs, liveObj)
+		liveObjsMap[desc] = liveObj
 	}
 
-	if diffFound {
-		return ErrDiffFound
-	}
-	return nil
+	return liveObjs, liveObjsMap, nil
 }
 
 func removeFields(config, live interface{}) interface{} {
diff --git a/utils/meta.go b/utils/meta.go
index 110aa7693fa5249178d6ce45fd3045a13f88c169..41c827781db14d716745b1163943f5f05c425d80 100644
--- a/utils/meta.go
+++ b/utils/meta.go
@@ -95,6 +95,12 @@ func ResourceNameFor(disco discovery.ServerResourcesInterface, o runtime.Object)
 	return strings.ToLower(gvk.Kind)
 }
 
+// GroupVersionKindFor returns a lowercased kind for an Kubernete's object
+func GroupVersionKindFor(o runtime.Object) string {
+	gvk := o.GetObjectKind().GroupVersionKind()
+	return strings.ToLower(gvk.Kind)
+}
+
 // FqName returns "namespace.name"
 func FqName(o metav1.Object) string {
 	if o.GetNamespace() == "" {