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 }