-
Notifications
You must be signed in to change notification settings - Fork 81
/
resolve.go
274 lines (243 loc) · 9.18 KB
/
resolve.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
package resolve
import (
"context"
"fmt"
"regexp"
"strings"
apipac "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys"
"github.com/openshift-pipelines/pipelines-as-code/pkg/formatting"
"github.com/openshift-pipelines/pipelines-as-code/pkg/matcher"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
"github.com/openshift-pipelines/pipelines-as-code/pkg/provider"
tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
tektonv1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"go.uber.org/zap"
k8scheme "k8s.io/client-go/kubernetes/scheme"
)
type TektonTypes struct {
PipelineRuns []*tektonv1.PipelineRun
Pipelines []*tektonv1.Pipeline
TaskRuns []*tektonv1.TaskRun
Tasks []*tektonv1.Task
}
var yamlDocSeparatorRe = regexp.MustCompile(`(?m)^---\s*$`)
// getTaskRunByName returns the taskrun with the given name the first one found
// will be matched. It does not handle conflicts so user has fetched multiple
// taskruns with the same name it will just pick up the first one.
// if the taskrun is not found it returns an error
func getTaskByName(name string, tasks []*tektonv1.Task) (*tektonv1.Task, error) {
for _, value := range tasks {
if value.Name == name {
return value, nil
}
}
return &tektonv1.Task{}, fmt.Errorf("cannot find referenced task %s. if it's a remote task make sure to add it in the annotations", name)
}
func getPipelineByName(name string, tasks []*tektonv1.Pipeline) (*tektonv1.Pipeline, error) {
for _, value := range tasks {
if value.Name == name {
return value, nil
}
}
return &tektonv1.Pipeline{}, fmt.Errorf("cannot find referenced pipeline %s. for a remote pipeline make sure to add it in the annotation", name)
}
func pipelineRunsWithSameName(prs []*tektonv1.PipelineRun) error {
prNames := map[string]bool{}
for _, pr := range prs {
name := pr.GetName()
generateName := pr.GetGenerateName()
if name != "" {
if _, exist := prNames[name]; exist {
return fmt.Errorf("found multiple pipelinerun in .tekton with the same name: %v, please update", name)
}
prNames[name] = true
}
if generateName != "" {
if _, exist := prNames[generateName]; exist {
return fmt.Errorf("found multiple pipelinerun in .tekton with the same generateName: %v, please update", generateName)
}
prNames[generateName] = true
}
}
return nil
}
func skippingTask(taskName string, skippedTasks []string) bool {
for _, value := range skippedTasks {
if value == taskName {
return true
}
}
return false
}
func isTektonAPIVersion(apiVersion string) bool {
return strings.HasPrefix(apiVersion, "tekton.dev/") || apiVersion == ""
}
func inlineTasks(tasks []tektonv1.PipelineTask, ropt *Opts, types TektonTypes) ([]tektonv1.PipelineTask, error) {
pipelineTasks := []tektonv1.PipelineTask{}
for _, task := range tasks {
if task.TaskRef != nil &&
task.TaskRef.Resolver == "" &&
isTektonAPIVersion(task.TaskRef.APIVersion) &&
string(task.TaskRef.Kind) != "ClusterTask" &&
!skippingTask(task.TaskRef.Name, ropt.SkipInlining) {
taskResolved, err := getTaskByName(task.TaskRef.Name, types.Tasks)
if err != nil {
return nil, err
}
task.TaskRef = nil
task.TaskSpec = &tektonv1.EmbeddedTask{TaskSpec: taskResolved.Spec}
}
pipelineTasks = append(pipelineTasks, task)
}
return pipelineTasks, nil
}
type Opts struct {
GenerateName bool // whether to GenerateName
RemoteTasks bool // whether to parse annotation to fetch tasks from remote
SkipInlining []string // task to skip inlining
ProviderToken string
}
func ReadTektonTypes(ctx context.Context, log *zap.SugaredLogger, data string) (TektonTypes, error) {
types := TektonTypes{}
decoder := k8scheme.Codecs.UniversalDeserializer()
for _, doc := range yamlDocSeparatorRe.Split(data, -1) {
if strings.TrimSpace(doc) == "" {
continue
}
obj, _, err := decoder.Decode([]byte(doc), nil, nil)
if err != nil {
log.Infof("Skipping document not looking like a kubernetes resources: %v", err)
continue
}
switch o := obj.(type) {
case *tektonv1beta1.Pipeline: //nolint: staticcheck // we need to support v1beta1
c := &tektonv1.Pipeline{}
if err := o.ConvertTo(ctx, c); err != nil {
return types, fmt.Errorf("pipeline v1beta1 %s cannot be converted as v1: err: %w", o.GetName(), err)
}
types.Pipelines = append(types.Pipelines, c)
case *tektonv1beta1.PipelineRun: //nolint: staticcheck // we need to support v1beta1
c := &tektonv1.PipelineRun{}
if err := o.ConvertTo(ctx, c); err != nil {
return types, fmt.Errorf("pipelinerun v1beta1 %s cannot be converted as v1: err: %w", o.GetName(), err)
}
types.PipelineRuns = append(types.PipelineRuns, c)
case *tektonv1beta1.Task: //nolint: staticcheck // we need to support v1beta1
c := &tektonv1.Task{}
if err := o.ConvertTo(ctx, c); err != nil {
return types, fmt.Errorf("task v1beta1 %s cannot be converted as v1: err: %w", o.GetName(), err)
}
types.Tasks = append(types.Tasks, c)
case *tektonv1.PipelineRun:
types.PipelineRuns = append(types.PipelineRuns, o)
case *tektonv1.Pipeline:
types.Pipelines = append(types.Pipelines, o)
case *tektonv1.Task:
types.Tasks = append(types.Tasks, o)
default:
log.Info("Skipping document not looking like a tekton resource we can Resolve.")
}
}
return types, nil
}
// Resolve gets a large string which is a yaml multi documents containing
// Pipeline/PipelineRuns/Tasks and resolve them inline as a single PipelineRun
// generateName can be set as True to set the name as a generateName + "-" for
// unique pipelinerun
func Resolve(ctx context.Context, cs *params.Run, logger *zap.SugaredLogger, providerintf provider.Interface, types TektonTypes, event *info.Event, ropt *Opts) ([]*tektonv1.PipelineRun, error) {
if len(types.PipelineRuns) == 0 {
return []*tektonv1.PipelineRun{}, fmt.Errorf("could not find any PipelineRun in your .tekton/ directory")
}
if _, err := MetadataResolve(types.PipelineRuns); err != nil {
return []*tektonv1.PipelineRun{}, err
}
// Resolve remote annotations on remote task or remote pipeline or tasks
// inside remote pipeline
if ropt.RemoteTasks {
rt := &matcher.RemoteTasks{
Run: cs,
Event: event,
ProviderInterface: providerintf,
Logger: logger,
}
var err error
if types, err = getRemotes(ctx, rt, types); err != nil {
return []*tektonv1.PipelineRun{}, err
}
}
// Resolve {Finally/Task}Ref inside Pipeline
for _, pipeline := range types.Pipelines {
pipelineTasks, err := inlineTasks(pipeline.Spec.Tasks, ropt, types)
if err != nil {
return nil, err
}
pipeline.Spec.Tasks = pipelineTasks
finallyTasks, err := inlineTasks(pipeline.Spec.Finally, ropt, types)
if err != nil {
return nil, err
}
pipeline.Spec.Finally = finallyTasks
}
for _, pipelinerun := range types.PipelineRuns {
// Resolve {Finally/Task}Ref inside PipelineSpec inside PipelineRun
if pipelinerun.Spec.PipelineSpec != nil {
turns, err := inlineTasks(pipelinerun.Spec.PipelineSpec.Tasks, ropt, types)
if err != nil {
return nil, err
}
pipelinerun.Spec.PipelineSpec.Tasks = turns
fruns, err := inlineTasks(pipelinerun.Spec.PipelineSpec.Finally, ropt, types)
if err != nil {
return nil, err
}
pipelinerun.Spec.PipelineSpec.Finally = fruns
}
// Resolve PipelineRef inside PipelineRef
if pipelinerun.Spec.PipelineRef != nil && pipelinerun.Spec.PipelineRef.Resolver == "" {
pipelineResolved, err := getPipelineByName(pipelinerun.Spec.PipelineRef.Name, types.Pipelines)
if err != nil {
return []*tektonv1.PipelineRun{}, err
}
pipelinerun.Spec.PipelineRef = nil
pipelinerun.Spec.PipelineSpec = &pipelineResolved.Spec
}
// Add a GenerateName based on the pipeline name and a "-"
// if we already have a GenerateName then just keep it like this
if ropt.GenerateName && pipelinerun.GenerateName == "" {
pipelinerun.GenerateName = pipelinerun.Name + "-"
pipelinerun.Name = ""
}
}
return types.PipelineRuns, nil
}
func MetadataResolve(prs []*tektonv1.PipelineRun) ([]*tektonv1.PipelineRun, error) {
if err := pipelineRunsWithSameName(prs); err != nil {
return []*tektonv1.PipelineRun{}, err
}
for _, prun := range prs {
originPipelineRunName := prun.GetName()
if originPipelineRunName == "" && prun.GenerateName != "" {
originPipelineRunName = prun.GetGenerateName()
}
// keep the originalPipelineRun in a label
// because we would need it later on when grouping by cleanups and we
// can attach that pr file from .tekton directory.
// Don't overwrite the labels if there is some who already exist set by the user in repo
if prun.GetLabels() == nil {
prun.Labels = map[string]string{}
}
// Don't overwrite the annotation if there is some who already exist set by the user in repo
if prun.GetAnnotations() == nil {
prun.Annotations = map[string]string{}
}
prun.GetLabels()[apipac.OriginalPRName] = formatting.CleanValueKubernetes(originPipelineRunName)
prun.GetAnnotations()[apipac.OriginalPRName] = originPipelineRunName
}
return prs, nil
}
//nolint:gochecknoinits
func init() {
_ = tektonv1.AddToScheme(k8scheme.Scheme)
_ = tektonv1beta1.AddToScheme(k8scheme.Scheme)
}