github.com/argoproj/argo-cd@v1.8.7/cmd/argocd-util/commands/settings_test.go (about)

     1  package commands
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"testing"
    11  
    12  	"github.com/argoproj/argo-cd/common"
    13  	utils "github.com/argoproj/argo-cd/util/io"
    14  	"github.com/argoproj/argo-cd/util/settings"
    15  
    16  	"github.com/stretchr/testify/assert"
    17  	v1 "k8s.io/api/core/v1"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/client-go/kubernetes/fake"
    20  )
    21  
    22  func captureStdout(callback func()) (string, error) {
    23  	oldStdout := os.Stdout
    24  	oldStderr := os.Stderr
    25  	r, w, err := os.Pipe()
    26  	if err != nil {
    27  		return "", err
    28  	}
    29  	os.Stdout = w
    30  	defer func() {
    31  		os.Stdout = oldStdout
    32  		os.Stderr = oldStderr
    33  	}()
    34  
    35  	callback()
    36  	utils.Close(w)
    37  
    38  	data, err := ioutil.ReadAll(r)
    39  
    40  	if err != nil {
    41  		return "", err
    42  	}
    43  	return string(data), err
    44  }
    45  
    46  func newSettingsManager(data map[string]string) *settings.SettingsManager {
    47  	clientset := fake.NewSimpleClientset(&v1.ConfigMap{
    48  		ObjectMeta: metav1.ObjectMeta{
    49  			Namespace: "default",
    50  			Name:      common.ArgoCDConfigMapName,
    51  			Labels: map[string]string{
    52  				"app.kubernetes.io/part-of": "argocd",
    53  			},
    54  		},
    55  		Data: data,
    56  	}, &v1.Secret{
    57  		ObjectMeta: metav1.ObjectMeta{
    58  			Namespace: "default",
    59  			Name:      common.ArgoCDSecretName,
    60  		},
    61  		Data: map[string][]byte{
    62  			"admin.password":   []byte("test"),
    63  			"server.secretkey": []byte("test"),
    64  		},
    65  	})
    66  	return settings.NewSettingsManager(context.Background(), clientset, "default")
    67  }
    68  
    69  type fakeCmdContext struct {
    70  	mgr *settings.SettingsManager
    71  	// nolint:unused,structcheck
    72  	out bytes.Buffer
    73  }
    74  
    75  func newCmdContext(data map[string]string) *fakeCmdContext {
    76  	return &fakeCmdContext{mgr: newSettingsManager(data)}
    77  }
    78  
    79  func (ctx *fakeCmdContext) createSettingsManager() (*settings.SettingsManager, error) {
    80  	return ctx.mgr, nil
    81  }
    82  
    83  type validatorTestCase struct {
    84  	validator       string
    85  	data            map[string]string
    86  	containsSummary string
    87  	containsError   string
    88  }
    89  
    90  func TestCreateSettingsManager(t *testing.T) {
    91  	f, closer, err := tempFile(`apiVersion: v1
    92  kind: ConfigMap
    93  metadata:
    94    name: argocd-cm
    95  data:
    96    url: https://myargocd.com`)
    97  	if !assert.NoError(t, err) {
    98  		return
    99  	}
   100  	defer utils.Close(closer)
   101  
   102  	opts := settingsOpts{argocdCMPath: f}
   103  	settingsManager, err := opts.createSettingsManager()
   104  
   105  	if !assert.NoError(t, err) {
   106  		return
   107  	}
   108  
   109  	argoCDSettings, err := settingsManager.GetSettings()
   110  	if !assert.NoError(t, err) {
   111  		return
   112  	}
   113  
   114  	assert.Equal(t, "https://myargocd.com", argoCDSettings.URL)
   115  }
   116  
   117  func TestValidator(t *testing.T) {
   118  	testCases := map[string]validatorTestCase{
   119  		"General_SSOIsNotConfigured": {
   120  			validator: "general", containsSummary: "SSO is not configured",
   121  		},
   122  		"General_DexInvalidConfig": {
   123  			validator:     "general",
   124  			data:          map[string]string{"dex.config": "abcdefg"},
   125  			containsError: "invalid dex.config",
   126  		},
   127  		"General_OIDCConfigured": {
   128  			validator: "general",
   129  			data: map[string]string{
   130  				"url": "https://myargocd.com",
   131  				"oidc.config": `
   132  name: Okta
   133  issuer: https://dev-123456.oktapreview.com
   134  clientID: aaaabbbbccccddddeee
   135  clientSecret: aaaabbbbccccddddeee`,
   136  			},
   137  			containsSummary: "OIDC is configured",
   138  		},
   139  		"General_DexConfiguredMissingURL": {
   140  			validator: "general",
   141  			data: map[string]string{
   142  				"dex.config": `connectors:
   143  - type: github
   144    name: GitHub
   145    config:
   146      clientID: aabbccddeeff00112233
   147      clientSecret: aabbccddeeff00112233`,
   148  			},
   149  			containsSummary: "Dex is configured ('url' field is missing)",
   150  		},
   151  		"Plugins_ValidConfig": {
   152  			validator: "plugins",
   153  			data: map[string]string{
   154  				"configManagementPlugins": `[{"name": "test1"}, {"name": "test2"}]`,
   155  			},
   156  			containsSummary: "2 plugins",
   157  		},
   158  		"Kustomize_ModifiedOptions": {
   159  			validator:       "kustomize",
   160  			containsSummary: "default options",
   161  		},
   162  		"Kustomize_DefaultOptions": {
   163  			validator: "kustomize",
   164  			data: map[string]string{
   165  				"kustomize.buildOptions":  "updated-options (2 versions)",
   166  				"kustomize.versions.v123": "binary-123",
   167  				"kustomize.versions.v321": "binary-321",
   168  			},
   169  			containsSummary: "updated-options",
   170  		},
   171  		"Repositories": {
   172  			validator: "repositories",
   173  			data: map[string]string{
   174  				"repositories": `
   175  - url: https://github.com/argoproj/my-private-repository1
   176  - url: https://github.com/argoproj/my-private-repository2`,
   177  			},
   178  			containsSummary: "2 repositories",
   179  		},
   180  		"Accounts": {
   181  			validator: "accounts",
   182  			data: map[string]string{
   183  				"accounts.user1": "apiKey, login",
   184  				"accounts.user2": "login",
   185  				"accounts.user3": "apiKey",
   186  			},
   187  			containsSummary: "4 accounts",
   188  		},
   189  		"ResourceOverrides": {
   190  			validator: "resource-overrides",
   191  			data: map[string]string{
   192  				"resource.customizations": `
   193  admissionregistration.k8s.io/MutatingWebhookConfiguration:
   194    ignoreDifferences: |
   195    jsonPointers:
   196    - /webhooks/0/clientConfig/caBundle`,
   197  			},
   198  			containsSummary: "2 resource overrides",
   199  		},
   200  	}
   201  	for name := range testCases {
   202  		tc := testCases[name]
   203  		t.Run(name, func(t *testing.T) {
   204  			validator, ok := validatorsByGroup[tc.validator]
   205  			if !assert.True(t, ok) {
   206  				return
   207  			}
   208  			summary, err := validator(newSettingsManager(tc.data))
   209  			if tc.containsSummary != "" {
   210  				assert.NoError(t, err)
   211  				assert.Contains(t, summary, tc.containsSummary)
   212  			} else if tc.containsError != "" {
   213  				if assert.Error(t, err) {
   214  					assert.Contains(t, err.Error(), tc.containsError)
   215  				}
   216  			}
   217  		})
   218  	}
   219  }
   220  
   221  const (
   222  	testDeploymentYAML = `apiVersion: v1
   223  apiVersion: apps/v1
   224  kind: Deployment
   225  metadata:
   226    name: nginx-deployment
   227    labels:
   228      app: nginx
   229  spec:
   230    replicas: 0`
   231  )
   232  
   233  func tempFile(content string) (string, io.Closer, error) {
   234  	f, err := ioutil.TempFile("", "*.yaml")
   235  	if err != nil {
   236  		return "", nil, err
   237  	}
   238  	_, err = f.Write([]byte(content))
   239  	if err != nil {
   240  		_ = os.Remove(f.Name())
   241  		return "", nil, err
   242  	}
   243  	return f.Name(), utils.NewCloser(func() error {
   244  		return os.Remove(f.Name())
   245  	}), nil
   246  }
   247  
   248  func TestValidateSettingsCommand_NoErrors(t *testing.T) {
   249  	cmd := NewValidateSettingsCommand(newCmdContext(map[string]string{}))
   250  	out, err := captureStdout(func() {
   251  		err := cmd.Execute()
   252  		assert.NoError(t, err)
   253  	})
   254  
   255  	assert.NoError(t, err)
   256  	for k := range validatorsByGroup {
   257  		assert.Contains(t, out, fmt.Sprintf("✅ %s", k))
   258  	}
   259  }
   260  
   261  func TestResourceOverrideIgnoreDifferences(t *testing.T) {
   262  	f, closer, err := tempFile(testDeploymentYAML)
   263  	if !assert.NoError(t, err) {
   264  		return
   265  	}
   266  	defer utils.Close(closer)
   267  
   268  	t.Run("NoOverridesConfigured", func(t *testing.T) {
   269  		cmd := NewResourceOverridesCommand(newCmdContext(map[string]string{}))
   270  		out, err := captureStdout(func() {
   271  			cmd.SetArgs([]string{"ignore-differences", f})
   272  			err := cmd.Execute()
   273  			assert.NoError(t, err)
   274  		})
   275  		assert.NoError(t, err)
   276  		assert.Contains(t, out, "No overrides configured")
   277  	})
   278  
   279  	t.Run("DataIgnored", func(t *testing.T) {
   280  		cmd := NewResourceOverridesCommand(newCmdContext(map[string]string{
   281  			"resource.customizations": `apps/Deployment:
   282    ignoreDifferences: |
   283      jsonPointers:
   284      - /spec`}))
   285  		out, err := captureStdout(func() {
   286  			cmd.SetArgs([]string{"ignore-differences", f})
   287  			err := cmd.Execute()
   288  			assert.NoError(t, err)
   289  		})
   290  		assert.NoError(t, err)
   291  		assert.Contains(t, out, "< spec:")
   292  	})
   293  }
   294  
   295  func TestResourceOverrideHealth(t *testing.T) {
   296  	f, closer, err := tempFile(testDeploymentYAML)
   297  	if !assert.NoError(t, err) {
   298  		return
   299  	}
   300  	defer utils.Close(closer)
   301  
   302  	t.Run("NoHealthAssessment", func(t *testing.T) {
   303  		cmd := NewResourceOverridesCommand(newCmdContext(map[string]string{
   304  			"resource.customizations": `apps/Deployment: {}`}))
   305  		out, err := captureStdout(func() {
   306  			cmd.SetArgs([]string{"health", f})
   307  			err := cmd.Execute()
   308  			assert.NoError(t, err)
   309  		})
   310  		assert.NoError(t, err)
   311  		assert.Contains(t, out, "Health script is not configured")
   312  	})
   313  
   314  	t.Run("HealthAssessmentConfigured", func(t *testing.T) {
   315  		cmd := NewResourceOverridesCommand(newCmdContext(map[string]string{
   316  			"resource.customizations": `apps/Deployment:
   317    health.lua: |
   318      return { status = "Progressing" }
   319  `}))
   320  		out, err := captureStdout(func() {
   321  			cmd.SetArgs([]string{"health", f})
   322  			err := cmd.Execute()
   323  			assert.NoError(t, err)
   324  		})
   325  		assert.NoError(t, err)
   326  		assert.Contains(t, out, "Progressing")
   327  	})
   328  }
   329  
   330  func TestResourceOverrideAction(t *testing.T) {
   331  	f, closer, err := tempFile(testDeploymentYAML)
   332  	if !assert.NoError(t, err) {
   333  		return
   334  	}
   335  	defer utils.Close(closer)
   336  
   337  	t.Run("NoActions", func(t *testing.T) {
   338  		cmd := NewResourceOverridesCommand(newCmdContext(map[string]string{
   339  			"resource.customizations": `apps/Deployment: {}`}))
   340  		out, err := captureStdout(func() {
   341  			cmd.SetArgs([]string{"run-action", f, "test"})
   342  			err := cmd.Execute()
   343  			assert.NoError(t, err)
   344  		})
   345  		assert.NoError(t, err)
   346  		assert.Contains(t, out, "Actions are not configured")
   347  	})
   348  
   349  	t.Run("ActionConfigured", func(t *testing.T) {
   350  		cmd := NewResourceOverridesCommand(newCmdContext(map[string]string{
   351  			"resource.customizations": `apps/Deployment:
   352    actions: |
   353      discovery.lua: |
   354        actions = {}
   355        actions["resume"] = {["disabled"] = false}
   356        actions["restart"] = {["disabled"] = false}
   357        return actions
   358      definitions:
   359      - name: test
   360        action.lua: |
   361          obj.metadata.labels["test"] = 'updated'
   362          return obj
   363  `}))
   364  		out, err := captureStdout(func() {
   365  			cmd.SetArgs([]string{"run-action", f, "test"})
   366  			err := cmd.Execute()
   367  			assert.NoError(t, err)
   368  		})
   369  		assert.NoError(t, err)
   370  		assert.Contains(t, out, "test: updated")
   371  
   372  		out, err = captureStdout(func() {
   373  			cmd.SetArgs([]string{"list-actions", f})
   374  			err := cmd.Execute()
   375  			assert.NoError(t, err)
   376  		})
   377  		assert.NoError(t, err)
   378  		assert.Contains(t, out, `NAME     ENABLED
   379  restart  false
   380  resume   false
   381  `)
   382  	})
   383  }