github.com/argoproj/argo-cd/v2@v2.10.9/test/e2e/fixture/applicationsets/expectation.go (about) 1 package applicationsets 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "strings" 8 9 "github.com/argoproj/gitops-engine/pkg/diff" 10 corev1 "k8s.io/api/core/v1" 11 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 13 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" 14 "github.com/argoproj/argo-cd/v2/test/e2e/fixture/applicationsets/utils" 15 ) 16 17 type state = string 18 19 const ( 20 failed = "failed" 21 pending = "pending" 22 succeeded = "succeeded" 23 ) 24 25 // Expectation returns succeeded on succes condition, or pending/failed on failure, along with 26 // a message to describe the success/failure condition. 27 type Expectation func(c *Consequences) (state state, message string) 28 29 // Success asserts that the last command was successful 30 func Success(message string) Expectation { 31 return func(c *Consequences) (state, string) { 32 if c.actions.lastError != nil { 33 return failed, fmt.Sprintf("error: %v", c.actions.lastError) 34 } 35 if !strings.Contains(c.actions.lastOutput, message) { 36 return failed, fmt.Sprintf("output did not contain '%s'", message) 37 } 38 return succeeded, fmt.Sprintf("no error and output contained '%s'", message) 39 } 40 } 41 42 // Error asserts that the last command was an error with substring match 43 func Error(message, err string) Expectation { 44 return func(c *Consequences) (state, string) { 45 if c.actions.lastError == nil { 46 return failed, "no error" 47 } 48 if !strings.Contains(c.actions.lastOutput, message) { 49 return failed, fmt.Sprintf("output does not contain '%s'", message) 50 } 51 if !strings.Contains(c.actions.lastError.Error(), err) { 52 return failed, fmt.Sprintf("error does not contain '%s'", message) 53 } 54 return succeeded, fmt.Sprintf("error '%s'", message) 55 } 56 } 57 58 // ApplicationsExist checks whether each of the 'expectedApps' exist in the namespace, and are 59 // equivalent to provided values. 60 func ApplicationsExist(expectedApps []v1alpha1.Application) Expectation { 61 return func(c *Consequences) (state, string) { 62 63 for _, expectedApp := range expectedApps { 64 foundApp := c.app(expectedApp.Name) 65 if foundApp == nil { 66 return pending, fmt.Sprintf("missing app '%s'", expectedApp.QualifiedName()) 67 } 68 69 if !appsAreEqual(expectedApp, *foundApp) { 70 71 diff, err := getDiff(filterFields(expectedApp), filterFields(*foundApp)) 72 if err != nil { 73 return failed, err.Error() 74 } 75 76 return pending, fmt.Sprintf("apps are not equal: '%s', diff: %s\n", expectedApp.QualifiedName(), diff) 77 78 } 79 80 } 81 82 return succeeded, "all apps successfully found" 83 } 84 } 85 86 // ApplicationSetHasConditions checks whether each of the 'expectedConditions' exist in the ApplicationSet status, and are 87 // equivalent to provided values. 88 func ApplicationSetHasConditions(applicationSetName string, expectedConditions []v1alpha1.ApplicationSetCondition) Expectation { 89 return func(c *Consequences) (state, string) { 90 91 // retrieve the application set 92 foundApplicationSet := c.applicationSet(applicationSetName) 93 if foundApplicationSet == nil { 94 return pending, fmt.Sprintf("application set '%s' not found", applicationSetName) 95 } 96 97 if !conditionsAreEqual(&expectedConditions, &foundApplicationSet.Status.Conditions) { 98 diff, err := getConditionDiff(expectedConditions, foundApplicationSet.Status.Conditions) 99 if err != nil { 100 return failed, err.Error() 101 } 102 return pending, fmt.Sprintf("application set conditions are not equal: '%s', diff: %s\n", expectedConditions, diff) 103 } 104 return succeeded, "application set successfully found" 105 } 106 } 107 108 // ApplicationsDoNotExist checks that each of the 'expectedApps' no longer exist in the namespace 109 func ApplicationsDoNotExist(expectedApps []v1alpha1.Application) Expectation { 110 return func(c *Consequences) (state, string) { 111 112 for _, expectedApp := range expectedApps { 113 foundApp := c.app(expectedApp.Name) 114 if foundApp != nil { 115 return pending, fmt.Sprintf("app '%s' should no longer exist", expectedApp.QualifiedName()) 116 } 117 } 118 119 return succeeded, "all apps do not exist" 120 } 121 } 122 123 // Pod checks whether a specified condition is true for any of the pods in the namespace 124 func Pod(predicate func(p corev1.Pod) bool) Expectation { 125 return func(c *Consequences) (state, string) { 126 pods, err := pods(utils.ApplicationsResourcesNamespace) 127 if err != nil { 128 return failed, err.Error() 129 } 130 for _, pod := range pods.Items { 131 if predicate(pod) { 132 return succeeded, fmt.Sprintf("pod predicate matched pod named '%s'", pod.GetName()) 133 } 134 } 135 return pending, "pod predicate does not match pods" 136 } 137 } 138 139 func pods(namespace string) (*corev1.PodList, error) { 140 fixtureClient := utils.GetE2EFixtureK8sClient() 141 142 pods, err := fixtureClient.KubeClientset.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{}) 143 return pods, err 144 } 145 146 // getDiff returns a string containing a comparison result of two applications (for test output/debug purposes) 147 func getDiff(orig, new v1alpha1.Application) (string, error) { 148 149 bytes, _, err := diff.CreateTwoWayMergePatch(orig, new, orig) 150 if err != nil { 151 return "", err 152 } 153 154 return string(bytes), nil 155 156 } 157 158 // getConditionDiff returns a string containing a comparison result of two ApplicationSetCondition (for test output/debug purposes) 159 func getConditionDiff(orig, new []v1alpha1.ApplicationSetCondition) (string, error) { 160 if len(orig) != len(new) { 161 return fmt.Sprintf("mismatch between condition sizes: %v %v", len(orig), len(new)), nil 162 } 163 164 var bytes []byte 165 166 for index := range orig { 167 b, _, err := diff.CreateTwoWayMergePatch(orig[index], new[index], orig[index]) 168 if err != nil { 169 return "", err 170 } 171 bytes = append(bytes, b...) 172 } 173 174 return string(bytes), nil 175 176 } 177 178 // filterFields returns a copy of Application, but with unnecessary (for testing) fields removed 179 func filterFields(input v1alpha1.Application) v1alpha1.Application { 180 181 spec := input.Spec 182 183 metaCopy := input.ObjectMeta.DeepCopy() 184 185 output := v1alpha1.Application{ 186 ObjectMeta: metav1.ObjectMeta{ 187 Labels: metaCopy.Labels, 188 Annotations: metaCopy.Annotations, 189 Name: metaCopy.Name, 190 Namespace: metaCopy.Namespace, 191 Finalizers: metaCopy.Finalizers, 192 }, 193 Spec: v1alpha1.ApplicationSpec{ 194 Source: &v1alpha1.ApplicationSource{ 195 Path: spec.GetSource().Path, 196 RepoURL: spec.GetSource().RepoURL, 197 TargetRevision: spec.GetSource().TargetRevision, 198 }, 199 Destination: v1alpha1.ApplicationDestination{ 200 Server: spec.Destination.Server, 201 Name: spec.Destination.Name, 202 Namespace: spec.Destination.Namespace, 203 }, 204 Project: spec.Project, 205 }, 206 } 207 208 return output 209 } 210 211 // filterConditionFields returns a copy of ApplicationSetCondition, but with unnecessary (for testing) fields removed 212 func filterConditionFields(input *[]v1alpha1.ApplicationSetCondition) *[]v1alpha1.ApplicationSetCondition { 213 214 var filteredConditions []v1alpha1.ApplicationSetCondition 215 for _, condition := range *input { 216 newCondition := &v1alpha1.ApplicationSetCondition{ 217 Type: condition.Type, 218 Status: condition.Status, 219 Message: condition.Message, 220 Reason: condition.Reason, 221 } 222 filteredConditions = append(filteredConditions, *newCondition) 223 } 224 225 return &filteredConditions 226 } 227 228 // appsAreEqual returns true if the apps are equal, comparing only fields of interest 229 func appsAreEqual(one v1alpha1.Application, two v1alpha1.Application) bool { 230 return reflect.DeepEqual(filterFields(one), filterFields(two)) 231 } 232 233 // conditionsAreEqual returns true if the appset status conditions are equal, comparing only fields of interest 234 func conditionsAreEqual(one, two *[]v1alpha1.ApplicationSetCondition) bool { 235 return reflect.DeepEqual(filterConditionFields(one), filterConditionFields(two)) 236 }