github.com/redhat-appstudio/e2e-tests@v0.0.0-20230619105049-9a422b2094d7/pkg/utils/tekton/controller.go (about) 1 package tekton 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "os/exec" 10 "strings" 11 "time" 12 13 buildservice "github.com/redhat-appstudio/build-service/api/v1alpha1" 14 "github.com/tektoncd/pipeline/pkg/apis/pipeline/pod" 15 "k8s.io/apimachinery/pkg/types" 16 "k8s.io/apimachinery/pkg/watch" 17 "k8s.io/utils/pointer" 18 19 "github.com/redhat-appstudio/e2e-tests/pkg/utils" 20 21 ecp "github.com/enterprise-contract/enterprise-contract-controller/api/v1alpha1" 22 kubeCl "github.com/redhat-appstudio/e2e-tests/pkg/apis/kubernetes" 23 "github.com/redhat-appstudio/e2e-tests/pkg/utils/common" 24 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" 25 corev1 "k8s.io/api/core/v1" 26 "k8s.io/apimachinery/pkg/api/errors" 27 "k8s.io/apimachinery/pkg/api/resource" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/labels" 30 "k8s.io/apimachinery/pkg/util/wait" 31 v1 "k8s.io/client-go/kubernetes/typed/core/v1" 32 crclient "sigs.k8s.io/controller-runtime/pkg/client" 33 34 g "github.com/onsi/ginkgo/v2" 35 ) 36 37 const quayBaseUrl = "https://quay.io/api/v1" 38 39 type KubeController struct { 40 Commonctrl common.SuiteController 41 Tektonctrl SuiteController 42 Namespace string 43 } 44 45 type Bundles struct { 46 FBCBuilderBundle string 47 DockerBuildBundle string 48 JavaBuilderBundle string 49 NodeJSBuilderBundle string 50 } 51 52 type QuayImageInfo struct { 53 ImageRef string 54 Layers []any 55 } 56 57 type TagResponse struct { 58 Tags []Tag `json:"tags"` 59 } 60 type Tag struct { 61 Digest string `json:"manifest_digest"` 62 } 63 type ManifestResponse struct { 64 Layers []any `json:"layers"` 65 } 66 67 // Create the struct for kubernetes clients 68 type SuiteController struct { 69 *kubeCl.CustomClient 70 } 71 72 type CosignResult struct { 73 signatureImageRef string 74 attestationImageRef string 75 } 76 77 func (c CosignResult) IsPresent() bool { 78 return c.signatureImageRef != "" && c.attestationImageRef != "" 79 } 80 81 func (c CosignResult) Missing(prefix string) string { 82 var ret []string = make([]string, 0, 2) 83 if c.signatureImageRef == "" { 84 ret = append(ret, prefix+".sig") 85 } 86 87 if c.attestationImageRef == "" { 88 ret = append(ret, prefix+".att") 89 } 90 91 return strings.Join(ret, " and ") 92 } 93 94 // Create controller for Tekton Task/Pipeline CRUD operations 95 func NewSuiteController(kube *kubeCl.CustomClient) *SuiteController { 96 return &SuiteController{kube} 97 } 98 99 func (s *SuiteController) NewBundles() (*Bundles, error) { 100 namespacedName := types.NamespacedName{ 101 Name: "build-pipeline-selector", 102 Namespace: "build-service", 103 } 104 bundles := &Bundles{} 105 pipelineSelector := &buildservice.BuildPipelineSelector{} 106 err := s.KubeRest().Get(context.TODO(), namespacedName, pipelineSelector) 107 if err != nil { 108 return nil, err 109 } 110 for _, selector := range pipelineSelector.Spec.Selectors { 111 bundleName := selector.PipelineRef.Name 112 bundleRef := selector.PipelineRef.Bundle 113 switch bundleName { 114 case "docker-build": 115 bundles.DockerBuildBundle = bundleRef 116 case "fbc-builder": 117 bundles.FBCBuilderBundle = bundleRef 118 case "java-builder": 119 bundles.JavaBuilderBundle = bundleRef 120 case "nodejs-builder": 121 bundles.NodeJSBuilderBundle = bundleRef 122 } 123 } 124 return bundles, nil 125 } 126 127 func (s *SuiteController) GetPipelineRun(pipelineRunName, namespace string) (*v1beta1.PipelineRun, error) { 128 return s.PipelineClient().TektonV1beta1().PipelineRuns(namespace).Get(context.TODO(), pipelineRunName, metav1.GetOptions{}) 129 } 130 131 func (s *SuiteController) WatchPipelineRun(ctx context.Context, namespace string) (watch.Interface, error) { 132 return s.PipelineClient().TektonV1beta1().PipelineRuns(namespace).Watch(ctx, metav1.ListOptions{}) 133 } 134 135 func (s *SuiteController) fetchContainerLog(podName, containerName, namespace string) (string, error) { 136 podClient := s.KubeInterface().CoreV1().Pods(namespace) 137 req := podClient.GetLogs(podName, &corev1.PodLogOptions{Container: containerName}) 138 readCloser, err := req.Stream(context.TODO()) 139 log := "" 140 if err != nil { 141 return log, err 142 } 143 defer readCloser.Close() 144 b, err := io.ReadAll(readCloser) 145 if err != nil { 146 return log, err 147 } 148 return string(b[:]), nil 149 } 150 151 func (s *SuiteController) GetPipelineRunLogs(pipelineRunName, namespace string) (string, error) { 152 podClient := s.KubeInterface().CoreV1().Pods(namespace) 153 podList, err := podClient.List(context.TODO(), metav1.ListOptions{}) 154 if err != nil { 155 return "", err 156 } 157 podLog := "" 158 for _, pod := range podList.Items { 159 if !strings.HasPrefix(pod.Name, pipelineRunName) { 160 continue 161 } 162 for _, c := range pod.Spec.InitContainers { 163 var err error 164 var cLog string 165 cLog, err = s.fetchContainerLog(pod.Name, c.Name, namespace) 166 podLog = podLog + fmt.Sprintf("\ninit container %s: \n", c.Name) + cLog 167 if err != nil { 168 return podLog, err 169 } 170 } 171 for _, c := range pod.Spec.Containers { 172 var err error 173 var cLog string 174 cLog, err = s.fetchContainerLog(pod.Name, c.Name, namespace) 175 podLog = podLog + fmt.Sprintf("\ncontainer %s: \n", c.Name) + cLog 176 if err != nil { 177 return podLog, err 178 } 179 } 180 } 181 return podLog, nil 182 } 183 184 func (s *SuiteController) GetTaskRunLogs(pipelineRunName, taskName, namespace string) (map[string]string, error) { 185 tektonClient := s.PipelineClient().TektonV1beta1().PipelineRuns(namespace) 186 pipelineRun, err := tektonClient.Get(context.TODO(), pipelineRunName, metav1.GetOptions{}) 187 if err != nil { 188 return nil, err 189 } 190 191 podName := "" 192 for _, childStatusReference := range pipelineRun.Status.ChildReferences { 193 if childStatusReference.PipelineTaskName == taskName { 194 taskRun := &v1beta1.TaskRun{} 195 taskRunKey := types.NamespacedName{Namespace: pipelineRun.Namespace, Name: childStatusReference.Name} 196 if err := s.KubeRest().Get(context.TODO(), taskRunKey, taskRun); err != nil { 197 return nil, err 198 } 199 podName = taskRun.Status.PodName 200 break 201 } 202 } 203 if podName == "" { 204 return nil, fmt.Errorf("task with %s name doesn't exist in %s pipelinerun", taskName, pipelineRunName) 205 } 206 207 podClient := s.KubeInterface().CoreV1().Pods(namespace) 208 pod, err := podClient.Get(context.TODO(), podName, metav1.GetOptions{}) 209 if err != nil { 210 return nil, err 211 } 212 213 logs := make(map[string]string) 214 for _, container := range pod.Spec.Containers { 215 containerName := container.Name 216 if containerLogs, err := s.fetchContainerLog(podName, containerName, namespace); err == nil { 217 logs[containerName] = containerLogs 218 } else { 219 logs[containerName] = "failed to get logs" 220 } 221 } 222 return logs, nil 223 } 224 225 func (s *SuiteController) CheckPipelineRunStarted(pipelineRunName, namespace string) wait.ConditionFunc { 226 return func() (bool, error) { 227 pr, err := s.GetPipelineRun(pipelineRunName, namespace) 228 if err != nil { 229 return false, nil 230 } 231 if pr.Status.StartTime != nil { 232 return true, nil 233 } 234 return false, nil 235 } 236 } 237 238 func (s *SuiteController) CheckPipelineRunFinished(pipelineRunName, namespace string) wait.ConditionFunc { 239 return func() (bool, error) { 240 pr, err := s.GetPipelineRun(pipelineRunName, namespace) 241 if err != nil { 242 return false, nil 243 } 244 if pr.Status.CompletionTime != nil { 245 return true, nil 246 } 247 return false, nil 248 } 249 } 250 251 func (s *SuiteController) CheckPipelineRunSucceeded(pipelineRunName, namespace string) wait.ConditionFunc { 252 return func() (bool, error) { 253 pr, err := s.GetPipelineRun(pipelineRunName, namespace) 254 if err != nil { 255 return false, err 256 } 257 if len(pr.Status.Conditions) > 0 { 258 for _, c := range pr.Status.Conditions { 259 if c.Type == "Succeeded" && c.Status == "True" { 260 return true, nil 261 } 262 } 263 } 264 return false, nil 265 } 266 } 267 268 // Create a tekton task and return the task or error 269 func (s *SuiteController) CreateTask(task *v1beta1.Task, ns string) (*v1beta1.Task, error) { 270 return s.PipelineClient().TektonV1beta1().Tasks(ns).Create(context.TODO(), task, metav1.CreateOptions{}) 271 } 272 273 func (s *SuiteController) DeleteTask(name, ns string) error { 274 return s.PipelineClient().TektonV1beta1().Tasks(ns).Delete(context.TODO(), name, metav1.DeleteOptions{}) 275 } 276 277 // Create a tekton pipelineRun and return the pipelineRun or error 278 func (s *SuiteController) CreatePipelineRun(pipelineRun *v1beta1.PipelineRun, ns string) (*v1beta1.PipelineRun, error) { 279 return s.PipelineClient().TektonV1beta1().PipelineRuns(ns).Create(context.TODO(), pipelineRun, metav1.CreateOptions{}) 280 } 281 282 func (s *SuiteController) DeletePipelineRun(name, ns string) error { 283 return s.PipelineClient().TektonV1beta1().PipelineRuns(ns).Delete(context.TODO(), name, metav1.DeleteOptions{}) 284 } 285 286 // Create a tekton pipeline and return the pipeline or error 287 func (s *SuiteController) CreatePipeline(pipeline *v1beta1.Pipeline, ns string) (*v1beta1.Pipeline, error) { 288 return s.PipelineClient().TektonV1beta1().Pipelines(ns).Create(context.TODO(), pipeline, metav1.CreateOptions{}) 289 } 290 291 func (s *SuiteController) DeletePipeline(name, ns string) error { 292 return s.PipelineClient().TektonV1beta1().Pipelines(ns).Delete(context.TODO(), name, metav1.DeleteOptions{}) 293 } 294 295 func (s *SuiteController) ListTaskRuns(ns string, labelKey string, labelValue string, selectorLimit int64) (*v1beta1.TaskRunList, error) { 296 labelSelector := metav1.LabelSelector{MatchLabels: map[string]string{labelKey: labelValue}} 297 listOptions := metav1.ListOptions{ 298 LabelSelector: labels.Set(labelSelector.MatchLabels).String(), 299 Limit: selectorLimit, 300 } 301 return s.PipelineClient().TektonV1beta1().TaskRuns(ns).List(context.TODO(), listOptions) 302 } 303 304 func (s *SuiteController) ListAllTaskRuns(ns string) (*v1beta1.TaskRunList, error) { 305 return s.PipelineClient().TektonV1beta1().TaskRuns(ns).List(context.TODO(), metav1.ListOptions{}) 306 } 307 308 func (s *SuiteController) ListAllPipelineRuns(ns string) (*v1beta1.PipelineRunList, error) { 309 return s.PipelineClient().TektonV1beta1().PipelineRuns(ns).List(context.TODO(), metav1.ListOptions{}) 310 } 311 312 func (s *SuiteController) DeleteTaskRun(name, ns string) error { 313 return s.PipelineClient().TektonV1beta1().TaskRuns(ns).Delete(context.TODO(), name, metav1.DeleteOptions{}) 314 } 315 316 func (k KubeController) WatchPipelineRun(pipelineRunName string, taskTimeout int) error { 317 g.GinkgoWriter.Printf("Waiting for pipeline %q to finish\n", pipelineRunName) 318 return utils.WaitUntil(k.Tektonctrl.CheckPipelineRunFinished(pipelineRunName, k.Namespace), time.Duration(taskTimeout)*time.Second) 319 } 320 321 func (k KubeController) WatchPipelineRunSucceeded(pipelineRunName string, taskTimeout int) error { 322 g.GinkgoWriter.Printf("Waiting for pipeline %q to finish\n", pipelineRunName) 323 return utils.WaitUntil(k.Tektonctrl.CheckPipelineRunSucceeded(pipelineRunName, k.Namespace), time.Duration(taskTimeout)*time.Second) 324 } 325 326 func (k KubeController) GetTaskRunResult(c crclient.Client, pr *v1beta1.PipelineRun, pipelineTaskName string, result string) (string, error) { 327 for _, chr := range pr.Status.ChildReferences { 328 if chr.PipelineTaskName != pipelineTaskName { 329 continue 330 } 331 332 taskRun := &v1beta1.TaskRun{} 333 taskRunKey := types.NamespacedName{Namespace: pr.Namespace, Name: chr.Name} 334 if err := c.Get(context.TODO(), taskRunKey, taskRun); err != nil { 335 return "", err 336 } 337 338 for _, trResult := range taskRun.Status.TaskRunResults { 339 if trResult.Name == result { 340 // for some reason the result might contain \n suffix 341 return strings.TrimSuffix(trResult.Value.StringVal, "\n"), nil 342 } 343 } 344 } 345 return "", fmt.Errorf( 346 "result %q not found in TaskRuns of PipelineRun %s/%s", result, pr.ObjectMeta.Namespace, pr.ObjectMeta.Name) 347 } 348 349 func (k KubeController) GetTaskRunStatus(c crclient.Client, pr *v1beta1.PipelineRun, pipelineTaskName string) (*v1beta1.PipelineRunTaskRunStatus, error) { 350 for _, chr := range pr.Status.ChildReferences { 351 if chr.PipelineTaskName == pipelineTaskName { 352 taskRun := &v1beta1.TaskRun{} 353 taskRunKey := types.NamespacedName{Namespace: pr.Namespace, Name: chr.Name} 354 if err := c.Get(context.TODO(), taskRunKey, taskRun); err != nil { 355 return nil, err 356 } 357 return &v1beta1.PipelineRunTaskRunStatus{PipelineTaskName: chr.PipelineTaskName, Status: &taskRun.Status}, nil 358 } 359 } 360 return nil, fmt.Errorf( 361 "TaskRun status for pipeline task name %q not found in the status of PipelineRun %s/%s", pipelineTaskName, pr.ObjectMeta.Namespace, pr.ObjectMeta.Name) 362 } 363 364 func (k KubeController) RunPipeline(g PipelineRunGenerator, taskTimeout int) (*v1beta1.PipelineRun, error) { 365 pr := g.Generate() 366 pvcs := k.Commonctrl.KubeInterface().CoreV1().PersistentVolumeClaims(pr.Namespace) 367 for _, w := range pr.Spec.Workspaces { 368 if w.PersistentVolumeClaim != nil { 369 pvcName := w.PersistentVolumeClaim.ClaimName 370 if _, err := pvcs.Get(context.TODO(), pvcName, metav1.GetOptions{}); err != nil { 371 if errors.IsNotFound(err) { 372 err := createPVC(pvcs, pvcName) 373 if err != nil { 374 return nil, err 375 } 376 } else { 377 return nil, err 378 } 379 } 380 } 381 } 382 383 return k.createAndWait(pr, taskTimeout) 384 } 385 386 // DeleteAllPipelineRunsInASpecificNamespace deletes all PipelineRuns in a given namespace (removing the finalizers field, first) 387 func (s *SuiteController) DeleteAllPipelineRunsInASpecificNamespace(ns string) error { 388 389 pipelineRunList, err := s.ListAllPipelineRuns(ns) 390 if err != nil || pipelineRunList == nil { 391 return fmt.Errorf("unable to delete all PipelineRuns in '%s': %v", ns, err) 392 } 393 394 for _, pipelineRun := range pipelineRunList.Items { 395 err := wait.PollImmediate(time.Second, 30*time.Second, func() (done bool, err error) { 396 pipelineRunCR := v1beta1.PipelineRun{ 397 ObjectMeta: metav1.ObjectMeta{ 398 Name: pipelineRun.Name, 399 Namespace: ns, 400 }, 401 } 402 if err := s.KubeRest().Get(context.TODO(), crclient.ObjectKeyFromObject(&pipelineRunCR), &pipelineRunCR); err != nil { 403 if errors.IsNotFound(err) { 404 // PipelinerRun CR is already removed 405 return true, nil 406 } 407 g.GinkgoWriter.Printf("unable to retrieve PipelineRun '%s' in '%s': %v\n", pipelineRunCR.Name, pipelineRunCR.Namespace, err) 408 return false, nil 409 410 } 411 412 // Remove the finalizer, so that it can be deleted. 413 pipelineRunCR.Finalizers = []string{} 414 if err := s.KubeRest().Update(context.TODO(), &pipelineRunCR); err != nil { 415 g.GinkgoWriter.Printf("unable to remove finalizers from PipelineRun '%s' in '%s': %v\n", pipelineRunCR.Name, pipelineRunCR.Namespace, err) 416 return false, nil 417 } 418 419 if err := s.KubeRest().Delete(context.TODO(), &pipelineRunCR); err != nil { 420 g.GinkgoWriter.Printf("unable to delete PipelineRun '%s' in '%s': %v\n", pipelineRunCR.Name, pipelineRunCR.Namespace, err) 421 return false, nil 422 } 423 return true, nil 424 }) 425 if err != nil { 426 return fmt.Errorf("deletion of PipelineRun '%s' in '%s' timed out", pipelineRun.Name, ns) 427 } 428 429 } 430 431 return nil 432 } 433 434 func createPVC(pvcs v1.PersistentVolumeClaimInterface, pvcName string) error { 435 pvc := &corev1.PersistentVolumeClaim{ 436 ObjectMeta: metav1.ObjectMeta{ 437 Name: pvcName, 438 }, 439 Spec: corev1.PersistentVolumeClaimSpec{ 440 AccessModes: []corev1.PersistentVolumeAccessMode{ 441 corev1.ReadWriteOnce, 442 }, 443 Resources: corev1.ResourceRequirements{ 444 Requests: corev1.ResourceList{ 445 corev1.ResourceStorage: resource.MustParse("1Gi"), 446 }, 447 }, 448 }, 449 } 450 451 if _, err := pvcs.Create(context.TODO(), pvc, metav1.CreateOptions{}); err != nil { 452 return err 453 } 454 455 return nil 456 } 457 458 func (k KubeController) AwaitAttestationAndSignature(image string, timeout time.Duration) error { 459 return wait.PollImmediate(time.Second, timeout, func() (done bool, err error) { 460 if _, err := k.FindCosignResultsForImage(image); err != nil { 461 g.GinkgoWriter.Printf("failed to get cosign result for image %s: %+v\n", image, err) 462 return false, nil 463 } 464 465 return true, nil 466 }) 467 } 468 469 func (k KubeController) createAndWait(pr *v1beta1.PipelineRun, taskTimeout int) (*v1beta1.PipelineRun, error) { 470 pipelineRun, err := k.Tektonctrl.CreatePipelineRun(pr, k.Namespace) 471 if err != nil { 472 return nil, err 473 } 474 g.GinkgoWriter.Printf("Creating Pipeline %q\n", pipelineRun.Name) 475 return pipelineRun, utils.WaitUntil(k.Tektonctrl.CheckPipelineRunStarted(pipelineRun.Name, k.Namespace), time.Duration(taskTimeout)*time.Second) 476 } 477 478 // FindCosignResultsForImage looks for .sig and .att image tags in the OpenShift image stream for the provided image reference. 479 // If none can be found errors.IsNotFound(err) is true, when err is nil CosignResult contains image references for signature and attestation images, otherwise other errors could be returned. 480 func (k KubeController) FindCosignResultsForImage(imageRef string) (*CosignResult, error) { 481 return findCosignResultsForImage(imageRef) 482 } 483 484 func findCosignResultsForImage(imageRef string) (*CosignResult, error) { 485 var errMsg string 486 // Split the image ref into image repo+tag (e.g quay.io/repo/name:tag), and image digest (sha256:abcd...) 487 imageInfo := strings.Split(imageRef, "@") 488 imageRegistryName := strings.Split(imageInfo[0], "/")[0] 489 // imageRepoName is stripped from container registry name and a tag e.g. "quay.io/<org>/<repo>:tagprefix" => "<org>/<repo>" 490 imageRepoName := strings.Split(strings.TrimPrefix(imageInfo[0], fmt.Sprintf("%s/", imageRegistryName)), ":")[0] 491 // Cosign creates tags for attestation and signature based on the image digest. Compute 492 // the expected prefix for later usage: sha256:abcd... -> sha256-abcd... 493 // Also, this prefix is really the prefix of the image tag resource which follows the 494 // format: <image-repo>:<tag-name> 495 imageTagPrefix := strings.Replace(imageInfo[1], ":", "-", 1) 496 497 results := CosignResult{} 498 signatureTag, err := getImageInfoFromQuay(imageRepoName, imageTagPrefix+".sig") 499 if err != nil { 500 errMsg += fmt.Sprintf("error when getting signature tag: %+v\n", err) 501 } else { 502 results.signatureImageRef = signatureTag.ImageRef 503 } 504 505 attestationTag, err := getImageInfoFromQuay(imageRepoName, imageTagPrefix+".att") 506 if err != nil { 507 errMsg += fmt.Sprintf("error when getting attestation tag: %+v\n", err) 508 } else { 509 results.attestationImageRef = attestationTag.ImageRef 510 // we want two layers, one for TaskRun and one for PipelineRun 511 // attestations, i.e. that the Chains controller reconciled both and 512 // uploaded them as layers 513 // 514 // this needs to change if/when Chains controller does not produce two layers 515 layersExpected := 2 516 if len(attestationTag.Layers) < layersExpected { 517 errMsg += fmt.Sprintf("attestation tag doesn't have the expected number of layers (%d)\n", layersExpected) 518 } 519 } 520 521 if len(errMsg) > 0 { 522 return &results, fmt.Errorf("failed to find cosign results for image %s: %s", imageRef, errMsg) 523 } 524 525 return &results, nil 526 } 527 528 func getImageInfoFromQuay(imageRepo, imageTag string) (*QuayImageInfo, error) { 529 530 res, err := http.Get(fmt.Sprintf("%s/repository/%s/tag/?specificTag=%s", quayBaseUrl, imageRepo, imageTag)) 531 if err != nil { 532 return nil, fmt.Errorf("cannot get quay.io/%s:%s image from container registry: %+v", imageRepo, imageTag, err) 533 } 534 body, err := io.ReadAll(res.Body) 535 if err != nil { 536 return nil, fmt.Errorf("cannot read body of a response from quay.io regarding quay.io/%s:%s image %+v", imageRepo, imageTag, err) 537 } 538 539 tagResponse := &TagResponse{} 540 if err = json.Unmarshal(body, tagResponse); err != nil { 541 return nil, fmt.Errorf("failed to unmarshal response from quay.io regarding quay.io/%s:%s image %+v", imageRepo, imageTag, err) 542 } 543 544 if len(tagResponse.Tags) < 1 { 545 return nil, fmt.Errorf("cannot get manifest digest from quay.io/%s:%s image. response body: %+v", imageRepo, imageTag, string(body)) 546 } 547 548 quayImageInfo := &QuayImageInfo{} 549 quayImageInfo.ImageRef = fmt.Sprintf("quay.io/%s@%s", imageRepo, tagResponse.Tags[0].Digest) 550 551 if strings.Contains(imageTag, ".att") { 552 res, err = http.Get(fmt.Sprintf("%s/repository/%s/manifest/%s", quayBaseUrl, imageRepo, tagResponse.Tags[0].Digest)) 553 if err != nil { 554 return nil, fmt.Errorf("cannot get quay.io/%s@%s image from container registry: %+v", imageRepo, quayImageInfo.ImageRef, err) 555 } 556 body, err = io.ReadAll(res.Body) 557 if err != nil { 558 return nil, fmt.Errorf("cannot read body of a response from quay.io regarding %s image: %+v", quayImageInfo.ImageRef, err) 559 } 560 manifestResponse := &ManifestResponse{} 561 if err := json.Unmarshal(body, manifestResponse); err != nil { 562 return nil, fmt.Errorf("failed to unmarshal response from quay.io regarding %s image: %+v", quayImageInfo.ImageRef, err) 563 } 564 565 if len(manifestResponse.Layers) < 1 { 566 return nil, fmt.Errorf("cannot get layers from %s image. response body: %+v", quayImageInfo.ImageRef, string(body)) 567 } 568 quayImageInfo.Layers = manifestResponse.Layers 569 } 570 571 return quayImageInfo, nil 572 } 573 574 func (k KubeController) CreateOrUpdateSigningSecret(publicKey []byte, name, namespace string) (err error) { 575 api := k.Tektonctrl.KubeInterface().CoreV1().Secrets(namespace) 576 ctx := context.TODO() 577 578 expectedSecret := &corev1.Secret{ 579 ObjectMeta: metav1.ObjectMeta{Name: name}, 580 Data: map[string][]byte{"cosign.pub": publicKey}, 581 } 582 583 s, err := api.Get(ctx, name, metav1.GetOptions{}) 584 if err != nil { 585 if !errors.IsNotFound(err) { 586 return 587 } 588 if _, err = api.Create(ctx, expectedSecret, metav1.CreateOptions{}); err != nil { 589 return 590 } 591 } else { 592 if string(s.Data["cosign.pub"]) != string(publicKey) { 593 if _, err = api.Update(ctx, expectedSecret, metav1.UpdateOptions{}); err != nil { 594 return 595 } 596 } 597 } 598 return 599 } 600 601 func (k KubeController) GetTektonChainsPublicKey() ([]byte, error) { 602 namespace := "tekton-chains" 603 secretName := "public-key" 604 dataKey := "cosign.pub" 605 606 secret, err := k.Tektonctrl.KubeInterface().CoreV1().Secrets(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) 607 if err != nil { 608 return nil, fmt.Errorf("couldn't get the secret %s from %s namespace: %+v", secretName, namespace, err) 609 } 610 publicKey := secret.Data[dataKey] 611 if len(publicKey) < 1 { 612 return nil, fmt.Errorf("the content of the public key '%s' in secret %s in %s namespace is empty", dataKey, secretName, namespace) 613 } 614 return publicKey, err 615 } 616 617 func (k KubeController) CreateOrUpdatePolicyConfiguration(namespace string, policy ecp.EnterpriseContractPolicySpec) error { 618 ecPolicy := ecp.EnterpriseContractPolicy{ 619 ObjectMeta: metav1.ObjectMeta{ 620 Name: "ec-policy", 621 Namespace: namespace, 622 }, 623 } 624 625 // fetch to see if it exists 626 err := k.Tektonctrl.KubeRest().Get(context.TODO(), crclient.ObjectKey{ 627 Namespace: namespace, 628 Name: "ec-policy", 629 }, &ecPolicy) 630 631 exists := true 632 if err != nil { 633 if errors.IsNotFound(err) { 634 exists = false 635 } else { 636 return err 637 } 638 } 639 640 ecPolicy.Spec = policy 641 if !exists { 642 // it doesn't, so create 643 if err := k.Tektonctrl.KubeRest().Create(context.TODO(), &ecPolicy); err != nil { 644 return err 645 } 646 } else { 647 // it does, so update 648 if err := k.Tektonctrl.KubeRest().Update(context.TODO(), &ecPolicy); err != nil { 649 return err 650 } 651 } 652 653 return nil 654 } 655 656 func (k KubeController) GetRekorHost() (rekorHost string, err error) { 657 api := k.Tektonctrl.KubeInterface().CoreV1().ConfigMaps("tekton-chains") 658 ctx := context.TODO() 659 660 cm, err := api.Get(ctx, "chains-config", metav1.GetOptions{}) 661 if err != nil { 662 return 663 } 664 665 rekorHost, ok := cm.Data["transparency.url"] 666 if !ok || rekorHost == "" { 667 rekorHost = "https://rekor.sigstore.dev" 668 } 669 return 670 } 671 672 // CreateEnterpriseContractPolicy creates an EnterpriseContractPolicy. 673 func (s *SuiteController) CreateEnterpriseContractPolicy(name, namespace string, ecpolicy ecp.EnterpriseContractPolicySpec) (*ecp.EnterpriseContractPolicy, error) { 674 ec := &ecp.EnterpriseContractPolicy{ 675 ObjectMeta: metav1.ObjectMeta{ 676 Name: name, 677 Namespace: namespace, 678 }, 679 Spec: ecpolicy, 680 } 681 return ec, s.KubeRest().Create(context.TODO(), ec) 682 } 683 684 // GetEnterpriseContractPolicy gets an EnterpriseContractPolicy from specified a namespace 685 func (k KubeController) GetEnterpriseContractPolicy(name, namespace string) (*ecp.EnterpriseContractPolicy, error) { 686 defaultEcPolicy := ecp.EnterpriseContractPolicy{ 687 ObjectMeta: metav1.ObjectMeta{ 688 Name: name, 689 Namespace: namespace, 690 }, 691 } 692 err := k.Tektonctrl.KubeRest().Get(context.TODO(), crclient.ObjectKey{ 693 Namespace: namespace, 694 Name: name, 695 }, &defaultEcPolicy) 696 697 return &defaultEcPolicy, err 698 } 699 700 // CreatePVCInAccessMode creates a PVC with mode as passed in arguments. 701 func (s *SuiteController) CreatePVCInAccessMode(name, namespace string, accessMode corev1.PersistentVolumeAccessMode) (*corev1.PersistentVolumeClaim, error) { 702 pvc := &corev1.PersistentVolumeClaim{ 703 ObjectMeta: metav1.ObjectMeta{ 704 Name: name, 705 Namespace: namespace, 706 }, 707 Spec: corev1.PersistentVolumeClaimSpec{ 708 AccessModes: []corev1.PersistentVolumeAccessMode{ 709 accessMode, 710 }, 711 Resources: corev1.ResourceRequirements{ 712 Requests: corev1.ResourceList{ 713 corev1.ResourceStorage: resource.MustParse("1Gi"), 714 }, 715 }, 716 }, 717 } 718 719 createdPVC, err := s.KubeInterface().CoreV1().PersistentVolumeClaims(namespace).Create(context.TODO(), pvc, metav1.CreateOptions{}) 720 if err != nil { 721 return nil, err 722 } 723 return createdPVC, err 724 } 725 726 // GetListOfPipelineRunsInNamespace returns a List of all PipelineRuns in namespace. 727 func (s *SuiteController) GetListOfPipelineRunsInNamespace(namespace string) (*v1beta1.PipelineRunList, error) { 728 return s.PipelineClient().TektonV1beta1().PipelineRuns(namespace).List(context.TODO(), metav1.ListOptions{}) 729 } 730 731 // CreateTaskRunCopy creates a TaskRun that copies one image to a second image repository 732 func (s *SuiteController) CreateTaskRunCopy(name, namespace, serviceAccountName, srcImageURL, destImageURL string) (*v1beta1.TaskRun, error) { 733 taskRun := v1beta1.TaskRun{ 734 ObjectMeta: metav1.ObjectMeta{ 735 Name: name, 736 Namespace: namespace, 737 }, 738 Spec: v1beta1.TaskRunSpec{ 739 ServiceAccountName: serviceAccountName, 740 TaskRef: &v1beta1.TaskRef{ 741 Name: "skopeo-copy", 742 }, 743 Params: []v1beta1.Param{ 744 { 745 Name: "srcImageURL", 746 Value: v1beta1.ParamValue{ 747 StringVal: srcImageURL, 748 Type: v1beta1.ParamTypeString, 749 }, 750 }, 751 { 752 Name: "destImageURL", 753 Value: v1beta1.ParamValue{ 754 StringVal: destImageURL, 755 Type: v1beta1.ParamTypeString, 756 }, 757 }, 758 }, 759 // workaround to avoid the error "container has runAsNonRoot and image will run as root" 760 PodTemplate: &pod.Template{ 761 SecurityContext: &corev1.PodSecurityContext{ 762 RunAsNonRoot: pointer.Bool(true), 763 RunAsUser: pointer.Int64(65532), 764 }, 765 }, 766 Workspaces: []v1beta1.WorkspaceBinding{ 767 { 768 Name: "images-url", 769 EmptyDir: &corev1.EmptyDirVolumeSource{}, 770 }, 771 }, 772 }, 773 } 774 775 err := s.KubeRest().Create(context.TODO(), &taskRun) 776 if err != nil { 777 return nil, err 778 } 779 return &taskRun, nil 780 } 781 782 // GetTaskRun returns the requested TaskRun object 783 func (s *SuiteController) GetTaskRun(name, namespace string) (*v1beta1.TaskRun, error) { 784 namespacedName := types.NamespacedName{ 785 Name: name, 786 Namespace: namespace, 787 } 788 789 taskRun := v1beta1.TaskRun{ 790 ObjectMeta: metav1.ObjectMeta{ 791 Name: name, 792 Namespace: namespace, 793 }, 794 } 795 err := s.KubeRest().Get(context.TODO(), namespacedName, &taskRun) 796 if err != nil { 797 return nil, err 798 } 799 return &taskRun, nil 800 } 801 802 // CreateSkopeoCopyTask creates a skopeo copy task in the given namespace 803 func (s *SuiteController) CreateSkopeoCopyTask(namespace string) error { 804 _, err := exec.Command( 805 "oc", 806 "apply", 807 "-f", 808 "https://api.hub.tekton.dev/v1/resource/tekton/task/skopeo-copy/0.2/raw", 809 "-n", 810 namespace).Output() 811 812 return err 813 } 814 815 // Remove all Tasks from a given repository. Useful when creating a lot of resources and wanting to remove all of them 816 func (h *SuiteController) DeleteAllTasksInASpecificNamespace(namespace string) error { 817 return h.KubeRest().DeleteAllOf(context.TODO(), &v1beta1.Task{}, crclient.InNamespace(namespace)) 818 } 819 820 // Remove all TaskRuns from a given repository. Useful when creating a lot of resources and wanting to remove all of them 821 func (h *SuiteController) DeleteAllTaskRunsInASpecificNamespace(namespace string) error { 822 return h.KubeRest().DeleteAllOf(context.TODO(), &v1beta1.TaskRun{}, crclient.InNamespace(namespace)) 823 } 824 825 // GetTask returns the requested Task object 826 func (s *SuiteController) GetTask(name, namespace string) (*v1beta1.Task, error) { 827 namespacedName := types.NamespacedName{ 828 Name: name, 829 Namespace: namespace, 830 } 831 832 task := v1beta1.Task{ 833 ObjectMeta: metav1.ObjectMeta{ 834 Name: name, 835 Namespace: namespace, 836 }, 837 } 838 err := s.KubeRest().Get(context.TODO(), namespacedName, &task) 839 if err != nil { 840 return nil, err 841 } 842 return &task, nil 843 }