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 }