@@ -20,14 +20,30 @@ import (
20
20
"context"
21
21
"errors"
22
22
"fmt"
23
+ "reflect"
23
24
"regexp"
25
+ "slices"
24
26
"strings"
27
+ "time"
25
28
29
+ "github.com/tektoncd/pipeline/internal/artifactref"
26
30
"github.com/tektoncd/pipeline/pkg/apis/config"
31
+ "github.com/tektoncd/pipeline/pkg/apis/pipeline"
32
+ "github.com/tektoncd/pipeline/pkg/internal/resultref"
33
+ "k8s.io/apimachinery/pkg/util/sets"
27
34
"k8s.io/apimachinery/pkg/util/validation"
28
35
"knative.dev/pkg/apis"
29
36
)
30
37
38
+ // Validate ensures that a supplied Ref field is populated
39
+ // correctly. No errors are returned for a nil Ref.
40
+ func (ref * Ref ) Validate (ctx context.Context ) (errs * apis.FieldError ) {
41
+ if ref == nil {
42
+ return errs
43
+ }
44
+ return validateRef (ctx , ref .Name , ref .Resolver , ref .Params )
45
+ }
46
+
31
47
func validateRef (ctx context.Context , refName string , refResolver ResolverName , refParams Params ) (errs * apis.FieldError ) {
32
48
switch {
33
49
case refResolver != "" || refParams != nil :
@@ -80,15 +96,6 @@ func validateRef(ctx context.Context, refName string, refResolver ResolverName,
80
96
return errs
81
97
}
82
98
83
- // Validate ensures that a supplied Ref field is populated
84
- // correctly. No errors are returned for a nil Ref.
85
- func (ref * Ref ) Validate (ctx context.Context ) (errs * apis.FieldError ) {
86
- if ref == nil {
87
- return errs
88
- }
89
- return validateRef (ctx , ref .Name , ref .Resolver , ref .Params )
90
- }
91
-
92
99
// RefNameLikeUrl checks if the name is url parsable and returns an error if it isn't.
93
100
func RefNameLikeUrl (name string ) error {
94
101
schemeRegex := regexp .MustCompile (`[\w-]+:\/\/*` )
@@ -97,3 +104,323 @@ func RefNameLikeUrl(name string) error {
97
104
}
98
105
return nil
99
106
}
107
+
108
+ func validateStep (ctx context.Context , s Step , names sets.String ) (errs * apis.FieldError ) {
109
+ if err := validateArtifactsReferencesInStep (ctx , s ); err != nil {
110
+ return err
111
+ }
112
+
113
+ if s .Ref != nil {
114
+ if ! config .FromContextOrDefaults (ctx ).FeatureFlags .EnableStepActions && isCreateOrUpdateAndDiverged (ctx , s ) {
115
+ return apis .ErrGeneric (fmt .Sprintf ("feature flag %s should be set to true to reference StepActions in Steps." , config .EnableStepActions ), "" )
116
+ }
117
+ errs = errs .Also (s .Ref .Validate (ctx ))
118
+ if s .Image != "" {
119
+ errs = errs .Also (& apis.FieldError {
120
+ Message : "image cannot be used with Ref" ,
121
+ Paths : []string {"image" },
122
+ })
123
+ }
124
+ if len (s .Command ) > 0 {
125
+ errs = errs .Also (& apis.FieldError {
126
+ Message : "command cannot be used with Ref" ,
127
+ Paths : []string {"command" },
128
+ })
129
+ }
130
+ if len (s .Args ) > 0 {
131
+ errs = errs .Also (& apis.FieldError {
132
+ Message : "args cannot be used with Ref" ,
133
+ Paths : []string {"args" },
134
+ })
135
+ }
136
+ if s .Script != "" {
137
+ errs = errs .Also (& apis.FieldError {
138
+ Message : "script cannot be used with Ref" ,
139
+ Paths : []string {"script" },
140
+ })
141
+ }
142
+ if s .WorkingDir != "" {
143
+ errs = errs .Also (& apis.FieldError {
144
+ Message : "working dir cannot be used with Ref" ,
145
+ Paths : []string {"workingDir" },
146
+ })
147
+ }
148
+ if s .Env != nil {
149
+ errs = errs .Also (& apis.FieldError {
150
+ Message : "env cannot be used with Ref" ,
151
+ Paths : []string {"env" },
152
+ })
153
+ }
154
+ if len (s .VolumeMounts ) > 0 {
155
+ errs = errs .Also (& apis.FieldError {
156
+ Message : "volumeMounts cannot be used with Ref" ,
157
+ Paths : []string {"volumeMounts" },
158
+ })
159
+ }
160
+ if len (s .Results ) > 0 {
161
+ errs = errs .Also (& apis.FieldError {
162
+ Message : "results cannot be used with Ref" ,
163
+ Paths : []string {"results" },
164
+ })
165
+ }
166
+ } else {
167
+ if len (s .Params ) > 0 {
168
+ errs = errs .Also (& apis.FieldError {
169
+ Message : "params cannot be used without Ref" ,
170
+ Paths : []string {"params" },
171
+ })
172
+ }
173
+ if len (s .Results ) > 0 {
174
+ if ! config .FromContextOrDefaults (ctx ).FeatureFlags .EnableStepActions && isCreateOrUpdateAndDiverged (ctx , s ) {
175
+ return apis .ErrGeneric (fmt .Sprintf ("feature flag %s should be set to true in order to use Results in Steps." , config .EnableStepActions ), "" )
176
+ }
177
+ }
178
+ if len (s .When ) > 0 {
179
+ if ! config .FromContextOrDefaults (ctx ).FeatureFlags .EnableStepActions && isCreateOrUpdateAndDiverged (ctx , s ) {
180
+ return apis .ErrGeneric (fmt .Sprintf ("feature flag %s should be set to true in order to use When in Steps." , config .EnableStepActions ), "" )
181
+ }
182
+ }
183
+ if s .Image == "" {
184
+ errs = errs .Also (apis .ErrMissingField ("Image" ))
185
+ }
186
+
187
+ if s .Script != "" {
188
+ if len (s .Command ) > 0 {
189
+ errs = errs .Also (& apis.FieldError {
190
+ Message : "script cannot be used with command" ,
191
+ Paths : []string {"script" },
192
+ })
193
+ }
194
+ }
195
+ }
196
+
197
+ if s .Name != "" {
198
+ if names .Has (s .Name ) {
199
+ errs = errs .Also (apis .ErrInvalidValue (s .Name , "name" ))
200
+ }
201
+ if e := validation .IsDNS1123Label (s .Name ); len (e ) > 0 {
202
+ errs = errs .Also (& apis.FieldError {
203
+ Message : fmt .Sprintf ("invalid value %q" , s .Name ),
204
+ Paths : []string {"name" },
205
+ Details : "Task step name must be a valid DNS Label, For more info refer to https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" ,
206
+ })
207
+ }
208
+ names .Insert (s .Name )
209
+ }
210
+
211
+ if s .Timeout != nil {
212
+ if s .Timeout .Duration < time .Duration (0 ) {
213
+ return apis .ErrInvalidValue (s .Timeout .Duration , "negative timeout" )
214
+ }
215
+ }
216
+
217
+ for j , vm := range s .VolumeMounts {
218
+ if strings .HasPrefix (vm .MountPath , "/tekton/" ) &&
219
+ ! strings .HasPrefix (vm .MountPath , "/tekton/home" ) {
220
+ errs = errs .Also (apis .ErrGeneric (fmt .Sprintf ("volumeMount cannot be mounted under /tekton/ (volumeMount %q mounted at %q)" , vm .Name , vm .MountPath ), "mountPath" ).ViaFieldIndex ("volumeMounts" , j ))
221
+ }
222
+ if strings .HasPrefix (vm .Name , "tekton-internal-" ) {
223
+ errs = errs .Also (apis .ErrGeneric (fmt .Sprintf (`volumeMount name %q cannot start with "tekton-internal-"` , vm .Name ), "name" ).ViaFieldIndex ("volumeMounts" , j ))
224
+ }
225
+ }
226
+
227
+ if s .OnError != "" {
228
+ if ! isParamRefs (string (s .OnError )) && s .OnError != Continue && s .OnError != StopAndFail {
229
+ errs = errs .Also (& apis.FieldError {
230
+ Message : fmt .Sprintf ("invalid value: \" %v\" " , s .OnError ),
231
+ Paths : []string {"onError" },
232
+ Details : "Task step onError must be either \" continue\" or \" stopAndFail\" " ,
233
+ })
234
+ }
235
+ }
236
+
237
+ if s .Script != "" {
238
+ cleaned := strings .TrimSpace (s .Script )
239
+ if strings .HasPrefix (cleaned , "#!win" ) {
240
+ errs = errs .Also (config .ValidateEnabledAPIFields (ctx , "windows script support" , config .AlphaAPIFields ).ViaField ("script" ))
241
+ }
242
+ }
243
+
244
+ // StdoutConfig is an alpha feature and will fail validation if it's used in a task spec
245
+ // when the enable-api-fields feature gate is not "alpha".
246
+ if s .StdoutConfig != nil {
247
+ errs = errs .Also (config .ValidateEnabledAPIFields (ctx , "step stdout stream support" , config .AlphaAPIFields ).ViaField ("stdoutconfig" ))
248
+ }
249
+ // StderrConfig is an alpha feature and will fail validation if it's used in a task spec
250
+ // when the enable-api-fields feature gate is not "alpha".
251
+ if s .StderrConfig != nil {
252
+ errs = errs .Also (config .ValidateEnabledAPIFields (ctx , "step stderr stream support" , config .AlphaAPIFields ).ViaField ("stderrconfig" ))
253
+ }
254
+
255
+ // Validate usage of step result reference.
256
+ // Referencing previous step's results are only allowed in `env`, `command` and `args`.
257
+ errs = errs .Also (validateStepResultReference (s ))
258
+
259
+ // Validate usage of step artifacts output reference
260
+ // Referencing previous step's results are only allowed in `env`, `command` and `args`, `script`.
261
+ errs = errs .Also (validateStepArtifactsReference (s ))
262
+ return errs
263
+ }
264
+
265
+ // isParamRefs attempts to check if a specified string looks like it contains any parameter reference
266
+ // This is useful to make sure the specified value looks like a Parameter Reference before performing any strict validation
267
+ func isParamRefs (s string ) bool {
268
+ return strings .HasPrefix (s , "$(" + ParamsPrefix )
269
+ }
270
+
271
+ func validateArtifactsReferencesInStep (ctx context.Context , s Step ) * apis.FieldError {
272
+ if ! config .FromContextOrDefaults (ctx ).FeatureFlags .EnableArtifacts {
273
+ var t []string
274
+ t = append (t , s .Script )
275
+ t = append (t , s .Command ... )
276
+ t = append (t , s .Args ... )
277
+ for _ , e := range s .Env {
278
+ t = append (t , e .Value )
279
+ }
280
+ if slices .ContainsFunc (t , stepArtifactReferenceExists ) || slices .ContainsFunc (t , taskArtifactReferenceExists ) {
281
+ return apis .ErrGeneric (fmt .Sprintf ("feature flag %s should be set to true to use artifacts feature." , config .EnableArtifacts ), "" )
282
+ }
283
+ }
284
+ return nil
285
+ }
286
+
287
+ func stepArtifactReferenceExists (src string ) bool {
288
+ return len (artifactref .StepArtifactRegex .FindAllStringSubmatch (src , - 1 )) > 0 || strings .Contains (src , "$(" + artifactref .StepArtifactPathPattern + ")" )
289
+ }
290
+
291
+ func taskArtifactReferenceExists (src string ) bool {
292
+ return len (artifactref .TaskArtifactRegex .FindAllStringSubmatch (src , - 1 )) > 0 || strings .Contains (src , "$(" + artifactref .TaskArtifactPathPattern + ")" )
293
+ }
294
+
295
+ // isCreateOrUpdateAndDiverged checks if the webhook event was create or update
296
+ // if create, it returns true.
297
+ // if update, it checks if the step results have diverged and returns if diverged.
298
+ // if neither, it returns false.
299
+ func isCreateOrUpdateAndDiverged (ctx context.Context , s Step ) bool {
300
+ if apis .IsInCreate (ctx ) {
301
+ return true
302
+ }
303
+ if apis .IsInUpdate (ctx ) {
304
+ baseline := apis .GetBaseline (ctx )
305
+ var baselineStep Step
306
+ switch o := baseline .(type ) {
307
+ case * TaskRun :
308
+ if o .Spec .TaskSpec != nil {
309
+ for _ , step := range o .Spec .TaskSpec .Steps {
310
+ if s .Name == step .Name {
311
+ baselineStep = step
312
+ break
313
+ }
314
+ }
315
+ }
316
+ default :
317
+ // the baseline is not a taskrun.
318
+ // return true so that the validation can happen
319
+ return true
320
+ }
321
+ // If an update event, check if the results have diverged from the baseline
322
+ // this way, the feature flag check wont happen.
323
+ // This will avoid issues like https://github.com/tektoncd/pipeline/issues/5203
324
+ // when the feature is turned off mid-run.
325
+ diverged := ! reflect .DeepEqual (s .Results , baselineStep .Results )
326
+ return diverged
327
+ }
328
+ return false
329
+ }
330
+
331
+ func validateStepResultReference (s Step ) (errs * apis.FieldError ) {
332
+ errs = errs .Also (errorIfStepResultReferencedInField (s .Name , "name" ))
333
+ errs = errs .Also (errorIfStepResultReferencedInField (s .Image , "image" ))
334
+ errs = errs .Also (errorIfStepResultReferencedInField (s .Script , "script" ))
335
+ errs = errs .Also (errorIfStepResultReferencedInField (string (s .ImagePullPolicy ), "imagePullPolicy" ))
336
+ errs = errs .Also (errorIfStepResultReferencedInField (s .WorkingDir , "workingDir" ))
337
+ for _ , e := range s .EnvFrom {
338
+ errs = errs .Also (errorIfStepResultReferencedInField (e .Prefix , "envFrom.prefix" ))
339
+ if e .ConfigMapRef != nil {
340
+ errs = errs .Also (errorIfStepResultReferencedInField (e .ConfigMapRef .LocalObjectReference .Name , "envFrom.configMapRef" ))
341
+ }
342
+ if e .SecretRef != nil {
343
+ errs = errs .Also (errorIfStepResultReferencedInField (e .SecretRef .LocalObjectReference .Name , "envFrom.secretRef" ))
344
+ }
345
+ }
346
+ for _ , v := range s .VolumeMounts {
347
+ errs = errs .Also (errorIfStepResultReferencedInField (v .Name , "volumeMounts.name" ))
348
+ errs = errs .Also (errorIfStepResultReferencedInField (v .MountPath , "volumeMounts.mountPath" ))
349
+ errs = errs .Also (errorIfStepResultReferencedInField (v .SubPath , "volumeMounts.subPath" ))
350
+ }
351
+ for _ , v := range s .VolumeDevices {
352
+ errs = errs .Also (errorIfStepResultReferencedInField (v .Name , "volumeDevices.name" ))
353
+ errs = errs .Also (errorIfStepResultReferencedInField (v .DevicePath , "volumeDevices.devicePath" ))
354
+ }
355
+ return errs
356
+ }
357
+
358
+ func errorIfStepResultReferencedInField (value , fieldName string ) (errs * apis.FieldError ) {
359
+ matches := resultref .StepResultRegex .FindAllStringSubmatch (value , - 1 )
360
+ if len (matches ) > 0 {
361
+ errs = errs .Also (& apis.FieldError {
362
+ Message : "stepResult substitutions are only allowed in env, command and args. Found usage in" ,
363
+ Paths : []string {fieldName },
364
+ })
365
+ }
366
+ return errs
367
+ }
368
+
369
+ func validateStepArtifactsReference (s Step ) (errs * apis.FieldError ) {
370
+ errs = errs .Also (errorIfStepArtifactReferencedInField (s .Name , "name" ))
371
+ errs = errs .Also (errorIfStepArtifactReferencedInField (s .Image , "image" ))
372
+ errs = errs .Also (errorIfStepArtifactReferencedInField (string (s .ImagePullPolicy ), "imagePullPolicy" ))
373
+ errs = errs .Also (errorIfStepArtifactReferencedInField (s .WorkingDir , "workingDir" ))
374
+ for _ , e := range s .EnvFrom {
375
+ errs = errs .Also (errorIfStepArtifactReferencedInField (e .Prefix , "envFrom.prefix" ))
376
+ if e .ConfigMapRef != nil {
377
+ errs = errs .Also (errorIfStepArtifactReferencedInField (e .ConfigMapRef .LocalObjectReference .Name , "envFrom.configMapRef" ))
378
+ }
379
+ if e .SecretRef != nil {
380
+ errs = errs .Also (errorIfStepArtifactReferencedInField (e .SecretRef .LocalObjectReference .Name , "envFrom.secretRef" ))
381
+ }
382
+ }
383
+ for _ , v := range s .VolumeMounts {
384
+ errs = errs .Also (errorIfStepArtifactReferencedInField (v .Name , "volumeMounts.name" ))
385
+ errs = errs .Also (errorIfStepArtifactReferencedInField (v .MountPath , "volumeMounts.mountPath" ))
386
+ errs = errs .Also (errorIfStepArtifactReferencedInField (v .SubPath , "volumeMounts.subPath" ))
387
+ }
388
+ for _ , v := range s .VolumeDevices {
389
+ errs = errs .Also (errorIfStepArtifactReferencedInField (v .Name , "volumeDevices.name" ))
390
+ errs = errs .Also (errorIfStepArtifactReferencedInField (v .DevicePath , "volumeDevices.devicePath" ))
391
+ }
392
+ return errs
393
+ }
394
+
395
+ func errorIfStepArtifactReferencedInField (value , fieldName string ) (errs * apis.FieldError ) {
396
+ if stepArtifactReferenceExists (value ) {
397
+ errs = errs .Also (& apis.FieldError {
398
+ Message : "stepArtifact substitutions are only allowed in env, command, args and script. Found usage in" ,
399
+ Paths : []string {fieldName },
400
+ })
401
+ }
402
+ return errs
403
+ }
404
+
405
+ func validateSidecar (errs * apis.FieldError , sc Sidecar ) * apis.FieldError {
406
+ if sc .Name == pipeline .ReservedResultsSidecarName {
407
+ errs = errs .Also (& apis.FieldError {
408
+ Message : fmt .Sprintf ("Invalid: cannot use reserved sidecar name %v " , sc .Name ),
409
+ Paths : []string {"name" },
410
+ })
411
+ }
412
+
413
+ if sc .Image == "" {
414
+ errs = errs .Also (apis .ErrMissingField ("image" ))
415
+ }
416
+
417
+ if sc .Script != "" {
418
+ if len (sc .Command ) > 0 {
419
+ errs = errs .Also (& apis.FieldError {
420
+ Message : "script cannot be used with command" ,
421
+ Paths : []string {"script" },
422
+ })
423
+ }
424
+ }
425
+ return errs
426
+ }
0 commit comments