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  }