github.com/argoproj/argo-cd/v3@v3.2.1/test/e2e/fixture/fixture.go (about)

     1  package fixture
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	stderrors "errors"
     7  	"fmt"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"reflect"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	corev1 "k8s.io/api/core/v1"
    18  	rbacv1 "k8s.io/api/rbac/v1"
    19  
    20  	jsonpatch "github.com/evanphx/json-patch"
    21  	log "github.com/sirupsen/logrus"
    22  	"github.com/stretchr/testify/require"
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/client-go/dynamic"
    25  	"k8s.io/client-go/kubernetes"
    26  	"k8s.io/client-go/rest"
    27  	"k8s.io/client-go/tools/clientcmd"
    28  	"sigs.k8s.io/yaml"
    29  
    30  	"github.com/argoproj/argo-cd/v3/common"
    31  	"github.com/argoproj/argo-cd/v3/pkg/apiclient"
    32  	sessionpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/session"
    33  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    34  	appclientset "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned"
    35  	"github.com/argoproj/argo-cd/v3/util/env"
    36  	"github.com/argoproj/argo-cd/v3/util/errors"
    37  	grpcutil "github.com/argoproj/argo-cd/v3/util/grpc"
    38  	utilio "github.com/argoproj/argo-cd/v3/util/io"
    39  	"github.com/argoproj/argo-cd/v3/util/rand"
    40  	"github.com/argoproj/argo-cd/v3/util/settings"
    41  )
    42  
    43  const (
    44  	defaultAPIServer        = "localhost:8080"
    45  	defaultAdminPassword    = "password"
    46  	defaultAdminUsername    = "admin"
    47  	DefaultTestUserPassword = "password"
    48  	TestingLabel            = "e2e.argoproj.io"
    49  	ArgoCDNamespace         = "argocd-e2e"
    50  	ArgoCDAppNamespace      = "argocd-e2e-external"
    51  
    52  	// notifications controller, metrics server port
    53  	defaultNotificationServer = "localhost:9001"
    54  
    55  	// ensure all repos are in one directory tree, so we can easily clean them up
    56  	TmpDir             = "/tmp/argo-e2e"
    57  	repoDir            = "testdata.git"
    58  	submoduleDir       = "submodule.git"
    59  	submoduleParentDir = "submoduleParent.git"
    60  
    61  	GuestbookPath = "guestbook"
    62  
    63  	ProjectName = "argo-project"
    64  
    65  	// cmp plugin sock file path
    66  	PluginSockFilePath = "/app/config/plugin"
    67  
    68  	E2ETestPrefix = "e2e-test-"
    69  
    70  	// Account for batch events processing (set to 1ms in e2e tests)
    71  	WhenThenSleepInterval = 5 * time.Millisecond
    72  )
    73  
    74  const (
    75  	EnvAdminUsername           = "ARGOCD_E2E_ADMIN_USERNAME"
    76  	EnvAdminPassword           = "ARGOCD_E2E_ADMIN_PASSWORD"
    77  	EnvArgoCDServerName        = "ARGOCD_E2E_SERVER_NAME"
    78  	EnvArgoCDRedisHAProxyName  = "ARGOCD_E2E_REDIS_HAPROXY_NAME"
    79  	EnvArgoCDRedisName         = "ARGOCD_E2E_REDIS_NAME"
    80  	EnvArgoCDRepoServerName    = "ARGOCD_E2E_REPO_SERVER_NAME"
    81  	EnvArgoCDAppControllerName = "ARGOCD_E2E_APPLICATION_CONTROLLER_NAME"
    82  )
    83  
    84  var (
    85  	id                      string
    86  	deploymentNamespace     string
    87  	name                    string
    88  	KubeClientset           kubernetes.Interface
    89  	KubeConfig              *rest.Config
    90  	DynamicClientset        dynamic.Interface
    91  	AppClientset            appclientset.Interface
    92  	ArgoCDClientset         apiclient.Client
    93  	adminUsername           string
    94  	AdminPassword           string
    95  	apiServerAddress        string
    96  	token                   string
    97  	plainText               bool
    98  	testsRun                map[string]bool
    99  	argoCDServerName        string
   100  	argoCDRedisHAProxyName  string
   101  	argoCDRedisName         string
   102  	argoCDRepoServerName    string
   103  	argoCDAppControllerName string
   104  )
   105  
   106  type RepoURLType string
   107  
   108  type ACL struct {
   109  	Resource string
   110  	Action   string
   111  	Scope    string
   112  }
   113  
   114  const (
   115  	RepoURLTypeFile                 = "file"
   116  	RepoURLTypeHTTPS                = "https"
   117  	RepoURLTypeHTTPSOrg             = "https-org"
   118  	RepoURLTypeHTTPSClientCert      = "https-cc"
   119  	RepoURLTypeHTTPSSubmodule       = "https-sub"
   120  	RepoURLTypeHTTPSSubmoduleParent = "https-par"
   121  	RepoURLTypeSSH                  = "ssh"
   122  	RepoURLTypeSSHSubmodule         = "ssh-sub"
   123  	RepoURLTypeSSHSubmoduleParent   = "ssh-par"
   124  	RepoURLTypeHelm                 = "helm"
   125  	RepoURLTypeHelmParent           = "helm-par"
   126  	RepoURLTypeHelmOCI              = "helm-oci"
   127  	RepoURLTypeOCI                  = "oci"
   128  	GitUsername                     = "admin"
   129  	GitPassword                     = "password"
   130  	GitBearerToken                  = "test"
   131  	GithubAppID                     = "2978632978"
   132  	GithubAppInstallationID         = "7893789433789"
   133  	GpgGoodKeyID                    = "D56C4FCA57A46444"
   134  	HelmOCIRegistryURL              = "localhost:5000/myrepo"
   135  	HelmAuthenticatedOCIRegistryURL = "localhost:5001/myrepo"
   136  	OCIRegistryURL                  = "oci://localhost:5000/my-oci-repo"
   137  	OCIHostURL                      = "oci://localhost:5000"
   138  	AuthenticatedOCIHostURL         = "oci://localhost:5001"
   139  )
   140  
   141  // TestNamespace returns the namespace where Argo CD E2E test instance will be
   142  // running in.
   143  func TestNamespace() string {
   144  	return GetEnvWithDefault("ARGOCD_E2E_NAMESPACE", ArgoCDNamespace)
   145  }
   146  
   147  func AppNamespace() string {
   148  	return GetEnvWithDefault("ARGOCD_E2E_APP_NAMESPACE", ArgoCDAppNamespace)
   149  }
   150  
   151  // getKubeConfig creates new kubernetes client config using specified config path and config overrides variables
   152  func getKubeConfig(configPath string, overrides clientcmd.ConfigOverrides) *rest.Config {
   153  	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
   154  	loadingRules.ExplicitPath = configPath
   155  	clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, &overrides, os.Stdin)
   156  
   157  	restConfig, err := clientConfig.ClientConfig()
   158  	errors.CheckError(err)
   159  	return restConfig
   160  }
   161  
   162  func GetEnvWithDefault(envName, defaultValue string) string {
   163  	r := os.Getenv(envName)
   164  	if r == "" {
   165  		return defaultValue
   166  	}
   167  	return r
   168  }
   169  
   170  // IsRemote returns true when the tests are being run against a workload that
   171  // is running in a remote cluster.
   172  func IsRemote() bool {
   173  	return env.ParseBoolFromEnv("ARGOCD_E2E_REMOTE", false)
   174  }
   175  
   176  // IsLocal returns when the tests are being run against a local workload
   177  func IsLocal() bool {
   178  	return !IsRemote()
   179  }
   180  
   181  // creates e2e tests fixture: ensures that Application CRD is installed, creates temporal namespace, starts repo and api server,
   182  // configure currently available cluster.
   183  func init() {
   184  	// ensure we log all shell execs
   185  	log.SetLevel(log.DebugLevel)
   186  	// set-up variables
   187  	config := getKubeConfig("", clientcmd.ConfigOverrides{})
   188  	AppClientset = appclientset.NewForConfigOrDie(config)
   189  	KubeClientset = kubernetes.NewForConfigOrDie(config)
   190  	DynamicClientset = dynamic.NewForConfigOrDie(config)
   191  	KubeConfig = config
   192  
   193  	apiServerAddress = GetEnvWithDefault(apiclient.EnvArgoCDServer, defaultAPIServer)
   194  	adminUsername = GetEnvWithDefault(EnvAdminUsername, defaultAdminUsername)
   195  	AdminPassword = GetEnvWithDefault(EnvAdminPassword, defaultAdminPassword)
   196  
   197  	argoCDServerName = GetEnvWithDefault(EnvArgoCDServerName, common.DefaultServerName)
   198  	argoCDRedisHAProxyName = GetEnvWithDefault(EnvArgoCDRedisHAProxyName, common.DefaultRedisHaProxyName)
   199  	argoCDRedisName = GetEnvWithDefault(EnvArgoCDRedisName, common.DefaultRedisName)
   200  	argoCDRepoServerName = GetEnvWithDefault(EnvArgoCDRepoServerName, common.DefaultRepoServerName)
   201  	argoCDAppControllerName = GetEnvWithDefault(EnvArgoCDAppControllerName, common.DefaultApplicationControllerName)
   202  
   203  	dialTime := 30 * time.Second
   204  	tlsTestResult, err := grpcutil.TestTLS(apiServerAddress, dialTime)
   205  	errors.CheckError(err)
   206  
   207  	ArgoCDClientset, err = apiclient.NewClient(&apiclient.ClientOptions{
   208  		Insecure:          true,
   209  		ServerAddr:        apiServerAddress,
   210  		PlainText:         !tlsTestResult.TLS,
   211  		ServerName:        argoCDServerName,
   212  		RedisHaProxyName:  argoCDRedisHAProxyName,
   213  		RedisName:         argoCDRedisName,
   214  		RepoServerName:    argoCDRepoServerName,
   215  		AppControllerName: argoCDAppControllerName,
   216  	})
   217  	errors.CheckError(err)
   218  
   219  	plainText = !tlsTestResult.TLS
   220  
   221  	errors.CheckError(LoginAs(adminUsername))
   222  
   223  	log.WithFields(log.Fields{"apiServerAddress": apiServerAddress}).Info("initialized")
   224  
   225  	// Preload a list of tests that should be skipped
   226  	testsRun = make(map[string]bool)
   227  	rf := os.Getenv("ARGOCD_E2E_RECORD")
   228  	if rf == "" {
   229  		return
   230  	}
   231  	f, err := os.Open(rf)
   232  	if err != nil {
   233  		if stderrors.Is(err, os.ErrNotExist) {
   234  			return
   235  		}
   236  		panic(fmt.Sprintf("Could not read record file %s: %v", rf, err))
   237  	}
   238  	defer func() {
   239  		err := f.Close()
   240  		if err != nil {
   241  			panic(fmt.Sprintf("Could not close record file %s: %v", rf, err))
   242  		}
   243  	}()
   244  	scanner := bufio.NewScanner(f)
   245  	for scanner.Scan() {
   246  		testsRun[scanner.Text()] = true
   247  	}
   248  }
   249  
   250  func loginAs(username, password string) error {
   251  	closer, client, err := ArgoCDClientset.NewSessionClient()
   252  	if err != nil {
   253  		return err
   254  	}
   255  	defer utilio.Close(closer)
   256  
   257  	userInfoResponse, err := client.GetUserInfo(context.Background(), &sessionpkg.GetUserInfoRequest{})
   258  	if err != nil {
   259  		return err
   260  	}
   261  	if userInfoResponse.Username == username && userInfoResponse.LoggedIn {
   262  		return nil
   263  	}
   264  
   265  	sessionResponse, err := client.Create(context.Background(), &sessionpkg.SessionCreateRequest{Username: username, Password: password})
   266  	if err != nil {
   267  		return err
   268  	}
   269  	token = sessionResponse.Token
   270  
   271  	ArgoCDClientset, err = apiclient.NewClient(&apiclient.ClientOptions{
   272  		Insecure:          true,
   273  		ServerAddr:        apiServerAddress,
   274  		AuthToken:         token,
   275  		PlainText:         plainText,
   276  		ServerName:        argoCDServerName,
   277  		RedisHaProxyName:  argoCDRedisHAProxyName,
   278  		RedisName:         argoCDRedisName,
   279  		RepoServerName:    argoCDRepoServerName,
   280  		AppControllerName: argoCDAppControllerName,
   281  	})
   282  	return err
   283  }
   284  
   285  func LoginAs(username string) error {
   286  	password := DefaultTestUserPassword
   287  	if username == "admin" {
   288  		password = AdminPassword
   289  	}
   290  	return loginAs(username, password)
   291  }
   292  
   293  func Name() string {
   294  	return name
   295  }
   296  
   297  func repoDirectory() string {
   298  	return path.Join(TmpDir, repoDir)
   299  }
   300  
   301  func submoduleDirectory() string {
   302  	return path.Join(TmpDir, submoduleDir)
   303  }
   304  
   305  func submoduleParentDirectory() string {
   306  	return path.Join(TmpDir, submoduleParentDir)
   307  }
   308  
   309  const (
   310  	EnvRepoURLTypeSSH                  = "ARGOCD_E2E_REPO_SSH"
   311  	EnvRepoURLTypeSSHSubmodule         = "ARGOCD_E2E_REPO_SSH_SUBMODULE"
   312  	EnvRepoURLTypeSSHSubmoduleParent   = "ARGOCD_E2E_REPO_SSH_SUBMODULE_PARENT"
   313  	EnvRepoURLTypeHTTPS                = "ARGOCD_E2E_REPO_HTTPS"
   314  	EnvRepoURLTypeHTTPSOrg             = "ARGOCD_E2E_REPO_HTTPS_ORG"
   315  	EnvRepoURLTypeHTTPSClientCert      = "ARGOCD_E2E_REPO_HTTPS_CLIENT_CERT"
   316  	EnvRepoURLTypeHTTPSSubmodule       = "ARGOCD_E2E_REPO_HTTPS_SUBMODULE"
   317  	EnvRepoURLTypeHTTPSSubmoduleParent = "ARGOCD_E2E_REPO_HTTPS_SUBMODULE_PARENT"
   318  	EnvRepoURLTypeHelm                 = "ARGOCD_E2E_REPO_HELM"
   319  	EnvRepoURLDefault                  = "ARGOCD_E2E_REPO_DEFAULT"
   320  )
   321  
   322  func RepoURL(urlType RepoURLType) string {
   323  	switch urlType {
   324  	// Git server via SSH
   325  	case RepoURLTypeSSH:
   326  		return GetEnvWithDefault(EnvRepoURLTypeSSH, "ssh://root@localhost:2222/tmp/argo-e2e/testdata.git")
   327  	// Git submodule repo
   328  	case RepoURLTypeSSHSubmodule:
   329  		return GetEnvWithDefault(EnvRepoURLTypeSSHSubmodule, "ssh://root@localhost:2222/tmp/argo-e2e/submodule.git")
   330  	// Git submodule parent repo
   331  	case RepoURLTypeSSHSubmoduleParent:
   332  		return GetEnvWithDefault(EnvRepoURLTypeSSHSubmoduleParent, "ssh://root@localhost:2222/tmp/argo-e2e/submoduleParent.git")
   333  	// Git server via HTTPS
   334  	case RepoURLTypeHTTPS:
   335  		return GetEnvWithDefault(EnvRepoURLTypeHTTPS, "https://localhost:9443/argo-e2e/testdata.git")
   336  	// Git "organisation" via HTTPS
   337  	case RepoURLTypeHTTPSOrg:
   338  		return GetEnvWithDefault(EnvRepoURLTypeHTTPSOrg, "https://localhost:9443/argo-e2e")
   339  	// Git server via HTTPS - Client Cert protected
   340  	case RepoURLTypeHTTPSClientCert:
   341  		return GetEnvWithDefault(EnvRepoURLTypeHTTPSClientCert, "https://localhost:9444/argo-e2e/testdata.git")
   342  	case RepoURLTypeHTTPSSubmodule:
   343  		return GetEnvWithDefault(EnvRepoURLTypeHTTPSSubmodule, "https://localhost:9443/argo-e2e/submodule.git")
   344  		// Git submodule parent repo
   345  	case RepoURLTypeHTTPSSubmoduleParent:
   346  		return GetEnvWithDefault(EnvRepoURLTypeHTTPSSubmoduleParent, "https://localhost:9443/argo-e2e/submoduleParent.git")
   347  	// Default - file based Git repository
   348  	case RepoURLTypeHelm:
   349  		return GetEnvWithDefault(EnvRepoURLTypeHelm, "https://localhost:9444/argo-e2e/testdata.git/helm-repo/local")
   350  	// When Helm Repo has sub repos, this is the parent repo URL
   351  	case RepoURLTypeHelmParent:
   352  		return GetEnvWithDefault(EnvRepoURLTypeHelm, "https://localhost:9444/argo-e2e/testdata.git/helm-repo")
   353  	case RepoURLTypeOCI:
   354  		return OCIRegistryURL
   355  	case RepoURLTypeHelmOCI:
   356  		return HelmOCIRegistryURL
   357  	default:
   358  		return GetEnvWithDefault(EnvRepoURLDefault, "file://"+repoDirectory())
   359  	}
   360  }
   361  
   362  func RepoBaseURL(urlType RepoURLType) string {
   363  	return path.Base(RepoURL(urlType))
   364  }
   365  
   366  func DeploymentNamespace() string {
   367  	return deploymentNamespace
   368  }
   369  
   370  // Convenience wrapper for updating argocd-cm
   371  func updateSettingConfigMap(updater func(cm *corev1.ConfigMap) error) error {
   372  	return updateGenericConfigMap(common.ArgoCDConfigMapName, updater)
   373  }
   374  
   375  // Convenience wrapper for updating argocd-notifications-cm
   376  func updateNotificationsConfigMap(updater func(cm *corev1.ConfigMap) error) error {
   377  	return updateGenericConfigMap(common.ArgoCDNotificationsConfigMapName, updater)
   378  }
   379  
   380  // Convenience wrapper for updating argocd-cm-rbac
   381  func updateRBACConfigMap(updater func(cm *corev1.ConfigMap) error) error {
   382  	return updateGenericConfigMap(common.ArgoCDRBACConfigMapName, updater)
   383  }
   384  
   385  func configMapsEquivalent(a *corev1.ConfigMap, b *corev1.ConfigMap) bool {
   386  	return reflect.DeepEqual(a.Immutable, b.Immutable) &&
   387  		reflect.DeepEqual(a.TypeMeta, b.TypeMeta) &&
   388  		reflect.DeepEqual(a.ObjectMeta, b.ObjectMeta) &&
   389  		// Covers cases when one map is nil and another is empty map
   390  		(len(a.Data) == 0 && len(b.Data) == 0 || reflect.DeepEqual(a.Data, b.Data)) &&
   391  		(len(a.BinaryData) == 0 && len(b.BinaryData) == 0 || reflect.DeepEqual(a.BinaryData, b.BinaryData))
   392  }
   393  
   394  // Updates a given config map in argocd-e2e namespace
   395  func updateGenericConfigMap(name string, updater func(cm *corev1.ConfigMap) error) error {
   396  	cm, err := KubeClientset.CoreV1().ConfigMaps(TestNamespace()).Get(context.Background(), name, metav1.GetOptions{})
   397  	if err != nil {
   398  		return err
   399  	}
   400  	oldCm := cm.DeepCopy()
   401  	if cm.Data == nil {
   402  		cm.Data = make(map[string]string)
   403  	}
   404  	err = updater(cm)
   405  	if err != nil {
   406  		return err
   407  	}
   408  	if !configMapsEquivalent(cm, oldCm) {
   409  		_, err = KubeClientset.CoreV1().ConfigMaps(TestNamespace()).Update(context.Background(), cm, metav1.UpdateOptions{})
   410  		if err != nil {
   411  			return err
   412  		}
   413  	}
   414  	return nil
   415  }
   416  
   417  func RegisterKustomizeVersion(version, path string) error {
   418  	return updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   419  		cm.Data["kustomize.version."+version] = path
   420  		return nil
   421  	})
   422  }
   423  
   424  func SetEnableManifestGeneration(val map[v1alpha1.ApplicationSourceType]bool) error {
   425  	return updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   426  		for k, v := range val {
   427  			cm.Data[strings.ToLower(string(k))+".enable"] = strconv.FormatBool(v)
   428  		}
   429  		return nil
   430  	})
   431  }
   432  
   433  func SetResourceOverrides(overrides map[string]v1alpha1.ResourceOverride) error {
   434  	err := updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   435  		if len(overrides) > 0 {
   436  			yamlBytes, err := yaml.Marshal(overrides)
   437  			if err != nil {
   438  				return err
   439  			}
   440  			cm.Data["resource.customizations"] = string(yamlBytes)
   441  		} else {
   442  			delete(cm.Data, "resource.customizations")
   443  		}
   444  		return nil
   445  	})
   446  	if err != nil {
   447  		return err
   448  	}
   449  
   450  	return SetResourceOverridesSplitKeys(overrides)
   451  }
   452  
   453  func SetInstallationID(installationID string) error {
   454  	return updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   455  		cm.Data["installationID"] = installationID
   456  		return nil
   457  	})
   458  }
   459  
   460  func SetTrackingMethod(trackingMethod string) error {
   461  	return updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   462  		cm.Data["application.resourceTrackingMethod"] = trackingMethod
   463  		return nil
   464  	})
   465  }
   466  
   467  func SetTrackingLabel(trackingLabel string) error {
   468  	return updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   469  		cm.Data["application.instanceLabelKey"] = trackingLabel
   470  		return nil
   471  	})
   472  }
   473  
   474  func SetImpersonationEnabled(impersonationEnabledFlag string) error {
   475  	return updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   476  		cm.Data["application.sync.impersonation.enabled"] = impersonationEnabledFlag
   477  		return nil
   478  	})
   479  }
   480  
   481  func CreateRBACResourcesForImpersonation(serviceAccountName string, policyRules []rbacv1.PolicyRule) error {
   482  	sa := &corev1.ServiceAccount{
   483  		ObjectMeta: metav1.ObjectMeta{
   484  			Name: serviceAccountName,
   485  		},
   486  	}
   487  	_, err := KubeClientset.CoreV1().ServiceAccounts(DeploymentNamespace()).Create(context.Background(), sa, metav1.CreateOptions{})
   488  	if err != nil {
   489  		return err
   490  	}
   491  	role := &rbacv1.Role{
   492  		ObjectMeta: metav1.ObjectMeta{
   493  			Name: fmt.Sprintf("%s-%s", serviceAccountName, "role"),
   494  		},
   495  		Rules: policyRules,
   496  	}
   497  	_, err = KubeClientset.RbacV1().Roles(DeploymentNamespace()).Create(context.Background(), role, metav1.CreateOptions{})
   498  	if err != nil {
   499  		return err
   500  	}
   501  	rolebinding := &rbacv1.RoleBinding{
   502  		ObjectMeta: metav1.ObjectMeta{
   503  			Name: fmt.Sprintf("%s-%s", serviceAccountName, "rolebinding"),
   504  		},
   505  		RoleRef: rbacv1.RoleRef{
   506  			APIGroup: "rbac.authorization.k8s.io",
   507  			Kind:     "Role",
   508  			Name:     fmt.Sprintf("%s-%s", serviceAccountName, "role"),
   509  		},
   510  		Subjects: []rbacv1.Subject{
   511  			{
   512  				Kind:      "ServiceAccount",
   513  				Name:      serviceAccountName,
   514  				Namespace: DeploymentNamespace(),
   515  			},
   516  		},
   517  	}
   518  	_, err = KubeClientset.RbacV1().RoleBindings(DeploymentNamespace()).Create(context.Background(), rolebinding, metav1.CreateOptions{})
   519  	if err != nil {
   520  		return err
   521  	}
   522  	return nil
   523  }
   524  
   525  func SetResourceOverridesSplitKeys(overrides map[string]v1alpha1.ResourceOverride) error {
   526  	return updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   527  		for k, v := range overrides {
   528  			if v.HealthLua != "" {
   529  				cm.Data[getResourceOverrideSplitKey(k, "health")] = v.HealthLua
   530  			}
   531  			cm.Data[getResourceOverrideSplitKey(k, "useOpenLibs")] = strconv.FormatBool(v.UseOpenLibs)
   532  			if v.Actions != "" {
   533  				cm.Data[getResourceOverrideSplitKey(k, "actions")] = v.Actions
   534  			}
   535  			if len(v.IgnoreDifferences.JSONPointers) > 0 ||
   536  				len(v.IgnoreDifferences.JQPathExpressions) > 0 ||
   537  				len(v.IgnoreDifferences.ManagedFieldsManagers) > 0 {
   538  				yamlBytes, err := yaml.Marshal(v.IgnoreDifferences)
   539  				if err != nil {
   540  					return err
   541  				}
   542  				cm.Data[getResourceOverrideSplitKey(k, "ignoreDifferences")] = string(yamlBytes)
   543  			}
   544  			if len(v.KnownTypeFields) > 0 {
   545  				yamlBytes, err := yaml.Marshal(v.KnownTypeFields)
   546  				if err != nil {
   547  					return err
   548  				}
   549  				cm.Data[getResourceOverrideSplitKey(k, "knownTypeFields")] = string(yamlBytes)
   550  			}
   551  		}
   552  		return nil
   553  	})
   554  }
   555  
   556  func getResourceOverrideSplitKey(key string, customizeType string) string {
   557  	groupKind := key
   558  	parts := strings.Split(key, "/")
   559  	if len(parts) == 2 {
   560  		groupKind = fmt.Sprintf("%s_%s", parts[0], parts[1])
   561  	}
   562  	return fmt.Sprintf("resource.customizations.%s.%s", customizeType, groupKind)
   563  }
   564  
   565  func SetAccounts(accounts map[string][]string) error {
   566  	return updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   567  		for k, v := range accounts {
   568  			cm.Data["accounts."+k] = strings.Join(v, ",")
   569  		}
   570  		return nil
   571  	})
   572  }
   573  
   574  func SetPermissions(permissions []ACL, username string, roleName string) error {
   575  	return updateRBACConfigMap(func(cm *corev1.ConfigMap) error {
   576  		var aclstr string
   577  
   578  		for _, permission := range permissions {
   579  			aclstr += fmt.Sprintf("p, role:%s, %s, %s, %s, allow \n", roleName, permission.Resource, permission.Action, permission.Scope)
   580  		}
   581  
   582  		aclstr += fmt.Sprintf("g, %s, role:%s", username, roleName)
   583  		cm.Data["policy.csv"] = aclstr
   584  
   585  		return nil
   586  	})
   587  }
   588  
   589  func SetResourceFilter(filters settings.ResourcesFilter) error {
   590  	return updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   591  		exclusions, err := yaml.Marshal(filters.ResourceExclusions)
   592  		if err != nil {
   593  			return err
   594  		}
   595  		inclusions, err := yaml.Marshal(filters.ResourceInclusions)
   596  		if err != nil {
   597  			return err
   598  		}
   599  		cm.Data["resource.exclusions"] = string(exclusions)
   600  		cm.Data["resource.inclusions"] = string(inclusions)
   601  		return nil
   602  	})
   603  }
   604  
   605  func SetProjectSpec(project string, spec v1alpha1.AppProjectSpec) error {
   606  	proj, err := AppClientset.ArgoprojV1alpha1().AppProjects(TestNamespace()).Get(context.Background(), project, metav1.GetOptions{})
   607  	if err != nil {
   608  		return err
   609  	}
   610  	proj.Spec = spec
   611  	_, err = AppClientset.ArgoprojV1alpha1().AppProjects(TestNamespace()).Update(context.Background(), proj, metav1.UpdateOptions{})
   612  	return err
   613  }
   614  
   615  func SetParamInSettingConfigMap(key, value string) error {
   616  	return updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   617  		cm.Data[key] = value
   618  		return nil
   619  	})
   620  }
   621  
   622  func SetParamInNotificationsConfigMap(key, value string) error {
   623  	return updateNotificationsConfigMap(func(cm *corev1.ConfigMap) error {
   624  		cm.Data[key] = value
   625  		return nil
   626  	})
   627  }
   628  
   629  type TestOption func(option *testOption)
   630  
   631  type testOption struct {
   632  	testdata string
   633  }
   634  
   635  func newTestOption(opts ...TestOption) *testOption {
   636  	to := &testOption{
   637  		testdata: "testdata",
   638  	}
   639  	for _, opt := range opts {
   640  		opt(to)
   641  	}
   642  	return to
   643  }
   644  
   645  func WithTestData(testdata string) TestOption {
   646  	return func(option *testOption) {
   647  		option.testdata = testdata
   648  	}
   649  }
   650  
   651  func EnsureCleanState(t *testing.T, opts ...TestOption) {
   652  	t.Helper()
   653  	opt := newTestOption(opts...)
   654  	// In large scenarios, we can skip tests that already run
   655  	SkipIfAlreadyRun(t)
   656  	// Register this test after it has been run & was successful
   657  	t.Cleanup(func() {
   658  		RecordTestRun(t)
   659  	})
   660  
   661  	start := time.Now()
   662  	policy := metav1.DeletePropagationBackground
   663  
   664  	RunFunctionsInParallelAndCheckErrors(t, []func() error{
   665  		func() error {
   666  			// kubectl delete apps ...
   667  			return AppClientset.ArgoprojV1alpha1().Applications(TestNamespace()).DeleteCollection(
   668  				t.Context(),
   669  				metav1.DeleteOptions{PropagationPolicy: &policy},
   670  				metav1.ListOptions{})
   671  		},
   672  		func() error {
   673  			// kubectl delete apps ...
   674  			return AppClientset.ArgoprojV1alpha1().Applications(AppNamespace()).DeleteCollection(
   675  				t.Context(),
   676  				metav1.DeleteOptions{PropagationPolicy: &policy},
   677  				metav1.ListOptions{})
   678  		},
   679  		func() error {
   680  			// kubectl delete appprojects --field-selector metadata.name!=default
   681  			return AppClientset.ArgoprojV1alpha1().AppProjects(TestNamespace()).DeleteCollection(
   682  				t.Context(),
   683  				metav1.DeleteOptions{PropagationPolicy: &policy},
   684  				metav1.ListOptions{FieldSelector: "metadata.name!=default"})
   685  		},
   686  		func() error {
   687  			// kubectl delete secrets -l argocd.argoproj.io/secret-type=repo-config
   688  			return KubeClientset.CoreV1().Secrets(TestNamespace()).DeleteCollection(
   689  				t.Context(),
   690  				metav1.DeleteOptions{PropagationPolicy: &policy},
   691  				metav1.ListOptions{LabelSelector: common.LabelKeySecretType + "=" + common.LabelValueSecretTypeRepository})
   692  		},
   693  		func() error {
   694  			// kubectl delete secrets -l argocd.argoproj.io/secret-type=repo-creds
   695  			return KubeClientset.CoreV1().Secrets(TestNamespace()).DeleteCollection(
   696  				t.Context(),
   697  				metav1.DeleteOptions{PropagationPolicy: &policy},
   698  				metav1.ListOptions{LabelSelector: common.LabelKeySecretType + "=" + common.LabelValueSecretTypeRepoCreds})
   699  		},
   700  		func() error {
   701  			// kubectl delete secrets -l argocd.argoproj.io/secret-type=cluster
   702  			return KubeClientset.CoreV1().Secrets(TestNamespace()).DeleteCollection(
   703  				t.Context(),
   704  				metav1.DeleteOptions{PropagationPolicy: &policy},
   705  				metav1.ListOptions{LabelSelector: common.LabelKeySecretType + "=" + common.LabelValueSecretTypeCluster})
   706  		},
   707  		func() error {
   708  			// kubectl delete secrets -l e2e.argoproj.io=true
   709  			return KubeClientset.CoreV1().Secrets(TestNamespace()).DeleteCollection(
   710  				t.Context(),
   711  				metav1.DeleteOptions{PropagationPolicy: &policy},
   712  				metav1.ListOptions{LabelSelector: TestingLabel + "=true"})
   713  		},
   714  	})
   715  
   716  	RunFunctionsInParallelAndCheckErrors(t, []func() error{
   717  		func() error {
   718  			// delete old namespaces which were created by tests
   719  			namespaces, err := KubeClientset.CoreV1().Namespaces().List(
   720  				t.Context(),
   721  				metav1.ListOptions{
   722  					LabelSelector: TestingLabel + "=true",
   723  					FieldSelector: "status.phase=Active",
   724  				},
   725  			)
   726  			if err != nil {
   727  				return err
   728  			}
   729  			if len(namespaces.Items) > 0 {
   730  				args := []string{"delete", "ns", "--wait=false"}
   731  				for _, namespace := range namespaces.Items {
   732  					args = append(args, namespace.Name)
   733  				}
   734  				_, err := Run("", "kubectl", args...)
   735  				if err != nil {
   736  					return err
   737  				}
   738  			}
   739  
   740  			namespaces, err = KubeClientset.CoreV1().Namespaces().List(t.Context(), metav1.ListOptions{})
   741  			if err != nil {
   742  				return err
   743  			}
   744  			testNamespaceNames := []string{}
   745  			for _, namespace := range namespaces.Items {
   746  				if strings.HasPrefix(namespace.Name, E2ETestPrefix) {
   747  					testNamespaceNames = append(testNamespaceNames, namespace.Name)
   748  				}
   749  			}
   750  			if len(testNamespaceNames) > 0 {
   751  				args := []string{"delete", "ns"}
   752  				args = append(args, testNamespaceNames...)
   753  				_, err := Run("", "kubectl", args...)
   754  				if err != nil {
   755  					return err
   756  				}
   757  			}
   758  			return nil
   759  		},
   760  		func() error {
   761  			// delete old CRDs which were created by tests, doesn't seem to have kube api to get items
   762  			_, err := Run("", "kubectl", "delete", "crd", "-l", TestingLabel+"=true", "--wait=false")
   763  			return err
   764  		},
   765  		func() error {
   766  			// delete old ClusterRoles which were created by tests
   767  			clusterRoles, err := KubeClientset.RbacV1().ClusterRoles().List(
   768  				t.Context(),
   769  				metav1.ListOptions{
   770  					LabelSelector: fmt.Sprintf("%s=%s", TestingLabel, "true"),
   771  				},
   772  			)
   773  			if err != nil {
   774  				return err
   775  			}
   776  			if len(clusterRoles.Items) > 0 {
   777  				args := []string{"delete", "clusterrole", "--wait=false"}
   778  				for _, clusterRole := range clusterRoles.Items {
   779  					args = append(args, clusterRole.Name)
   780  				}
   781  				_, err := Run("", "kubectl", args...)
   782  				if err != nil {
   783  					return err
   784  				}
   785  			}
   786  
   787  			clusterRoles, err = KubeClientset.RbacV1().ClusterRoles().List(t.Context(), metav1.ListOptions{})
   788  			if err != nil {
   789  				return err
   790  			}
   791  			testClusterRoleNames := []string{}
   792  			for _, clusterRole := range clusterRoles.Items {
   793  				if strings.HasPrefix(clusterRole.Name, E2ETestPrefix) {
   794  					testClusterRoleNames = append(testClusterRoleNames, clusterRole.Name)
   795  				}
   796  			}
   797  			if len(testClusterRoleNames) > 0 {
   798  				args := []string{"delete", "clusterrole"}
   799  				args = append(args, testClusterRoleNames...)
   800  				_, err := Run("", "kubectl", args...)
   801  				if err != nil {
   802  					return err
   803  				}
   804  			}
   805  			return nil
   806  		},
   807  		func() error {
   808  			// delete old ClusterRoleBindings which were created by tests
   809  			clusterRoleBindings, err := KubeClientset.RbacV1().ClusterRoleBindings().List(t.Context(), metav1.ListOptions{})
   810  			if err != nil {
   811  				return err
   812  			}
   813  			testClusterRoleBindingNames := []string{}
   814  			for _, clusterRoleBinding := range clusterRoleBindings.Items {
   815  				if strings.HasPrefix(clusterRoleBinding.Name, E2ETestPrefix) {
   816  					testClusterRoleBindingNames = append(testClusterRoleBindingNames, clusterRoleBinding.Name)
   817  				}
   818  			}
   819  			if len(testClusterRoleBindingNames) > 0 {
   820  				args := []string{"delete", "clusterrolebinding"}
   821  				args = append(args, testClusterRoleBindingNames...)
   822  				_, err := Run("", "kubectl", args...)
   823  				if err != nil {
   824  					return err
   825  				}
   826  			}
   827  			return nil
   828  		},
   829  		func() error {
   830  			err := updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   831  				cm.Data = map[string]string{}
   832  				return nil
   833  			})
   834  			if err != nil {
   835  				return err
   836  			}
   837  			err = updateNotificationsConfigMap(func(cm *corev1.ConfigMap) error {
   838  				cm.Data = map[string]string{}
   839  				return nil
   840  			})
   841  			if err != nil {
   842  				return err
   843  			}
   844  			err = updateRBACConfigMap(func(cm *corev1.ConfigMap) error {
   845  				cm.Data = map[string]string{}
   846  				return nil
   847  			})
   848  			if err != nil {
   849  				return err
   850  			}
   851  			return updateGenericConfigMap(common.ArgoCDGPGKeysConfigMapName, func(cm *corev1.ConfigMap) error {
   852  				cm.Data = map[string]string{}
   853  				return nil
   854  			})
   855  		},
   856  		func() error {
   857  			// We can switch user and as result in previous state we will have non-admin user, this case should be reset
   858  			return LoginAs(adminUsername)
   859  		},
   860  	})
   861  
   862  	RunFunctionsInParallelAndCheckErrors(t, []func() error{
   863  		func() error {
   864  			err := SetProjectSpec("default", v1alpha1.AppProjectSpec{
   865  				OrphanedResources:        nil,
   866  				SourceRepos:              []string{"*"},
   867  				Destinations:             []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "*"}},
   868  				ClusterResourceWhitelist: []metav1.GroupKind{{Group: "*", Kind: "*"}},
   869  				SourceNamespaces:         []string{AppNamespace()},
   870  			})
   871  			if err != nil {
   872  				return err
   873  			}
   874  
   875  			// Create separate project for testing gpg signature verification
   876  			_, err = AppClientset.ArgoprojV1alpha1().AppProjects(TestNamespace()).Create(
   877  				t.Context(),
   878  				&v1alpha1.AppProject{
   879  					ObjectMeta: metav1.ObjectMeta{
   880  						Name: "gpg",
   881  					},
   882  					Spec: v1alpha1.AppProjectSpec{
   883  						OrphanedResources:        nil,
   884  						SourceRepos:              []string{"*"},
   885  						Destinations:             []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "*"}},
   886  						ClusterResourceWhitelist: []metav1.GroupKind{{Group: "*", Kind: "*"}},
   887  						SignatureKeys:            []v1alpha1.SignatureKey{{KeyID: GpgGoodKeyID}},
   888  						SourceNamespaces:         []string{AppNamespace()},
   889  					},
   890  				},
   891  				metav1.CreateOptions{},
   892  			)
   893  			return err
   894  		},
   895  		func() error {
   896  			err := os.RemoveAll(TmpDir)
   897  			if err != nil {
   898  				return err
   899  			}
   900  			_, err = Run("", "mkdir", "-p", TmpDir)
   901  			if err != nil {
   902  				return err
   903  			}
   904  
   905  			// create TLS and SSH certificate directories
   906  			if IsLocal() {
   907  				_, err = Run("", "mkdir", "-p", TmpDir+"/app/config/tls")
   908  				if err != nil {
   909  					return err
   910  				}
   911  				_, err = Run("", "mkdir", "-p", TmpDir+"/app/config/ssh")
   912  				if err != nil {
   913  					return err
   914  				}
   915  			}
   916  
   917  			// For signing during the tests
   918  			_, err = Run("", "mkdir", "-p", TmpDir+"/gpg")
   919  			if err != nil {
   920  				return err
   921  			}
   922  			_, err = Run("", "chmod", "0700", TmpDir+"/gpg")
   923  			if err != nil {
   924  				return err
   925  			}
   926  			prevGnuPGHome := os.Getenv("GNUPGHOME")
   927  			t.Setenv("GNUPGHOME", TmpDir+"/gpg")
   928  			//nolint:errcheck
   929  			Run("", "pkill", "-9", "gpg-agent")
   930  			_, err = Run("", "gpg", "--import", "../fixture/gpg/signingkey.asc")
   931  			if err != nil {
   932  				return err
   933  			}
   934  			t.Setenv("GNUPGHOME", prevGnuPGHome)
   935  
   936  			// recreate GPG directories
   937  			if IsLocal() {
   938  				_, err = Run("", "mkdir", "-p", TmpDir+"/app/config/gpg/source")
   939  				if err != nil {
   940  					return err
   941  				}
   942  				_, err = Run("", "mkdir", "-p", TmpDir+"/app/config/gpg/keys")
   943  				if err != nil {
   944  					return err
   945  				}
   946  				_, err = Run("", "chmod", "0700", TmpDir+"/app/config/gpg/keys")
   947  				if err != nil {
   948  					return err
   949  				}
   950  				_, err = Run("", "mkdir", "-p", TmpDir+PluginSockFilePath)
   951  				if err != nil {
   952  					return err
   953  				}
   954  				_, err = Run("", "chmod", "0700", TmpDir+PluginSockFilePath)
   955  				if err != nil {
   956  					return err
   957  				}
   958  			}
   959  
   960  			// set-up tmp repo, must have unique name
   961  			_, err = Run("", "cp", "-Rf", opt.testdata, repoDirectory())
   962  			if err != nil {
   963  				return err
   964  			}
   965  			_, err = Run(repoDirectory(), "chmod", "777", ".")
   966  			if err != nil {
   967  				return err
   968  			}
   969  			_, err = Run(repoDirectory(), "git", "init", "-b", "master")
   970  			if err != nil {
   971  				return err
   972  			}
   973  			_, err = Run(repoDirectory(), "git", "add", ".")
   974  			if err != nil {
   975  				return err
   976  			}
   977  			_, err = Run(repoDirectory(), "git", "commit", "-q", "-m", "initial commit")
   978  			if err != nil {
   979  				return err
   980  			}
   981  
   982  			if IsRemote() {
   983  				_, err = Run(repoDirectory(), "git", "remote", "add", "origin", os.Getenv("ARGOCD_E2E_GIT_SERVICE"))
   984  				if err != nil {
   985  					return err
   986  				}
   987  				_, err = Run(repoDirectory(), "git", "push", "origin", "master", "-f")
   988  				if err != nil {
   989  					return err
   990  				}
   991  			}
   992  			return nil
   993  		},
   994  		func() error {
   995  			// random id - unique across test runs
   996  			randString, err := rand.String(5)
   997  			if err != nil {
   998  				return err
   999  			}
  1000  			postFix := "-" + strings.ToLower(randString)
  1001  			id = t.Name() + postFix
  1002  			name = DnsFriendly(t.Name(), "")
  1003  			deploymentNamespace = DnsFriendly("argocd-e2e-"+t.Name(), postFix)
  1004  			// create namespace
  1005  			_, err = Run("", "kubectl", "create", "ns", DeploymentNamespace())
  1006  			if err != nil {
  1007  				return err
  1008  			}
  1009  			_, err = Run("", "kubectl", "label", "ns", DeploymentNamespace(), TestingLabel+"=true")
  1010  			return err
  1011  		},
  1012  	})
  1013  
  1014  	log.WithFields(log.Fields{
  1015  		"duration": time.Since(start),
  1016  		"name":     t.Name(),
  1017  		"id":       id,
  1018  		"username": "admin",
  1019  		"password": "password",
  1020  	}).Info("clean state")
  1021  }
  1022  
  1023  // RunCliWithRetry executes an Argo CD CLI command with retry logic.
  1024  func RunCliWithRetry(maxRetries int, args ...string) (string, error) {
  1025  	var out string
  1026  	var err error
  1027  	for i := 0; i < maxRetries; i++ {
  1028  		out, err = RunCli(args...)
  1029  		if err == nil {
  1030  			break
  1031  		}
  1032  		time.Sleep(time.Second)
  1033  	}
  1034  	return out, err
  1035  }
  1036  
  1037  // RunCli executes an Argo CD CLI command with no stdin input and default server authentication.
  1038  func RunCli(args ...string) (string, error) {
  1039  	return RunCliWithStdin("", false, args...)
  1040  }
  1041  
  1042  // RunCliWithStdin executes an Argo CD CLI command with optional stdin input and authentication.
  1043  func RunCliWithStdin(stdin string, isKubeConextOnlyCli bool, args ...string) (string, error) {
  1044  	if plainText {
  1045  		args = append(args, "--plaintext")
  1046  	}
  1047  
  1048  	// For commands executed with Kubernetes context server argument causes a conflict (for those commands server argument is for KubeAPI server), also authentication is not required
  1049  	if !isKubeConextOnlyCli {
  1050  		args = append(args, "--server", apiServerAddress, "--auth-token", token)
  1051  	}
  1052  
  1053  	args = append(args, "--insecure")
  1054  
  1055  	// Create a redactor that only redacts the auth token value
  1056  	redactor := func(text string) string {
  1057  		if token == "" {
  1058  			return text
  1059  		}
  1060  		// Use a more precise approach to only redact the exact auth token
  1061  		// Look for --auth-token followed by the exact token value
  1062  		authTokenPattern := "--auth-token " + token
  1063  		return strings.ReplaceAll(text, authTokenPattern, "--auth-token ******")
  1064  	}
  1065  
  1066  	return RunWithStdinWithRedactor(stdin, "", "../../dist/argocd", redactor, args...)
  1067  }
  1068  
  1069  // RunPluginCli executes an Argo CD CLI plugin with optional stdin input.
  1070  func RunPluginCli(stdin string, args ...string) (string, error) {
  1071  	return RunWithStdin(stdin, "", "../../dist/argocd", args...)
  1072  }
  1073  
  1074  func Patch(t *testing.T, path string, jsonPatch string) {
  1075  	t.Helper()
  1076  	log.WithFields(log.Fields{"path": path, "jsonPatch": jsonPatch}).Info("patching")
  1077  
  1078  	filename := filepath.Join(repoDirectory(), path)
  1079  	bytes, err := os.ReadFile(filename)
  1080  	require.NoError(t, err)
  1081  
  1082  	patch, err := jsonpatch.DecodePatch([]byte(jsonPatch))
  1083  	require.NoError(t, err)
  1084  
  1085  	isYaml := strings.HasSuffix(filename, ".yaml")
  1086  	if isYaml {
  1087  		log.Info("converting YAML to JSON")
  1088  		bytes, err = yaml.YAMLToJSON(bytes)
  1089  		require.NoError(t, err)
  1090  	}
  1091  
  1092  	log.WithFields(log.Fields{"bytes": string(bytes)}).Info("JSON")
  1093  
  1094  	bytes, err = patch.Apply(bytes)
  1095  	require.NoError(t, err)
  1096  
  1097  	if isYaml {
  1098  		log.Info("converting JSON back to YAML")
  1099  		bytes, err = yaml.JSONToYAML(bytes)
  1100  		require.NoError(t, err)
  1101  	}
  1102  
  1103  	require.NoError(t, os.WriteFile(filename, bytes, 0o644))
  1104  	errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "diff"))
  1105  	errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "commit", "-am", "patch"))
  1106  	if IsRemote() {
  1107  		errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "push", "-f", "origin", "master"))
  1108  	}
  1109  }
  1110  
  1111  func Delete(t *testing.T, path string) {
  1112  	t.Helper()
  1113  	log.WithFields(log.Fields{"path": path}).Info("deleting")
  1114  
  1115  	require.NoError(t, os.Remove(filepath.Join(repoDirectory(), path)))
  1116  
  1117  	errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "diff"))
  1118  	errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "commit", "-am", "delete"))
  1119  	if IsRemote() {
  1120  		errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "push", "-f", "origin", "master"))
  1121  	}
  1122  }
  1123  
  1124  func WriteFile(t *testing.T, path, contents string) {
  1125  	t.Helper()
  1126  	log.WithFields(log.Fields{"path": path}).Info("adding")
  1127  
  1128  	require.NoError(t, os.WriteFile(filepath.Join(repoDirectory(), path), []byte(contents), 0o644))
  1129  }
  1130  
  1131  func AddFile(t *testing.T, path, contents string) {
  1132  	t.Helper()
  1133  	WriteFile(t, path, contents)
  1134  
  1135  	errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "diff"))
  1136  	errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "add", "."))
  1137  	errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "commit", "-am", "add file"))
  1138  
  1139  	if IsRemote() {
  1140  		errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "push", "-f", "origin", "master"))
  1141  	}
  1142  }
  1143  
  1144  func AddSignedFile(t *testing.T, path, contents string) {
  1145  	t.Helper()
  1146  	WriteFile(t, path, contents)
  1147  
  1148  	prevGnuPGHome := os.Getenv("GNUPGHOME")
  1149  	t.Setenv("GNUPGHOME", TmpDir+"/gpg")
  1150  	errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "diff"))
  1151  	errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "add", "."))
  1152  	errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "-c", "user.signingkey="+GpgGoodKeyID, "commit", "-S", "-am", "add file"))
  1153  	t.Setenv("GNUPGHOME", prevGnuPGHome)
  1154  	if IsRemote() {
  1155  		errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "push", "-f", "origin", "master"))
  1156  	}
  1157  }
  1158  
  1159  func AddSignedTag(t *testing.T, name string) {
  1160  	t.Helper()
  1161  	prevGnuPGHome := os.Getenv("GNUPGHOME")
  1162  	t.Setenv("GNUPGHOME", TmpDir+"/gpg")
  1163  	defer t.Setenv("GNUPGHOME", prevGnuPGHome)
  1164  	errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "-c", "user.signingkey="+GpgGoodKeyID, "tag", "-sm", "add signed tag", name))
  1165  	if IsRemote() {
  1166  		errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "push", "--tags", "-f", "origin", "master"))
  1167  	}
  1168  }
  1169  
  1170  func AddTag(t *testing.T, name string) {
  1171  	t.Helper()
  1172  	prevGnuPGHome := os.Getenv("GNUPGHOME")
  1173  	t.Setenv("GNUPGHOME", TmpDir+"/gpg")
  1174  	defer t.Setenv("GNUPGHOME", prevGnuPGHome)
  1175  	errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "tag", name))
  1176  	if IsRemote() {
  1177  		errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "push", "--tags", "-f", "origin", "master"))
  1178  	}
  1179  }
  1180  
  1181  func AddTagWithForce(t *testing.T, name string) {
  1182  	t.Helper()
  1183  	prevGnuPGHome := os.Getenv("GNUPGHOME")
  1184  	t.Setenv("GNUPGHOME", TmpDir+"/gpg")
  1185  	defer t.Setenv("GNUPGHOME", prevGnuPGHome)
  1186  	errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "tag", "-f", name))
  1187  	if IsRemote() {
  1188  		errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "push", "--tags", "-f", "origin", "master"))
  1189  	}
  1190  }
  1191  
  1192  func AddAnnotatedTag(t *testing.T, name string, message string) {
  1193  	t.Helper()
  1194  	errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "tag", "-f", "-a", name, "-m", message))
  1195  	if IsRemote() {
  1196  		errors.NewHandler(t).FailOnErr(Run(repoDirectory(), "git", "push", "--tags", "-f", "origin", "master"))
  1197  	}
  1198  }
  1199  
  1200  // create the resource by creating using "kubectl apply", with bonus templating
  1201  func Declarative(t *testing.T, filename string, values any) (string, error) {
  1202  	t.Helper()
  1203  	bytes, err := os.ReadFile(path.Join("testdata", filename))
  1204  	require.NoError(t, err)
  1205  
  1206  	tmpFile, err := os.CreateTemp(t.TempDir(), "")
  1207  	require.NoError(t, err)
  1208  	_, err = tmpFile.WriteString(Tmpl(t, string(bytes), values))
  1209  	require.NoError(t, err)
  1210  	defer tmpFile.Close()
  1211  	return Run("", "kubectl", "-n", TestNamespace(), "apply", "-f", tmpFile.Name())
  1212  }
  1213  
  1214  func CreateSubmoduleRepos(t *testing.T, repoType string) {
  1215  	t.Helper()
  1216  	// set-up submodule repo
  1217  	errors.NewHandler(t).FailOnErr(Run("", "cp", "-Rf", "testdata/git-submodule/", submoduleDirectory()))
  1218  	errors.NewHandler(t).FailOnErr(Run(submoduleDirectory(), "chmod", "777", "."))
  1219  	errors.NewHandler(t).FailOnErr(Run(submoduleDirectory(), "git", "init", "-b", "master"))
  1220  	errors.NewHandler(t).FailOnErr(Run(submoduleDirectory(), "git", "add", "."))
  1221  	errors.NewHandler(t).FailOnErr(Run(submoduleDirectory(), "git", "commit", "-q", "-m", "initial commit"))
  1222  
  1223  	if IsRemote() {
  1224  		errors.NewHandler(t).FailOnErr(Run(submoduleDirectory(), "git", "remote", "add", "origin", os.Getenv("ARGOCD_E2E_GIT_SERVICE_SUBMODULE")))
  1225  		errors.NewHandler(t).FailOnErr(Run(submoduleDirectory(), "git", "push", "origin", "master", "-f"))
  1226  	}
  1227  
  1228  	// set-up submodule parent repo
  1229  	errors.NewHandler(t).FailOnErr(Run("", "mkdir", submoduleParentDirectory()))
  1230  	errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "chmod", "777", "."))
  1231  	errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "git", "init", "-b", "master"))
  1232  	errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "git", "add", "."))
  1233  	if IsRemote() {
  1234  		errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "git", "submodule", "add", "-b", "master", os.Getenv("ARGOCD_E2E_GIT_SERVICE_SUBMODULE"), "submodule/test"))
  1235  	} else {
  1236  		t.Setenv("GIT_ALLOW_PROTOCOL", "file")
  1237  		errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "git", "submodule", "add", "-b", "master", "../submodule.git", "submodule/test"))
  1238  	}
  1239  	switch repoType {
  1240  	case "ssh":
  1241  		errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "git", "config", "--file=.gitmodules", "submodule.submodule/test.url", RepoURL(RepoURLTypeSSHSubmodule)))
  1242  	case "https":
  1243  		errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "git", "config", "--file=.gitmodules", "submodule.submodule/test.url", RepoURL(RepoURLTypeHTTPSSubmodule)))
  1244  	}
  1245  	errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "git", "add", "--all"))
  1246  	errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "git", "commit", "-q", "-m", "commit with submodule"))
  1247  
  1248  	if IsRemote() {
  1249  		errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "git", "remote", "add", "origin", os.Getenv("ARGOCD_E2E_GIT_SERVICE_SUBMODULE_PARENT")))
  1250  		errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "git", "push", "origin", "master", "-f"))
  1251  	}
  1252  }
  1253  
  1254  func RemoveSubmodule(t *testing.T) {
  1255  	t.Helper()
  1256  	log.Info("removing submodule")
  1257  
  1258  	errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "git", "rm", "submodule/test"))
  1259  	errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "touch", "submodule/.gitkeep"))
  1260  	errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "git", "add", "submodule/.gitkeep"))
  1261  	errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "git", "commit", "-m", "remove submodule"))
  1262  	if IsRemote() {
  1263  		errors.NewHandler(t).FailOnErr(Run(submoduleParentDirectory(), "git", "push", "-f", "origin", "master"))
  1264  	}
  1265  }
  1266  
  1267  // RestartRepoServer performs a restart of the repo server deployment and waits
  1268  // until the rollout has completed.
  1269  func RestartRepoServer(t *testing.T) {
  1270  	t.Helper()
  1271  	if IsRemote() {
  1272  		log.Infof("Waiting for repo server to restart")
  1273  		prefix := os.Getenv("ARGOCD_E2E_NAME_PREFIX")
  1274  		workload := "argocd-repo-server"
  1275  		if prefix != "" {
  1276  			workload = prefix + "-repo-server"
  1277  		}
  1278  		errors.NewHandler(t).FailOnErr(Run("", "kubectl", "rollout", "-n", TestNamespace(), "restart", "deployment", workload))
  1279  		errors.NewHandler(t).FailOnErr(Run("", "kubectl", "rollout", "-n", TestNamespace(), "status", "deployment", workload))
  1280  		// wait longer to avoid error on s390x
  1281  		time.Sleep(5 * time.Second)
  1282  	}
  1283  }
  1284  
  1285  // RestartAPIServer performs a restart of the API server deployemt and waits
  1286  // until the rollout has completed.
  1287  func RestartAPIServer(t *testing.T) {
  1288  	t.Helper()
  1289  	if IsRemote() {
  1290  		log.Infof("Waiting for API server to restart")
  1291  		prefix := os.Getenv("ARGOCD_E2E_NAME_PREFIX")
  1292  		workload := "argocd-server"
  1293  		if prefix != "" {
  1294  			workload = prefix + "-server"
  1295  		}
  1296  		errors.NewHandler(t).FailOnErr(Run("", "kubectl", "rollout", "-n", TestNamespace(), "restart", "deployment", workload))
  1297  		errors.NewHandler(t).FailOnErr(Run("", "kubectl", "rollout", "-n", TestNamespace(), "status", "deployment", workload))
  1298  	}
  1299  }
  1300  
  1301  // LocalOrRemotePath selects a path for a given application based on whether
  1302  // tests are running local or remote.
  1303  func LocalOrRemotePath(base string) string {
  1304  	if IsRemote() {
  1305  		return base + "/remote"
  1306  	}
  1307  	return base + "/local"
  1308  }
  1309  
  1310  // SkipOnEnv allows to skip a test when a given environment variable is set.
  1311  // Environment variable names follow the ARGOCD_E2E_SKIP_<suffix> pattern,
  1312  // and must be set to the string value 'true' in order to skip a test.
  1313  func SkipOnEnv(t *testing.T, suffixes ...string) {
  1314  	t.Helper()
  1315  	for _, suffix := range suffixes {
  1316  		e := os.Getenv("ARGOCD_E2E_SKIP_" + suffix)
  1317  		if e == "true" {
  1318  			t.Skip()
  1319  		}
  1320  	}
  1321  }
  1322  
  1323  // SkipIfAlreadyRun skips a test if it has been already run by a previous
  1324  // test cycle and was recorded.
  1325  func SkipIfAlreadyRun(t *testing.T) {
  1326  	t.Helper()
  1327  	if _, ok := testsRun[t.Name()]; ok {
  1328  		t.Skip()
  1329  	}
  1330  }
  1331  
  1332  // RecordTestRun records a test that has been run successfully to a text file,
  1333  // so that it can be automatically skipped if requested.
  1334  func RecordTestRun(t *testing.T) {
  1335  	t.Helper()
  1336  	if t.Skipped() || t.Failed() {
  1337  		return
  1338  	}
  1339  	rf := os.Getenv("ARGOCD_E2E_RECORD")
  1340  	if rf == "" {
  1341  		return
  1342  	}
  1343  	log.Infof("Registering test execution at %s", rf)
  1344  	f, err := os.OpenFile(rf, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
  1345  	if err != nil {
  1346  		t.Fatalf("could not open record file %s: %v", rf, err)
  1347  	}
  1348  	defer func() {
  1349  		err := f.Close()
  1350  		if err != nil {
  1351  			t.Fatalf("could not close record file %s: %v", rf, err)
  1352  		}
  1353  	}()
  1354  	if _, err := f.WriteString(t.Name() + "\n"); err != nil {
  1355  		t.Fatalf("could not write to %s: %v", rf, err)
  1356  	}
  1357  }
  1358  
  1359  func GetApiServerAddress() string { //nolint:revive //FIXME(var-naming)
  1360  	return apiServerAddress
  1361  }
  1362  
  1363  func GetNotificationServerAddress() string {
  1364  	return defaultNotificationServer
  1365  }
  1366  
  1367  func GetToken() string {
  1368  	return token
  1369  }