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 }