github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/tekton/pipelines.go (about)

     1  package tekton
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sort"
     7  	"strconv"
     8  	"time"
     9  
    10  	jenkinsio "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io"
    11  	v1 "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io/v1"
    12  	clientv1 "github.com/jenkins-x/jx-api/pkg/client/clientset/versioned/typed/jenkins.io/v1"
    13  	"github.com/olli-ai/jx/v2/pkg/auth"
    14  	"github.com/olli-ai/jx/v2/pkg/cmd/clients"
    15  	"github.com/olli-ai/jx/v2/pkg/jxfactory"
    16  	"github.com/olli-ai/jx/v2/pkg/kube/naming"
    17  	"github.com/tektoncd/pipeline/pkg/apis/pipeline"
    18  	"k8s.io/apimachinery/pkg/labels"
    19  	"k8s.io/apimachinery/pkg/util/rand"
    20  	"k8s.io/client-go/kubernetes"
    21  
    22  	"github.com/jenkins-x/jx-api/pkg/client/clientset/versioned"
    23  	jxClient "github.com/jenkins-x/jx-api/pkg/client/clientset/versioned"
    24  	"github.com/jenkins-x/jx-logging/pkg/log"
    25  	"github.com/olli-ai/jx/v2/pkg/gits"
    26  	"github.com/olli-ai/jx/v2/pkg/kube"
    27  	"github.com/olli-ai/jx/v2/pkg/tekton/syntax"
    28  	"github.com/olli-ai/jx/v2/pkg/util"
    29  	"github.com/pkg/errors"
    30  	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
    31  	pipelineapi "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
    32  	tektonclient "github.com/tektoncd/pipeline/pkg/client/clientset/versioned"
    33  	corev1 "k8s.io/api/core/v1"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  )
    36  
    37  // PipelineType is used to differentiate between actual build pipelines and pipelines to create the build pipelines,
    38  // aka meta pipelines.
    39  type PipelineType int
    40  
    41  const (
    42  	// BuildPipeline is the yype for the actual build pipeline
    43  	BuildPipeline PipelineType = iota
    44  
    45  	// MetaPipeline type for the meta pipeline used to generate the build pipeline
    46  	MetaPipeline
    47  )
    48  
    49  func (s PipelineType) String() string {
    50  	return [...]string{"build", "meta"}[s]
    51  }
    52  
    53  // GeneratePipelineActivity generates a initial PipelineActivity CRD so UI/get act can get an earlier notification that the jobs have been scheduled
    54  func GeneratePipelineActivity(buildNumber string, branch string, gitInfo *gits.GitRepository, context string, pr *PullRefs) *kube.PromoteStepActivityKey {
    55  	name := gitInfo.Organisation + "-" + gitInfo.Name + "-" + branch + "-" + buildNumber
    56  
    57  	pipeline := gitInfo.Organisation + "/" + gitInfo.Name + "/" + branch
    58  	log.Logger().Infof("PipelineActivity for %s", name)
    59  	key := &kube.PromoteStepActivityKey{
    60  		PipelineActivityKey: kube.PipelineActivityKey{
    61  			Name:     name,
    62  			Pipeline: pipeline,
    63  			Build:    buildNumber,
    64  			GitInfo:  gitInfo,
    65  			Context:  context,
    66  		},
    67  	}
    68  
    69  	if pr != nil {
    70  		key.PullRefs = pr.ToMerge
    71  	}
    72  
    73  	return key
    74  }
    75  
    76  // CreateOrUpdateSourceResource lazily creates a Tekton Pipeline PipelineResource for the given git repository
    77  func CreateOrUpdateSourceResource(tektonClient tektonclient.Interface, ns string, created *v1alpha1.PipelineResource) (*v1alpha1.PipelineResource, error) {
    78  	resourceName := created.Name
    79  	factory := jxfactory.NewFactory()
    80  
    81  	resourceClient, _, err := factory.CreateTektonPipelineResourceClient()
    82  	if err != nil {
    83  		return nil, errors.Wrap(err, "unable to create Tekton PipelineResource client")
    84  	}
    85  	resourceInterface := resourceClient.TektonV1alpha1().PipelineResources(ns)
    86  
    87  	_, err = resourceInterface.Create(created)
    88  	if err == nil {
    89  		return created, nil
    90  	}
    91  
    92  	answer, err2 := resourceInterface.Get(resourceName, metav1.GetOptions{})
    93  	if err2 != nil {
    94  		return answer, errors.Wrapf(err, "failed to get PipelineResource %s with %v after failing to create a new one", resourceName, err2)
    95  	}
    96  	if !reflect.DeepEqual(&created.Spec, &answer.Spec) {
    97  		answer.Spec = created.Spec
    98  		answer, err = resourceInterface.Update(answer)
    99  		if err != nil {
   100  			return nil, errors.Wrapf(err, "failed to update PipelineResource %s", resourceName)
   101  		}
   102  	}
   103  	return answer, nil
   104  }
   105  
   106  // CreateOrUpdateTask lazily creates a Tekton Pipeline Task
   107  func CreateOrUpdateTask(tektonClient tektonclient.Interface, ns string, created *v1alpha1.Task) (*v1alpha1.Task, error) {
   108  	resourceName := created.Name
   109  	if resourceName == "" {
   110  		return nil, fmt.Errorf("the Task must have a name")
   111  	}
   112  	resourceInterface := tektonClient.TektonV1alpha1().Tasks(ns)
   113  
   114  	_, err := resourceInterface.Create(created)
   115  	if err == nil {
   116  		return created, nil
   117  	}
   118  
   119  	answer, err2 := resourceInterface.Get(resourceName, metav1.GetOptions{})
   120  	if err2 != nil {
   121  		return answer, errors.Wrapf(err, "failed to get PipelineResource %s with %v after failing to create a new one", resourceName, err2.Error())
   122  	}
   123  	if !reflect.DeepEqual(&created.Spec, &answer.Spec) || !reflect.DeepEqual(created.Annotations, answer.Annotations) || !reflect.DeepEqual(created.Labels, answer.Labels) {
   124  		answer.Spec = created.Spec
   125  		answer.Labels = util.MergeMaps(answer.Labels, created.Labels)
   126  		answer.Annotations = util.MergeMaps(answer.Annotations, created.Annotations)
   127  		answer, err = resourceInterface.Update(answer)
   128  		if err != nil {
   129  			return nil, errors.Wrapf(err, "failed to update PipelineResource %s", resourceName)
   130  		}
   131  	}
   132  	return answer, nil
   133  }
   134  
   135  func nextBuildNumberFromActivity(activityInterface clientv1.PipelineActivityInterface, gitInfo *gits.GitRepository, branch string) (string, error) {
   136  	labelMap := labels.Set{
   137  		"owner":      gitInfo.Organisation,
   138  		"repository": gitInfo.Name,
   139  		"branch":     branch,
   140  	}
   141  
   142  	activityList, err := kube.ListSelectedPipelineActivities(activityInterface, labelMap.AsSelector(), nil)
   143  	if err != nil {
   144  		return "", errors.Wrapf(err, "Unable to list pipeline activities for %s/%s/%s", gitInfo.Organisation, gitInfo.Name, branch)
   145  	}
   146  	if len(activityList.Items) == 0 {
   147  		return "1", nil
   148  	}
   149  	sort.Slice(activityList.Items, func(i, j int) bool {
   150  		iBuildNum, err := strconv.Atoi(activityList.Items[i].Spec.Build)
   151  		if err != nil {
   152  			iBuildNum = 0
   153  		}
   154  		jBuildNum, err := strconv.Atoi(activityList.Items[j].Spec.Build)
   155  		if err != nil {
   156  			jBuildNum = 0
   157  		}
   158  		return iBuildNum >= jBuildNum
   159  	})
   160  	// Iterate over the sorted (highest to lowest build number) list of activities, returning a new build number
   161  	// as soon as we reach one we can parse to an int and add one to.
   162  	for _, activity := range activityList.Items {
   163  		actBuildNum, err := strconv.Atoi(activity.Spec.Build)
   164  		if err != nil {
   165  			continue
   166  		}
   167  		return strconv.Itoa(actBuildNum + 1), nil
   168  	}
   169  	// If we couldn't parse any build numbers, just set the next build number to 1.
   170  	return "1", nil
   171  }
   172  
   173  func nextBuildNumberFromSourceRepo(tektonClient tektonclient.Interface, jxClient jxClient.Interface, ns string, gitInfo *gits.GitRepository, branch string, context string) (string, error) {
   174  	resourceInterface := jxClient.JenkinsV1().SourceRepositories(ns)
   175  	// TODO: How does SourceRepository handle name overlap?
   176  	sourceRepoName := naming.ToValidName(gitInfo.Organisation + "-" + gitInfo.Name)
   177  
   178  	lastBuildNumber := 0
   179  	sourceRepo, err := kube.GetOrCreateSourceRepository(jxClient, ns, gitInfo.Name, gitInfo.Organisation, gitInfo.ProviderURL())
   180  	if err != nil {
   181  		return "", errors.Wrapf(err, "Unable to generate next build number for %s/%s", sourceRepoName, branch)
   182  	}
   183  	sourceRepoName = sourceRepo.Name
   184  	if sourceRepo.Annotations == nil {
   185  		sourceRepo.Annotations = make(map[string]string, 1)
   186  	}
   187  	annKey := LastBuildNumberAnnotationPrefix + naming.ToValidName(branch)
   188  	annVal := sourceRepo.Annotations[annKey]
   189  	if annVal != "" {
   190  		lastBuildNumber, err = strconv.Atoi(annVal)
   191  		if err != nil {
   192  			return "", errors.Wrapf(err, "Expected number but SourceRepository %s has annotation %s with value %s\n", sourceRepoName, annKey, annVal)
   193  		}
   194  	}
   195  
   196  	for nextNumber := lastBuildNumber + 1; true; nextNumber++ {
   197  		// lets check there is not already a PipelineRun for this number
   198  		buildIdentifier := strconv.Itoa(nextNumber)
   199  
   200  		labelSelector := fmt.Sprintf("owner=%s,repo=%s,branch=%s,build=%s", gitInfo.Organisation, gitInfo.Name, branch, buildIdentifier)
   201  		if context != "" {
   202  			labelSelector += fmt.Sprintf(",context=%s", context)
   203  		}
   204  
   205  		prs, err := tektonClient.TektonV1alpha1().PipelineRuns(ns).List(metav1.ListOptions{
   206  			LabelSelector: labelSelector,
   207  		})
   208  		if err == nil && len(prs.Items) > 0 {
   209  			// lets try make another build number as there's already a PipelineRun
   210  			// which could be due to name clashes
   211  			continue
   212  		}
   213  		if sourceRepo != nil {
   214  			sourceRepo.Annotations[annKey] = buildIdentifier
   215  			if _, err := resourceInterface.Update(sourceRepo); err != nil {
   216  				return "", err
   217  			}
   218  		}
   219  
   220  		return buildIdentifier, nil
   221  	}
   222  	// We've somehow gotten here without determining the next build number, so let's error.
   223  	return "", fmt.Errorf("couldn't determine next build number for %s/%s/%s", gitInfo.Organisation, gitInfo.Name, branch)
   224  }
   225  
   226  // GenerateNextBuildNumber generates a new build number for the given project.
   227  func GenerateNextBuildNumber(tektonClient tektonclient.Interface, jxClient jxClient.Interface, ns string, gitInfo *gits.GitRepository, branch string, duration time.Duration, context string, useActivity bool) (string, error) {
   228  	nextBuildNumber := ""
   229  	activityInterface := jxClient.JenkinsV1().PipelineActivities(ns)
   230  
   231  	f := func() error {
   232  		if useActivity {
   233  			bn, err := nextBuildNumberFromActivity(activityInterface, gitInfo, branch)
   234  			if err != nil {
   235  				return err
   236  			}
   237  			nextBuildNumber = bn
   238  		} else {
   239  			bn, err := nextBuildNumberFromSourceRepo(tektonClient, jxClient, ns, gitInfo, branch, context)
   240  			if err != nil {
   241  				return err
   242  			}
   243  			nextBuildNumber = bn
   244  		}
   245  		return nil
   246  	}
   247  
   248  	err := util.Retry(duration, f)
   249  	if err != nil {
   250  		return "", err
   251  	}
   252  	return nextBuildNumber, nil
   253  }
   254  
   255  // GenerateSourceRepoResource generates the PipelineResource for the git repository we are operating on.
   256  func GenerateSourceRepoResource(name string, gitInfo *gits.GitRepository, revision string) *pipelineapi.PipelineResource {
   257  	if gitInfo == nil || gitInfo.HttpsURL() == "" {
   258  		return nil
   259  
   260  	}
   261  
   262  	// lets use the URL property as this preserves any provider specific paths; e.g. `/scm` on bitbucket server
   263  	u := gitInfo.URL
   264  	if u == "" {
   265  		u = gitInfo.HttpsURL()
   266  	}
   267  	resource := &pipelineapi.PipelineResource{
   268  		TypeMeta: metav1.TypeMeta{
   269  			APIVersion: syntax.TektonAPIVersion,
   270  			Kind:       "PipelineResource",
   271  		},
   272  		ObjectMeta: metav1.ObjectMeta{
   273  			Name: name,
   274  		},
   275  		Spec: pipelineapi.PipelineResourceSpec{
   276  			Type: pipelineapi.PipelineResourceTypeGit,
   277  			Params: []pipelineapi.ResourceParam{
   278  				{
   279  					Name:  "revision",
   280  					Value: revision,
   281  				},
   282  				{
   283  					Name:  "url",
   284  					Value: u,
   285  				},
   286  			},
   287  		},
   288  	}
   289  
   290  	return resource
   291  }
   292  
   293  // CreatePipelineRun creates the PipelineRun struct.
   294  func CreatePipelineRun(resources []*pipelineapi.PipelineResource,
   295  	name string,
   296  	apiVersion string,
   297  	labels map[string]string,
   298  	serviceAccount string,
   299  	pipelineParams []pipelineapi.Param,
   300  	timeout *metav1.Duration,
   301  	affinity *corev1.Affinity,
   302  	tolerations []corev1.Toleration) *pipelineapi.PipelineRun {
   303  	var resourceBindings []pipelineapi.PipelineResourceBinding
   304  	for _, resource := range resources {
   305  		resourceBindings = append(resourceBindings, pipelineapi.PipelineResourceBinding{
   306  			Name: resource.Name,
   307  			ResourceRef: &pipelineapi.PipelineResourceRef{
   308  				Name:       resource.Name,
   309  				APIVersion: resource.APIVersion,
   310  			},
   311  		})
   312  	}
   313  
   314  	if serviceAccount == "" {
   315  		serviceAccount = DefaultPipelineSA
   316  	}
   317  	if timeout == nil {
   318  		timeout = &metav1.Duration{Duration: 240 * time.Hour}
   319  	}
   320  
   321  	pipelineRun := &pipelineapi.PipelineRun{
   322  		TypeMeta: metav1.TypeMeta{
   323  			APIVersion: syntax.TektonAPIVersion,
   324  			Kind:       "PipelineRun",
   325  		},
   326  		ObjectMeta: metav1.ObjectMeta{
   327  			Name:   name,
   328  			Labels: util.MergeMaps(labels),
   329  		},
   330  		Spec: pipelineapi.PipelineRunSpec{
   331  			ServiceAccountName: serviceAccount,
   332  			PipelineRef: &pipelineapi.PipelineRef{
   333  				Name:       name,
   334  				APIVersion: apiVersion,
   335  			},
   336  			Resources: resourceBindings,
   337  			Params:    pipelineParams,
   338  			// TODO: We shouldn't have to set a default timeout in the first place. See https://github.com/tektoncd/pipeline/issues/978
   339  			Timeout: timeout,
   340  			PodTemplate: &pipelineapi.PodTemplate{
   341  				Affinity:    affinity,
   342  				Tolerations: tolerations,
   343  			},
   344  		},
   345  	}
   346  
   347  	return pipelineRun
   348  }
   349  
   350  // ApplyPipelineRun lazily creates a Tekton PipelineRun.
   351  func ApplyPipelineRun(tektonClient tektonclient.Interface, ns string, run *v1alpha1.PipelineRun) (*v1alpha1.PipelineRun, error) {
   352  	resourceName := run.Name
   353  	resourceInterface := tektonClient.TektonV1alpha1().PipelineRuns(ns)
   354  
   355  	answer, err := resourceInterface.Create(run)
   356  	if err != nil {
   357  		return nil, errors.Wrapf(err, "Failed to create PipelineRun %s", resourceName)
   358  	}
   359  	return answer, nil
   360  }
   361  
   362  // CreateOrUpdatePipeline lazily creates a Tekton Pipeline for the given git repository, branch and context
   363  func CreateOrUpdatePipeline(tektonClient tektonclient.Interface, ns string, created *v1alpha1.Pipeline) (*v1alpha1.Pipeline, error) {
   364  	resourceName := created.Name
   365  	resourceInterface := tektonClient.TektonV1alpha1().Pipelines(ns)
   366  
   367  	answer, err := resourceInterface.Create(created)
   368  	if err == nil {
   369  		return answer, nil
   370  	}
   371  
   372  	answer, err = resourceInterface.Get(resourceName, metav1.GetOptions{})
   373  	if err != nil {
   374  		return answer, errors.Wrapf(err, "failed to get Pipeline %s after failing to create a new one", resourceName)
   375  	}
   376  
   377  	if !reflect.DeepEqual(&created.Spec, &answer.Spec) || !reflect.DeepEqual(created.Labels, answer.Labels) {
   378  		answer.Annotations = util.MergeMaps(answer.Annotations, created.Annotations)
   379  		answer.Spec = created.Spec
   380  		answer, err = resourceInterface.Update(answer)
   381  		if err != nil {
   382  			return nil, errors.Wrapf(err, "failed to update Pipeline %s", resourceName)
   383  		}
   384  	}
   385  	return answer, nil
   386  }
   387  
   388  // PipelineResourceNameFromGitInfo returns the pipeline resource name for the given git repository, branch and context
   389  func PipelineResourceNameFromGitInfo(gitInfo *gits.GitRepository, branch string, context string, pipelineType string) string {
   390  	return PipelineResourceName(gitInfo.Organisation, gitInfo.Name, branch, context, pipelineType)
   391  }
   392  
   393  // PipelineResourceName returns the pipeline resource name for the given git org, repo name, branch and context. It will always be unique.
   394  func PipelineResourceName(organisation string, name string, branch string, context string, pipelineType string) string {
   395  	return possiblyUniquePipelineResourceName(organisation, name, branch, context, pipelineType, true)
   396  }
   397  
   398  // possiblyUniquePipelineResourceName returns the pipeline resource name for the given git org, repo name, branch and context, possibly forcing it to be unique
   399  func possiblyUniquePipelineResourceName(organisation string, name string, branch string, context string, pipelineType string, forceUnique bool) string {
   400  	dirtyName := organisation + "-" + name + "-" + branch
   401  	if context != "" {
   402  		dirtyName += "-" + context
   403  	}
   404  
   405  	if pipelineType == MetaPipeline.String() {
   406  		dirtyName = pipelineType + "-" + dirtyName
   407  	}
   408  	resourceName := naming.ToValidNameTruncated(dirtyName, 31)
   409  
   410  	if forceUnique {
   411  		return resourceName + "-" + rand.String(5)
   412  	}
   413  	return resourceName
   414  }
   415  
   416  // ApplyPipeline applies the tasks and pipeline to the cluster
   417  // and creates and applies a PipelineResource for their source repo and a pipelineRun
   418  // to execute them.
   419  func ApplyPipeline(jxClient versioned.Interface, kubeClient kubernetes.Interface, tektonClient tektonclient.Interface, crds *CRDWrapper, ns string, activityKey *kube.PromoteStepActivityKey) error {
   420  	info := util.ColorInfo
   421  
   422  	var activityOwnerReference *metav1.OwnerReference
   423  
   424  	if activityKey != nil {
   425  		activity, _, err := activityKey.GetOrCreate(jxClient, crds.Pipeline().Namespace)
   426  		if err != nil {
   427  			return err
   428  		}
   429  
   430  		activityOwnerReference = &metav1.OwnerReference{
   431  			APIVersion: jenkinsio.GroupAndVersion,
   432  			Kind:       "PipelineActivity",
   433  			Name:       activity.Name,
   434  			UID:        activity.UID,
   435  		}
   436  	}
   437  
   438  	gitKind := ""
   439  	if activityKey != nil {
   440  		clusterAuthConfigSvc, err := clients.NewFactory().CreateGitAuthConfigService(ns, "")
   441  		if err != nil {
   442  			return errors.Wrapf(err, "getting cluster-based auth configmap for checking kind of git url %s", activityKey.GitInfo.HostURL())
   443  		}
   444  		var clusterAuthConfig *auth.AuthConfig
   445  		if clusterAuthConfigSvc != nil {
   446  			clusterAuthConfig = clusterAuthConfigSvc.Config()
   447  		}
   448  		// Don't bother checking for an error here - we'll just fall back to no kind specified.
   449  		gitKind, _ = kube.GetGitServiceKind(jxClient, kubeClient, ns, clusterAuthConfig, activityKey.GitInfo.HostURL())
   450  	}
   451  	for _, resource := range crds.Resources() {
   452  		if activityOwnerReference != nil {
   453  			resource.OwnerReferences = []metav1.OwnerReference{*activityOwnerReference}
   454  		}
   455  		_, err := CreateOrUpdateSourceResource(tektonClient, ns, resource)
   456  		if err != nil {
   457  			return errors.Wrapf(err, "failed to create/update PipelineResource %s in namespace %s", resource.Name, ns)
   458  		}
   459  		if resource.Spec.Type == pipelineapi.PipelineResourceTypeGit {
   460  			gitURL := gits.HttpCloneURL(activityKey.GitInfo, gitKind)
   461  			log.Logger().Infof("upserted PipelineResource %s for the git repository %s", info(resource.Name), info(gitURL))
   462  		} else {
   463  			log.Logger().Infof("upserted PipelineResource %s", info(resource.Name))
   464  		}
   465  	}
   466  
   467  	for _, task := range crds.Tasks() {
   468  		if activityOwnerReference != nil {
   469  			task.OwnerReferences = []metav1.OwnerReference{*activityOwnerReference}
   470  		}
   471  		_, err := CreateOrUpdateTask(tektonClient, ns, task)
   472  		if err != nil {
   473  			return errors.Wrapf(err, "failed to create/update the task %s in namespace %s", task.Name, ns)
   474  		}
   475  		log.Logger().Infof("upserted Task %s", info(task.Name))
   476  	}
   477  
   478  	if activityOwnerReference != nil {
   479  		crds.Pipeline().OwnerReferences = []metav1.OwnerReference{*activityOwnerReference}
   480  	}
   481  
   482  	pipeline, err := CreateOrUpdatePipeline(tektonClient, ns, crds.Pipeline())
   483  	if err != nil {
   484  		return errors.Wrapf(err, "failed to create/update the pipeline in namespace %s", ns)
   485  	}
   486  	log.Logger().Infof("upserted Pipeline %s", info(pipeline.Name))
   487  
   488  	pipelineOwnerReference := metav1.OwnerReference{
   489  		APIVersion: syntax.TektonAPIVersion,
   490  		Kind:       "pipeline",
   491  		Name:       pipeline.Name,
   492  		UID:        pipeline.UID,
   493  	}
   494  
   495  	crds.structure.OwnerReferences = []metav1.OwnerReference{pipelineOwnerReference}
   496  
   497  	_, err = ApplyPipelineRun(tektonClient, ns, crds.PipelineRun())
   498  	if err != nil {
   499  		return errors.Wrapf(err, "failed to create the pipelineRun in namespace %s", ns)
   500  	}
   501  	log.Logger().Infof("created PipelineRun %s", info(crds.PipelineRun().Name))
   502  
   503  	if crds.Structure() != nil {
   504  		crds.Structure().PipelineRunRef = &crds.PipelineRun().Name
   505  
   506  		structuresClient := jxClient.JenkinsV1().PipelineStructures(ns)
   507  
   508  		// Reset the structure name to be the run's name and set the PipelineRef and PipelineRunRef
   509  		if crds.Structure().PipelineRef == nil {
   510  			crds.Structure().PipelineRef = &pipeline.Name
   511  		}
   512  		crds.Structure().Name = crds.PipelineRun().Name
   513  		crds.Structure().PipelineRunRef = &crds.PipelineRun().Name
   514  
   515  		if _, structErr := structuresClient.Create(crds.Structure()); structErr != nil {
   516  			return errors.Wrapf(structErr, "failed to create the PipelineStructure in namespace %s", ns)
   517  		}
   518  		log.Logger().Infof("created PipelineStructure %s", info(crds.Structure().Name))
   519  	}
   520  
   521  	return nil
   522  }
   523  
   524  // StructureForPipelineRun finds the PipelineStructure for the given PipelineRun, trying its name first and then its
   525  // Pipeline name, returning an error if no PipelineStructure can be found.
   526  func StructureForPipelineRun(jxClient versioned.Interface, ns string, run *pipelineapi.PipelineRun) (*v1.PipelineStructure, error) {
   527  	// Use the Pipeline name for this run.
   528  	pipelineName := run.Labels[pipeline.GroupName+pipeline.PipelineLabelKey]
   529  	// Fall back on the PipelineRef.Name if there isn't a label.
   530  	if pipelineName == "" {
   531  		pipelineName = run.Spec.PipelineRef.Name
   532  	}
   533  	// If we still have no name, error out.
   534  	if pipelineName == "" {
   535  		return nil, fmt.Errorf("couldn't find a Pipeline name for PipelineRun %s", run.Name)
   536  	}
   537  	structure, err := jxClient.JenkinsV1().PipelineStructures(ns).Get(pipelineName, metav1.GetOptions{})
   538  	if err != nil {
   539  		return nil, errors.Wrapf(err, "getting PipelineStructure with Pipeline name %s for PipelineRun %s", pipelineName, run.Name)
   540  	}
   541  	return structure, nil
   542  }
   543  
   544  // PipelineRunIsNotPending returns true if the PipelineRun has completed or has running steps.
   545  func PipelineRunIsNotPending(pr *pipelineapi.PipelineRun) bool {
   546  	if pr.Status.CompletionTime != nil {
   547  		return true
   548  	}
   549  	if len(pr.Status.TaskRuns) > 0 {
   550  		for _, v := range pr.Status.TaskRuns {
   551  			if v.Status != nil {
   552  				for _, stepState := range v.Status.Steps {
   553  					if stepState.Waiting == nil || stepState.Waiting.Reason == "PodInitializing" {
   554  						return true
   555  					}
   556  				}
   557  			}
   558  		}
   559  	}
   560  	return false
   561  }
   562  
   563  // PipelineRunIsComplete returns true if the PipelineRun has completed or has running steps.
   564  func PipelineRunIsComplete(pr *pipelineapi.PipelineRun) bool {
   565  	if pr.Status.CompletionTime != nil {
   566  		return true
   567  	}
   568  	return false
   569  }
   570  
   571  // CancelPipelineRun cancels a Pipeline
   572  func CancelPipelineRun(tektonClient tektonclient.Interface, ns string, pr *pipelineapi.PipelineRun) error {
   573  	pr.Spec.Status = pipelineapi.PipelineRunSpecStatusCancelled
   574  	_, err := tektonClient.TektonV1alpha1().PipelineRuns(ns).Update(pr)
   575  	if err != nil {
   576  		return errors.Wrapf(err, "failed to update PipelineRun %s in namespace %s to mark it as cancelled", pr.Name, ns)
   577  	}
   578  	return nil
   579  }