func TestRedactedSecretData(t *testing.T) {
	configUn := unmarshalFile("testdata/wordpress-config.json")
	liveUn := unmarshalFile("testdata/wordpress-live.json")
	configData := configUn.Object["data"].(map[string]interface{})
	liveData := liveUn.Object["data"].(map[string]interface{})
	configData["wordpress-password"] = "++++++++"
	configData["smtp-password"] = "++++++++"
	liveData["wordpress-password"] = "++++++++++++"
	liveData["smtp-password"] = "++++++++++++"
	dr := diff(t, configUn, liveUn, diffOptionsForTest()...)
	if !assert.True(t, dr.Modified) {
		ascii, err := printDiff(dr)
		require.NoError(t, err)
		t.Log(ascii)
	}
}
TestRedactedSecretData tests we are able to perform diff on redacted secret data, which has
invalid characters (*) for the the data byte array field.
package diff
import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/equality"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/klog/v2/klogr"
	"sigs.k8s.io/yaml"
)
func printDiff(result *DiffResult) (string, error) {
	var live unstructured.Unstructured
	if err := json.Unmarshal(result.NormalizedLive, &live); err != nil {
		return "", err
	}
	var target unstructured.Unstructured
	if err := json.Unmarshal(result.PredictedLive, &target); err != nil {
		return "", err
	}
	out, _ := printDiffInternal("diff", &live, &target)
	return string(out), nil
}
func printDiffInternal(name string, live *unstructured.Unstructured, target *unstructured.Unstructured) ([]byte, error) {
	tempDir, err := ioutil.TempDir("", "argocd-diff")
	if err != nil {
		return nil, err
	}
	targetFile := filepath.Join(tempDir, name)
	var targetData []byte
	if target != nil {
		targetData, err = yaml.Marshal(target)
		if err != nil {
			return nil, err
		}
	}
	err = ioutil.WriteFile(targetFile, targetData, 0644)
	if err != nil {
		return nil, err
	}
	liveFile := filepath.Join(tempDir, fmt.Sprintf("%s-live.yaml", name))
	liveData := []byte("")
	if live != nil {
		liveData, err = yaml.Marshal(live)
		if err != nil {
			return nil, err
		}
	}
	err = ioutil.WriteFile(liveFile, liveData, 0644)
	if err != nil {
		return nil, err
	}
	cmd := exec.Command("diff", liveFile, targetFile)
	return cmd.Output()
}
func toUnstructured(obj interface{}) (*unstructured.Unstructured, error) {
	uObj, err := runtime.NewTestUnstructuredConverter(equality.Semantic).ToUnstructured(obj)
	if err != nil {
		return nil, err
	}
	return &unstructured.Unstructured{Object: uObj}, nil
}
func mustToUnstructured(obj interface{}) *unstructured.Unstructured {
	un, err := toUnstructured(obj)
	if err != nil {
		panic(err)
	}
	return un
}
func unmarshalFile(path string) *unstructured.Unstructured {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}
	var un unstructured.Unstructured
	err = json.Unmarshal(data, &un.Object)
	if err != nil {
		panic(err)
	}
	return &un
}
func newDeployment() *appsv1.Deployment {
	var two int32 = 2
	return &appsv1.Deployment{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "apps/v1beta1",
			Kind:       "Deployment",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      "demo",
			Namespace: "test",
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: &two,
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"app": "demo",
				},
			},
			Template: v1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"app": "demo",
					},
				},
				Spec: v1.PodSpec{
					Containers: []v1.Container{
						{
							Name:  "demo",
							Image: "gcr.io/kuar-demo/kuard-amd64:1",
							Ports: []v1.ContainerPort{
								{
									ContainerPort: 80,
								},
							},
						},
					},
				},
			},
		},
	}
}
func diff(t *testing.T, config, live *unstructured.Unstructured, options ...Option) *DiffResult {
	res, err := Diff(config, live, options...)
	assert.NoError(t, err)
	return res
}
func TestDiff(t *testing.T) {
	leftDep := newDeployment()
	leftUn := mustToUnstructured(leftDep)
	diffRes := diff(t, leftUn, leftUn, diffOptionsForTest()...)
	assert.False(t, diffRes.Modified)
	ascii, err := printDiff(diffRes)
	require.NoError(t, err)
	if ascii != "" {
		t.Log(ascii)
	}
}
func TestDiff_KnownTypeInvalidValue(t *testing.T) {
	leftDep := newDeployment()
	leftUn := mustToUnstructured(leftDep)
	if !assert.NoError(t, unstructured.SetNestedField(leftUn.Object, "badValue", "spec", "revisionHistoryLimit")) {
		return
	}
	t.Run("NoDifference", func(t *testing.T) {
		diffRes := diff(t, leftUn, leftUn, diffOptionsForTest()...)
		assert.False(t, diffRes.Modified)
		ascii, err := printDiff(diffRes)
		require.NoError(t, err)
		if ascii != "" {
			t.Log(ascii)
		}
	})
	t.Run("HasDifference", func(t *testing.T) {
		rightUn := leftUn.DeepCopy()
		if !assert.NoError(t, unstructured.SetNestedField(rightUn.Object, "3", "spec", "revisionHistoryLimit")) {
			return
		}
		diffRes := diff(t, leftUn, rightUn, diffOptionsForTest()...)
		assert.True(t, diffRes.Modified)
	})
}
func TestDiffWithNils(t *testing.T) {
	dep := newDeployment()
	resource := mustToUnstructured(dep)
	diffRes := diff(t, nil, resource, diffOptionsForTest()...)
	assert.False(t, diffRes.Modified)
	diffRes, err := TwoWayDiff(nil, resource)
	assert.NoError(t, err)
	assert.False(t, diffRes.Modified)
	diffRes = diff(t, resource, nil, diffOptionsForTest()...)
	assert.True(t, diffRes.Modified)
	diffRes, err = TwoWayDiff(resource, nil)
	assert.NoError(t, err)
	assert.True(t, diffRes.Modified)
}
func TestDiffNilFieldInLive(t *testing.T) {
	leftDep := newDeployment()
	rightDep := leftDep.DeepCopy()
	leftUn := mustToUnstructured(leftDep)
	rightUn := mustToUnstructured(rightDep)
	err := unstructured.SetNestedField(rightUn.Object, nil, "spec")
	require.NoError(t, err)
	diffRes := diff(t, leftUn, rightUn, diffOptionsForTest()...)
	assert.True(t, diffRes.Modified)
}
func TestDiffArraySame(t *testing.T) {
	leftDep := newDeployment()
	rightDep := leftDep.DeepCopy()
	leftUn := mustToUnstructured(leftDep)
	rightUn := mustToUnstructured(rightDep)
	left := []*unstructured.Unstructured{leftUn}
	right := []*unstructured.Unstructured{rightUn}
	diffResList, err := DiffArray(left, right, diffOptionsForTest()...)
	require.NoError(t, err)
	assert.False(t, diffResList.Modified)
}
func TestDiffArrayAdditions(t *testing.T) {
	leftDep := newDeployment()
	rightDep := leftDep.DeepCopy()
	rightDep.Status.Replicas = 1
	leftUn := mustToUnstructured(leftDep)
	rightUn := mustToUnstructured(rightDep)
	left := []*unstructured.Unstructured{leftUn}
	right := []*unstructured.Unstructured{rightUn}
	diffResList, err := DiffArray(left, right, diffOptionsForTest()...)
	require.NoError(t, err)
	assert.False(t, diffResList.Modified)
}
func TestDiffArrayModification(t *testing.T) {
	leftDep := newDeployment()
	rightDep := leftDep.DeepCopy()
	ten := int32(10)
	rightDep.Spec.Replicas = &ten
	leftUn := mustToUnstructured(leftDep)
	rightUn := mustToUnstructured(rightDep)
	left := []*unstructured.Unstructured{leftUn}
	right := []*unstructured.Unstructured{rightUn}
	diffResList, err := DiffArray(left, right, diffOptionsForTest()...)
	require.NoError(t, err)
	assert.True(t, diffResList.Modified)
}
func TestThreeWayDiff(t *testing.T) {
	configDep := newDeployment()
	configDep.ObjectMeta.Namespace = ""
	configDep.Annotations = map[string]string{
		"foo": "bar",
	}
	liveDep := configDep.DeepCopy()
	liveDep.SetNamespace("default")
	configUn := mustToUnstructured(configDep)
	liveUn := mustToUnstructured(liveDep)
	res := diff(t, configUn, liveUn, diffOptionsForTest()...)
	if !assert.False(t, res.Modified) {
		ascii, err := printDiff(res)
		require.NoError(t, err)
		t.Log(ascii)
	}
	configBytes, err := json.Marshal(configDep)
	require.NoError(t, err)
	liveDep.Annotations[v1.LastAppliedConfigAnnotation] = string(configBytes)
	configUn = mustToUnstructured(configDep)
	liveUn = mustToUnstructured(liveDep)
	res = diff(t, configUn, liveUn, diffOptionsForTest()...)
	if !assert.False(t, res.Modified) {
		ascii, err := printDiff(res)
		require.NoError(t, err)
		t.Log(ascii)
	}
	delete(configDep.Annotations, "foo")
	configUn = mustToUnstructured(configDep)
	liveUn = mustToUnstructured(liveDep)
	res = diff(t, configUn, liveUn, diffOptionsForTest()...)
	assert.True(t, res.Modified)
	delete(liveDep.Annotations, v1.LastAppliedConfigAnnotation)
	configUn = mustToUnstructured(configDep)
	liveUn = mustToUnstructured(liveDep)
	res = diff(t, configUn, liveUn, diffOptionsForTest()...)
	ascii, err := printDiff(res)
	require.NoError(t, err)
	if ascii != "" {
		t.Log(ascii)
	}
	assert.False(t, res.Modified)
}
var demoConfig = `
{
  "apiVersion": "v1",
  "kind": "ServiceAccount",
  "metadata": {
    "labels": {
      "app.kubernetes.io/instance": "argocd-demo"
    },
    "name": "argocd-application-controller"
  }
}
`
var demoLive = `
{
  "apiVersion": "v1",
  "kind": "ServiceAccount",
  "metadata": {
    "annotations": {
      "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"ServiceAccount\",\"metadata\":{\"annotations\":{},\"labels\":{\"app.kubernetes.io/instance\":\"argocd-demo\"},\"name\":\"argocd-application-controller\",\"namespace\":\"argocd-demo\"}}\n"
    },
    "creationTimestamp": "2018-04-16T22:08:57Z",
    "labels": {
      "app.kubernetes.io/instance": "argocd-demo"
    },
    "name": "argocd-application-controller",
    "namespace": "argocd-demo",
    "resourceVersion": "7584502",
    "selfLink": "/api/v1/namespaces/argocd-demo/serviceaccounts/argocd-application-controller",
    "uid": "c22bb2b4-41c2-11e8-978a-028445d52ec8"
  },
  "secrets": [
    {
      "name": "argocd-application-controller-token-kfxct"
    }
  ]
}
`
func TestThreeWayDiffExample1(t *testing.T) {
	var configUn, liveUn unstructured.Unstructured
	err := json.Unmarshal([]byte(demoConfig), &configUn.Object)
	require.NoError(t, err)
	err = json.Unmarshal([]byte(demoLive), &liveUn.Object)
	require.NoError(t, err)
	dr := diff(t, &configUn, &liveUn, diffOptionsForTest()...)
	assert.False(t, dr.Modified)
	ascii, err := printDiff(dr)
	require.NoError(t, err)
	if ascii != "" {
		t.Log(ascii)
	}
}
func TestDiffOptionIgnoreAggregateRoles(t *testing.T) {
	{
		configUn := unmarshalFile("testdata/aggr-clusterrole-config.json")
		liveUn := unmarshalFile("testdata/aggr-clusterrole-live.json")
		dr := diff(t, configUn, liveUn, IgnoreAggregatedRoles(true))
		assert.False(t, dr.Modified)
		ascii, err := printDiff(dr)
		require.NoError(t, err)
		t.Log(ascii)
	}
	{
		configUn := unmarshalFile("testdata/aggr-clusterrole-config.json")
		liveUn := unmarshalFile("testdata/aggr-clusterrole-live.json")
		dr := diff(t, configUn, liveUn, IgnoreAggregatedRoles(false))
		assert.True(t, dr.Modified)
		ascii, err := printDiff(dr)
		require.NoError(t, err)
		t.Log(ascii)
	}
}
func TestThreeWayDiffExample2(t *testing.T) {
	configUn := unmarshalFile("testdata/elasticsearch-config.json")
	liveUn := unmarshalFile("testdata/elasticsearch-live.json")
	dr := diff(t, configUn, liveUn, diffOptionsForTest()...)
	assert.False(t, dr.Modified)
	ascii, err := printDiff(dr)
	require.NoError(t, err)
	t.Log(ascii)
}
func TestThreeWayDiffExample3(t *testing.T) {
	configUn := unmarshalFile("testdata/deployment-config.json")
	liveUn := unmarshalFile("testdata/deployment-live.json")
	dr := diff(t, configUn, liveUn, diffOptionsForTest()...)
	assert.False(t, dr.Modified)
	ascii, err := printDiff(dr)
	require.NoError(t, err)
	if ascii != "" {
		t.Log(ascii)
	}
}
func TestThreeWayDiffExample4(t *testing.T) {
	configUn := unmarshalFile("testdata/mutatingwebhookconfig-config.json")
	liveUn := unmarshalFile("testdata/mutatingwebhookconfig-live.json")
	dr := diff(t, configUn, liveUn, diffOptionsForTest()...)
	assert.False(t, dr.Modified)
	ascii, err := printDiff(dr)
	require.NoError(t, err)
	if ascii != "" {
		t.Log(ascii)
	}
}
func TestThreeWayDiffExample2WithDifference(t *testing.T) {
	configUn := unmarshalFile("testdata/elasticsearch-config.json")
	liveUn := unmarshalFile("testdata/elasticsearch-live.json")
	labels := configUn.GetLabels()
	labels["foo"] = "bar"
	labels["chart"] = "elasticsearch-1.7.1"
	delete(labels, "release")
	configUn.SetLabels(labels)
	dr := diff(t, configUn, liveUn, diffOptionsForTest()...)
	assert.True(t, dr.Modified)
	ascii, err := printDiff(dr)
	require.NoError(t, err, ascii)
	t.Log(ascii)
	showsMissing := 0
	showsExtra := 0
	showsChanged := 0
	for _, line := range strings.Split(ascii, "\n") {
		if strings.HasPrefix(line, `>     foo: bar`) {
			showsMissing++
		}
		if strings.HasPrefix(line, `<     release: elasticsearch4`) {
			showsExtra++
		}
		if strings.HasPrefix(line, `>     chart: elasticsearch-1.7.1`) {
			showsChanged++
		}
		if strings.HasPrefix(line, `<     chart: elasticsearch-1.7.0`) {
			showsChanged++
		}
	}
	assert.Equal(t, 1, showsMissing)
	assert.Equal(t, 1, showsExtra)
	assert.Equal(t, 2, showsChanged)
}
func TestThreeWayDiffExplicitNamespace(t *testing.T) {
	configUn := unmarshalFile("testdata/spinnaker-sa-config.json")
	liveUn := unmarshalFile("testdata/spinnaker-sa-live.json")
	dr := diff(t, configUn, liveUn, diffOptionsForTest()...)
	assert.False(t, dr.Modified)
	ascii, err := printDiff(dr)
	require.NoError(t, err)
	t.Log(ascii)
}
func TestDiffResourceWithInvalidField(t *testing.T) {
	leftDep := `{
			"apiVersion": "v1",
			"kind": "ConfigMap",
			"metadata": {
			  "name": "invalid-cm"
			},
			"invalidKey": "asdf"
		  }`
	var leftUn unstructured.Unstructured
	err := json.Unmarshal([]byte(leftDep), &leftUn.Object)
	if err != nil {
		panic(err)
	}
	rightUn := leftUn.DeepCopy()
	unstructured.RemoveNestedField(rightUn.Object, "invalidKey")
	diffRes := diff(t, &leftUn, rightUn, diffOptionsForTest()...)
	assert.True(t, diffRes.Modified)
	ascii, err := printDiff(diffRes)
	assert.Nil(t, err)
	assert.True(t, strings.Contains(ascii, "invalidKey"))
	if ascii != "" {
		t.Log(ascii)
	}
}
func TestRemoveNamespaceAnnotation(t *testing.T) {
	obj := removeNamespaceAnnotation(&unstructured.Unstructured{Object: map[string]interface{}{
		"metadata": map[string]interface{}{
			"name":      "test",
			"namespace": "default",
		},
	}})
	assert.Equal(t, "", obj.GetNamespace())
	obj = removeNamespaceAnnotation(&unstructured.Unstructured{Object: map[string]interface{}{
		"metadata": map[string]interface{}{
			"name":        "test",
			"namespace":   "default",
			"annotations": make(map[string]interface{}),
		},
	}})
	assert.Equal(t, "", obj.GetNamespace())
	assert.Nil(t, obj.GetAnnotations())
}
const customObjConfig = `
apiVersion: foo.io/v1
kind: Foo
metadata:
  name: my-foo
  namespace: kube-system
spec:
  foo: bar
`
const customObjLive = `
apiVersion: foo.io/v1
kind: Foo
metadata:
  creationTimestamp: 2018-07-17 09:17:05 UTC
  name: my-foo
  resourceVersion: '10308211'
  selfLink: "/apis/rbac.authorization.k8s.io/v1/clusterroles/argocd-manager-role"
  uid: 2c3d5405-89a2-11e8-aff0-42010a8a0fc6
spec:
  foo: bar
`
func TestIgnoreNamespaceForClusterScopedResources(t *testing.T) {
	var configUn unstructured.Unstructured
	var liveUn unstructured.Unstructured
	err := yaml.Unmarshal([]byte(customObjLive), &liveUn)
	require.NoError(t, err)
	err = yaml.Unmarshal([]byte(customObjConfig), &configUn)
	require.NoError(t, err)
	dr := diff(t, &configUn, &liveUn, diffOptionsForTest()...)
	assert.False(t, dr.Modified)
}
const secretConfig = `
apiVersion: v1
kind: Secret
metadata:
  name: my-secret
type: Opaque
stringData:
  foo: bar
  bar: "1234"
data:
  baz: cXV4
`
const secretLive = `
apiVersion: v1
kind: Secret
metadata:
  creationTimestamp: 2018-11-19T11:30:40Z
  name: my-secret
  namespace: argocd
  resourceVersion: "25848035"
  selfLink: /api/v1/namespaces/argocd/secrets/my-secret
  uid: 8b4a2766-ebee-11e8-93c0-42010a8a0013
type: Opaque
data:
  foo: YmFy
  bar: MTIzNA==
  baz: cXV4
`
func TestSecretStringData(t *testing.T) {
	var err error
	var configUn unstructured.Unstructured
	err = yaml.Unmarshal([]byte(secretConfig), &configUn)
	require.NoError(t, err)
	var liveUn unstructured.Unstructured
	err = yaml.Unmarshal([]byte(secretLive), &liveUn)
	require.NoError(t, err)
	dr := diff(t, &configUn, &liveUn, diffOptionsForTest()...)
	if !assert.False(t, dr.Modified) {
		ascii, err := printDiff(dr)
		require.NoError(t, err)
		t.Log(ascii)
	}
}
const secretInvalidConfig = `
apiVersion: v1
kind: Secret
metadata:
  name: my-secret
type: Opaque
stringData:
  foo: 1234
`
const secretInvalidLive = `
apiVersion: v1
kind: Secret
metadata:
  creationTimestamp: 2018-11-19T11:30:40Z
  name: my-secret
  namespace: argocd
  resourceVersion: "25848035"
  selfLink: /api/v1/namespaces/argocd/secrets/my-secret
  uid: 8b4a2766-ebee-11e8-93c0-42010a8a0013
type: Opaque
data:
  foo: MTIzNA==
`
func TestInvalidSecretStringData(t *testing.T) {
	var err error
	var configUn unstructured.Unstructured
	err = yaml.Unmarshal([]byte(secretInvalidConfig), &configUn)
	require.NoError(t, err)
	var liveUn unstructured.Unstructured
	err = yaml.Unmarshal([]byte(secretInvalidLive), &liveUn)
	require.NoError(t, err)
	dr := diff(t, &configUn, nil, diffOptionsForTest()...)
	assert.True(t, dr.Modified)
}
func TestNullSecretData(t *testing.T) {
	configUn := unmarshalFile("testdata/wordpress-config.json")
	liveUn := unmarshalFile("testdata/wordpress-live.json")
	dr := diff(t, configUn, liveUn, diffOptionsForTest()...)
	if !assert.False(t, dr.Modified) {
		ascii, err := printDiff(dr)
		require.NoError(t, err)
		t.Log(ascii)
	}
}