github.com/argoproj/argo-cd/v3@v3.2.1/test/e2e/fixture/app/expectation.go (about) 1 package app 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "regexp" 8 "strings" 9 10 "github.com/argoproj/gitops-engine/pkg/health" 11 "github.com/argoproj/gitops-engine/pkg/sync/common" 12 corev1 "k8s.io/api/core/v1" 13 apierrors "k8s.io/apimachinery/pkg/api/errors" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 "k8s.io/apimachinery/pkg/fields" 16 17 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 18 "github.com/argoproj/argo-cd/v3/test/e2e/fixture" 19 ) 20 21 type state = string 22 23 const ( 24 failed = "failed" 25 pending = "pending" 26 succeeded = "succeeded" 27 ) 28 29 type Expectation func(c *Consequences) (state state, message string) 30 31 func OperationPhaseIs(expected common.OperationPhase) Expectation { 32 return func(c *Consequences) (state, string) { 33 operationState := c.app().Status.OperationState 34 actual := common.OperationRunning 35 msg := "" 36 if operationState != nil { 37 actual = operationState.Phase 38 msg = operationState.Message 39 } 40 message := fmt.Sprintf("operation phase should be %s, is %s, message: '%s'", expected, actual, msg) 41 return simple(actual == expected, message) 42 } 43 } 44 45 func OperationMessageContains(text string) Expectation { 46 return func(c *Consequences) (state, string) { 47 operationState := c.app().Status.OperationState 48 actual := "" 49 if operationState != nil { 50 actual = operationState.Message 51 } 52 return simple(strings.Contains(actual, text), fmt.Sprintf("operation message should contains '%s', got: '%s'", text, actual)) 53 } 54 } 55 56 func OperationRetriedMinimumTimes(minRetries int64) Expectation { 57 return func(c *Consequences) (state, string) { 58 operationState := c.app().Status.OperationState 59 actual := operationState.RetryCount 60 message := fmt.Sprintf("operation state retry cound should be at least %d, is %d, message: '%s'", minRetries, actual, operationState.Message) 61 return simple(actual >= minRetries, message) 62 } 63 } 64 65 func simple(success bool, message string) (state, string) { 66 if success { 67 return succeeded, message 68 } 69 return pending, message 70 } 71 72 func SyncStatusIs(expected v1alpha1.SyncStatusCode) Expectation { 73 return func(c *Consequences) (state, string) { 74 actual := c.app().Status.Sync.Status 75 return simple(actual == expected, fmt.Sprintf("sync status to be %s, is %s", expected, actual)) 76 } 77 } 78 79 func HydrationPhaseIs(expected v1alpha1.HydrateOperationPhase) Expectation { 80 return func(c *Consequences) (state, string) { 81 actual := c.app().Status.SourceHydrator.CurrentOperation.Phase 82 return simple(actual == expected, fmt.Sprintf("hydration phase to be %s, is %s", expected, actual)) 83 } 84 } 85 86 func Condition(conditionType v1alpha1.ApplicationConditionType, conditionMessage string) Expectation { 87 return func(c *Consequences) (state, string) { 88 got := c.app().Status.Conditions 89 message := fmt.Sprintf("condition {%s %s} in %v", conditionType, conditionMessage, got) 90 for _, condition := range got { 91 if conditionType == condition.Type && strings.Contains(condition.Message, conditionMessage) { 92 return succeeded, message 93 } 94 } 95 return pending, message 96 } 97 } 98 99 func NoConditions() Expectation { 100 return func(c *Consequences) (state, string) { 101 message := "no conditions" 102 if len(c.app().Status.Conditions) == 0 { 103 return succeeded, message 104 } 105 return pending, message 106 } 107 } 108 109 func NoStatus() Expectation { 110 return func(c *Consequences) (state, string) { 111 message := "no status" 112 if reflect.ValueOf(c.app().Status).IsZero() { 113 return succeeded, message 114 } 115 return pending, message 116 } 117 } 118 119 func StatusExists() Expectation { 120 return func(c *Consequences) (state, string) { 121 message := "status exists" 122 if !reflect.ValueOf(c.app().Status).IsZero() { 123 return succeeded, message 124 } 125 return pending, message 126 } 127 } 128 129 func Status(f func(v1alpha1.ApplicationStatus) (bool, string)) Expectation { 130 return func(c *Consequences) (state, string) { 131 ok, msg := f(c.app().Status) 132 if !ok { 133 return pending, msg 134 } 135 return succeeded, msg 136 } 137 } 138 139 func Namespace(name string, block func(app *v1alpha1.Application, ns *corev1.Namespace)) Expectation { 140 return func(c *Consequences) (state, string) { 141 ns, err := namespace(name) 142 if err != nil { 143 return failed, "namespace not found " + err.Error() 144 } 145 146 block(c.app(), ns) 147 return succeeded, fmt.Sprintf("namespace %s assertions passed", name) 148 } 149 } 150 151 func HealthIs(expected health.HealthStatusCode) Expectation { 152 return func(c *Consequences) (state, string) { 153 actual := c.app().Status.Health.Status 154 return simple(actual == expected, fmt.Sprintf("health to should %s, is %s", expected, actual)) 155 } 156 } 157 158 func ResourceSyncStatusIs(kind, resource string, expected v1alpha1.SyncStatusCode) Expectation { 159 return func(c *Consequences) (state, string) { 160 actual := c.resource(kind, resource, "").Status 161 return simple(actual == expected, fmt.Sprintf("resource '%s/%s' sync status should be %s, is %s", kind, resource, expected, actual)) 162 } 163 } 164 165 func ResourceSyncStatusWithNamespaceIs(kind, resource, namespace string, expected v1alpha1.SyncStatusCode) Expectation { 166 return func(c *Consequences) (state, string) { 167 actual := c.resource(kind, resource, namespace).Status 168 return simple(actual == expected, fmt.Sprintf("resource '%s/%s' sync status should be %s, is %s", kind, resource, expected, actual)) 169 } 170 } 171 172 func ResourceHealthIs(kind, resource string, expected health.HealthStatusCode) Expectation { 173 return func(c *Consequences) (state, string) { 174 var actual health.HealthStatusCode 175 resourceHealth := c.resource(kind, resource, "").Health 176 if resourceHealth != nil { 177 actual = resourceHealth.Status 178 } else { 179 // Some resources like ConfigMap may not have health status when they are okay 180 actual = health.HealthStatusHealthy 181 } 182 return simple(actual == expected, fmt.Sprintf("resource '%s/%s' health should be %s, is %s", kind, resource, expected, actual)) 183 } 184 } 185 186 func ResourceHealthWithNamespaceIs(kind, resource, namespace string, expected health.HealthStatusCode) Expectation { 187 return func(c *Consequences) (state, string) { 188 var actual health.HealthStatusCode 189 resourceHealth := c.resource(kind, resource, namespace).Health 190 if resourceHealth != nil { 191 actual = resourceHealth.Status 192 } else { 193 // Some resources like ConfigMap may not have health status when they are okay 194 actual = health.HealthStatusHealthy 195 } 196 return simple(actual == expected, fmt.Sprintf("resource '%s/%s' health should be %s, is %s", kind, resource, expected, actual)) 197 } 198 } 199 200 func ResourceResultNumbering(num int) Expectation { 201 return func(c *Consequences) (state, string) { 202 actualNum := len(c.app().Status.OperationState.SyncResult.Resources) 203 if actualNum < num { 204 return pending, fmt.Sprintf("not enough results yet, want %d, got %d", num, actualNum) 205 } else if actualNum == num { 206 return succeeded, fmt.Sprintf("right number of results, want %d, got %d", num, actualNum) 207 } 208 return failed, fmt.Sprintf("too many results, want %d, got %d", num, actualNum) 209 } 210 } 211 212 func ResourceResultIs(result v1alpha1.ResourceResult) Expectation { 213 return func(c *Consequences) (state, string) { 214 results := c.app().Status.OperationState.SyncResult.Resources 215 for _, res := range results { 216 if reflect.DeepEqual(*res, result) { 217 return succeeded, fmt.Sprintf("found resource result %v", result) 218 } 219 } 220 return pending, fmt.Sprintf("waiting for resource result %v in %v", result, results) 221 } 222 } 223 224 func sameResourceResult(res1, res2 v1alpha1.ResourceResult) bool { 225 return res1.Kind == res2.Kind && 226 res1.Group == res2.Group && 227 res1.Namespace == res2.Namespace && 228 res1.Name == res2.Name && 229 res1.SyncPhase == res2.SyncPhase && 230 res1.Status == res2.Status && 231 res1.HookPhase == res2.HookPhase 232 } 233 234 func ResourceResultMatches(result v1alpha1.ResourceResult) Expectation { 235 return func(c *Consequences) (state, string) { 236 results := c.app().Status.OperationState.SyncResult.Resources 237 for _, res := range results { 238 if sameResourceResult(*res, result) { 239 re := regexp.MustCompile(result.Message) 240 if re.MatchString(res.Message) { 241 return succeeded, fmt.Sprintf("found resource result %v", result) 242 } 243 } 244 } 245 return pending, fmt.Sprintf("waiting for resource result %v in %v", result, results) 246 } 247 } 248 249 func DoesNotExist() Expectation { 250 return func(c *Consequences) (state, string) { 251 _, err := c.get() 252 if err != nil { 253 if apierrors.IsNotFound(err) { 254 return succeeded, "app does not exist" 255 } 256 return failed, err.Error() 257 } 258 return pending, "app should not exist" 259 } 260 } 261 262 func DoesNotExistNow() Expectation { 263 return func(c *Consequences) (state, string) { 264 _, err := c.get() 265 if err != nil { 266 if apierrors.IsNotFound(err) { 267 return succeeded, "app does not exist" 268 } 269 return failed, err.Error() 270 } 271 return failed, "app should not exist" 272 } 273 } 274 275 func Pod(predicate func(p corev1.Pod) bool) Expectation { 276 return func(_ *Consequences) (state, string) { 277 pods, err := pods() 278 if err != nil { 279 return failed, err.Error() 280 } 281 for _, pod := range pods.Items { 282 if predicate(pod) { 283 return succeeded, fmt.Sprintf("pod predicate matched pod named '%s'", pod.GetName()) 284 } 285 } 286 return pending, "pod predicate does not match pods" 287 } 288 } 289 290 func NotPod(predicate func(p corev1.Pod) bool) Expectation { 291 return func(_ *Consequences) (state, string) { 292 pods, err := pods() 293 if err != nil { 294 return failed, err.Error() 295 } 296 for _, pod := range pods.Items { 297 if predicate(pod) { 298 return pending, fmt.Sprintf("pod predicate matched pod named '%s'", pod.GetName()) 299 } 300 } 301 return succeeded, "pod predicate did not match any pod" 302 } 303 } 304 305 func pods() (*corev1.PodList, error) { 306 fixture.KubeClientset.CoreV1() 307 pods, err := fixture.KubeClientset.CoreV1().Pods(fixture.DeploymentNamespace()).List(context.Background(), metav1.ListOptions{}) 308 return pods, err 309 } 310 311 func NoNamespace(name string) Expectation { 312 return func(_ *Consequences) (state, string) { 313 _, err := namespace(name) 314 if err != nil { 315 return succeeded, "namespace not found" 316 } 317 318 return failed, "found namespace " + name 319 } 320 } 321 322 func namespace(name string) (*corev1.Namespace, error) { 323 fixture.KubeClientset.CoreV1() 324 return fixture.KubeClientset.CoreV1().Namespaces().Get(context.Background(), name, metav1.GetOptions{}) 325 } 326 327 func event(namespace string, reason string, message string) Expectation { 328 return func(c *Consequences) (state, string) { 329 list, err := fixture.KubeClientset.CoreV1().Events(namespace).List(context.Background(), metav1.ListOptions{ 330 FieldSelector: fields.SelectorFromSet(map[string]string{ 331 "involvedObject.name": c.context.AppName(), 332 "involvedObject.namespace": namespace, 333 }).String(), 334 }) 335 if err != nil { 336 return failed, err.Error() 337 } 338 339 for i := range list.Items { 340 event := list.Items[i] 341 if event.Reason == reason && strings.Contains(event.Message, message) { 342 return succeeded, fmt.Sprintf("found event with reason=%s; message=%s", reason, message) 343 } 344 } 345 return failed, fmt.Sprintf("unable to find event with reason=%s; message=%s", reason, message) 346 } 347 } 348 349 func Event(reason string, message string) Expectation { 350 return event(fixture.TestNamespace(), reason, message) 351 } 352 353 func NamespacedEvent(namespace string, reason string, message string) Expectation { 354 return event(namespace, reason, message) 355 } 356 357 // Success asserts that the last command was successful and that the output contains the given message. 358 func Success(message string, matchers ...func(string, string) bool) Expectation { 359 if len(matchers) == 0 { 360 matchers = append(matchers, strings.Contains) 361 } 362 match := func(actual, expected string) bool { 363 for i := range matchers { 364 if !matchers[i](actual, expected) { 365 return false 366 } 367 } 368 return true 369 } 370 return func(c *Consequences) (state, string) { 371 if c.actions.lastError != nil { 372 return failed, fmt.Errorf("error: %w", c.actions.lastError).Error() 373 } 374 if !match(c.actions.lastOutput, message) { 375 return failed, fmt.Sprintf("output did not contain '%s'", message) 376 } 377 return succeeded, fmt.Sprintf("no error and output contained '%s'", message) 378 } 379 } 380 381 // Error asserts that the last command was an error with substring match 382 func Error(message, err string, matchers ...func(string, string) bool) Expectation { 383 if len(matchers) == 0 { 384 matchers = append(matchers, strings.Contains) 385 } 386 match := func(actual, expected string) bool { 387 for i := range matchers { 388 if !matchers[i](actual, expected) { 389 return false 390 } 391 } 392 return true 393 } 394 return func(c *Consequences) (state, string) { 395 if c.actions.lastError == nil { 396 return failed, "no error" 397 } 398 if !match(c.actions.lastOutput, message) { 399 return failed, fmt.Sprintf("output does not contain '%s'", message) 400 } 401 if !match(c.actions.lastError.Error(), err) { 402 return failed, fmt.Sprintf("error does not contain '%s'", err) 403 } 404 return succeeded, fmt.Sprintf("error '%s'", message) 405 } 406 } 407 408 // ErrorRegex asserts that the last command was an error that matches given regex epxression 409 func ErrorRegex(messagePattern, err string) Expectation { 410 return Error(messagePattern, err, func(actual, expected string) bool { 411 return regexp.MustCompile(expected).MatchString(actual) 412 }) 413 } 414 415 // SuccessRegex asserts that the last command was successful and output matches given regex expression 416 func SuccessRegex(messagePattern string) Expectation { 417 return Success(messagePattern, func(actual, expected string) bool { 418 return regexp.MustCompile(expected).MatchString(actual) 419 }) 420 }