github.com/redhat-appstudio/e2e-tests@v0.0.0-20240520140907-9709f6f59323/tests/build/tkn-bundle.go (about) 1 package build 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "strings" 8 9 "github.com/google/go-containerregistry/pkg/authn" 10 "github.com/google/go-containerregistry/pkg/crane" 11 . "github.com/onsi/ginkgo/v2" 12 . "github.com/onsi/gomega" 13 kubeapi "github.com/redhat-appstudio/e2e-tests/pkg/clients/kubernetes" 14 "github.com/redhat-appstudio/e2e-tests/pkg/constants" 15 "github.com/redhat-appstudio/e2e-tests/pkg/framework" 16 "github.com/redhat-appstudio/e2e-tests/pkg/utils" 17 "github.com/redhat-appstudio/e2e-tests/pkg/utils/build" 18 "github.com/tektoncd/cli/pkg/bundle" 19 pipeline "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" 20 corev1 "k8s.io/api/core/v1" 21 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 "k8s.io/apimachinery/pkg/runtime" 23 ) 24 25 /* to run locally on a kind cluster 26 1. set environment variables with examples 27 - E2E_APPLICATIONS_NAMESPACE=konflux-tasks 28 - TKN_BUNDLE_REPO=quay.io/my-user-org/tkn-bundle:latest 29 2. AFTER the namespace is created, create a docker secret and patch the sa 30 - kubectl create secret generic docker-config --from-file=.dockerconfigjson="$HOME/.docker/config.json" --type=kubernetes.io/dockerconfigjson --dry-run=client -o yaml | kubectl apply -f 31 - kubectl patch serviceaccount appstudio-pipeline -p '{"imagePullSecrets": [{"name": "docker-config"}], "secrets": [{"name": "docker-config"}]}' 32 */ 33 34 var _ = framework.TknBundleSuiteDescribe("tkn bundle task", Label("tasks", "HACBS"), func() { 35 36 defer GinkgoRecover() 37 38 var namespace string 39 var kubeClient *framework.ControllerHub 40 var fwk *framework.Framework 41 var taskName string = "tkn-bundle" 42 var pathInRepo string = fmt.Sprintf("task/%s/0.1/%s.yaml", taskName, taskName) 43 var pvcName string = "source-pvc" 44 var pvcAccessMode corev1.PersistentVolumeAccessMode = "ReadWriteOnce" 45 var baseTaskRun *pipeline.TaskRun 46 var qeBundleRepo string = fmt.Sprintf("quay.io/%s/test-images:%s", utils.GetQuayIOOrganization(), taskName) 47 48 var gitRevision, gitURL, bundleImg string 49 50 AfterEach(framework.ReportFailure(&fwk)) 51 52 BeforeAll(func() { 53 namespace = os.Getenv(constants.E2E_APPLICATIONS_NAMESPACE_ENV) 54 if len(namespace) > 0 { 55 adminClient, err := kubeapi.NewAdminKubernetesClient() 56 Expect(err).ShouldNot(HaveOccurred()) 57 kubeClient, err = framework.InitControllerHub(adminClient) 58 Expect(err).ShouldNot(HaveOccurred()) 59 _, err = kubeClient.CommonController.CreateTestNamespace(namespace) 60 Expect(err).ShouldNot(HaveOccurred()) 61 62 // set a custom bundle repo for the task 63 bundleImg = utils.GetEnv("TKN_BUNDLE_REPO", qeBundleRepo) 64 } else { 65 var err error 66 fwk, err = framework.NewFramework(utils.GetGeneratedNamespace("konflux-task-runner")) 67 Expect(err).NotTo(HaveOccurred()) 68 Expect(fwk.UserNamespace).NotTo(BeNil(), "failed to create sandbox user") 69 namespace = fwk.UserNamespace 70 kubeClient = fwk.AsKubeAdmin 71 72 err = kubeClient.CommonController.CreateQuayRegistrySecret(namespace) 73 Expect(err).NotTo(HaveOccurred()) 74 bundleImg = qeBundleRepo 75 } 76 77 // resolve the gitURL and gitRevision 78 var err error 79 gitURL, gitRevision, err = build.ResolveGitDetails(constants.TASK_REPO_URL_ENV, constants.TASK_REPO_REVISION_ENV) 80 Expect(err).NotTo(HaveOccurred()) 81 82 // if pvc does not exist create it 83 if _, err := kubeClient.TektonController.GetPVC(pvcName, namespace); err != nil { 84 _, err = kubeClient.TektonController.CreatePVCInAccessMode(pvcName, namespace, pvcAccessMode) 85 Expect(err).NotTo(HaveOccurred()) 86 } 87 // use a pod to copy test data to the pvc 88 testData, err := setupTestData(pvcName) 89 Expect(err).NotTo(HaveOccurred()) 90 pod, err := kubeClient.CommonController.CreatePod(testData, namespace) 91 Expect(err).NotTo(HaveOccurred()) 92 // wait for setup pod. make sure it's successful 93 err = kubeClient.CommonController.WaitForPod(kubeClient.CommonController.IsPodSuccessful(pod.Name, namespace), 300) 94 Expect(err).NotTo(HaveOccurred()) 95 }) 96 97 BeforeEach(func() { 98 resolverRef := pipeline.ResolverRef{ 99 Resolver: "git", 100 Params: []pipeline.Param{ 101 {Name: "url", Value: *pipeline.NewStructuredValues(gitURL)}, 102 {Name: "revision", Value: *pipeline.NewStructuredValues(gitRevision)}, 103 {Name: "pathInRepo", Value: *pipeline.NewStructuredValues(pathInRepo)}, 104 }, 105 } 106 // get a new taskRun on each Entry 107 baseTaskRun = taskRunTemplate(taskName, pvcName, bundleImg, resolverRef) 108 }) 109 110 DescribeTable("creates Tekton bundles with different params", 111 func(params map[string]string, expectedOutput, notExpectedOutput []string, expectedHomeVar, stepImage string) { 112 for key, val := range params { 113 baseTaskRun.Spec.Params = append(baseTaskRun.Spec.Params, pipeline.Param{ 114 Name: key, 115 Value: pipeline.ParamValue{ 116 Type: "string", 117 StringVal: val, 118 }, 119 }) 120 } 121 tr, err := kubeClient.TektonController.RunTaskAndWait(baseTaskRun, namespace) 122 Expect(err).NotTo(HaveOccurred()) 123 124 // check for a success of the taskRun 125 status, err := kubeClient.TektonController.CheckTaskRunSucceeded(tr.Name, namespace)() 126 Expect(err).NotTo(HaveOccurred()) 127 Expect(status).To(BeTrue(), fmt.Sprintf("taskRun %q failed", tr.Name)) 128 129 // verify taskRun results 130 imgUrl, err := kubeClient.TektonController.GetResultFromTaskRun(tr, "IMAGE_URL") 131 Expect(err).NotTo(HaveOccurred()) 132 Expect(imgUrl).To(Equal(bundleImg)) 133 134 imgDigest, err := kubeClient.TektonController.GetResultFromTaskRun(tr, "IMAGE_DIGEST") 135 Expect(err).NotTo(HaveOccurred()) 136 Expect(imgDigest).To(MatchRegexp(`^sha256:[a-fA-F0-9]{64}$`)) 137 138 // verify taskRun log output 139 podLogs, err := kubeClient.CommonController.GetPodLogsByName(tr.Status.PodName, namespace) 140 Expect(err).NotTo(HaveOccurred()) 141 podLog := fmt.Sprintf("pod-%s-step-build.log", tr.Status.PodName) 142 matchOutput(podLogs[podLog], expectedOutput) 143 notMatchOutput(podLogs[podLog], notExpectedOutput) 144 145 // verify environment variables 146 envVar, err := kubeClient.TektonController.GetEnvVariable(tr, "HOME") 147 Expect(err).NotTo(HaveOccurred()) 148 Expect(envVar).To(Equal(expectedHomeVar)) 149 150 // verify the step images 151 visitor := func(apiVersion, kind, name string, obj runtime.Object, raw []byte) { 152 task := &pipeline.Task{} 153 err := json.Unmarshal(raw, task) 154 Expect(err).ToNot(HaveOccurred()) 155 for _, step := range task.Spec.Steps { 156 Expect(step.Image).To(Equal(stepImage)) 157 } 158 } 159 bundle := fmt.Sprintf("%s@%s", imgUrl, imgDigest) 160 GinkgoWriter.Print(bundle) 161 fetchImage(fmt.Sprintf("%s@%s", imgUrl, imgDigest), visitor) 162 163 }, 164 Entry("when context points to a file", map[string]string{"CONTEXT": "task2.yaml"}, 165 []string{ 166 "\t- Added Task: task2 to image", 167 }, 168 []string{ 169 "\t- Added Task: task1 to image", 170 "\t- Added Task: task3 to image", 171 }, 172 "/tekton/home", 173 "ubuntu", 174 ), 175 Entry("creates Tekton bundles from specific context", map[string]string{"CONTEXT": "sub"}, []string{ 176 "\t- Added Task: task3 to image", 177 }, 178 []string{ 179 "\t- Added Task: task1 to image", 180 "\t- Added Task: task2 to image", 181 }, 182 "/tekton/home", 183 "ubuntu", 184 ), 185 Entry("when context is the root directory", map[string]string{}, []string{ 186 "\t- Added Task: task1 to image", 187 "\t- Added Task: task2 to image", 188 "\t- Added Task: task3 to image", 189 }, 190 []string{}, 191 "/tekton/home", 192 "ubuntu", 193 ), 194 Entry("creates Tekton bundles when context points to a file and a directory", map[string]string{"CONTEXT": "task2.yaml,sub"}, []string{ 195 "\t- Added Task: task2 to image", 196 "\t- Added Task: task3 to image", 197 }, 198 []string{ 199 "\t- Added Task: task1 to image", 200 }, 201 "/tekton/home", 202 "ubuntu", 203 ), 204 Entry("creates Tekton bundles when using negation", map[string]string{"CONTEXT": "!sub"}, []string{ 205 "\t- Added Task: task1 to image", 206 "\t- Added Task: task2 to image", 207 }, 208 []string{ 209 "\t- Added Task: task3 to image", 210 }, 211 "/tekton/home", 212 "ubuntu", 213 ), 214 Entry("allows overriding HOME environment variable", map[string]string{"CONTEXT": ".", "HOME": "/tekton/summer-home"}, []string{ 215 "\t- Added Task: task1 to image", 216 "\t- Added Task: task2 to image", 217 "\t- Added Task: task3 to image", 218 }, 219 []string{}, 220 "/tekton/summer-home", 221 "ubuntu", 222 ), 223 Entry("allows overriding STEP image", map[string]string{"STEPS_IMAGE": "quay.io/enterprise-contract/contract:latest"}, []string{ 224 "\t- Added Task: task1 to image", 225 "\t- Added Task: task2 to image", 226 "\t- Added Task: task3 to image", 227 }, 228 []string{}, 229 "/tekton/home", 230 "quay.io/enterprise-contract/contract:latest", 231 ), 232 ) 233 }) 234 235 // check output that should exist 236 func matchOutput(logs []byte, expectedOutput []string) { 237 for _, out := range expectedOutput { 238 Expect(strings.Split(string(logs), "\n")).To(ContainElement(out)) 239 } 240 } 241 242 // check that output does not exist 243 func notMatchOutput(logs []byte, expectedOutput []string) { 244 for _, out := range expectedOutput { 245 Expect(strings.Split(string(logs), "\n")).NotTo(ContainElement(out)) 246 } 247 } 248 249 // fetch the image 250 func fetchImage(image string, visitor func(version, kind, name string, element runtime.Object, raw []byte)) { 251 img, err := crane.Pull(image, crane.WithAuthFromKeychain(authn.DefaultKeychain)) 252 Expect(err).ToNot(HaveOccurred()) 253 254 err = bundle.List(img, visitor) 255 Expect(err).ToNot(HaveOccurred()) 256 } 257 258 // sets the task files on a pvc for use by the task 259 func setupTestData(pvcName string) (*corev1.Pod, error) { 260 // setup test data 261 testTasks, err := testData([]string{"task1", "task2", "task3"}) 262 if err != nil { 263 return nil, err 264 } 265 266 return &corev1.Pod{ 267 TypeMeta: metav1.TypeMeta{ 268 Kind: "Pod", 269 APIVersion: "v1", 270 }, 271 ObjectMeta: metav1.ObjectMeta{ 272 GenerateName: "setup-pod-", 273 }, 274 Spec: corev1.PodSpec{ 275 RestartPolicy: "Never", 276 Containers: []corev1.Container{ 277 { 278 Command: []string{ 279 "bash", 280 "-c", 281 "mkdir -p /source/sub; echo $TASK1_JSON > /source/task1.yaml; echo $TASK2_JSON > /source/task2.yaml; echo $TASK3_JSON > /source/sub/task3.yaml", 282 }, 283 Image: "registry.access.redhat.com/ubi9/ubi-minimal:latest", 284 Name: "setup-pod", 285 VolumeMounts: []corev1.VolumeMount{ 286 { 287 MountPath: "/source", 288 Name: "source", 289 }, 290 }, 291 Env: []corev1.EnvVar{ 292 { 293 Name: "TASK1_JSON", 294 Value: testTasks["task1"], 295 }, 296 { 297 Name: "TASK2_JSON", 298 Value: testTasks["task2"], 299 }, 300 { 301 Name: "TASK3_JSON", 302 Value: testTasks["task3"], 303 }, 304 }, 305 }, 306 }, 307 Volumes: []corev1.Volume{ 308 { 309 Name: "source", 310 VolumeSource: corev1.VolumeSource{ 311 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 312 ClaimName: pvcName, 313 }, 314 }, 315 }, 316 }, 317 }, 318 }, nil 319 } 320 321 // the test tasks 322 func testData(tasks []string) (map[string]string, error) { 323 apiVersion := "tekton.dev/v1" 324 allTasks := make(map[string]string) 325 for idx, task := range tasks { 326 taskJson, err := serializeTask(&pipeline.Task{ 327 TypeMeta: metav1.TypeMeta{ 328 Kind: "Task", 329 APIVersion: apiVersion, 330 }, 331 ObjectMeta: metav1.ObjectMeta{ 332 Name: task, 333 }, 334 Spec: pipeline.TaskSpec{ 335 Steps: []pipeline.Step{ 336 { 337 Name: fmt.Sprintf("test%d-step", idx), 338 Image: "ubuntu", 339 }, 340 }, 341 }, 342 }) 343 if err != nil { 344 return nil, err 345 } 346 allTasks[task] = taskJson 347 } 348 return allTasks, nil 349 } 350 351 // the taskRun that runs tkn-bundle 352 func taskRunTemplate(taskName, pvcName, bundleImg string, resolverRef pipeline.ResolverRef) *pipeline.TaskRun { 353 return &pipeline.TaskRun{ 354 TypeMeta: metav1.TypeMeta{ 355 Kind: "Task", 356 APIVersion: "tekton.dev/v1", 357 }, 358 ObjectMeta: metav1.ObjectMeta{ 359 GenerateName: fmt.Sprintf("%s-", taskName), 360 }, 361 Spec: pipeline.TaskRunSpec{ 362 ServiceAccountName: constants.DefaultPipelineServiceAccount, 363 TaskRef: &pipeline.TaskRef{ 364 ResolverRef: resolverRef, 365 }, 366 Params: pipeline.Params{ 367 { 368 Name: "IMAGE", 369 Value: pipeline.ParamValue{ 370 Type: "string", 371 StringVal: bundleImg, 372 }, 373 }, 374 }, 375 Workspaces: []pipeline.WorkspaceBinding{ 376 { 377 Name: "source", 378 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 379 ClaimName: pvcName, 380 }, 381 }, 382 }, 383 }, 384 } 385 } 386 387 func serializeTask(task *pipeline.Task) (string, error) { 388 taskJson, err := json.Marshal(task) 389 if err != nil { 390 return "", err 391 } 392 return string(taskJson), nil 393 }