github.com/redhat-appstudio/release-service@v0.0.0-20240507143925-083712697924/tekton/utils/pipeline_run_builder.go (about)

     1  /*
     2  Copyright 2023.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package utils
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"reflect"
    23  	"unicode"
    24  
    25  	"github.com/hashicorp/go-multierror"
    26  	libhandler "github.com/operator-framework/operator-lib/handler"
    27  	tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
    28  	corev1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/api/resource"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"sigs.k8s.io/controller-runtime/pkg/client"
    32  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    33  )
    34  
    35  type PipelineRunBuilder struct {
    36  	err         *multierror.Error
    37  	pipelineRun *tektonv1.PipelineRun
    38  }
    39  
    40  // NewPipelineRunBuilder initializes a new PipelineRunBuilder with the given name prefix and namespace.
    41  // It sets the name of the PipelineRun to be generated with the provided prefix and sets its namespace.
    42  func NewPipelineRunBuilder(namePrefix, namespace string) *PipelineRunBuilder {
    43  	return &PipelineRunBuilder{
    44  		pipelineRun: &tektonv1.PipelineRun{
    45  			ObjectMeta: metav1.ObjectMeta{
    46  				GenerateName: namePrefix + "-",
    47  				Namespace:    namespace,
    48  			},
    49  			Spec: tektonv1.PipelineRunSpec{},
    50  		},
    51  	}
    52  }
    53  
    54  // Build returns the constructed PipelineRun and any accumulated error.
    55  func (b *PipelineRunBuilder) Build() (*tektonv1.PipelineRun, error) {
    56  	return b.pipelineRun, b.err.ErrorOrNil()
    57  }
    58  
    59  // WithAnnotations appends or updates annotations to the PipelineRun's metadata.
    60  // If the PipelineRun does not have existing annotations, it initializes them before adding.
    61  func (b *PipelineRunBuilder) WithAnnotations(annotations map[string]string) *PipelineRunBuilder {
    62  	if b.pipelineRun.ObjectMeta.Annotations == nil {
    63  		b.pipelineRun.ObjectMeta.Annotations = make(map[string]string)
    64  	}
    65  
    66  	for key, value := range annotations {
    67  		b.pipelineRun.ObjectMeta.Annotations[key] = value
    68  	}
    69  
    70  	return b
    71  }
    72  
    73  // WithFinalizer adds the given finalizer to the PipelineRun's metadata.
    74  func (b *PipelineRunBuilder) WithFinalizer(finalizer string) *PipelineRunBuilder {
    75  	controllerutil.AddFinalizer(b.pipelineRun, finalizer)
    76  
    77  	return b
    78  }
    79  
    80  // WithLabels appends or updates labels to the PipelineRun's metadata.
    81  // If the PipelineRun does not have existing labels, it initializes them before adding.
    82  func (b *PipelineRunBuilder) WithLabels(labels map[string]string) *PipelineRunBuilder {
    83  	if b.pipelineRun.ObjectMeta.Labels == nil {
    84  		b.pipelineRun.ObjectMeta.Labels = make(map[string]string)
    85  	}
    86  
    87  	for key, value := range labels {
    88  		b.pipelineRun.ObjectMeta.Labels[key] = value
    89  	}
    90  
    91  	return b
    92  }
    93  
    94  // WithObjectReferences constructs tektonv1.Param entries for each of the provided client.Objects.
    95  // Each param name is derived from the object's Kind (with the first letter made lowercase) and
    96  // the value is a combination of the object's Namespace and Name.
    97  func (b *PipelineRunBuilder) WithObjectReferences(objects ...client.Object) *PipelineRunBuilder {
    98  	for _, obj := range objects {
    99  		name := []rune(obj.GetObjectKind().GroupVersionKind().Kind)
   100  		name[0] = unicode.ToLower(name[0])
   101  
   102  		b.WithParams(tektonv1.Param{
   103  			Name: string(name),
   104  			Value: tektonv1.ParamValue{
   105  				Type:      tektonv1.ParamTypeString,
   106  				StringVal: obj.GetNamespace() + "/" + obj.GetName(),
   107  			},
   108  		})
   109  	}
   110  
   111  	return b
   112  }
   113  
   114  // WithObjectSpecsAsJson constructs tektonv1.Param entries for the Spec field of each of the provided client.Objects.
   115  // Each param name is derived from the object's Kind (with the first letter made lowercase).
   116  // The value for each param is the JSON representation of the object's Spec.
   117  // If an error occurs during extraction or serialization, it's accumulated in the builder's err field using multierror.
   118  func (b *PipelineRunBuilder) WithObjectSpecsAsJson(objects ...client.Object) *PipelineRunBuilder {
   119  	for _, obj := range objects {
   120  		name := []rune(obj.GetObjectKind().GroupVersionKind().Kind)
   121  		name[0] = unicode.ToLower(name[0])
   122  
   123  		value := reflect.ValueOf(obj).Elem().FieldByName("Spec")
   124  		if !value.IsValid() {
   125  			b.err = multierror.Append(b.err, fmt.Errorf("failed to extract spec for object: %s", string(name)))
   126  			continue
   127  		}
   128  
   129  		jsonData, err := json.Marshal(value.Interface())
   130  		if err != nil {
   131  			b.err = multierror.Append(b.err, fmt.Errorf("failed to serialize spec of object %s to JSON: %v", string(name), err))
   132  			continue
   133  		}
   134  
   135  		b.WithParams(tektonv1.Param{
   136  			Name: string(name),
   137  			Value: tektonv1.ParamValue{
   138  				Type:      tektonv1.ParamTypeString,
   139  				StringVal: string(jsonData),
   140  			},
   141  		})
   142  	}
   143  
   144  	return b
   145  }
   146  
   147  // WithOwner sets the given client.Object as the owner of the PipelineRun.
   148  // It also adds the ReleaseFinalizer to the PipelineRun.
   149  func (b *PipelineRunBuilder) WithOwner(object client.Object) *PipelineRunBuilder {
   150  	if err := libhandler.SetOwnerAnnotations(object, b.pipelineRun); err != nil {
   151  		b.err = multierror.Append(b.err, fmt.Errorf("failed to set owner annotations: %v", err))
   152  		return b
   153  	}
   154  
   155  	return b
   156  }
   157  
   158  // WithParams appends the provided params to the PipelineRun's spec.
   159  func (b *PipelineRunBuilder) WithParams(params ...tektonv1.Param) *PipelineRunBuilder {
   160  	if b.pipelineRun.Spec.Params == nil {
   161  		b.pipelineRun.Spec.Params = make([]tektonv1.Param, 0)
   162  	}
   163  
   164  	b.pipelineRun.Spec.Params = append(b.pipelineRun.Spec.Params, params...)
   165  
   166  	return b
   167  }
   168  
   169  // WithParamsFromConfigMap adds parameters to the PipelineRun based on the provided keys from a given ConfigMap.
   170  // If a key is present in the ConfigMap, a new tektonv1.Param is constructed with the key as the name and the associated
   171  // value from the ConfigMap. Keys not found in the ConfigMap are ignored.
   172  func (b *PipelineRunBuilder) WithParamsFromConfigMap(configMap *corev1.ConfigMap, keys []string) *PipelineRunBuilder {
   173  	if configMap == nil {
   174  		return b
   175  	}
   176  
   177  	var params []tektonv1.Param
   178  	for _, key := range keys {
   179  		if value, exists := configMap.Data[key]; exists {
   180  			params = append(params, tektonv1.Param{
   181  				Name: key,
   182  				Value: tektonv1.ParamValue{
   183  					Type:      tektonv1.ParamTypeString,
   184  					StringVal: value,
   185  				},
   186  			})
   187  		}
   188  	}
   189  
   190  	return b.WithParams(params...)
   191  }
   192  
   193  // WithPipelineRef sets the PipelineRef for the PipelineRun's spec.
   194  func (b *PipelineRunBuilder) WithPipelineRef(pipelineRef *tektonv1.PipelineRef) *PipelineRunBuilder {
   195  	b.pipelineRun.Spec.PipelineRef = pipelineRef
   196  
   197  	if pipelineRef.Resolver == "git" {
   198  		for _, param := range pipelineRef.Params {
   199  			if param.Name == "revision" {
   200  				b.WithParams(tektonv1.Param{
   201  					Name: "taskGitRevision",
   202  					Value: tektonv1.ParamValue{
   203  						Type:      tektonv1.ParamTypeString,
   204  						StringVal: param.Value.StringVal,
   205  					},
   206  				})
   207  			}
   208  
   209  			if param.Name == "url" {
   210  				b.WithParams(tektonv1.Param{
   211  					Name: "taskGitUrl",
   212  					Value: tektonv1.ParamValue{
   213  						Type:      tektonv1.ParamTypeString,
   214  						StringVal: param.Value.StringVal,
   215  					},
   216  				})
   217  			}
   218  		}
   219  	}
   220  
   221  	return b
   222  }
   223  
   224  // WithServiceAccount sets the ServiceAccountName for the PipelineRun's TaskRunTemplate.
   225  func (b *PipelineRunBuilder) WithServiceAccount(serviceAccount string) *PipelineRunBuilder {
   226  	b.pipelineRun.Spec.TaskRunTemplate.ServiceAccountName = serviceAccount
   227  
   228  	return b
   229  }
   230  
   231  // WithTimeouts sets the Timeouts for the PipelineRun.
   232  func (b *PipelineRunBuilder) WithTimeouts(timeouts, defaultTimeouts *tektonv1.TimeoutFields) *PipelineRunBuilder {
   233  	if timeouts == nil || *timeouts == (tektonv1.TimeoutFields{}) {
   234  		b.pipelineRun.Spec.Timeouts = defaultTimeouts
   235  	} else {
   236  		b.pipelineRun.Spec.Timeouts = timeouts
   237  	}
   238  
   239  	return b
   240  }
   241  
   242  // WithWorkspaceFromVolumeTemplate creates and adds a workspace binding to the PipelineRun's spec using
   243  // the provided workspace name and volume size.
   244  func (b *PipelineRunBuilder) WithWorkspaceFromVolumeTemplate(name, size string) *PipelineRunBuilder {
   245  	if b.pipelineRun.Spec.Workspaces == nil {
   246  		b.pipelineRun.Spec.Workspaces = []tektonv1.WorkspaceBinding{}
   247  	}
   248  
   249  	quantity, err := resource.ParseQuantity(size)
   250  	if err != nil {
   251  		b.err = multierror.Append(b.err, fmt.Errorf("invalid size format: %v", err))
   252  		return b
   253  	}
   254  
   255  	workspace := tektonv1.WorkspaceBinding{
   256  		Name: name,
   257  		VolumeClaimTemplate: &corev1.PersistentVolumeClaim{
   258  			Spec: corev1.PersistentVolumeClaimSpec{
   259  				AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
   260  				Resources: corev1.VolumeResourceRequirements{
   261  					Requests: corev1.ResourceList{
   262  						corev1.ResourceStorage: quantity,
   263  					},
   264  				},
   265  			},
   266  		},
   267  	}
   268  
   269  	b.pipelineRun.Spec.Workspaces = append(b.pipelineRun.Spec.Workspaces, workspace)
   270  
   271  	return b
   272  }