@@ -21,13 +21,29 @@ import (
21
21
"errors"
22
22
"fmt"
23
23
"regexp"
24
+ "slices"
24
25
"strings"
26
+ "time"
25
27
28
+ "github.com/tektoncd/pipeline/internal/artifactref"
26
29
"github.com/tektoncd/pipeline/pkg/apis/config"
30
+ "github.com/tektoncd/pipeline/pkg/apis/pipeline"
31
+ "github.com/tektoncd/pipeline/pkg/internal/resultref"
32
+ "github.com/tektoncd/pipeline/pkg/substitution"
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,329 @@ func RefNameLikeUrl(name string) error {
97
104
}
98
105
return nil
99
106
}
107
+
108
+ func validateSteps (ctx context.Context , steps []Step ) (errs * apis.FieldError ) {
109
+ // Task must not have duplicate step names.
110
+ names := sets .NewString ()
111
+ for idx , s := range steps {
112
+ errs = errs .Also (validateStep (ctx , s , names ).ViaIndex (idx ))
113
+ if s .Results != nil {
114
+ errs = errs .Also (ValidateStepResultsVariables (ctx , s .Results , s .Script ).ViaIndex (idx ))
115
+ errs = errs .Also (ValidateStepResults (ctx , s .Results ).ViaIndex (idx ).ViaField ("results" ))
116
+ }
117
+ if len (s .When ) > 0 {
118
+ errs = errs .Also (s .When .validate (ctx ).ViaIndex (idx ))
119
+ }
120
+ }
121
+ return errs
122
+ }
123
+
124
+ func validateStep (ctx context.Context , s Step , names sets.String ) (errs * apis.FieldError ) {
125
+ if err := validateArtifactsReferencesInStep (ctx , s ); err != nil {
126
+ return err
127
+ }
128
+
129
+ if s .Ref != nil {
130
+ if ! config .FromContextOrDefaults (ctx ).FeatureFlags .EnableStepActions && isCreateOrUpdateAndDiverged (ctx , s ) {
131
+ return apis .ErrGeneric (fmt .Sprintf ("feature flag %s should be set to true to reference StepActions in Steps." , config .EnableStepActions ), "" )
132
+ }
133
+ errs = errs .Also (s .Ref .Validate (ctx ))
134
+ if s .Image != "" {
135
+ errs = errs .Also (& apis.FieldError {
136
+ Message : "image cannot be used with Ref" ,
137
+ Paths : []string {"image" },
138
+ })
139
+ }
140
+ if len (s .Command ) > 0 {
141
+ errs = errs .Also (& apis.FieldError {
142
+ Message : "command cannot be used with Ref" ,
143
+ Paths : []string {"command" },
144
+ })
145
+ }
146
+ if len (s .Args ) > 0 {
147
+ errs = errs .Also (& apis.FieldError {
148
+ Message : "args cannot be used with Ref" ,
149
+ Paths : []string {"args" },
150
+ })
151
+ }
152
+ if s .Script != "" {
153
+ errs = errs .Also (& apis.FieldError {
154
+ Message : "script cannot be used with Ref" ,
155
+ Paths : []string {"script" },
156
+ })
157
+ }
158
+ if s .WorkingDir != "" {
159
+ errs = errs .Also (& apis.FieldError {
160
+ Message : "working dir cannot be used with Ref" ,
161
+ Paths : []string {"workingDir" },
162
+ })
163
+ }
164
+ if s .Env != nil {
165
+ errs = errs .Also (& apis.FieldError {
166
+ Message : "env cannot be used with Ref" ,
167
+ Paths : []string {"env" },
168
+ })
169
+ }
170
+ if len (s .VolumeMounts ) > 0 {
171
+ errs = errs .Also (& apis.FieldError {
172
+ Message : "volumeMounts cannot be used with Ref" ,
173
+ Paths : []string {"volumeMounts" },
174
+ })
175
+ }
176
+ if len (s .Results ) > 0 {
177
+ errs = errs .Also (& apis.FieldError {
178
+ Message : "results cannot be used with Ref" ,
179
+ Paths : []string {"results" },
180
+ })
181
+ }
182
+ } else {
183
+ if len (s .Params ) > 0 {
184
+ errs = errs .Also (& apis.FieldError {
185
+ Message : "params cannot be used without Ref" ,
186
+ Paths : []string {"params" },
187
+ })
188
+ }
189
+ if len (s .Results ) > 0 {
190
+ if ! config .FromContextOrDefaults (ctx ).FeatureFlags .EnableStepActions && isCreateOrUpdateAndDiverged (ctx , s ) {
191
+ return apis .ErrGeneric (fmt .Sprintf ("feature flag %s should be set to true in order to use Results in Steps." , config .EnableStepActions ), "" )
192
+ }
193
+ }
194
+ if len (s .When ) > 0 {
195
+ if ! config .FromContextOrDefaults (ctx ).FeatureFlags .EnableStepActions && isCreateOrUpdateAndDiverged (ctx , s ) {
196
+ return apis .ErrGeneric (fmt .Sprintf ("feature flag %s should be set to true in order to use When in Steps." , config .EnableStepActions ), "" )
197
+ }
198
+ }
199
+ if s .Image == "" {
200
+ errs = errs .Also (apis .ErrMissingField ("Image" ))
201
+ }
202
+
203
+ if s .Script != "" {
204
+ if len (s .Command ) > 0 {
205
+ errs = errs .Also (& apis.FieldError {
206
+ Message : "script cannot be used with command" ,
207
+ Paths : []string {"script" },
208
+ })
209
+ }
210
+ }
211
+ }
212
+
213
+ if s .Name != "" {
214
+ if names .Has (s .Name ) {
215
+ errs = errs .Also (apis .ErrInvalidValue (s .Name , "name" ))
216
+ }
217
+ if e := validation .IsDNS1123Label (s .Name ); len (e ) > 0 {
218
+ errs = errs .Also (& apis.FieldError {
219
+ Message : fmt .Sprintf ("invalid value %q" , s .Name ),
220
+ Paths : []string {"name" },
221
+ 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" ,
222
+ })
223
+ }
224
+ names .Insert (s .Name )
225
+ }
226
+
227
+ if s .Timeout != nil {
228
+ if s .Timeout .Duration < time .Duration (0 ) {
229
+ return apis .ErrInvalidValue (s .Timeout .Duration , "negative timeout" )
230
+ }
231
+ }
232
+
233
+ for j , vm := range s .VolumeMounts {
234
+ if strings .HasPrefix (vm .MountPath , "/tekton/" ) &&
235
+ ! strings .HasPrefix (vm .MountPath , "/tekton/home" ) {
236
+ 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 ))
237
+ }
238
+ if strings .HasPrefix (vm .Name , "tekton-internal-" ) {
239
+ errs = errs .Also (apis .ErrGeneric (fmt .Sprintf (`volumeMount name %q cannot start with "tekton-internal-"` , vm .Name ), "name" ).ViaFieldIndex ("volumeMounts" , j ))
240
+ }
241
+ }
242
+
243
+ if s .OnError != "" {
244
+ if ! isParamRefs (string (s .OnError )) && s .OnError != Continue && s .OnError != StopAndFail {
245
+ errs = errs .Also (& apis.FieldError {
246
+ Message : fmt .Sprintf ("invalid value: \" %v\" " , s .OnError ),
247
+ Paths : []string {"onError" },
248
+ Details : "Task step onError must be either \" continue\" or \" stopAndFail\" " ,
249
+ })
250
+ }
251
+ }
252
+
253
+ if s .Script != "" {
254
+ cleaned := strings .TrimSpace (s .Script )
255
+ if strings .HasPrefix (cleaned , "#!win" ) {
256
+ errs = errs .Also (config .ValidateEnabledAPIFields (ctx , "windows script support" , config .AlphaAPIFields ).ViaField ("script" ))
257
+ }
258
+ }
259
+
260
+ // StdoutConfig is an alpha feature and will fail validation if it's used in a task spec
261
+ // when the enable-api-fields feature gate is not "alpha".
262
+ if s .StdoutConfig != nil {
263
+ errs = errs .Also (config .ValidateEnabledAPIFields (ctx , "step stdout stream support" , config .AlphaAPIFields ).ViaField ("stdoutconfig" ))
264
+ }
265
+ // StderrConfig is an alpha feature and will fail validation if it's used in a task spec
266
+ // when the enable-api-fields feature gate is not "alpha".
267
+ if s .StderrConfig != nil {
268
+ errs = errs .Also (config .ValidateEnabledAPIFields (ctx , "step stderr stream support" , config .AlphaAPIFields ).ViaField ("stderrconfig" ))
269
+ }
270
+
271
+ // Validate usage of step result reference.
272
+ // Referencing previous step's results are only allowed in `env`, `command` and `args`.
273
+ errs = errs .Also (validateStepResultReference (s ))
274
+
275
+ // Validate usage of step artifacts output reference
276
+ // Referencing previous step's results are only allowed in `env`, `command` and `args`, `script`.
277
+ errs = errs .Also (validateStepArtifactsReference (s ))
278
+ return errs
279
+ }
280
+
281
+ // isParamRefs attempts to check if a specified string looks like it contains any parameter reference
282
+ // This is useful to make sure the specified value looks like a Parameter Reference before performing any strict validation
283
+ func isParamRefs (s string ) bool {
284
+ return strings .HasPrefix (s , "$(" + ParamsPrefix )
285
+ }
286
+
287
+ func validateArtifactsReferencesInStep (ctx context.Context , s Step ) * apis.FieldError {
288
+ if ! config .FromContextOrDefaults (ctx ).FeatureFlags .EnableArtifacts {
289
+ var t []string
290
+ t = append (t , s .Script )
291
+ t = append (t , s .Command ... )
292
+ t = append (t , s .Args ... )
293
+ for _ , e := range s .Env {
294
+ t = append (t , e .Value )
295
+ }
296
+ if slices .ContainsFunc (t , stepArtifactReferenceExists ) || slices .ContainsFunc (t , taskArtifactReferenceExists ) {
297
+ return apis .ErrGeneric (fmt .Sprintf ("feature flag %s should be set to true to use artifacts feature." , config .EnableArtifacts ), "" )
298
+ }
299
+ }
300
+ return nil
301
+ }
302
+
303
+ func stepArtifactReferenceExists (src string ) bool {
304
+ return len (artifactref .StepArtifactRegex .FindAllStringSubmatch (src , - 1 )) > 0 || strings .Contains (src , "$(" + artifactref .StepArtifactPathPattern + ")" )
305
+ }
306
+
307
+ func taskArtifactReferenceExists (src string ) bool {
308
+ return len (artifactref .TaskArtifactRegex .FindAllStringSubmatch (src , - 1 )) > 0 || strings .Contains (src , "$(" + artifactref .TaskArtifactPathPattern + ")" )
309
+ }
310
+
311
+ func validateStepResultReference (s Step ) (errs * apis.FieldError ) {
312
+ errs = errs .Also (errorIfStepResultReferencedInField (s .Name , "name" ))
313
+ errs = errs .Also (errorIfStepResultReferencedInField (s .Image , "image" ))
314
+ errs = errs .Also (errorIfStepResultReferencedInField (s .Script , "script" ))
315
+ errs = errs .Also (errorIfStepResultReferencedInField (string (s .ImagePullPolicy ), "imagePullPolicy" ))
316
+ errs = errs .Also (errorIfStepResultReferencedInField (s .WorkingDir , "workingDir" ))
317
+ for _ , e := range s .EnvFrom {
318
+ errs = errs .Also (errorIfStepResultReferencedInField (e .Prefix , "envFrom.prefix" ))
319
+ if e .ConfigMapRef != nil {
320
+ errs = errs .Also (errorIfStepResultReferencedInField (e .ConfigMapRef .LocalObjectReference .Name , "envFrom.configMapRef" ))
321
+ }
322
+ if e .SecretRef != nil {
323
+ errs = errs .Also (errorIfStepResultReferencedInField (e .SecretRef .LocalObjectReference .Name , "envFrom.secretRef" ))
324
+ }
325
+ }
326
+ for _ , v := range s .VolumeMounts {
327
+ errs = errs .Also (errorIfStepResultReferencedInField (v .Name , "volumeMounts.name" ))
328
+ errs = errs .Also (errorIfStepResultReferencedInField (v .MountPath , "volumeMounts.mountPath" ))
329
+ errs = errs .Also (errorIfStepResultReferencedInField (v .SubPath , "volumeMounts.subPath" ))
330
+ }
331
+ for _ , v := range s .VolumeDevices {
332
+ errs = errs .Also (errorIfStepResultReferencedInField (v .Name , "volumeDevices.name" ))
333
+ errs = errs .Also (errorIfStepResultReferencedInField (v .DevicePath , "volumeDevices.devicePath" ))
334
+ }
335
+ return errs
336
+ }
337
+
338
+ func errorIfStepResultReferencedInField (value , fieldName string ) (errs * apis.FieldError ) {
339
+ matches := resultref .StepResultRegex .FindAllStringSubmatch (value , - 1 )
340
+ if len (matches ) > 0 {
341
+ errs = errs .Also (& apis.FieldError {
342
+ Message : "stepResult substitutions are only allowed in env, command and args. Found usage in" ,
343
+ Paths : []string {fieldName },
344
+ })
345
+ }
346
+ return errs
347
+ }
348
+
349
+ func validateStepArtifactsReference (s Step ) (errs * apis.FieldError ) {
350
+ errs = errs .Also (errorIfStepArtifactReferencedInField (s .Name , "name" ))
351
+ errs = errs .Also (errorIfStepArtifactReferencedInField (s .Image , "image" ))
352
+ errs = errs .Also (errorIfStepArtifactReferencedInField (string (s .ImagePullPolicy ), "imagePullPolicy" ))
353
+ errs = errs .Also (errorIfStepArtifactReferencedInField (s .WorkingDir , "workingDir" ))
354
+ for _ , e := range s .EnvFrom {
355
+ errs = errs .Also (errorIfStepArtifactReferencedInField (e .Prefix , "envFrom.prefix" ))
356
+ if e .ConfigMapRef != nil {
357
+ errs = errs .Also (errorIfStepArtifactReferencedInField (e .ConfigMapRef .LocalObjectReference .Name , "envFrom.configMapRef" ))
358
+ }
359
+ if e .SecretRef != nil {
360
+ errs = errs .Also (errorIfStepArtifactReferencedInField (e .SecretRef .LocalObjectReference .Name , "envFrom.secretRef" ))
361
+ }
362
+ }
363
+ for _ , v := range s .VolumeMounts {
364
+ errs = errs .Also (errorIfStepArtifactReferencedInField (v .Name , "volumeMounts.name" ))
365
+ errs = errs .Also (errorIfStepArtifactReferencedInField (v .MountPath , "volumeMounts.mountPath" ))
366
+ errs = errs .Also (errorIfStepArtifactReferencedInField (v .SubPath , "volumeMounts.subPath" ))
367
+ }
368
+ for _ , v := range s .VolumeDevices {
369
+ errs = errs .Also (errorIfStepArtifactReferencedInField (v .Name , "volumeDevices.name" ))
370
+ errs = errs .Also (errorIfStepArtifactReferencedInField (v .DevicePath , "volumeDevices.devicePath" ))
371
+ }
372
+ return errs
373
+ }
374
+
375
+ func errorIfStepArtifactReferencedInField (value , fieldName string ) (errs * apis.FieldError ) {
376
+ if stepArtifactReferenceExists (value ) {
377
+ errs = errs .Also (& apis.FieldError {
378
+ Message : "stepArtifact substitutions are only allowed in env, command, args and script. Found usage in" ,
379
+ Paths : []string {fieldName },
380
+ })
381
+ }
382
+ return errs
383
+ }
384
+
385
+ // ValidateStepResultsVariables validates if the StepResults referenced in step script are defined in step's results.
386
+ func ValidateStepResultsVariables (ctx context.Context , results []StepResult , script string ) (errs * apis.FieldError ) {
387
+ resultsNames := sets .NewString ()
388
+ for _ , r := range results {
389
+ resultsNames .Insert (r .Name )
390
+ }
391
+ errs = errs .Also (substitution .ValidateNoReferencesToUnknownVariables (script , "step.results" , resultsNames ).ViaField ("script" ))
392
+ errs = errs .Also (substitution .ValidateNoReferencesToUnknownVariables (script , "results" , resultsNames ).ViaField ("script" ))
393
+ return errs
394
+ }
395
+
396
+ // ValidateStepResults validates that all of the declared StepResults are valid.
397
+ func ValidateStepResults (ctx context.Context , results []StepResult ) (errs * apis.FieldError ) {
398
+ for index , result := range results {
399
+ errs = errs .Also (result .Validate (ctx ).ViaIndex (index ))
400
+ }
401
+ return errs
402
+ }
403
+
404
+ func validateSidecars (sidecars []Sidecar ) (errs * apis.FieldError ) {
405
+ for _ , sc := range sidecars {
406
+ errs = errs .Also (validateSidecarName (sc ))
407
+
408
+ if sc .Image == "" {
409
+ errs = errs .Also (apis .ErrMissingField ("image" ))
410
+ }
411
+
412
+ if sc .Script != "" {
413
+ if len (sc .Command ) > 0 {
414
+ errs = errs .Also (& apis.FieldError {
415
+ Message : "script cannot be used with command" ,
416
+ Paths : []string {"script" },
417
+ })
418
+ }
419
+ }
420
+ }
421
+ return errs
422
+ }
423
+
424
+ func validateSidecarName (sc Sidecar ) (errs * apis.FieldError ) {
425
+ if sc .Name == pipeline .ReservedResultsSidecarName {
426
+ errs = errs .Also (& apis.FieldError {
427
+ Message : fmt .Sprintf ("Invalid: cannot use reserved sidecar name %v " , sc .Name ),
428
+ Paths : []string {"name" },
429
+ })
430
+ }
431
+ return errs
432
+ }
0 commit comments