github.com/argoproj/argo-cd/v3@v3.2.1/test/e2e/fixture/app/actions.go (about) 1 package app 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "slices" 8 "strconv" 9 10 rbacv1 "k8s.io/api/rbac/v1" 11 12 log "github.com/sirupsen/logrus" 13 "github.com/stretchr/testify/require" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 16 client "github.com/argoproj/argo-cd/v3/pkg/apiclient/application" 17 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 18 "github.com/argoproj/argo-cd/v3/test/e2e/fixture" 19 "github.com/argoproj/argo-cd/v3/util/grpc" 20 ) 21 22 // this implements the "when" part of given/when/then 23 // 24 // none of the func implement error checks, and that is complete intended, you should check for errors 25 // using the Then() 26 type Actions struct { 27 context *Context 28 lastOutput string 29 lastError error 30 ignoreErrors bool 31 } 32 33 func (a *Actions) IgnoreErrors() *Actions { 34 a.ignoreErrors = true 35 return a 36 } 37 38 func (a *Actions) DoNotIgnoreErrors() *Actions { 39 a.ignoreErrors = false 40 return a 41 } 42 43 func (a *Actions) PatchFile(file string, jsonPatch string) *Actions { 44 a.context.t.Helper() 45 fixture.Patch(a.context.t, a.context.path+"/"+file, jsonPatch) 46 return a 47 } 48 49 func (a *Actions) DeleteFile(file string) *Actions { 50 a.context.t.Helper() 51 fixture.Delete(a.context.t, a.context.path+"/"+file) 52 return a 53 } 54 55 func (a *Actions) WriteFile(fileName, fileContents string) *Actions { 56 a.context.t.Helper() 57 fixture.WriteFile(a.context.t, a.context.path+"/"+fileName, fileContents) 58 return a 59 } 60 61 func (a *Actions) AddFile(fileName, fileContents string) *Actions { 62 a.context.t.Helper() 63 fixture.AddFile(a.context.t, a.context.path+"/"+fileName, fileContents) 64 return a 65 } 66 67 func (a *Actions) AddSignedFile(fileName, fileContents string) *Actions { 68 a.context.t.Helper() 69 fixture.AddSignedFile(a.context.t, a.context.path+"/"+fileName, fileContents) 70 return a 71 } 72 73 func (a *Actions) AddSignedTag(name string) *Actions { 74 a.context.t.Helper() 75 fixture.AddSignedTag(a.context.t, name) 76 return a 77 } 78 79 func (a *Actions) AddTag(name string) *Actions { 80 a.context.t.Helper() 81 fixture.AddTag(a.context.t, name) 82 return a 83 } 84 85 func (a *Actions) AddAnnotatedTag(name string, message string) *Actions { 86 a.context.t.Helper() 87 fixture.AddAnnotatedTag(a.context.t, name, message) 88 return a 89 } 90 91 func (a *Actions) AddTagWithForce(name string) *Actions { 92 a.context.t.Helper() 93 fixture.AddTagWithForce(a.context.t, name) 94 return a 95 } 96 97 func (a *Actions) RemoveSubmodule() *Actions { 98 a.context.t.Helper() 99 fixture.RemoveSubmodule(a.context.t) 100 return a 101 } 102 103 func (a *Actions) CreateFromPartialFile(data string, flags ...string) *Actions { 104 a.context.t.Helper() 105 tmpFile, err := os.CreateTemp("", "") 106 require.NoError(a.context.t, err) 107 _, err = tmpFile.WriteString(data) 108 require.NoError(a.context.t, err) 109 110 args := append([]string{ 111 "app", "create", 112 "-f", tmpFile.Name(), 113 "--name", a.context.AppName(), 114 "--repo", fixture.RepoURL(a.context.repoURLType), 115 "--dest-server", a.context.destServer, 116 "--dest-namespace", fixture.DeploymentNamespace(), 117 }, flags...) 118 if a.context.appNamespace != "" { 119 args = append(args, "--app-namespace", a.context.appNamespace) 120 } 121 defer tmpFile.Close() 122 a.runCli(args...) 123 return a 124 } 125 126 func (a *Actions) CreateFromFile(handler func(app *v1alpha1.Application), flags ...string) *Actions { 127 a.context.t.Helper() 128 app := &v1alpha1.Application{ 129 ObjectMeta: metav1.ObjectMeta{ 130 Name: a.context.AppName(), 131 Namespace: a.context.AppNamespace(), 132 }, 133 Spec: v1alpha1.ApplicationSpec{ 134 Project: a.context.project, 135 Source: &v1alpha1.ApplicationSource{ 136 RepoURL: fixture.RepoURL(a.context.repoURLType), 137 Path: a.context.path, 138 }, 139 Destination: v1alpha1.ApplicationDestination{ 140 Server: a.context.destServer, 141 Namespace: fixture.DeploymentNamespace(), 142 }, 143 }, 144 } 145 source := app.Spec.GetSource() 146 if a.context.namePrefix != "" || a.context.nameSuffix != "" { 147 source.Kustomize = &v1alpha1.ApplicationSourceKustomize{ 148 NamePrefix: a.context.namePrefix, 149 NameSuffix: a.context.nameSuffix, 150 } 151 } 152 if a.context.configManagementPlugin != "" { 153 source.Plugin = &v1alpha1.ApplicationSourcePlugin{ 154 Name: a.context.configManagementPlugin, 155 } 156 } 157 158 if len(a.context.parameters) > 0 { 159 log.Fatal("v1alpha1.Application parameters or json tlas are not supported") 160 } 161 162 if a.context.directoryRecurse { 163 source.Directory = &v1alpha1.ApplicationSourceDirectory{Recurse: true} 164 } 165 app.Spec.Source = &source 166 167 handler(app) 168 data := grpc.MustMarshal(app) 169 tmpFile, err := os.CreateTemp("", "") 170 require.NoError(a.context.t, err) 171 _, err = tmpFile.Write(data) 172 require.NoError(a.context.t, err) 173 174 args := append([]string{ 175 "app", "create", 176 "-f", tmpFile.Name(), 177 }, flags...) 178 defer tmpFile.Close() 179 a.runCli(args...) 180 return a 181 } 182 183 func (a *Actions) CreateMultiSourceAppFromFile(flags ...string) *Actions { 184 a.context.t.Helper() 185 app := &v1alpha1.Application{ 186 ObjectMeta: metav1.ObjectMeta{ 187 Name: a.context.AppName(), 188 Namespace: a.context.AppNamespace(), 189 }, 190 Spec: v1alpha1.ApplicationSpec{ 191 Project: a.context.project, 192 Sources: a.context.sources, 193 Destination: v1alpha1.ApplicationDestination{ 194 Server: a.context.destServer, 195 Namespace: fixture.DeploymentNamespace(), 196 }, 197 SyncPolicy: &v1alpha1.SyncPolicy{ 198 Automated: &v1alpha1.SyncPolicyAutomated{ 199 SelfHeal: true, 200 }, 201 }, 202 }, 203 } 204 205 data := grpc.MustMarshal(app) 206 tmpFile, err := os.CreateTemp("", "") 207 require.NoError(a.context.t, err) 208 _, err = tmpFile.Write(data) 209 require.NoError(a.context.t, err) 210 211 args := append([]string{ 212 "app", "create", 213 "-f", tmpFile.Name(), 214 }, flags...) 215 defer tmpFile.Close() 216 a.runCli(args...) 217 return a 218 } 219 220 func (a *Actions) CreateWithNoNameSpace(args ...string) *Actions { 221 args = a.prepareCreateAppArgs(args) 222 // are you adding new context values? if you only use them for this func, then use args instead 223 a.runCli(args...) 224 return a 225 } 226 227 func (a *Actions) CreateApp(args ...string) *Actions { 228 args = a.prepareCreateAppArgs(args) 229 args = append(args, "--dest-namespace", fixture.DeploymentNamespace()) 230 231 // are you adding new context values? if you only use them for this func, then use args instead 232 a.runCli(args...) 233 234 return a 235 } 236 237 func (a *Actions) prepareCreateAppArgs(args []string) []string { 238 a.context.t.Helper() 239 args = append([]string{ 240 "app", "create", a.context.AppQualifiedName(), 241 }, args...) 242 243 if a.context.drySourceRevision != "" || a.context.drySourcePath != "" || a.context.syncSourcePath != "" || a.context.syncSourceBranch != "" || a.context.hydrateToBranch != "" { 244 args = append(args, "--dry-source-repo", fixture.RepoURL(a.context.repoURLType)) 245 } else { 246 var repo string 247 if a.context.ociRegistryPath != "" && a.context.repoURLType == fixture.RepoURLTypeOCI { 248 repo = fmt.Sprintf("%s/%s", a.context.ociRegistry, a.context.ociRegistryPath) 249 } else { 250 repo = fixture.RepoURL(a.context.repoURLType) 251 } 252 args = append(args, "--repo", repo) 253 } 254 255 if a.context.destName != "" && a.context.isDestServerInferred && !slices.Contains(args, "--dest-server") { 256 args = append(args, "--dest-name", a.context.destName) 257 } else { 258 args = append(args, "--dest-server", a.context.destServer) 259 } 260 if a.context.path != "" { 261 args = append(args, "--path", a.context.path) 262 } 263 264 if a.context.drySourceRevision != "" { 265 args = append(args, "--dry-source-revision", a.context.drySourceRevision) 266 } 267 268 if a.context.drySourcePath != "" { 269 args = append(args, "--dry-source-path", a.context.drySourcePath) 270 } 271 272 if a.context.syncSourceBranch != "" { 273 args = append(args, "--sync-source-branch", a.context.syncSourceBranch) 274 } 275 276 if a.context.syncSourcePath != "" { 277 args = append(args, "--sync-source-path", a.context.syncSourcePath) 278 } 279 280 if a.context.hydrateToBranch != "" { 281 args = append(args, "--hydrate-to-branch", a.context.hydrateToBranch) 282 } 283 284 if a.context.chart != "" { 285 args = append(args, "--helm-chart", a.context.chart) 286 } 287 288 if a.context.env != "" { 289 args = append(args, "--env", a.context.env) 290 } 291 292 for _, parameter := range a.context.parameters { 293 args = append(args, "--parameter", parameter) 294 } 295 296 args = append(args, "--project", a.context.project) 297 298 if a.context.namePrefix != "" { 299 args = append(args, "--nameprefix", a.context.namePrefix) 300 } 301 302 if a.context.nameSuffix != "" { 303 args = append(args, "--namesuffix", a.context.nameSuffix) 304 } 305 306 if a.context.configManagementPlugin != "" { 307 args = append(args, "--config-management-plugin", a.context.configManagementPlugin) 308 } 309 310 if a.context.revision != "" { 311 args = append(args, "--revision", a.context.revision) 312 } 313 if a.context.helmPassCredentials { 314 args = append(args, "--helm-pass-credentials") 315 } 316 if a.context.helmSkipCrds { 317 args = append(args, "--helm-skip-crds") 318 } 319 if a.context.helmSkipSchemaValidation { 320 args = append(args, "--helm-skip-schema-validation") 321 } 322 if a.context.helmSkipTests { 323 args = append(args, "--helm-skip-tests") 324 } 325 return args 326 } 327 328 func (a *Actions) Declarative(filename string) *Actions { 329 a.context.t.Helper() 330 return a.DeclarativeWithCustomRepo(filename, fixture.RepoURL(a.context.repoURLType)) 331 } 332 333 func (a *Actions) DeclarativeWithCustomRepo(filename string, repoURL string) *Actions { 334 a.context.t.Helper() 335 values := map[string]any{ 336 "ArgoCDNamespace": fixture.TestNamespace(), 337 "DeploymentNamespace": fixture.DeploymentNamespace(), 338 "Name": a.context.AppName(), 339 "Path": a.context.path, 340 "Project": a.context.project, 341 "RepoURL": repoURL, 342 } 343 a.lastOutput, a.lastError = fixture.Declarative(a.context.t, filename, values) 344 a.verifyAction() 345 return a 346 } 347 348 func (a *Actions) PatchApp(patch string) *Actions { 349 a.context.t.Helper() 350 a.runCli("app", "patch", a.context.AppQualifiedName(), "--patch", patch) 351 return a 352 } 353 354 func (a *Actions) PatchAppHttp(patch string) *Actions { //nolint:revive //FIXME(var-naming) 355 a.context.t.Helper() 356 var application v1alpha1.Application 357 patchType := "merge" 358 appName := a.context.AppQualifiedName() 359 appNamespace := a.context.AppNamespace() 360 patchRequest := &client.ApplicationPatchRequest{ 361 Name: &appName, 362 PatchType: &patchType, 363 Patch: &patch, 364 AppNamespace: &appNamespace, 365 } 366 jsonBytes, err := json.MarshalIndent(patchRequest, "", " ") 367 require.NoError(a.context.t, err) 368 err = fixture.DoHttpJsonRequest("PATCH", 369 fmt.Sprintf("/api/v1/applications/%v", appName), 370 &application, 371 jsonBytes...) 372 require.NoError(a.context.t, err) 373 return a 374 } 375 376 func (a *Actions) AppSet(flags ...string) *Actions { 377 a.context.t.Helper() 378 args := []string{"app", "set", a.context.AppQualifiedName()} 379 args = append(args, flags...) 380 a.runCli(args...) 381 return a 382 } 383 384 func (a *Actions) AppUnSet(flags ...string) *Actions { 385 a.context.t.Helper() 386 args := []string{"app", "unset", a.context.AppQualifiedName()} 387 args = append(args, flags...) 388 a.runCli(args...) 389 return a 390 } 391 392 func (a *Actions) Sync(args ...string) *Actions { 393 a.context.t.Helper() 394 args = append([]string{"app", "sync"}, args...) 395 if a.context.name != "" { 396 args = append(args, a.context.AppQualifiedName()) 397 } 398 args = append(args, "--timeout", strconv.Itoa(a.context.timeout)) 399 400 if a.context.async { 401 args = append(args, "--async") 402 } 403 404 if a.context.prune { 405 args = append(args, "--prune") 406 } 407 408 if a.context.resource != "" { 409 // Waiting for the app to be successfully created. 410 // Else the sync would fail to retrieve the app resources. 411 a.context.Sleep(5) 412 args = append(args, "--resource", a.context.resource) 413 } 414 415 if a.context.localPath != "" { 416 args = append(args, "--local", a.context.localPath) 417 } 418 419 if a.context.force { 420 args = append(args, "--force") 421 } 422 423 if a.context.applyOutOfSyncOnly { 424 args = append(args, "--apply-out-of-sync-only") 425 } 426 427 if a.context.replace { 428 args = append(args, "--replace") 429 } 430 431 // are you adding new context values? if you only use them for this func, then use args instead 432 433 a.runCli(args...) 434 435 return a 436 } 437 438 func (a *Actions) ConfirmDeletion() *Actions { 439 a.context.t.Helper() 440 441 a.runCli("app", "confirm-deletion", a.context.AppQualifiedName()) 442 443 return a 444 } 445 446 func (a *Actions) TerminateOp() *Actions { 447 a.context.t.Helper() 448 a.runCli("app", "terminate-op", a.context.AppQualifiedName()) 449 return a 450 } 451 452 func (a *Actions) Refresh(refreshType v1alpha1.RefreshType) *Actions { 453 a.context.t.Helper() 454 flag := map[v1alpha1.RefreshType]string{ 455 v1alpha1.RefreshTypeNormal: "--refresh", 456 v1alpha1.RefreshTypeHard: "--hard-refresh", 457 }[refreshType] 458 459 a.runCli("app", "get", a.context.AppQualifiedName(), flag) 460 461 return a 462 } 463 464 func (a *Actions) Get() *Actions { 465 a.context.t.Helper() 466 a.runCli("app", "get", a.context.AppQualifiedName()) 467 return a 468 } 469 470 func (a *Actions) Delete(cascade bool) *Actions { 471 a.context.t.Helper() 472 a.runCli("app", "delete", a.context.AppQualifiedName(), fmt.Sprintf("--cascade=%v", cascade), "--yes") 473 return a 474 } 475 476 func (a *Actions) DeleteBySelector(selector string) *Actions { 477 a.context.t.Helper() 478 a.runCli("app", "delete", "--selector="+selector, "--yes") 479 return a 480 } 481 482 func (a *Actions) DeleteBySelectorWithWait(selector string) *Actions { 483 a.context.t.Helper() 484 a.runCli("app", "delete", "--selector="+selector, "--yes", "--wait") 485 return a 486 } 487 488 func (a *Actions) Wait(args ...string) *Actions { 489 a.context.t.Helper() 490 args = append([]string{"app", "wait"}, args...) 491 if a.context.name != "" { 492 args = append(args, a.context.AppQualifiedName()) 493 } 494 args = append(args, "--timeout", strconv.Itoa(a.context.timeout)) 495 a.runCli(args...) 496 return a 497 } 498 499 func (a *Actions) SetParamInSettingConfigMap(key, value string) *Actions { 500 a.context.t.Helper() 501 require.NoError(a.context.t, fixture.SetParamInSettingConfigMap(key, value)) 502 return a 503 } 504 505 func (a *Actions) And(block func()) *Actions { 506 a.context.t.Helper() 507 block() 508 return a 509 } 510 511 func (a *Actions) Then() *Consequences { 512 a.context.t.Helper() 513 return &Consequences{a.context, a, 15} 514 } 515 516 func (a *Actions) runCli(args ...string) { 517 a.context.t.Helper() 518 a.lastOutput, a.lastError = fixture.RunCli(args...) 519 a.verifyAction() 520 } 521 522 func (a *Actions) verifyAction() { 523 a.context.t.Helper() 524 if !a.ignoreErrors { 525 a.Then().Expect(Success("")) 526 } 527 } 528 529 func (a *Actions) SetTrackingMethod(trackingMethod string) *Actions { 530 a.context.t.Helper() 531 require.NoError(a.context.t, fixture.SetTrackingMethod(trackingMethod)) 532 return a 533 } 534 535 func (a *Actions) SetInstallationID(installationID string) *Actions { 536 a.context.t.Helper() 537 require.NoError(a.context.t, fixture.SetInstallationID(installationID)) 538 return a 539 } 540 541 func (a *Actions) SetTrackingLabel(trackingLabel string) *Actions { 542 a.context.t.Helper() 543 require.NoError(a.context.t, fixture.SetTrackingLabel(trackingLabel)) 544 return a 545 } 546 547 func (a *Actions) WithImpersonationEnabled(serviceAccountName string, policyRules []rbacv1.PolicyRule) *Actions { 548 a.context.t.Helper() 549 require.NoError(a.context.t, fixture.SetImpersonationEnabled("true")) 550 if serviceAccountName == "" || policyRules == nil { 551 return a 552 } 553 require.NoError(a.context.t, fixture.CreateRBACResourcesForImpersonation(serviceAccountName, policyRules)) 554 return a 555 } 556 557 func (a *Actions) WithImpersonationDisabled() *Actions { 558 a.context.t.Helper() 559 require.NoError(a.context.t, fixture.SetImpersonationEnabled("false")) 560 return a 561 }