github.com/argoproj/argo-cd/v3@v3.2.1/util/argo/managedfields/managed_fields_test.go (about)

     1  package managedfields_test
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/stretchr/testify/assert"
     7  	"github.com/stretchr/testify/require"
     8  	arv1 "k8s.io/api/admissionregistration/v1"
     9  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    10  	"k8s.io/apimachinery/pkg/runtime"
    11  	"sigs.k8s.io/yaml"
    12  
    13  	"github.com/argoproj/gitops-engine/pkg/utils/kube/scheme"
    14  
    15  	"github.com/argoproj/argo-cd/v3/util/argo/managedfields"
    16  	"github.com/argoproj/argo-cd/v3/util/argo/testdata"
    17  )
    18  
    19  func TestNormalize(t *testing.T) {
    20  	parser := scheme.StaticParser()
    21  	t.Run("will remove conflicting fields if managed by trusted managers", func(t *testing.T) {
    22  		// given
    23  		desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
    24  		liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
    25  		trustedManagers := []string{"kube-controller-manager", "revision-history-manager"}
    26  		pt := parser.Type("io.k8s.api.apps.v1.Deployment")
    27  
    28  		// when
    29  		liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers, &pt)
    30  
    31  		// then
    32  		require.NoError(t, err)
    33  		require.NotNil(t, liveResult)
    34  		require.NotNil(t, desiredResult)
    35  		desiredReplicas, ok, err := unstructured.NestedFloat64(desiredResult.Object, "spec", "replicas")
    36  		assert.False(t, ok)
    37  		require.NoError(t, err)
    38  		liveReplicas, ok, err := unstructured.NestedFloat64(liveResult.Object, "spec", "replicas")
    39  		assert.False(t, ok)
    40  		require.NoError(t, err)
    41  		assert.Zero(t, desiredReplicas)
    42  		assert.Zero(t, liveReplicas)
    43  		liveRevisionHistory, ok, err := unstructured.NestedFloat64(liveResult.Object, "spec", "revisionHistoryLimit")
    44  		assert.False(t, ok)
    45  		require.NoError(t, err)
    46  		desiredRevisionHistory, ok, err := unstructured.NestedFloat64(desiredResult.Object, "spec", "revisionHistoryLimit")
    47  		assert.False(t, ok)
    48  		require.NoError(t, err)
    49  		assert.Zero(t, desiredRevisionHistory)
    50  		assert.Zero(t, liveRevisionHistory)
    51  	})
    52  	t.Run("will keep conflicting fields if not from trusted manager", func(t *testing.T) {
    53  		// given
    54  		desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
    55  		liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
    56  		trustedManagers := []string{"another-manager"}
    57  		pt := parser.Type("io.k8s.api.apps.v1.Deployment")
    58  
    59  		// when
    60  		liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers, &pt)
    61  
    62  		// then
    63  		require.NoError(t, err)
    64  		validateNestedFloat64(t, float64(3), desiredResult, "spec", "replicas")
    65  		validateNestedFloat64(t, float64(1), desiredResult, "spec", "revisionHistoryLimit")
    66  		validateNestedFloat64(t, float64(2), liveResult, "spec", "replicas")
    67  		validateNestedFloat64(t, float64(3), liveResult, "spec", "revisionHistoryLimit")
    68  	})
    69  	t.Run("no-op if live state is nil", func(t *testing.T) {
    70  		// given
    71  		desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
    72  		trustedManagers := []string{"kube-controller-manager"}
    73  		pt := parser.Type("io.k8s.api.apps.v1.Deployment")
    74  
    75  		// when
    76  		liveResult, desiredResult, err := managedfields.Normalize(nil, desiredState, trustedManagers, &pt)
    77  
    78  		// then
    79  		require.NoError(t, err)
    80  		assert.Nil(t, liveResult)
    81  		assert.Nil(t, desiredResult)
    82  		validateNestedFloat64(t, float64(3), desiredState, "spec", "replicas")
    83  		validateNestedFloat64(t, float64(1), desiredState, "spec", "revisionHistoryLimit")
    84  	})
    85  	t.Run("no-op if desired state is nil", func(t *testing.T) {
    86  		// given
    87  		liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
    88  		trustedManagers := []string{"kube-controller-manager"}
    89  		pt := parser.Type("io.k8s.api.apps.v1.Deployment")
    90  
    91  		// when
    92  		liveResult, desiredResult, err := managedfields.Normalize(liveState, nil, trustedManagers, &pt)
    93  
    94  		// then
    95  		require.NoError(t, err)
    96  		assert.Nil(t, liveResult)
    97  		assert.Nil(t, desiredResult)
    98  		validateNestedFloat64(t, float64(2), liveState, "spec", "replicas")
    99  		validateNestedFloat64(t, float64(3), liveState, "spec", "revisionHistoryLimit")
   100  	})
   101  	t.Run("no-op if trusted manager list is empty", func(t *testing.T) {
   102  		// given
   103  		desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
   104  		liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
   105  		pt := parser.Type("io.k8s.api.apps.v1.Deployment")
   106  
   107  		// when
   108  		liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, []string{}, &pt)
   109  
   110  		// then
   111  		require.NoError(t, err)
   112  		assert.Nil(t, liveResult)
   113  		assert.Nil(t, desiredResult)
   114  		validateNestedFloat64(t, float64(3), desiredState, "spec", "replicas")
   115  		validateNestedFloat64(t, float64(1), desiredState, "spec", "revisionHistoryLimit")
   116  		validateNestedFloat64(t, float64(2), liveState, "spec", "replicas")
   117  		validateNestedFloat64(t, float64(3), liveState, "spec", "revisionHistoryLimit")
   118  	})
   119  	t.Run("will normalize successfully inside a list", func(t *testing.T) {
   120  		// given
   121  		desiredState := StrToUnstructured(testdata.DesiredValidatingWebhookYaml)
   122  		liveState := StrToUnstructured(testdata.LiveValidatingWebhookYaml)
   123  		trustedManagers := []string{"external-secrets"}
   124  		pt := parser.Type("io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration")
   125  
   126  		// when
   127  		liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers, &pt)
   128  
   129  		// then
   130  		require.NoError(t, err)
   131  		require.NotNil(t, liveResult)
   132  		require.NotNil(t, desiredResult)
   133  
   134  		var vwcLive arv1.ValidatingWebhookConfiguration
   135  		err = runtime.DefaultUnstructuredConverter.FromUnstructured(liveResult.Object, &vwcLive)
   136  		require.NoError(t, err)
   137  		assert.Len(t, vwcLive.Webhooks, 1)
   138  		assert.Empty(t, string(vwcLive.Webhooks[0].ClientConfig.CABundle))
   139  
   140  		var vwcConfig arv1.ValidatingWebhookConfiguration
   141  		err = runtime.DefaultUnstructuredConverter.FromUnstructured(desiredResult.Object, &vwcConfig)
   142  		require.NoError(t, err)
   143  		assert.Len(t, vwcConfig.Webhooks, 1)
   144  		assert.Empty(t, string(vwcConfig.Webhooks[0].ClientConfig.CABundle))
   145  	})
   146  	t.Run("does not fail if object fails validation schema", func(t *testing.T) {
   147  		desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
   148  		require.NoError(t, unstructured.SetNestedField(desiredState.Object, "spec", "hello", "world"))
   149  		liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
   150  
   151  		pt := parser.Type("io.k8s.api.apps.v1.Deployment")
   152  
   153  		_, _, err := managedfields.Normalize(liveState, desiredState, []string{}, &pt)
   154  		require.NoError(t, err)
   155  	})
   156  }
   157  
   158  func validateNestedFloat64(t *testing.T, expected float64, obj *unstructured.Unstructured, fields ...string) {
   159  	t.Helper()
   160  	current := getNestedFloat64(t, obj, fields...)
   161  	assert.InEpsilon(t, expected, current, 0.0001)
   162  }
   163  
   164  func getNestedFloat64(t *testing.T, obj *unstructured.Unstructured, fields ...string) float64 {
   165  	t.Helper()
   166  	current, ok, err := unstructured.NestedFloat64(obj.Object, fields...)
   167  	assert.True(t, ok, "nested field not found")
   168  	require.NoError(t, err)
   169  	return current
   170  }
   171  
   172  func StrToUnstructured(jsonStr string) *unstructured.Unstructured {
   173  	obj := make(map[string]any)
   174  	err := yaml.Unmarshal([]byte(jsonStr), &obj)
   175  	if err != nil {
   176  		panic(err)
   177  	}
   178  	return &unstructured.Unstructured{Object: obj}
   179  }