github.com/argoproj/argo-cd@v1.8.7/test/e2e/fixture/fixture.go (about) 1 package fixture 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path" 9 "path/filepath" 10 "strings" 11 "testing" 12 "time" 13 14 "github.com/argoproj/pkg/errors" 15 jsonpatch "github.com/evanphx/json-patch" 16 "github.com/ghodss/yaml" 17 log "github.com/sirupsen/logrus" 18 corev1 "k8s.io/api/core/v1" 19 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 "k8s.io/client-go/dynamic" 21 "k8s.io/client-go/kubernetes" 22 "k8s.io/client-go/rest" 23 "k8s.io/client-go/tools/clientcmd" 24 25 "github.com/argoproj/argo-cd/common" 26 argocdclient "github.com/argoproj/argo-cd/pkg/apiclient" 27 sessionpkg "github.com/argoproj/argo-cd/pkg/apiclient/session" 28 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" 29 appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned" 30 . "github.com/argoproj/argo-cd/util/errors" 31 grpcutil "github.com/argoproj/argo-cd/util/grpc" 32 "github.com/argoproj/argo-cd/util/io" 33 "github.com/argoproj/argo-cd/util/rand" 34 "github.com/argoproj/argo-cd/util/settings" 35 ) 36 37 const ( 38 defaultApiServer = "localhost:8080" 39 adminPassword = "password" 40 testingLabel = "e2e.argoproj.io" 41 ArgoCDNamespace = "argocd-e2e" 42 43 // ensure all repos are in one directory tree, so we can easily clean them up 44 TmpDir = "/tmp/argo-e2e" 45 repoDir = "testdata.git" 46 submoduleDir = "submodule.git" 47 submoduleParentDir = "submoduleParent.git" 48 49 GuestbookPath = "guestbook" 50 ) 51 52 var ( 53 id string 54 deploymentNamespace string 55 name string 56 KubeClientset kubernetes.Interface 57 DynamicClientset dynamic.Interface 58 AppClientset appclientset.Interface 59 ArgoCDClientset argocdclient.Client 60 apiServerAddress string 61 token string 62 plainText bool 63 ) 64 65 type RepoURLType string 66 67 const ( 68 RepoURLTypeFile = "file" 69 RepoURLTypeHTTPS = "https" 70 RepoURLTypeHTTPSClientCert = "https-cc" 71 RepoURLTypeHTTPSSubmodule = "https-sub" 72 RepoURLTypeHTTPSSubmoduleParent = "https-par" 73 RepoURLTypeSSH = "ssh" 74 RepoURLTypeSSHSubmodule = "ssh-sub" 75 RepoURLTypeSSHSubmoduleParent = "ssh-par" 76 RepoURLTypeHelm = "helm" 77 GitUsername = "admin" 78 GitPassword = "password" 79 GpgGoodKeyID = "D56C4FCA57A46444" 80 ) 81 82 // getKubeConfig creates new kubernetes client config using specified config path and config overrides variables 83 func getKubeConfig(configPath string, overrides clientcmd.ConfigOverrides) *rest.Config { 84 loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() 85 loadingRules.ExplicitPath = configPath 86 clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, &overrides, os.Stdin) 87 88 restConfig, err := clientConfig.ClientConfig() 89 CheckError(err) 90 return restConfig 91 } 92 93 // creates e2e tests fixture: ensures that Application CRD is installed, creates temporal namespace, starts repo and api server, 94 // configure currently available cluster. 95 func init() { 96 // ensure we log all shell execs 97 log.SetLevel(log.DebugLevel) 98 // set-up variables 99 config := getKubeConfig("", clientcmd.ConfigOverrides{}) 100 AppClientset = appclientset.NewForConfigOrDie(config) 101 KubeClientset = kubernetes.NewForConfigOrDie(config) 102 DynamicClientset = dynamic.NewForConfigOrDie(config) 103 apiServerAddress = os.Getenv(argocdclient.EnvArgoCDServer) 104 if apiServerAddress == "" { 105 apiServerAddress = defaultApiServer 106 } 107 108 tlsTestResult, err := grpcutil.TestTLS(apiServerAddress) 109 CheckError(err) 110 111 ArgoCDClientset, err = argocdclient.NewClient(&argocdclient.ClientOptions{Insecure: true, ServerAddr: apiServerAddress, PlainText: !tlsTestResult.TLS}) 112 CheckError(err) 113 114 closer, client, err := ArgoCDClientset.NewSessionClient() 115 CheckError(err) 116 defer io.Close(closer) 117 118 sessionResponse, err := client.Create(context.Background(), &sessionpkg.SessionCreateRequest{Username: "admin", Password: adminPassword}) 119 CheckError(err) 120 121 ArgoCDClientset, err = argocdclient.NewClient(&argocdclient.ClientOptions{ 122 Insecure: true, 123 ServerAddr: apiServerAddress, 124 AuthToken: sessionResponse.Token, 125 PlainText: !tlsTestResult.TLS, 126 }) 127 CheckError(err) 128 129 token = sessionResponse.Token 130 plainText = !tlsTestResult.TLS 131 132 log.WithFields(log.Fields{"apiServerAddress": apiServerAddress}).Info("initialized") 133 } 134 135 func Name() string { 136 return name 137 } 138 139 func repoDirectory() string { 140 return path.Join(TmpDir, repoDir) 141 } 142 143 func submoduleDirectory() string { 144 return path.Join(TmpDir, submoduleDir) 145 } 146 147 func submoduleParentDirectory() string { 148 return path.Join(TmpDir, submoduleParentDir) 149 } 150 151 func RepoURL(urlType RepoURLType) string { 152 switch urlType { 153 // Git server via SSH 154 case RepoURLTypeSSH: 155 return "ssh://root@localhost:2222/tmp/argo-e2e/testdata.git" 156 // Git submodule repo 157 case RepoURLTypeSSHSubmodule: 158 return "ssh://root@localhost:2222/tmp/argo-e2e/submodule.git" 159 // Git submodule parent repo 160 case RepoURLTypeSSHSubmoduleParent: 161 return "ssh://root@localhost:2222/tmp/argo-e2e/submoduleParent.git" 162 // Git server via HTTPS 163 case RepoURLTypeHTTPS: 164 return "https://localhost:9443/argo-e2e/testdata.git" 165 // Git server via HTTPS - Client Cert protected 166 case RepoURLTypeHTTPSClientCert: 167 return "https://localhost:9444/argo-e2e/testdata.git" 168 case RepoURLTypeHTTPSSubmodule: 169 return "https://localhost:9443/argo-e2e/submodule.git" 170 // Git submodule parent repo 171 case RepoURLTypeHTTPSSubmoduleParent: 172 return "https://localhost:9443/argo-e2e/submoduleParent.git" 173 // Default - file based Git repository 174 case RepoURLTypeHelm: 175 return "https://localhost:9444/argo-e2e/testdata.git/helm-repo" 176 default: 177 return fmt.Sprintf("file://%s", repoDirectory()) 178 } 179 } 180 181 func RepoBaseURL(urlType RepoURLType) string { 182 return path.Base(RepoURL(urlType)) 183 } 184 185 func DeploymentNamespace() string { 186 return deploymentNamespace 187 } 188 189 // creates a secret for the current test, this currently can only create a single secret 190 func CreateSecret(username, password string) string { 191 secretName := fmt.Sprintf("argocd-e2e-%s", name) 192 FailOnErr(Run("", "kubectl", "create", "secret", "generic", secretName, 193 "--from-literal=username="+username, 194 "--from-literal=password="+password, 195 "-n", ArgoCDNamespace)) 196 FailOnErr(Run("", "kubectl", "label", "secret", secretName, testingLabel+"=true", "-n", ArgoCDNamespace)) 197 return secretName 198 } 199 200 // Convinience wrapper for updating argocd-cm 201 func updateSettingConfigMap(updater func(cm *corev1.ConfigMap) error) { 202 updateGenericConfigMap(common.ArgoCDConfigMapName, updater) 203 } 204 205 // Updates a given config map in argocd-e2e namespace 206 func updateGenericConfigMap(name string, updater func(cm *corev1.ConfigMap) error) { 207 cm, err := KubeClientset.CoreV1().ConfigMaps(ArgoCDNamespace).Get(context.Background(), name, v1.GetOptions{}) 208 errors.CheckError(err) 209 if cm.Data == nil { 210 cm.Data = make(map[string]string) 211 } 212 errors.CheckError(updater(cm)) 213 _, err = KubeClientset.CoreV1().ConfigMaps(ArgoCDNamespace).Update(context.Background(), cm, v1.UpdateOptions{}) 214 errors.CheckError(err) 215 } 216 217 func SetResourceOverrides(overrides map[string]v1alpha1.ResourceOverride) { 218 updateSettingConfigMap(func(cm *corev1.ConfigMap) error { 219 if len(overrides) > 0 { 220 yamlBytes, err := yaml.Marshal(overrides) 221 if err != nil { 222 return err 223 } 224 cm.Data["resource.customizations"] = string(yamlBytes) 225 } else { 226 delete(cm.Data, "resource.customizations") 227 } 228 return nil 229 }) 230 } 231 232 func SetAccounts(accounts map[string][]string) { 233 updateSettingConfigMap(func(cm *corev1.ConfigMap) error { 234 for k, v := range accounts { 235 cm.Data[fmt.Sprintf("accounts.%s", k)] = strings.Join(v, ",") 236 } 237 return nil 238 }) 239 } 240 241 func SetConfigManagementPlugins(plugin ...v1alpha1.ConfigManagementPlugin) { 242 updateSettingConfigMap(func(cm *corev1.ConfigMap) error { 243 yamlBytes, err := yaml.Marshal(plugin) 244 if err != nil { 245 return err 246 } 247 cm.Data["configManagementPlugins"] = string(yamlBytes) 248 return nil 249 }) 250 } 251 252 func SetResourceFilter(filters settings.ResourcesFilter) { 253 updateSettingConfigMap(func(cm *corev1.ConfigMap) error { 254 exclusions, err := yaml.Marshal(filters.ResourceExclusions) 255 if err != nil { 256 return err 257 } 258 inclusions, err := yaml.Marshal(filters.ResourceInclusions) 259 if err != nil { 260 return err 261 } 262 cm.Data["resource.exclusions"] = string(exclusions) 263 cm.Data["resource.inclusions"] = string(inclusions) 264 return nil 265 }) 266 } 267 268 func SetHelmRepos(repos ...settings.HelmRepoCredentials) { 269 updateSettingConfigMap(func(cm *corev1.ConfigMap) error { 270 yamlBytes, err := yaml.Marshal(repos) 271 if err != nil { 272 return err 273 } 274 cm.Data["helm.repositories"] = string(yamlBytes) 275 return nil 276 }) 277 } 278 279 func SetRepos(repos ...settings.RepositoryCredentials) { 280 updateSettingConfigMap(func(cm *corev1.ConfigMap) error { 281 yamlBytes, err := yaml.Marshal(repos) 282 if err != nil { 283 return err 284 } 285 cm.Data["repositories"] = string(yamlBytes) 286 return nil 287 }) 288 } 289 290 func SetProjectSpec(project string, spec v1alpha1.AppProjectSpec) { 291 proj, err := AppClientset.ArgoprojV1alpha1().AppProjects(ArgoCDNamespace).Get(context.Background(), project, v1.GetOptions{}) 292 errors.CheckError(err) 293 proj.Spec = spec 294 _, err = AppClientset.ArgoprojV1alpha1().AppProjects(ArgoCDNamespace).Update(context.Background(), proj, v1.UpdateOptions{}) 295 errors.CheckError(err) 296 } 297 298 func EnsureCleanState(t *testing.T) { 299 300 start := time.Now() 301 302 policy := v1.DeletePropagationBackground 303 // delete resources 304 // kubectl delete apps --all 305 CheckError(AppClientset.ArgoprojV1alpha1().Applications(ArgoCDNamespace).DeleteCollection(context.Background(), v1.DeleteOptions{PropagationPolicy: &policy}, v1.ListOptions{})) 306 // kubectl delete appprojects --field-selector metadata.name!=default 307 CheckError(AppClientset.ArgoprojV1alpha1().AppProjects(ArgoCDNamespace).DeleteCollection(context.Background(), 308 v1.DeleteOptions{PropagationPolicy: &policy}, v1.ListOptions{FieldSelector: "metadata.name!=default"})) 309 // kubectl delete secrets -l e2e.argoproj.io=true 310 CheckError(KubeClientset.CoreV1().Secrets(ArgoCDNamespace).DeleteCollection(context.Background(), 311 v1.DeleteOptions{PropagationPolicy: &policy}, v1.ListOptions{LabelSelector: testingLabel + "=true"})) 312 313 FailOnErr(Run("", "kubectl", "delete", "ns", "-l", testingLabel+"=true", "--field-selector", "status.phase=Active", "--wait=false")) 314 FailOnErr(Run("", "kubectl", "delete", "crd", "-l", testingLabel+"=true", "--wait=false")) 315 316 // reset settings 317 updateSettingConfigMap(func(cm *corev1.ConfigMap) error { 318 cm.Data = map[string]string{} 319 return nil 320 }) 321 322 // reset gpg-keys config map 323 updateGenericConfigMap(common.ArgoCDGPGKeysConfigMapName, func(cm *corev1.ConfigMap) error { 324 cm.Data = map[string]string{} 325 return nil 326 }) 327 328 SetProjectSpec("default", v1alpha1.AppProjectSpec{ 329 OrphanedResources: nil, 330 SourceRepos: []string{"*"}, 331 Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "*"}}, 332 ClusterResourceWhitelist: []v1.GroupKind{{Group: "*", Kind: "*"}}, 333 }) 334 335 // Create separate project for testing gpg signature verification 336 FailOnErr(RunCli("proj", "create", "gpg")) 337 SetProjectSpec("gpg", v1alpha1.AppProjectSpec{ 338 OrphanedResources: nil, 339 SourceRepos: []string{"*"}, 340 Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "*"}}, 341 ClusterResourceWhitelist: []v1.GroupKind{{Group: "*", Kind: "*"}}, 342 SignatureKeys: []v1alpha1.SignatureKey{{KeyID: GpgGoodKeyID}}, 343 }) 344 345 // remove tmp dir 346 CheckError(os.RemoveAll(TmpDir)) 347 348 // random id - unique across test runs 349 postFix := "-" + strings.ToLower(rand.RandString(5)) 350 id = t.Name() + postFix 351 name = DnsFriendly(t.Name(), "") 352 deploymentNamespace = DnsFriendly(fmt.Sprintf("argocd-e2e-%s", t.Name()), postFix) 353 354 // create tmp dir 355 FailOnErr(Run("", "mkdir", "-p", TmpDir)) 356 357 // create TLS and SSH certificate directories 358 FailOnErr(Run("", "mkdir", "-p", TmpDir+"/app/config/tls")) 359 FailOnErr(Run("", "mkdir", "-p", TmpDir+"/app/config/ssh")) 360 361 // For signing during the tests 362 FailOnErr(Run("", "mkdir", "-p", TmpDir+"/gpg")) 363 FailOnErr(Run("", "chmod", "0700", TmpDir+"/gpg")) 364 prevGnuPGHome := os.Getenv("GNUPGHOME") 365 os.Setenv("GNUPGHOME", TmpDir+"/gpg") 366 // nolint:errcheck 367 Run("", "pkill", "-9", "gpg-agent") 368 FailOnErr(Run("", "gpg", "--import", "../fixture/gpg/signingkey.asc")) 369 os.Setenv("GNUPGHOME", prevGnuPGHome) 370 371 // recreate GPG directories 372 FailOnErr(Run("", "mkdir", "-p", TmpDir+"/app/config/gpg/source")) 373 FailOnErr(Run("", "mkdir", "-p", TmpDir+"/app/config/gpg/keys")) 374 FailOnErr(Run("", "chmod", "0700", TmpDir+"/app/config/gpg/keys")) 375 376 // set-up tmp repo, must have unique name 377 FailOnErr(Run("", "cp", "-Rf", "testdata", repoDirectory())) 378 FailOnErr(Run(repoDirectory(), "chmod", "777", ".")) 379 FailOnErr(Run(repoDirectory(), "git", "init")) 380 FailOnErr(Run(repoDirectory(), "git", "add", ".")) 381 FailOnErr(Run(repoDirectory(), "git", "commit", "-q", "-m", "initial commit")) 382 383 // create namespace 384 FailOnErr(Run("", "kubectl", "create", "ns", DeploymentNamespace())) 385 FailOnErr(Run("", "kubectl", "label", "ns", DeploymentNamespace(), testingLabel+"=true")) 386 387 log.WithFields(log.Fields{"duration": time.Since(start), "name": t.Name(), "id": id, "username": "admin", "password": "password"}).Info("clean state") 388 } 389 390 func RunCli(args ...string) (string, error) { 391 return RunCliWithStdin("", args...) 392 } 393 394 func RunCliWithStdin(stdin string, args ...string) (string, error) { 395 if plainText { 396 args = append(args, "--plaintext") 397 } 398 399 args = append(args, "--server", apiServerAddress, "--auth-token", token, "--insecure") 400 401 return RunWithStdin(stdin, "", "../../dist/argocd", args...) 402 } 403 404 func Patch(path string, jsonPatch string) { 405 406 log.WithFields(log.Fields{"path": path, "jsonPatch": jsonPatch}).Info("patching") 407 408 filename := filepath.Join(repoDirectory(), path) 409 bytes, err := ioutil.ReadFile(filename) 410 CheckError(err) 411 412 patch, err := jsonpatch.DecodePatch([]byte(jsonPatch)) 413 CheckError(err) 414 415 isYaml := strings.HasSuffix(filename, ".yaml") 416 if isYaml { 417 log.Info("converting YAML to JSON") 418 bytes, err = yaml.YAMLToJSON(bytes) 419 CheckError(err) 420 } 421 422 log.WithFields(log.Fields{"bytes": string(bytes)}).Info("JSON") 423 424 bytes, err = patch.Apply(bytes) 425 CheckError(err) 426 427 if isYaml { 428 log.Info("converting JSON back to YAML") 429 bytes, err = yaml.JSONToYAML(bytes) 430 CheckError(err) 431 } 432 433 CheckError(ioutil.WriteFile(filename, bytes, 0644)) 434 FailOnErr(Run(repoDirectory(), "git", "diff")) 435 FailOnErr(Run(repoDirectory(), "git", "commit", "-am", "patch")) 436 } 437 438 func Delete(path string) { 439 440 log.WithFields(log.Fields{"path": path}).Info("deleting") 441 442 CheckError(os.Remove(filepath.Join(repoDirectory(), path))) 443 444 FailOnErr(Run(repoDirectory(), "git", "diff")) 445 FailOnErr(Run(repoDirectory(), "git", "commit", "-am", "delete")) 446 } 447 448 func WriteFile(path, contents string) { 449 log.WithFields(log.Fields{"path": path}).Info("adding") 450 451 CheckError(ioutil.WriteFile(filepath.Join(repoDirectory(), path), []byte(contents), 0644)) 452 } 453 454 func AddFile(path, contents string) { 455 456 WriteFile(path, contents) 457 458 FailOnErr(Run(repoDirectory(), "git", "diff")) 459 FailOnErr(Run(repoDirectory(), "git", "add", ".")) 460 FailOnErr(Run(repoDirectory(), "git", "commit", "-am", "add file")) 461 } 462 463 func AddSignedFile(path, contents string) { 464 WriteFile(path, contents) 465 466 prevGnuPGHome := os.Getenv("GNUPGHOME") 467 os.Setenv("GNUPGHOME", TmpDir+"/gpg") 468 FailOnErr(Run(repoDirectory(), "git", "diff")) 469 FailOnErr(Run(repoDirectory(), "git", "add", ".")) 470 FailOnErr(Run(repoDirectory(), "git", "-c", fmt.Sprintf("user.signingkey=%s", GpgGoodKeyID), "commit", "-S", "-am", "add file")) 471 os.Setenv("GNUPGHOME", prevGnuPGHome) 472 } 473 474 // create the resource by creating using "kubectl apply", with bonus templating 475 func Declarative(filename string, values interface{}) (string, error) { 476 477 bytes, err := ioutil.ReadFile(path.Join("testdata", filename)) 478 CheckError(err) 479 480 tmpFile, err := ioutil.TempFile("", "") 481 CheckError(err) 482 _, err = tmpFile.WriteString(Tmpl(string(bytes), values)) 483 CheckError(err) 484 485 return Run("", "kubectl", "-n", ArgoCDNamespace, "apply", "-f", tmpFile.Name()) 486 } 487 488 func CreateSubmoduleRepos(repoType string) { 489 490 // set-up submodule repo 491 FailOnErr(Run("", "cp", "-Rf", "testdata/git-submodule/", submoduleDirectory())) 492 FailOnErr(Run(submoduleDirectory(), "chmod", "777", ".")) 493 FailOnErr(Run(submoduleDirectory(), "git", "init")) 494 FailOnErr(Run(submoduleDirectory(), "git", "add", ".")) 495 FailOnErr(Run(submoduleDirectory(), "git", "commit", "-q", "-m", "initial commit")) 496 497 // set-up submodule parent repo 498 FailOnErr(Run("", "mkdir", submoduleParentDirectory())) 499 FailOnErr(Run(submoduleParentDirectory(), "chmod", "777", ".")) 500 FailOnErr(Run(submoduleParentDirectory(), "git", "init")) 501 FailOnErr(Run(submoduleParentDirectory(), "git", "add", ".")) 502 FailOnErr(Run(submoduleParentDirectory(), "git", "submodule", "add", "-b", "master", "../submodule.git", "submodule/test")) 503 if repoType == "ssh" { 504 FailOnErr(Run(submoduleParentDirectory(), "git", "config", "--file=.gitmodules", "submodule.submodule/test.url", RepoURL(RepoURLTypeSSHSubmodule))) 505 } else if repoType == "https" { 506 FailOnErr(Run(submoduleParentDirectory(), "git", "config", "--file=.gitmodules", "submodule.submodule/test.url", RepoURL(RepoURLTypeHTTPSSubmodule))) 507 } 508 FailOnErr(Run(submoduleParentDirectory(), "git", "add", "--all")) 509 FailOnErr(Run(submoduleParentDirectory(), "git", "commit", "-q", "-m", "commit with submodule")) 510 }