github.com/redhat-appstudio/e2e-tests@v0.0.0-20240520140907-9709f6f59323/pkg/utils/build/task_results.go (about)

     1  package build
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  
     8  	"github.com/redhat-appstudio/e2e-tests/pkg/constants"
     9  	pipeline "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
    10  	"k8s.io/apimachinery/pkg/types"
    11  
    12  	crclient "sigs.k8s.io/controller-runtime/pkg/client"
    13  )
    14  
    15  var taskNames = []string{"clair-scan", "clamav-scan", "deprecated-base-image-check", "inspect-image", "sbom-json-check"}
    16  
    17  type TestOutput struct {
    18  	Result    string `json:"result"`
    19  	Timestamp string `json:"timestamp"`
    20  	Note      string `json:"note"`
    21  	Namespace string `json:"namespace"`
    22  	Successes int    `json:"successes"`
    23  	Failures  int    `json:"failures"`
    24  	Warnings  int    `json:"warnings"`
    25  }
    26  
    27  type ClairScanResult struct {
    28  	Vulnerabilities Vulnerabilities `json:"vulnerabilities"`
    29  }
    30  
    31  type Vulnerabilities struct {
    32  	Critical int `json:"critical"`
    33  	High     int `json:"high"`
    34  	Medium   int `json:"medium"`
    35  	Low      int `json:"low"`
    36  }
    37  
    38  type PipelineBuildInfo struct {
    39  	runtime  string
    40  	strategy string
    41  }
    42  
    43  func GetPipelineBuildInfo(pr *pipeline.PipelineRun) PipelineBuildInfo {
    44  	labels := pr.GetLabels()
    45  	runtime := labels["pipelines.openshift.io/runtime"]
    46  	strategy := labels["pipelines.openshift.io/strategy"]
    47  	return PipelineBuildInfo{
    48  		runtime:  runtime,
    49  		strategy: strategy,
    50  	}
    51  }
    52  
    53  func IsDockerBuild(pr *pipeline.PipelineRun) bool {
    54  	info := GetPipelineBuildInfo(pr)
    55  	return info.runtime == "generic" && info.strategy == "docker"
    56  }
    57  
    58  func IsFBCBuild(pr *pipeline.PipelineRun) bool {
    59  	info := GetPipelineBuildInfo(pr)
    60  	return info.runtime == "fbc" && info.strategy == "fbc"
    61  }
    62  
    63  func ValidateBuildPipelineTestResults(pipelineRun *pipeline.PipelineRun, c crclient.Client) error {
    64  	for _, taskName := range taskNames {
    65  		// The inspect-image task is only required for FBC pipelines which we can infer by the component name
    66  		isFBCBuild := IsFBCBuild(pipelineRun)
    67  
    68  		if !isFBCBuild && taskName == "inspect-image" {
    69  			continue
    70  		}
    71  		if isFBCBuild && (taskName == "clair-scan" || taskName == "clamav-scan") {
    72  			continue
    73  		}
    74  		results, err := fetchTaskRunResults(c, pipelineRun, taskName)
    75  		if err != nil {
    76  			return err
    77  		}
    78  
    79  		resultsToValidate := []string{constants.TektonTaskTestOutputName}
    80  
    81  		switch taskName {
    82  		case "clair-scan":
    83  			resultsToValidate = append(resultsToValidate, "CLAIR_SCAN_RESULT")
    84  		case "deprecated-image-check":
    85  			resultsToValidate = append(resultsToValidate, "PYXIS_HTTP_CODE")
    86  		case "inspect-image":
    87  			resultsToValidate = append(resultsToValidate, "BASE_IMAGE", "BASE_IMAGE_REPOSITORY")
    88  		}
    89  
    90  		if err := validateTaskRunResult(results, resultsToValidate, taskName); err != nil {
    91  			return err
    92  		}
    93  
    94  	}
    95  	return nil
    96  }
    97  
    98  func fetchTaskRunResults(c crclient.Client, pr *pipeline.PipelineRun, pipelineTaskName string) ([]pipeline.TaskRunResult, error) {
    99  	for _, chr := range pr.Status.ChildReferences {
   100  		if chr.PipelineTaskName != pipelineTaskName {
   101  			continue
   102  		}
   103  		taskRun := &pipeline.TaskRun{}
   104  		taskRunKey := types.NamespacedName{Namespace: pr.Namespace, Name: chr.Name}
   105  		if err := c.Get(context.Background(), taskRunKey, taskRun); err != nil {
   106  			return nil, err
   107  		}
   108  		return taskRun.Status.TaskRunStatusFields.Results, nil
   109  	}
   110  	return nil, fmt.Errorf(
   111  		"pipelineTaskName %q not found in PipelineRun %s/%s", pipelineTaskName, pr.GetName(), pr.GetNamespace())
   112  }
   113  
   114  func validateTaskRunResult(trResults []pipeline.TaskRunResult, expectedResultNames []string, taskName string) error {
   115  	for _, rn := range expectedResultNames {
   116  		found := false
   117  		for _, r := range trResults {
   118  			if rn == r.Name {
   119  				found = true
   120  				switch r.Name {
   121  				case constants.TektonTaskTestOutputName:
   122  					var testOutput = &TestOutput{}
   123  					err := json.Unmarshal([]byte(r.Value.StringVal), &testOutput)
   124  					if err != nil {
   125  						return fmt.Errorf("cannot parse %q result: %+v", constants.TektonTaskTestOutputName, err)
   126  					}
   127  					// If the test result isn't SUCCESS, the overall outcome is a failure
   128  					if taskName == "sbom-json-check" {
   129  						if testOutput.Result == "FAILURE" {
   130  							return fmt.Errorf("expected Result for Task name %q to be SUCCESS: %+v", taskName, testOutput)
   131  						}
   132  					}
   133  				case "CLAIR_SCAN_RESULT":
   134  					var testOutput = &ClairScanResult{}
   135  					err := json.Unmarshal([]byte(r.Value.StringVal), &testOutput)
   136  					if err != nil {
   137  						return fmt.Errorf("cannot parse CLAIR_SCAN_RESULT result: %+v", err)
   138  					}
   139  				case "PYXIS_HTTP_CODE", "BASE_IMAGE", "BASE_IMAGE_REPOSITORY":
   140  					if len(r.Value.StringVal) < 1 {
   141  						return fmt.Errorf("value of %q result is empty", r.Name)
   142  					}
   143  				}
   144  			}
   145  		}
   146  		if !found {
   147  			return fmt.Errorf("expected result name %q not found in Task %q result", rn, taskName)
   148  		}
   149  	}
   150  	return nil
   151  }