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

     1  package fixture
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	goerrors "errors"
     7  	"fmt"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/argoproj/pkg/errors"
    17  	jsonpatch "github.com/evanphx/json-patch"
    18  	log "github.com/sirupsen/logrus"
    19  	corev1 "k8s.io/api/core/v1"
    20  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    21  	"k8s.io/client-go/dynamic"
    22  	"k8s.io/client-go/kubernetes"
    23  	"k8s.io/client-go/rest"
    24  	"k8s.io/client-go/tools/clientcmd"
    25  	"sigs.k8s.io/yaml"
    26  
    27  	"github.com/argoproj/argo-cd/v2/common"
    28  	"github.com/argoproj/argo-cd/v2/pkg/apiclient"
    29  	sessionpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/session"
    30  	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    31  	appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
    32  	"github.com/argoproj/argo-cd/v2/util/env"
    33  	. "github.com/argoproj/argo-cd/v2/util/errors"
    34  	grpcutil "github.com/argoproj/argo-cd/v2/util/grpc"
    35  	"github.com/argoproj/argo-cd/v2/util/io"
    36  	"github.com/argoproj/argo-cd/v2/util/rand"
    37  	"github.com/argoproj/argo-cd/v2/util/settings"
    38  )
    39  
    40  const (
    41  	defaultApiServer        = "localhost:8080"
    42  	defaultAdminPassword    = "password"
    43  	defaultAdminUsername    = "admin"
    44  	DefaultTestUserPassword = "password"
    45  	TestingLabel            = "e2e.argoproj.io"
    46  	ArgoCDNamespace         = "argocd-e2e"
    47  	ArgoCDAppNamespace      = "argocd-e2e-external"
    48  
    49  	// ensure all repos are in one directory tree, so we can easily clean them up
    50  	TmpDir             = "/tmp/argo-e2e"
    51  	repoDir            = "testdata.git"
    52  	submoduleDir       = "submodule.git"
    53  	submoduleParentDir = "submoduleParent.git"
    54  
    55  	GuestbookPath = "guestbook"
    56  
    57  	ProjectName = "argo-project"
    58  
    59  	// cmp plugin sock file path
    60  	PluginSockFilePath = "/app/config/plugin"
    61  
    62  	E2ETestPrefix = "e2e-test-"
    63  )
    64  
    65  const (
    66  	EnvAdminUsername           = "ARGOCD_E2E_ADMIN_USERNAME"
    67  	EnvAdminPassword           = "ARGOCD_E2E_ADMIN_PASSWORD"
    68  	EnvArgoCDServerName        = "ARGOCD_E2E_SERVER_NAME"
    69  	EnvArgoCDRedisHAProxyName  = "ARGOCD_E2E_REDIS_HAPROXY_NAME"
    70  	EnvArgoCDRedisName         = "ARGOCD_E2E_REDIS_NAME"
    71  	EnvArgoCDRepoServerName    = "ARGOCD_E2E_REPO_SERVER_NAME"
    72  	EnvArgoCDAppControllerName = "ARGOCD_E2E_APPLICATION_CONTROLLER_NAME"
    73  )
    74  
    75  var (
    76  	id                      string
    77  	deploymentNamespace     string
    78  	name                    string
    79  	KubeClientset           kubernetes.Interface
    80  	KubeConfig              *rest.Config
    81  	DynamicClientset        dynamic.Interface
    82  	AppClientset            appclientset.Interface
    83  	ArgoCDClientset         apiclient.Client
    84  	adminUsername           string
    85  	AdminPassword           string
    86  	apiServerAddress        string
    87  	token                   string
    88  	plainText               bool
    89  	testsRun                map[string]bool
    90  	argoCDServerName        string
    91  	argoCDRedisHAProxyName  string
    92  	argoCDRedisName         string
    93  	argoCDRepoServerName    string
    94  	argoCDAppControllerName string
    95  )
    96  
    97  type RepoURLType string
    98  
    99  type ACL struct {
   100  	Resource string
   101  	Action   string
   102  	Scope    string
   103  }
   104  
   105  const (
   106  	RepoURLTypeFile                 = "file"
   107  	RepoURLTypeHTTPS                = "https"
   108  	RepoURLTypeHTTPSOrg             = "https-org"
   109  	RepoURLTypeHTTPSClientCert      = "https-cc"
   110  	RepoURLTypeHTTPSSubmodule       = "https-sub"
   111  	RepoURLTypeHTTPSSubmoduleParent = "https-par"
   112  	RepoURLTypeSSH                  = "ssh"
   113  	RepoURLTypeSSHSubmodule         = "ssh-sub"
   114  	RepoURLTypeSSHSubmoduleParent   = "ssh-par"
   115  	RepoURLTypeHelm                 = "helm"
   116  	RepoURLTypeHelmParent           = "helm-par"
   117  	RepoURLTypeHelmOCI              = "helm-oci"
   118  	GitUsername                     = "admin"
   119  	GitPassword                     = "password"
   120  	GithubAppID                     = "2978632978"
   121  	GithubAppInstallationID         = "7893789433789"
   122  	GpgGoodKeyID                    = "D56C4FCA57A46444"
   123  	HelmOCIRegistryURL              = "localhost:5000/myrepo"
   124  )
   125  
   126  // TestNamespace returns the namespace where Argo CD E2E test instance will be
   127  // running in.
   128  func TestNamespace() string {
   129  	return GetEnvWithDefault("ARGOCD_E2E_NAMESPACE", ArgoCDNamespace)
   130  }
   131  
   132  func AppNamespace() string {
   133  	return GetEnvWithDefault("ARGOCD_E2E_APP_NAMESPACE", ArgoCDAppNamespace)
   134  }
   135  
   136  // getKubeConfig creates new kubernetes client config using specified config path and config overrides variables
   137  func getKubeConfig(configPath string, overrides clientcmd.ConfigOverrides) *rest.Config {
   138  	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
   139  	loadingRules.ExplicitPath = configPath
   140  	clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, &overrides, os.Stdin)
   141  
   142  	restConfig, err := clientConfig.ClientConfig()
   143  	CheckError(err)
   144  	return restConfig
   145  }
   146  
   147  func GetEnvWithDefault(envName, defaultValue string) string {
   148  	r := os.Getenv(envName)
   149  	if r == "" {
   150  		return defaultValue
   151  	}
   152  	return r
   153  }
   154  
   155  // IsRemote returns true when the tests are being run against a workload that
   156  // is running in a remote cluster.
   157  func IsRemote() bool {
   158  	return env.ParseBoolFromEnv("ARGOCD_E2E_REMOTE", false)
   159  }
   160  
   161  // IsLocal returns when the tests are being run against a local workload
   162  func IsLocal() bool {
   163  	return !IsRemote()
   164  }
   165  
   166  // creates e2e tests fixture: ensures that Application CRD is installed, creates temporal namespace, starts repo and api server,
   167  // configure currently available cluster.
   168  func init() {
   169  	// ensure we log all shell execs
   170  	log.SetLevel(log.DebugLevel)
   171  	// set-up variables
   172  	config := getKubeConfig("", clientcmd.ConfigOverrides{})
   173  	AppClientset = appclientset.NewForConfigOrDie(config)
   174  	KubeClientset = kubernetes.NewForConfigOrDie(config)
   175  	DynamicClientset = dynamic.NewForConfigOrDie(config)
   176  	KubeConfig = config
   177  
   178  	apiServerAddress = GetEnvWithDefault(apiclient.EnvArgoCDServer, defaultApiServer)
   179  	adminUsername = GetEnvWithDefault(EnvAdminUsername, defaultAdminUsername)
   180  	AdminPassword = GetEnvWithDefault(EnvAdminPassword, defaultAdminPassword)
   181  
   182  	argoCDServerName = GetEnvWithDefault(EnvArgoCDServerName, common.DefaultServerName)
   183  	argoCDRedisHAProxyName = GetEnvWithDefault(EnvArgoCDRedisHAProxyName, common.DefaultRedisHaProxyName)
   184  	argoCDRedisName = GetEnvWithDefault(EnvArgoCDRedisName, common.DefaultRedisName)
   185  	argoCDRepoServerName = GetEnvWithDefault(EnvArgoCDRepoServerName, common.DefaultRepoServerName)
   186  	argoCDAppControllerName = GetEnvWithDefault(EnvArgoCDAppControllerName, common.DefaultApplicationControllerName)
   187  
   188  	dialTime := 30 * time.Second
   189  	tlsTestResult, err := grpcutil.TestTLS(apiServerAddress, dialTime)
   190  	CheckError(err)
   191  
   192  	ArgoCDClientset, err = apiclient.NewClient(&apiclient.ClientOptions{
   193  		Insecure:          true,
   194  		ServerAddr:        apiServerAddress,
   195  		PlainText:         !tlsTestResult.TLS,
   196  		ServerName:        argoCDServerName,
   197  		RedisHaProxyName:  argoCDRedisHAProxyName,
   198  		RedisName:         argoCDRedisName,
   199  		RepoServerName:    argoCDRepoServerName,
   200  		AppControllerName: argoCDAppControllerName,
   201  	})
   202  	CheckError(err)
   203  
   204  	plainText = !tlsTestResult.TLS
   205  
   206  	LoginAs(adminUsername)
   207  
   208  	log.WithFields(log.Fields{"apiServerAddress": apiServerAddress}).Info("initialized")
   209  
   210  	// Preload a list of tests that should be skipped
   211  	testsRun = make(map[string]bool)
   212  	rf := os.Getenv("ARGOCD_E2E_RECORD")
   213  	if rf == "" {
   214  		return
   215  	}
   216  	f, err := os.Open(rf)
   217  	if err != nil {
   218  		if goerrors.Is(err, os.ErrNotExist) {
   219  			return
   220  		} else {
   221  			panic(fmt.Sprintf("Could not read record file %s: %v", rf, err))
   222  		}
   223  	}
   224  	defer func() {
   225  		err := f.Close()
   226  		if err != nil {
   227  			panic(fmt.Sprintf("Could not close record file %s: %v", rf, err))
   228  		}
   229  	}()
   230  	scanner := bufio.NewScanner(f)
   231  	for scanner.Scan() {
   232  		testsRun[scanner.Text()] = true
   233  	}
   234  
   235  }
   236  
   237  func loginAs(username, password string) {
   238  	closer, client, err := ArgoCDClientset.NewSessionClient()
   239  	CheckError(err)
   240  	defer io.Close(closer)
   241  
   242  	sessionResponse, err := client.Create(context.Background(), &sessionpkg.SessionCreateRequest{Username: username, Password: password})
   243  	CheckError(err)
   244  	token = sessionResponse.Token
   245  
   246  	ArgoCDClientset, err = apiclient.NewClient(&apiclient.ClientOptions{
   247  		Insecure:          true,
   248  		ServerAddr:        apiServerAddress,
   249  		AuthToken:         token,
   250  		PlainText:         plainText,
   251  		ServerName:        argoCDServerName,
   252  		RedisHaProxyName:  argoCDRedisHAProxyName,
   253  		RedisName:         argoCDRedisName,
   254  		RepoServerName:    argoCDRepoServerName,
   255  		AppControllerName: argoCDAppControllerName,
   256  	})
   257  	CheckError(err)
   258  }
   259  
   260  func LoginAs(username string) {
   261  	password := DefaultTestUserPassword
   262  	if username == "admin" {
   263  		password = AdminPassword
   264  	}
   265  	loginAs(username, password)
   266  }
   267  
   268  func Name() string {
   269  	return name
   270  }
   271  
   272  func repoDirectory() string {
   273  	return path.Join(TmpDir, repoDir)
   274  }
   275  
   276  func submoduleDirectory() string {
   277  	return path.Join(TmpDir, submoduleDir)
   278  }
   279  
   280  func submoduleParentDirectory() string {
   281  	return path.Join(TmpDir, submoduleParentDir)
   282  }
   283  
   284  const (
   285  	EnvRepoURLTypeSSH                  = "ARGOCD_E2E_REPO_SSH"
   286  	EnvRepoURLTypeSSHSubmodule         = "ARGOCD_E2E_REPO_SSH_SUBMODULE"
   287  	EnvRepoURLTypeSSHSubmoduleParent   = "ARGOCD_E2E_REPO_SSH_SUBMODULE_PARENT"
   288  	EnvRepoURLTypeHTTPS                = "ARGOCD_E2E_REPO_HTTPS"
   289  	EnvRepoURLTypeHTTPSOrg             = "ARGOCD_E2E_REPO_HTTPS_ORG"
   290  	EnvRepoURLTypeHTTPSClientCert      = "ARGOCD_E2E_REPO_HTTPS_CLIENT_CERT"
   291  	EnvRepoURLTypeHTTPSSubmodule       = "ARGOCD_E2E_REPO_HTTPS_SUBMODULE"
   292  	EnvRepoURLTypeHTTPSSubmoduleParent = "ARGOCD_E2E_REPO_HTTPS_SUBMODULE_PARENT"
   293  	EnvRepoURLTypeHelm                 = "ARGOCD_E2E_REPO_HELM"
   294  	EnvRepoURLDefault                  = "ARGOCD_E2E_REPO_DEFAULT"
   295  )
   296  
   297  func RepoURL(urlType RepoURLType) string {
   298  	switch urlType {
   299  	// Git server via SSH
   300  	case RepoURLTypeSSH:
   301  		return GetEnvWithDefault(EnvRepoURLTypeSSH, "ssh://root@localhost:2222/tmp/argo-e2e/testdata.git")
   302  	// Git submodule repo
   303  	case RepoURLTypeSSHSubmodule:
   304  		return GetEnvWithDefault(EnvRepoURLTypeSSHSubmodule, "ssh://root@localhost:2222/tmp/argo-e2e/submodule.git")
   305  	// Git submodule parent repo
   306  	case RepoURLTypeSSHSubmoduleParent:
   307  		return GetEnvWithDefault(EnvRepoURLTypeSSHSubmoduleParent, "ssh://root@localhost:2222/tmp/argo-e2e/submoduleParent.git")
   308  	// Git server via HTTPS
   309  	case RepoURLTypeHTTPS:
   310  		return GetEnvWithDefault(EnvRepoURLTypeHTTPS, "https://localhost:9443/argo-e2e/testdata.git")
   311  	// Git "organisation" via HTTPS
   312  	case RepoURLTypeHTTPSOrg:
   313  		return GetEnvWithDefault(EnvRepoURLTypeHTTPSOrg, "https://localhost:9443/argo-e2e")
   314  	// Git server via HTTPS - Client Cert protected
   315  	case RepoURLTypeHTTPSClientCert:
   316  		return GetEnvWithDefault(EnvRepoURLTypeHTTPSClientCert, "https://localhost:9444/argo-e2e/testdata.git")
   317  	case RepoURLTypeHTTPSSubmodule:
   318  		return GetEnvWithDefault(EnvRepoURLTypeHTTPSSubmodule, "https://localhost:9443/argo-e2e/submodule.git")
   319  		// Git submodule parent repo
   320  	case RepoURLTypeHTTPSSubmoduleParent:
   321  		return GetEnvWithDefault(EnvRepoURLTypeHTTPSSubmoduleParent, "https://localhost:9443/argo-e2e/submoduleParent.git")
   322  	// Default - file based Git repository
   323  	case RepoURLTypeHelm:
   324  		return GetEnvWithDefault(EnvRepoURLTypeHelm, "https://localhost:9444/argo-e2e/testdata.git/helm-repo/local")
   325  	// When Helm Repo has sub repos, this is the parent repo URL
   326  	case RepoURLTypeHelmParent:
   327  		return GetEnvWithDefault(EnvRepoURLTypeHelm, "https://localhost:9444/argo-e2e/testdata.git/helm-repo")
   328  	case RepoURLTypeHelmOCI:
   329  		return HelmOCIRegistryURL
   330  	default:
   331  		return GetEnvWithDefault(EnvRepoURLDefault, fmt.Sprintf("file://%s", repoDirectory()))
   332  	}
   333  }
   334  
   335  func RepoBaseURL(urlType RepoURLType) string {
   336  	return path.Base(RepoURL(urlType))
   337  }
   338  
   339  func DeploymentNamespace() string {
   340  	return deploymentNamespace
   341  }
   342  
   343  // creates a secret for the current test, this currently can only create a single secret
   344  func CreateSecret(username, password string) string {
   345  	secretName := fmt.Sprintf("argocd-e2e-%s", name)
   346  	FailOnErr(Run("", "kubectl", "create", "secret", "generic", secretName,
   347  		"--from-literal=username="+username,
   348  		"--from-literal=password="+password,
   349  		"-n", TestNamespace()))
   350  	FailOnErr(Run("", "kubectl", "label", "secret", secretName, TestingLabel+"=true", "-n", TestNamespace()))
   351  	return secretName
   352  }
   353  
   354  // Convenience wrapper for updating argocd-cm
   355  func updateSettingConfigMap(updater func(cm *corev1.ConfigMap) error) {
   356  	updateGenericConfigMap(common.ArgoCDConfigMapName, updater)
   357  }
   358  
   359  // Convenience wrapper for updating argocd-notifications-cm
   360  func updateNotificationsConfigMap(updater func(cm *corev1.ConfigMap) error) {
   361  	updateGenericConfigMap(common.ArgoCDNotificationsConfigMapName, updater)
   362  }
   363  
   364  // Convenience wrapper for updating argocd-cm-rbac
   365  func updateRBACConfigMap(updater func(cm *corev1.ConfigMap) error) {
   366  	updateGenericConfigMap(common.ArgoCDRBACConfigMapName, updater)
   367  }
   368  
   369  // Updates a given config map in argocd-e2e namespace
   370  func updateGenericConfigMap(name string, updater func(cm *corev1.ConfigMap) error) {
   371  	cm, err := KubeClientset.CoreV1().ConfigMaps(TestNamespace()).Get(context.Background(), name, v1.GetOptions{})
   372  	errors.CheckError(err)
   373  	if cm.Data == nil {
   374  		cm.Data = make(map[string]string)
   375  	}
   376  	errors.CheckError(updater(cm))
   377  	_, err = KubeClientset.CoreV1().ConfigMaps(TestNamespace()).Update(context.Background(), cm, v1.UpdateOptions{})
   378  	errors.CheckError(err)
   379  }
   380  
   381  func SetEnableManifestGeneration(val map[v1alpha1.ApplicationSourceType]bool) {
   382  	updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   383  		for k, v := range val {
   384  			cm.Data[fmt.Sprintf("%s.enable", strings.ToLower(string(k)))] = strconv.FormatBool(v)
   385  		}
   386  		return nil
   387  	})
   388  }
   389  
   390  func SetResourceOverrides(overrides map[string]v1alpha1.ResourceOverride) {
   391  	updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   392  		if len(overrides) > 0 {
   393  			yamlBytes, err := yaml.Marshal(overrides)
   394  			if err != nil {
   395  				return err
   396  			}
   397  			cm.Data["resource.customizations"] = string(yamlBytes)
   398  		} else {
   399  			delete(cm.Data, "resource.customizations")
   400  		}
   401  		return nil
   402  	})
   403  
   404  	SetResourceOverridesSplitKeys(overrides)
   405  }
   406  
   407  func SetTrackingMethod(trackingMethod string) {
   408  	updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   409  		cm.Data["application.resourceTrackingMethod"] = trackingMethod
   410  		return nil
   411  	})
   412  }
   413  
   414  func SetTrackingLabel(trackingLabel string) {
   415  	updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   416  		cm.Data["application.instanceLabelKey"] = trackingLabel
   417  		return nil
   418  	})
   419  }
   420  
   421  func SetResourceOverridesSplitKeys(overrides map[string]v1alpha1.ResourceOverride) {
   422  	updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   423  		for k, v := range overrides {
   424  			if v.HealthLua != "" {
   425  				cm.Data[getResourceOverrideSplitKey(k, "health")] = v.HealthLua
   426  			}
   427  			cm.Data[getResourceOverrideSplitKey(k, "useOpenLibs")] = strconv.FormatBool(v.UseOpenLibs)
   428  			if v.Actions != "" {
   429  				cm.Data[getResourceOverrideSplitKey(k, "actions")] = v.Actions
   430  			}
   431  			if len(v.IgnoreDifferences.JSONPointers) > 0 ||
   432  				len(v.IgnoreDifferences.JQPathExpressions) > 0 ||
   433  				len(v.IgnoreDifferences.ManagedFieldsManagers) > 0 {
   434  				yamlBytes, err := yaml.Marshal(v.IgnoreDifferences)
   435  				if err != nil {
   436  					return err
   437  				}
   438  				cm.Data[getResourceOverrideSplitKey(k, "ignoreDifferences")] = string(yamlBytes)
   439  			}
   440  			if len(v.KnownTypeFields) > 0 {
   441  				yamlBytes, err := yaml.Marshal(v.KnownTypeFields)
   442  				if err != nil {
   443  					return err
   444  				}
   445  				cm.Data[getResourceOverrideSplitKey(k, "knownTypeFields")] = string(yamlBytes)
   446  			}
   447  		}
   448  		return nil
   449  	})
   450  }
   451  
   452  func getResourceOverrideSplitKey(key string, customizeType string) string {
   453  	groupKind := key
   454  	parts := strings.Split(key, "/")
   455  	if len(parts) == 2 {
   456  		groupKind = fmt.Sprintf("%s_%s", parts[0], parts[1])
   457  	}
   458  	return fmt.Sprintf("resource.customizations.%s.%s", customizeType, groupKind)
   459  }
   460  
   461  func SetAccounts(accounts map[string][]string) {
   462  	updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   463  		for k, v := range accounts {
   464  			cm.Data[fmt.Sprintf("accounts.%s", k)] = strings.Join(v, ",")
   465  		}
   466  		return nil
   467  	})
   468  }
   469  
   470  func SetPermissions(permissions []ACL, username string, roleName string) {
   471  	updateRBACConfigMap(func(cm *corev1.ConfigMap) error {
   472  		var aclstr string
   473  
   474  		for _, permission := range permissions {
   475  			aclstr += fmt.Sprintf("p, role:%s, %s, %s, %s, allow \n", roleName, permission.Resource, permission.Action, permission.Scope)
   476  		}
   477  
   478  		aclstr += fmt.Sprintf("g, %s, role:%s", username, roleName)
   479  		cm.Data["policy.csv"] = aclstr
   480  
   481  		return nil
   482  	})
   483  }
   484  
   485  func SetResourceFilter(filters settings.ResourcesFilter) {
   486  	updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   487  		exclusions, err := yaml.Marshal(filters.ResourceExclusions)
   488  		if err != nil {
   489  			return err
   490  		}
   491  		inclusions, err := yaml.Marshal(filters.ResourceInclusions)
   492  		if err != nil {
   493  			return err
   494  		}
   495  		cm.Data["resource.exclusions"] = string(exclusions)
   496  		cm.Data["resource.inclusions"] = string(inclusions)
   497  		return nil
   498  	})
   499  }
   500  
   501  func SetHelmRepos(repos ...settings.HelmRepoCredentials) {
   502  	updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   503  		yamlBytes, err := yaml.Marshal(repos)
   504  		if err != nil {
   505  			return err
   506  		}
   507  		cm.Data["helm.repositories"] = string(yamlBytes)
   508  		return nil
   509  	})
   510  }
   511  
   512  func SetRepos(repos ...settings.RepositoryCredentials) {
   513  	updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   514  		yamlBytes, err := yaml.Marshal(repos)
   515  		if err != nil {
   516  			return err
   517  		}
   518  		cm.Data["repositories"] = string(yamlBytes)
   519  		return nil
   520  	})
   521  }
   522  
   523  func SetProjectSpec(project string, spec v1alpha1.AppProjectSpec) {
   524  	proj, err := AppClientset.ArgoprojV1alpha1().AppProjects(TestNamespace()).Get(context.Background(), project, v1.GetOptions{})
   525  	errors.CheckError(err)
   526  	proj.Spec = spec
   527  	_, err = AppClientset.ArgoprojV1alpha1().AppProjects(TestNamespace()).Update(context.Background(), proj, v1.UpdateOptions{})
   528  	errors.CheckError(err)
   529  }
   530  
   531  func SetParamInSettingConfigMap(key, value string) {
   532  	updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   533  		cm.Data[key] = value
   534  		return nil
   535  	})
   536  }
   537  
   538  func SetParamInNotificationsConfigMap(key, value string) {
   539  	updateNotificationsConfigMap(func(cm *corev1.ConfigMap) error {
   540  		cm.Data[key] = value
   541  		return nil
   542  	})
   543  }
   544  
   545  type TestOption func(option *testOption)
   546  
   547  type testOption struct {
   548  	testdata string
   549  }
   550  
   551  func newTestOption(opts ...TestOption) *testOption {
   552  	to := &testOption{
   553  		testdata: "testdata",
   554  	}
   555  	for _, opt := range opts {
   556  		opt(to)
   557  	}
   558  	return to
   559  }
   560  
   561  func WithTestData(testdata string) TestOption {
   562  	return func(option *testOption) {
   563  		option.testdata = testdata
   564  	}
   565  }
   566  
   567  func EnsureCleanState(t *testing.T, opts ...TestOption) {
   568  	opt := newTestOption(opts...)
   569  	// In large scenarios, we can skip tests that already run
   570  	SkipIfAlreadyRun(t)
   571  	// Register this test after it has been run & was successfull
   572  	t.Cleanup(func() {
   573  		RecordTestRun(t)
   574  	})
   575  
   576  	start := time.Now()
   577  
   578  	policy := v1.DeletePropagationBackground
   579  	// delete resources
   580  	// kubectl delete apps --all
   581  	CheckError(AppClientset.ArgoprojV1alpha1().Applications(TestNamespace()).DeleteCollection(context.Background(), v1.DeleteOptions{PropagationPolicy: &policy}, v1.ListOptions{}))
   582  	CheckError(AppClientset.ArgoprojV1alpha1().Applications(AppNamespace()).DeleteCollection(context.Background(), v1.DeleteOptions{PropagationPolicy: &policy}, v1.ListOptions{}))
   583  	// kubectl delete appprojects --field-selector metadata.name!=default
   584  	CheckError(AppClientset.ArgoprojV1alpha1().AppProjects(TestNamespace()).DeleteCollection(context.Background(),
   585  		v1.DeleteOptions{PropagationPolicy: &policy}, v1.ListOptions{FieldSelector: "metadata.name!=default"}))
   586  	// kubectl delete secrets -l argocd.argoproj.io/secret-type=repo-config
   587  	CheckError(KubeClientset.CoreV1().Secrets(TestNamespace()).DeleteCollection(context.Background(),
   588  		v1.DeleteOptions{PropagationPolicy: &policy}, v1.ListOptions{LabelSelector: common.LabelKeySecretType + "=" + common.LabelValueSecretTypeRepository}))
   589  	// kubectl delete secrets -l argocd.argoproj.io/secret-type=repo-creds
   590  	CheckError(KubeClientset.CoreV1().Secrets(TestNamespace()).DeleteCollection(context.Background(),
   591  		v1.DeleteOptions{PropagationPolicy: &policy}, v1.ListOptions{LabelSelector: common.LabelKeySecretType + "=" + common.LabelValueSecretTypeRepoCreds}))
   592  	// kubectl delete secrets -l argocd.argoproj.io/secret-type=cluster
   593  	CheckError(KubeClientset.CoreV1().Secrets(TestNamespace()).DeleteCollection(context.Background(),
   594  		v1.DeleteOptions{PropagationPolicy: &policy}, v1.ListOptions{LabelSelector: common.LabelKeySecretType + "=" + common.LabelValueSecretTypeCluster}))
   595  	// kubectl delete secrets -l e2e.argoproj.io=true
   596  	CheckError(KubeClientset.CoreV1().Secrets(TestNamespace()).DeleteCollection(context.Background(),
   597  		v1.DeleteOptions{PropagationPolicy: &policy}, v1.ListOptions{LabelSelector: TestingLabel + "=true"}))
   598  
   599  	FailOnErr(Run("", "kubectl", "delete", "ns", "-l", TestingLabel+"=true", "--field-selector", "status.phase=Active", "--wait=false"))
   600  	FailOnErr(Run("", "kubectl", "delete", "crd", "-l", TestingLabel+"=true", "--wait=false"))
   601  	FailOnErr(Run("", "kubectl", "delete", "clusterroles", "-l", TestingLabel+"=true", "--wait=false"))
   602  
   603  	// reset settings
   604  	updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
   605  		cm.Data = map[string]string{}
   606  		return nil
   607  	})
   608  
   609  	updateNotificationsConfigMap(func(cm *corev1.ConfigMap) error {
   610  		cm.Data = map[string]string{}
   611  		return nil
   612  	})
   613  
   614  	// reset rbac
   615  	updateRBACConfigMap(func(cm *corev1.ConfigMap) error {
   616  		cm.Data = map[string]string{}
   617  		return nil
   618  	})
   619  
   620  	// We can switch user and as result in previous state we will have non-admin user, this case should be reset
   621  	LoginAs(adminUsername)
   622  
   623  	// reset gpg-keys config map
   624  	updateGenericConfigMap(common.ArgoCDGPGKeysConfigMapName, func(cm *corev1.ConfigMap) error {
   625  		cm.Data = map[string]string{}
   626  		return nil
   627  	})
   628  
   629  	SetProjectSpec("default", v1alpha1.AppProjectSpec{
   630  		OrphanedResources:        nil,
   631  		SourceRepos:              []string{"*"},
   632  		Destinations:             []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "*"}},
   633  		ClusterResourceWhitelist: []v1.GroupKind{{Group: "*", Kind: "*"}},
   634  		SourceNamespaces:         []string{AppNamespace()},
   635  	})
   636  
   637  	// Create separate project for testing gpg signature verification
   638  	FailOnErr(RunCli("proj", "create", "gpg"))
   639  	SetProjectSpec("gpg", v1alpha1.AppProjectSpec{
   640  		OrphanedResources:        nil,
   641  		SourceRepos:              []string{"*"},
   642  		Destinations:             []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "*"}},
   643  		ClusterResourceWhitelist: []v1.GroupKind{{Group: "*", Kind: "*"}},
   644  		SignatureKeys:            []v1alpha1.SignatureKey{{KeyID: GpgGoodKeyID}},
   645  		SourceNamespaces:         []string{AppNamespace()},
   646  	})
   647  
   648  	// Recreate temp dir
   649  	CheckError(os.RemoveAll(TmpDir))
   650  	FailOnErr(Run("", "mkdir", "-p", TmpDir))
   651  
   652  	// random id - unique across test runs
   653  	randString, err := rand.String(5)
   654  	CheckError(err)
   655  	postFix := "-" + strings.ToLower(randString)
   656  	id = t.Name() + postFix
   657  	name = DnsFriendly(t.Name(), "")
   658  	deploymentNamespace = DnsFriendly(fmt.Sprintf("argocd-e2e-%s", t.Name()), postFix)
   659  
   660  	// create TLS and SSH certificate directories
   661  	if IsLocal() {
   662  		FailOnErr(Run("", "mkdir", "-p", TmpDir+"/app/config/tls"))
   663  		FailOnErr(Run("", "mkdir", "-p", TmpDir+"/app/config/ssh"))
   664  	}
   665  
   666  	// For signing during the tests
   667  	FailOnErr(Run("", "mkdir", "-p", TmpDir+"/gpg"))
   668  	FailOnErr(Run("", "chmod", "0700", TmpDir+"/gpg"))
   669  	prevGnuPGHome := os.Getenv("GNUPGHOME")
   670  	os.Setenv("GNUPGHOME", TmpDir+"/gpg")
   671  	// nolint:errcheck
   672  	Run("", "pkill", "-9", "gpg-agent")
   673  	FailOnErr(Run("", "gpg", "--import", "../fixture/gpg/signingkey.asc"))
   674  	os.Setenv("GNUPGHOME", prevGnuPGHome)
   675  
   676  	// recreate GPG directories
   677  	if IsLocal() {
   678  		FailOnErr(Run("", "mkdir", "-p", TmpDir+"/app/config/gpg/source"))
   679  		FailOnErr(Run("", "mkdir", "-p", TmpDir+"/app/config/gpg/keys"))
   680  		FailOnErr(Run("", "chmod", "0700", TmpDir+"/app/config/gpg/keys"))
   681  		FailOnErr(Run("", "mkdir", "-p", TmpDir+PluginSockFilePath))
   682  		FailOnErr(Run("", "chmod", "0700", TmpDir+PluginSockFilePath))
   683  	}
   684  
   685  	// set-up tmp repo, must have unique name
   686  	FailOnErr(Run("", "cp", "-Rf", opt.testdata, repoDirectory()))
   687  	FailOnErr(Run(repoDirectory(), "chmod", "777", "."))
   688  	FailOnErr(Run(repoDirectory(), "git", "init", "-b", "master"))
   689  	FailOnErr(Run(repoDirectory(), "git", "add", "."))
   690  	FailOnErr(Run(repoDirectory(), "git", "commit", "-q", "-m", "initial commit"))
   691  
   692  	if IsRemote() {
   693  		FailOnErr(Run(repoDirectory(), "git", "remote", "add", "origin", os.Getenv("ARGOCD_E2E_GIT_SERVICE")))
   694  		FailOnErr(Run(repoDirectory(), "git", "push", "origin", "master", "-f"))
   695  	}
   696  
   697  	// create namespace
   698  	FailOnErr(Run("", "kubectl", "create", "ns", DeploymentNamespace()))
   699  	FailOnErr(Run("", "kubectl", "label", "ns", DeploymentNamespace(), TestingLabel+"=true"))
   700  
   701  	// delete old namespaces used by E2E tests
   702  	namespaces, err := KubeClientset.CoreV1().Namespaces().List(context.Background(), v1.ListOptions{})
   703  	CheckError(err)
   704  	for _, namespace := range namespaces.Items {
   705  		if strings.HasPrefix(namespace.Name, E2ETestPrefix) {
   706  			FailOnErr(Run("", "kubectl", "delete", "ns", namespace.Name))
   707  		}
   708  	}
   709  
   710  	// delete old ClusterRoles that begin with "e2e-test-" prefix (E2ETestPrefix), which were created by tests
   711  	clusterRoles, err := KubeClientset.RbacV1().ClusterRoles().List(context.Background(), v1.ListOptions{})
   712  	CheckError(err)
   713  	for _, clusterRole := range clusterRoles.Items {
   714  		if strings.HasPrefix(clusterRole.Name, E2ETestPrefix) {
   715  			FailOnErr(Run("", "kubectl", "delete", "clusterrole", clusterRole.Name))
   716  		}
   717  	}
   718  
   719  	// delete old ClusterRoleBindings that begin with "e2e-test-prefix", which were created by E2E tests
   720  	clusterRoleBindings, err := KubeClientset.RbacV1().ClusterRoleBindings().List(context.Background(), v1.ListOptions{})
   721  	CheckError(err)
   722  	for _, clusterRoleBinding := range clusterRoleBindings.Items {
   723  		if strings.HasPrefix(clusterRoleBinding.Name, E2ETestPrefix) {
   724  			FailOnErr(Run("", "kubectl", "delete", "clusterrolebinding", clusterRoleBinding.Name))
   725  		}
   726  	}
   727  
   728  	log.WithFields(log.Fields{"duration": time.Since(start), "name": t.Name(), "id": id, "username": "admin", "password": "password"}).Info("clean state")
   729  }
   730  
   731  func RunCliWithRetry(maxRetries int, args ...string) (string, error) {
   732  	var out string
   733  	var err error
   734  	for i := 0; i < maxRetries; i++ {
   735  		out, err = RunCli(args...)
   736  		if err == nil {
   737  			break
   738  		}
   739  		time.Sleep(time.Second)
   740  	}
   741  	return out, err
   742  }
   743  
   744  func RunCli(args ...string) (string, error) {
   745  	return RunCliWithStdin("", args...)
   746  }
   747  
   748  func RunCliWithStdin(stdin string, args ...string) (string, error) {
   749  	if plainText {
   750  		args = append(args, "--plaintext")
   751  	}
   752  
   753  	args = append(args, "--server", apiServerAddress, "--auth-token", token, "--insecure")
   754  
   755  	return RunWithStdin(stdin, "", "../../dist/argocd", args...)
   756  }
   757  
   758  func Patch(path string, jsonPatch string) {
   759  
   760  	log.WithFields(log.Fields{"path": path, "jsonPatch": jsonPatch}).Info("patching")
   761  
   762  	filename := filepath.Join(repoDirectory(), path)
   763  	bytes, err := os.ReadFile(filename)
   764  	CheckError(err)
   765  
   766  	patch, err := jsonpatch.DecodePatch([]byte(jsonPatch))
   767  	CheckError(err)
   768  
   769  	isYaml := strings.HasSuffix(filename, ".yaml")
   770  	if isYaml {
   771  		log.Info("converting YAML to JSON")
   772  		bytes, err = yaml.YAMLToJSON(bytes)
   773  		CheckError(err)
   774  	}
   775  
   776  	log.WithFields(log.Fields{"bytes": string(bytes)}).Info("JSON")
   777  
   778  	bytes, err = patch.Apply(bytes)
   779  	CheckError(err)
   780  
   781  	if isYaml {
   782  		log.Info("converting JSON back to YAML")
   783  		bytes, err = yaml.JSONToYAML(bytes)
   784  		CheckError(err)
   785  	}
   786  
   787  	CheckError(os.WriteFile(filename, bytes, 0644))
   788  	FailOnErr(Run(repoDirectory(), "git", "diff"))
   789  	FailOnErr(Run(repoDirectory(), "git", "commit", "-am", "patch"))
   790  	if IsRemote() {
   791  		FailOnErr(Run(repoDirectory(), "git", "push", "-f", "origin", "master"))
   792  	}
   793  }
   794  
   795  func Delete(path string) {
   796  
   797  	log.WithFields(log.Fields{"path": path}).Info("deleting")
   798  
   799  	CheckError(os.Remove(filepath.Join(repoDirectory(), path)))
   800  
   801  	FailOnErr(Run(repoDirectory(), "git", "diff"))
   802  	FailOnErr(Run(repoDirectory(), "git", "commit", "-am", "delete"))
   803  	if IsRemote() {
   804  		FailOnErr(Run(repoDirectory(), "git", "push", "-f", "origin", "master"))
   805  	}
   806  }
   807  
   808  func WriteFile(path, contents string) {
   809  	log.WithFields(log.Fields{"path": path}).Info("adding")
   810  
   811  	CheckError(os.WriteFile(filepath.Join(repoDirectory(), path), []byte(contents), 0644))
   812  }
   813  
   814  func AddFile(path, contents string) {
   815  
   816  	WriteFile(path, contents)
   817  
   818  	FailOnErr(Run(repoDirectory(), "git", "diff"))
   819  	FailOnErr(Run(repoDirectory(), "git", "add", "."))
   820  	FailOnErr(Run(repoDirectory(), "git", "commit", "-am", "add file"))
   821  
   822  	if IsRemote() {
   823  		FailOnErr(Run(repoDirectory(), "git", "push", "-f", "origin", "master"))
   824  	}
   825  }
   826  
   827  func AddSignedFile(path, contents string) {
   828  	WriteFile(path, contents)
   829  
   830  	prevGnuPGHome := os.Getenv("GNUPGHOME")
   831  	os.Setenv("GNUPGHOME", TmpDir+"/gpg")
   832  	FailOnErr(Run(repoDirectory(), "git", "diff"))
   833  	FailOnErr(Run(repoDirectory(), "git", "add", "."))
   834  	FailOnErr(Run(repoDirectory(), "git", "-c", fmt.Sprintf("user.signingkey=%s", GpgGoodKeyID), "commit", "-S", "-am", "add file"))
   835  	os.Setenv("GNUPGHOME", prevGnuPGHome)
   836  	if IsRemote() {
   837  		FailOnErr(Run(repoDirectory(), "git", "push", "-f", "origin", "master"))
   838  	}
   839  }
   840  
   841  func AddSignedTag(name string) {
   842  	prevGnuPGHome := os.Getenv("GNUPGHOME")
   843  	os.Setenv("GNUPGHOME", TmpDir+"/gpg")
   844  	defer os.Setenv("GNUPGHOME", prevGnuPGHome)
   845  	FailOnErr(Run(repoDirectory(), "git", "-c", fmt.Sprintf("user.signingkey=%s", GpgGoodKeyID), "tag", "-sm", "add signed tag", name))
   846  	if IsRemote() {
   847  		FailOnErr(Run(repoDirectory(), "git", "push", "--tags", "-f", "origin", "master"))
   848  	}
   849  }
   850  
   851  func AddTag(name string) {
   852  	prevGnuPGHome := os.Getenv("GNUPGHOME")
   853  	os.Setenv("GNUPGHOME", TmpDir+"/gpg")
   854  	defer os.Setenv("GNUPGHOME", prevGnuPGHome)
   855  	FailOnErr(Run(repoDirectory(), "git", "tag", name))
   856  	if IsRemote() {
   857  		FailOnErr(Run(repoDirectory(), "git", "push", "--tags", "-f", "origin", "master"))
   858  	}
   859  }
   860  
   861  // create the resource by creating using "kubectl apply", with bonus templating
   862  func Declarative(filename string, values interface{}) (string, error) {
   863  
   864  	bytes, err := os.ReadFile(path.Join("testdata", filename))
   865  	CheckError(err)
   866  
   867  	tmpFile, err := os.CreateTemp("", "")
   868  	CheckError(err)
   869  	_, err = tmpFile.WriteString(Tmpl(string(bytes), values))
   870  	CheckError(err)
   871  	defer tmpFile.Close()
   872  	return Run("", "kubectl", "-n", TestNamespace(), "apply", "-f", tmpFile.Name())
   873  }
   874  
   875  func CreateSubmoduleRepos(repoType string) {
   876  	// set-up submodule repo
   877  	FailOnErr(Run("", "cp", "-Rf", "testdata/git-submodule/", submoduleDirectory()))
   878  	FailOnErr(Run(submoduleDirectory(), "chmod", "777", "."))
   879  	FailOnErr(Run(submoduleDirectory(), "git", "init", "-b", "master"))
   880  	FailOnErr(Run(submoduleDirectory(), "git", "add", "."))
   881  	FailOnErr(Run(submoduleDirectory(), "git", "commit", "-q", "-m", "initial commit"))
   882  
   883  	if IsRemote() {
   884  		FailOnErr(Run(submoduleDirectory(), "git", "remote", "add", "origin", os.Getenv("ARGOCD_E2E_GIT_SERVICE_SUBMODULE")))
   885  		FailOnErr(Run(submoduleDirectory(), "git", "push", "origin", "master", "-f"))
   886  	}
   887  
   888  	// set-up submodule parent repo
   889  	FailOnErr(Run("", "mkdir", submoduleParentDirectory()))
   890  	FailOnErr(Run(submoduleParentDirectory(), "chmod", "777", "."))
   891  	FailOnErr(Run(submoduleParentDirectory(), "git", "init", "-b", "master"))
   892  	FailOnErr(Run(submoduleParentDirectory(), "git", "add", "."))
   893  	if IsRemote() {
   894  		FailOnErr(Run(submoduleParentDirectory(), "git", "submodule", "add", "-b", "master", os.Getenv("ARGOCD_E2E_GIT_SERVICE_SUBMODULE"), "submodule/test"))
   895  	} else {
   896  		oldAllowProtocol, isAllowProtocolSet := os.LookupEnv("GIT_ALLOW_PROTOCOL")
   897  		CheckError(os.Setenv("GIT_ALLOW_PROTOCOL", "file"))
   898  		FailOnErr(Run(submoduleParentDirectory(), "git", "submodule", "add", "-b", "master", "../submodule.git", "submodule/test"))
   899  		if isAllowProtocolSet {
   900  			CheckError(os.Setenv("GIT_ALLOW_PROTOCOL", oldAllowProtocol))
   901  		} else {
   902  			CheckError(os.Unsetenv("GIT_ALLOW_PROTOCOL"))
   903  		}
   904  	}
   905  	if repoType == "ssh" {
   906  		FailOnErr(Run(submoduleParentDirectory(), "git", "config", "--file=.gitmodules", "submodule.submodule/test.url", RepoURL(RepoURLTypeSSHSubmodule)))
   907  	} else if repoType == "https" {
   908  		FailOnErr(Run(submoduleParentDirectory(), "git", "config", "--file=.gitmodules", "submodule.submodule/test.url", RepoURL(RepoURLTypeHTTPSSubmodule)))
   909  	}
   910  	FailOnErr(Run(submoduleParentDirectory(), "git", "add", "--all"))
   911  	FailOnErr(Run(submoduleParentDirectory(), "git", "commit", "-q", "-m", "commit with submodule"))
   912  
   913  	if IsRemote() {
   914  		FailOnErr(Run(submoduleParentDirectory(), "git", "remote", "add", "origin", os.Getenv("ARGOCD_E2E_GIT_SERVICE_SUBMODULE_PARENT")))
   915  		FailOnErr(Run(submoduleParentDirectory(), "git", "push", "origin", "master", "-f"))
   916  	}
   917  }
   918  
   919  func RemoveSubmodule() {
   920  	log.Info("removing submodule")
   921  
   922  	FailOnErr(Run(submoduleParentDirectory(), "git", "rm", "submodule/test"))
   923  	FailOnErr(Run(submoduleParentDirectory(), "touch", "submodule/.gitkeep"))
   924  	FailOnErr(Run(submoduleParentDirectory(), "git", "add", "submodule/.gitkeep"))
   925  	FailOnErr(Run(submoduleParentDirectory(), "git", "commit", "-m", "remove submodule"))
   926  	if IsRemote() {
   927  		FailOnErr(Run(submoduleParentDirectory(), "git", "push", "-f", "origin", "master"))
   928  	}
   929  }
   930  
   931  // RestartRepoServer performs a restart of the repo server deployment and waits
   932  // until the rollout has completed.
   933  func RestartRepoServer() {
   934  	if IsRemote() {
   935  		log.Infof("Waiting for repo server to restart")
   936  		prefix := os.Getenv("ARGOCD_E2E_NAME_PREFIX")
   937  		workload := "argocd-repo-server"
   938  		if prefix != "" {
   939  			workload = prefix + "-repo-server"
   940  		}
   941  		FailOnErr(Run("", "kubectl", "rollout", "-n", TestNamespace(), "restart", "deployment", workload))
   942  		FailOnErr(Run("", "kubectl", "rollout", "-n", TestNamespace(), "status", "deployment", workload))
   943  		// wait longer to avoid error on s390x
   944  		time.Sleep(10 * time.Second)
   945  	}
   946  }
   947  
   948  // RestartAPIServer performs a restart of the API server deployemt and waits
   949  // until the rollout has completed.
   950  func RestartAPIServer() {
   951  	if IsRemote() {
   952  		log.Infof("Waiting for API server to restart")
   953  		prefix := os.Getenv("ARGOCD_E2E_NAME_PREFIX")
   954  		workload := "argocd-server"
   955  		if prefix != "" {
   956  			workload = prefix + "-server"
   957  		}
   958  		FailOnErr(Run("", "kubectl", "rollout", "-n", TestNamespace(), "restart", "deployment", workload))
   959  		FailOnErr(Run("", "kubectl", "rollout", "-n", TestNamespace(), "status", "deployment", workload))
   960  	}
   961  }
   962  
   963  // LocalOrRemotePath selects a path for a given application based on whether
   964  // tests are running local or remote.
   965  func LocalOrRemotePath(base string) string {
   966  	if IsRemote() {
   967  		return base + "/remote"
   968  	} else {
   969  		return base + "/local"
   970  	}
   971  }
   972  
   973  // SkipOnEnv allows to skip a test when a given environment variable is set.
   974  // Environment variable names follow the ARGOCD_E2E_SKIP_<suffix> pattern,
   975  // and must be set to the string value 'true' in order to skip a test.
   976  func SkipOnEnv(t *testing.T, suffixes ...string) {
   977  	for _, suffix := range suffixes {
   978  		e := os.Getenv("ARGOCD_E2E_SKIP_" + suffix)
   979  		if e == "true" {
   980  			t.Skip()
   981  		}
   982  	}
   983  }
   984  
   985  // SkipIfAlreadyRun skips a test if it has been already run by a previous
   986  // test cycle and was recorded.
   987  func SkipIfAlreadyRun(t *testing.T) {
   988  	if _, ok := testsRun[t.Name()]; ok {
   989  		t.Skip()
   990  	}
   991  }
   992  
   993  // RecordTestRun records a test that has been run successfully to a text file,
   994  // so that it can be automatically skipped if requested.
   995  func RecordTestRun(t *testing.T) {
   996  	if t.Skipped() || t.Failed() {
   997  		return
   998  	}
   999  	rf := os.Getenv("ARGOCD_E2E_RECORD")
  1000  	if rf == "" {
  1001  		return
  1002  	}
  1003  	log.Infof("Registering test execution at %s", rf)
  1004  	f, err := os.OpenFile(rf, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
  1005  	if err != nil {
  1006  		t.Fatalf("could not open record file %s: %v", rf, err)
  1007  	}
  1008  	defer func() {
  1009  		err := f.Close()
  1010  		if err != nil {
  1011  			t.Fatalf("could not close record file %s: %v", rf, err)
  1012  		}
  1013  	}()
  1014  	if _, err := f.WriteString(fmt.Sprintf("%s\n", t.Name())); err != nil {
  1015  		t.Fatalf("could not write to %s: %v", rf, err)
  1016  	}
  1017  }