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