forked from tektoncd/pipeline
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtask_validation.go
675 lines (610 loc) · 28.1 KB
/
task_validation.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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
/*
Copyright 2022 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
"context"
"fmt"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/tektoncd/pipeline/pkg/apis/config"
"github.com/tektoncd/pipeline/pkg/apis/pipeline"
"github.com/tektoncd/pipeline/pkg/apis/validate"
"github.com/tektoncd/pipeline/pkg/substitution"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation"
"knative.dev/pkg/apis"
"knative.dev/pkg/webhook/resourcesemantics"
)
const (
// stringAndArrayVariableNameFormat is the regex to validate if string/array variable name format follows the following rules.
// - Must only contain alphanumeric characters, hyphens (-), underscores (_), and dots (.)
// - Must begin with a letter or an underscore (_)
stringAndArrayVariableNameFormat = "^[_a-zA-Z][_a-zA-Z0-9.-]*$"
// objectVariableNameFormat is the regext used to validate object name and key names format
// The difference with the array or string name format is that object variable names shouldn't contain dots.
objectVariableNameFormat = "^[_a-zA-Z][_a-zA-Z0-9-]*$"
)
var _ apis.Validatable = (*Task)(nil)
var _ resourcesemantics.VerbLimited = (*Task)(nil)
// SupportedVerbs returns the operations that validation should be called for
func (t *Task) SupportedVerbs() []admissionregistrationv1.OperationType {
return []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update}
}
var stringAndArrayVariableNameFormatRegex = regexp.MustCompile(stringAndArrayVariableNameFormat)
var objectVariableNameFormatRegex = regexp.MustCompile(objectVariableNameFormat)
// Validate implements apis.Validatable
func (t *Task) Validate(ctx context.Context) *apis.FieldError {
errs := validate.ObjectMetadata(t.GetObjectMeta()).ViaField("metadata")
errs = errs.Also(t.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec"))
// When a Task is created directly, instead of declared inline in a TaskRun or PipelineRun,
// we do not support propagated parameters. Validate that all params it uses are declared.
errs = errs.Also(ValidateUsageOfDeclaredParameters(ctx, t.Spec.Steps, t.Spec.Params).ViaField("spec"))
return errs
}
// Validate implements apis.Validatable
func (ts *TaskSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
errs = errs.Also(ts.ValidateBetaFields(ctx))
if len(ts.Steps) == 0 {
errs = errs.Also(apis.ErrMissingField("steps"))
}
errs = errs.Also(ValidateVolumes(ts.Volumes).ViaField("volumes"))
errs = errs.Also(validateDeclaredWorkspaces(ts.Workspaces, ts.Steps, ts.StepTemplate).ViaField("workspaces"))
errs = errs.Also(validateWorkspaceUsages(ctx, ts))
mergedSteps, err := MergeStepsWithStepTemplate(ts.StepTemplate, ts.Steps)
if err != nil {
errs = errs.Also(&apis.FieldError{
Message: fmt.Sprintf("error merging step template and steps: %s", err),
Paths: []string{"stepTemplate"},
Details: err.Error(),
})
}
errs = errs.Also(validateSteps(ctx, mergedSteps).ViaField("steps"))
errs = errs.Also(validateSidecarNames(ts.Sidecars))
errs = errs.Also(ValidateParameterTypes(ctx, ts.Params).ViaField("params"))
errs = errs.Also(ValidateParameterVariables(ctx, ts.Steps, ts.Params))
errs = errs.Also(validateTaskContextVariables(ctx, ts.Steps))
errs = errs.Also(validateTaskResultsVariables(ctx, ts.Steps, ts.Results))
errs = errs.Also(validateResults(ctx, ts.Results).ViaField("results"))
return errs
}
// ValidateBetaFields returns an error if the Task spec uses beta features but does not
// have "enable-api-fields" set to "alpha" or "beta".
func (ts *TaskSpec) ValidateBetaFields(ctx context.Context) *apis.FieldError {
var errs *apis.FieldError
// Object parameters
for i, p := range ts.Params {
if p.Type == ParamTypeObject {
errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "object type parameter", config.BetaAPIFields).ViaFieldIndex("params", i))
}
}
// Indexing into array parameters
arrayIndexParamRefs := ts.GetIndexingReferencesToArrayParams()
if len(arrayIndexParamRefs) != 0 {
errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "indexing into array parameters", config.BetaAPIFields))
}
// Array and object results
for i, result := range ts.Results {
switch result.Type {
case ResultsTypeObject:
errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "object results", config.BetaAPIFields).ViaFieldIndex("results", i))
case ResultsTypeArray:
errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "array results", config.BetaAPIFields).ViaFieldIndex("results", i))
case ResultsTypeString:
default:
}
}
return errs
}
// ValidateUsageOfDeclaredParameters validates that all parameters referenced in the Task are declared by the Task.
func ValidateUsageOfDeclaredParameters(ctx context.Context, steps []Step, params ParamSpecs) *apis.FieldError {
var errs *apis.FieldError
_, _, objectParams := params.SortByType()
allParameterNames := sets.NewString(params.GetNames()...)
errs = errs.Also(validateVariables(ctx, steps, "params", allParameterNames))
errs = errs.Also(validateObjectUsage(ctx, steps, objectParams))
errs = errs.Also(ValidateObjectParamsHaveProperties(ctx, params))
return errs
}
// ValidateObjectParamsHaveProperties returns an error if any declared object params are missing properties
func ValidateObjectParamsHaveProperties(ctx context.Context, params ParamSpecs) *apis.FieldError {
var errs *apis.FieldError
for _, p := range params {
if p.Type == ParamTypeObject && p.Properties == nil {
errs = errs.Also(apis.ErrMissingField(fmt.Sprintf("%s.properties", p.Name)))
}
}
return errs
}
func validateSidecarNames(sidecars []Sidecar) (errs *apis.FieldError) {
for _, sc := range sidecars {
if sc.Name == pipeline.ReservedResultsSidecarName {
errs = errs.Also(&apis.FieldError{
Message: fmt.Sprintf("Invalid: cannot use reserved sidecar name %v ", sc.Name),
Paths: []string{"sidecars"},
})
}
}
return errs
}
func validateResults(ctx context.Context, results []TaskResult) (errs *apis.FieldError) {
for index, result := range results {
errs = errs.Also(result.Validate(ctx).ViaIndex(index))
}
return errs
}
// a mount path which conflicts with any other declared workspaces, with the explicitly
// declared volume mounts, or with the stepTemplate. The names must also be unique.
func validateDeclaredWorkspaces(workspaces []WorkspaceDeclaration, steps []Step, stepTemplate *StepTemplate) (errs *apis.FieldError) {
mountPaths := sets.NewString()
for _, step := range steps {
for _, vm := range step.VolumeMounts {
mountPaths.Insert(filepath.Clean(vm.MountPath))
}
}
if stepTemplate != nil {
for _, vm := range stepTemplate.VolumeMounts {
mountPaths.Insert(filepath.Clean(vm.MountPath))
}
}
wsNames := sets.NewString()
for idx, w := range workspaces {
// Workspace names must be unique
if wsNames.Has(w.Name) {
errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("workspace name %q must be unique", w.Name), "name").ViaIndex(idx))
} else {
wsNames.Insert(w.Name)
}
// Workspaces must not try to use mount paths that are already used
mountPath := filepath.Clean(w.GetMountPath())
if _, ok := mountPaths[mountPath]; ok {
errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("workspace mount path %q must be unique", mountPath), "mountpath").ViaIndex(idx))
}
mountPaths[mountPath] = struct{}{}
}
return errs
}
// validateWorkspaceUsages checks that all WorkspaceUsage objects in Steps
// refer to workspaces that are defined in the Task.
//
// This is a beta feature and will fail validation if it's used by a step
// or sidecar when the enable-api-fields feature gate is anything but "beta".
func validateWorkspaceUsages(ctx context.Context, ts *TaskSpec) (errs *apis.FieldError) {
workspaces := ts.Workspaces
steps := ts.Steps
sidecars := ts.Sidecars
wsNames := sets.NewString()
for _, w := range workspaces {
wsNames.Insert(w.Name)
}
for stepIdx, step := range steps {
if len(step.Workspaces) != 0 {
errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "step workspaces", config.BetaAPIFields).ViaIndex(stepIdx).ViaField("steps"))
}
for workspaceIdx, w := range step.Workspaces {
if !wsNames.Has(w.Name) {
errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("undefined workspace %q", w.Name), "name").ViaIndex(workspaceIdx).ViaField("workspaces").ViaIndex(stepIdx).ViaField("steps"))
}
}
}
for sidecarIdx, sidecar := range sidecars {
if len(sidecar.Workspaces) != 0 {
errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "sidecar workspaces", config.BetaAPIFields).ViaIndex(sidecarIdx).ViaField("sidecars"))
}
for workspaceIdx, w := range sidecar.Workspaces {
if !wsNames.Has(w.Name) {
errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("undefined workspace %q", w.Name), "name").ViaIndex(workspaceIdx).ViaField("workspaces").ViaIndex(sidecarIdx).ViaField("sidecars"))
}
}
}
return errs
}
// ValidateVolumes validates a slice of volumes to make sure there are no dupilcate names
func ValidateVolumes(volumes []corev1.Volume) (errs *apis.FieldError) {
// Task must not have duplicate volume names.
vols := sets.NewString()
for idx, v := range volumes {
if vols.Has(v.Name) {
errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("multiple volumes with same name %q", v.Name), "name").ViaIndex(idx))
} else {
vols.Insert(v.Name)
}
}
return errs
}
func validateSteps(ctx context.Context, steps []Step) (errs *apis.FieldError) {
// Task must not have duplicate step names.
names := sets.NewString()
for idx, s := range steps {
errs = errs.Also(validateStep(ctx, s, names).ViaIndex(idx))
}
return errs
}
func validateStep(ctx context.Context, s Step, names sets.String) (errs *apis.FieldError) {
if s.Ref != nil {
if !config.FromContextOrDefaults(ctx).FeatureFlags.EnableStepActions {
return apis.ErrGeneric("feature flag %s should be set to true to reference StepActions in Steps.", config.EnableStepActions)
}
errs = s.Ref.Validate(ctx)
if s.Image != "" {
errs = errs.Also(&apis.FieldError{
Message: "image cannot be used with Ref",
Paths: []string{"image"},
})
}
if len(s.Command) > 0 {
errs = errs.Also(&apis.FieldError{
Message: "command cannot be used with Ref",
Paths: []string{"command"},
})
}
if len(s.Args) > 0 {
errs = errs.Also(&apis.FieldError{
Message: "args cannot be used with Ref",
Paths: []string{"args"},
})
}
if s.Script != "" {
errs = errs.Also(&apis.FieldError{
Message: "script cannot be used with Ref",
Paths: []string{"script"},
})
}
if s.Env != nil {
errs = errs.Also(&apis.FieldError{
Message: "env cannot be used with Ref",
Paths: []string{"env"},
})
}
if len(s.VolumeMounts) > 0 {
errs = errs.Also(&apis.FieldError{
Message: "volumeMounts cannot be used with Ref",
Paths: []string{"volumeMounts"},
})
}
} else {
if len(s.Params) > 0 {
errs = errs.Also(&apis.FieldError{
Message: "params cannot be used without Ref",
Paths: []string{"params"},
})
}
if s.Image == "" {
errs = errs.Also(apis.ErrMissingField("Image"))
}
if s.Script != "" {
if len(s.Command) > 0 {
errs = errs.Also(&apis.FieldError{
Message: "script cannot be used with command",
Paths: []string{"script"},
})
}
}
}
if s.Name != "" {
if names.Has(s.Name) {
errs = errs.Also(apis.ErrInvalidValue(s.Name, "name"))
}
if e := validation.IsDNS1123Label(s.Name); len(e) > 0 {
errs = errs.Also(&apis.FieldError{
Message: fmt.Sprintf("invalid value %q", s.Name),
Paths: []string{"name"},
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",
})
}
names.Insert(s.Name)
}
if s.Timeout != nil {
if s.Timeout.Duration < time.Duration(0) {
return apis.ErrInvalidValue(s.Timeout.Duration, "negative timeout")
}
}
for j, vm := range s.VolumeMounts {
if strings.HasPrefix(vm.MountPath, "/tekton/") &&
!strings.HasPrefix(vm.MountPath, "/tekton/home") {
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))
}
if strings.HasPrefix(vm.Name, "tekton-internal-") {
errs = errs.Also(apis.ErrGeneric(fmt.Sprintf(`volumeMount name %q cannot start with "tekton-internal-"`, vm.Name), "name").ViaFieldIndex("volumeMounts", j))
}
}
if s.OnError != "" {
if !isParamRefs(string(s.OnError)) && s.OnError != Continue && s.OnError != StopAndFail {
errs = errs.Also(&apis.FieldError{
Message: fmt.Sprintf("invalid value: \"%v\"", s.OnError),
Paths: []string{"onError"},
Details: "Task step onError must be either \"continue\" or \"stopAndFail\"",
})
}
}
if s.Script != "" {
cleaned := strings.TrimSpace(s.Script)
if strings.HasPrefix(cleaned, "#!win") {
errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "windows script support", config.AlphaAPIFields).ViaField("script"))
}
}
// StdoutConfig is an alpha feature and will fail validation if it's used in a task spec
// when the enable-api-fields feature gate is not "alpha".
if s.StdoutConfig != nil {
errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "step stdout stream support", config.AlphaAPIFields).ViaField("stdoutconfig"))
}
// StderrConfig is an alpha feature and will fail validation if it's used in a task spec
// when the enable-api-fields feature gate is not "alpha".
if s.StderrConfig != nil {
errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "step stderr stream support", config.AlphaAPIFields).ViaField("stderrconfig"))
}
return errs
}
// ValidateParameterTypes validates all the types within a slice of ParamSpecs
func ValidateParameterTypes(ctx context.Context, params []ParamSpec) (errs *apis.FieldError) {
for _, p := range params {
errs = errs.Also(p.ValidateType(ctx))
}
return errs
}
// ValidateType checks that the type of a ParamSpec is allowed and its default value matches that type
func (p ParamSpec) ValidateType(ctx context.Context) *apis.FieldError {
// Ensure param has a valid type.
validType := false
for _, allowedType := range AllParamTypes {
if p.Type == allowedType {
validType = true
}
}
if !validType {
return apis.ErrInvalidValue(p.Type, fmt.Sprintf("%s.type", p.Name))
}
// If a default value is provided, ensure its type matches param's declared type.
if (p.Default != nil) && (p.Default.Type != p.Type) {
return &apis.FieldError{
Message: fmt.Sprintf(
"\"%v\" type does not match default value's type: \"%v\"", p.Type, p.Default.Type),
Paths: []string{
fmt.Sprintf("%s.type", p.Name),
fmt.Sprintf("%s.default.type", p.Name),
},
}
}
// Check object type and its PropertySpec type
return p.ValidateObjectType(ctx)
}
// ValidateObjectType checks that object type parameter does not miss the
// definition of `properties` section and the type of a PropertySpec is allowed.
// (Currently, only string is allowed)
func (p ParamSpec) ValidateObjectType(ctx context.Context) *apis.FieldError {
invalidKeys := []string{}
for key, propertySpec := range p.Properties {
if propertySpec.Type != ParamTypeString {
invalidKeys = append(invalidKeys, key)
}
}
if len(invalidKeys) != 0 {
return &apis.FieldError{
Message: fmt.Sprintf("The value type specified for these keys %v is invalid", invalidKeys),
Paths: []string{fmt.Sprintf("%s.properties", p.Name)},
}
}
return nil
}
// ValidateParameterVariables validates all variables within a slice of ParamSpecs against a slice of Steps
func ValidateParameterVariables(ctx context.Context, steps []Step, params ParamSpecs) *apis.FieldError {
var errs *apis.FieldError
errs = errs.Also(params.ValidateNoDuplicateNames())
errs = errs.Also(params.validateParamEnums(ctx).ViaField("params"))
stringParams, arrayParams, objectParams := params.SortByType()
stringParameterNames := sets.NewString(stringParams.GetNames()...)
arrayParameterNames := sets.NewString(arrayParams.GetNames()...)
errs = errs.Also(ValidateNameFormat(stringParameterNames.Insert(arrayParameterNames.List()...), objectParams))
return errs.Also(validateArrayUsage(steps, "params", arrayParameterNames))
}
// validateTaskContextVariables returns an error if any Steps reference context variables that don't exist.
func validateTaskContextVariables(ctx context.Context, steps []Step) *apis.FieldError {
taskRunContextNames := sets.NewString().Insert(
"name",
"namespace",
"uid",
)
taskContextNames := sets.NewString().Insert(
"name",
"retry-count",
)
errs := validateVariables(ctx, steps, "context\\.taskRun", taskRunContextNames)
return errs.Also(validateVariables(ctx, steps, "context\\.task", taskContextNames))
}
// validateTaskResultsVariables validates if the results referenced in step script are defined in task results
func validateTaskResultsVariables(ctx context.Context, steps []Step, results []TaskResult) (errs *apis.FieldError) {
resultsNames := sets.NewString()
for _, r := range results {
resultsNames.Insert(r.Name)
}
for idx, step := range steps {
errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(step.Script, "results", resultsNames).ViaField("script").ViaFieldIndex("steps", idx))
}
return errs
}
// validateObjectUsage validates the usage of individual attributes of an object param and the usage of the entire object
func validateObjectUsage(ctx context.Context, steps []Step, params []ParamSpec) (errs *apis.FieldError) {
objectParameterNames := sets.NewString()
for _, p := range params {
// collect all names of object type params
objectParameterNames.Insert(p.Name)
// collect all keys for this object param
objectKeys := sets.NewString()
for key := range p.Properties {
objectKeys.Insert(key)
}
// check if the object's key names are referenced correctly i.e. param.objectParam.key1
errs = errs.Also(validateVariables(ctx, steps, fmt.Sprintf("params\\.%s", p.Name), objectKeys))
}
return errs.Also(validateObjectUsageAsWhole(steps, "params", objectParameterNames))
}
// validateObjectUsageAsWhole returns an error if the Steps contain references to the entire input object params in fields where these references are prohibited
func validateObjectUsageAsWhole(steps []Step, prefix string, vars sets.String) (errs *apis.FieldError) {
for idx, step := range steps {
errs = errs.Also(validateStepObjectUsageAsWhole(step, prefix, vars)).ViaFieldIndex("steps", idx)
}
return errs
}
// validateStepObjectUsageAsWhole returns an error if the Step contains references to the entire input object params in fields where these references are prohibited
func validateStepObjectUsageAsWhole(step Step, prefix string, vars sets.String) *apis.FieldError {
errs := substitution.ValidateNoReferencesToEntireProhibitedVariables(step.Name, prefix, vars).ViaField("name")
errs = errs.Also(substitution.ValidateNoReferencesToEntireProhibitedVariables(step.Image, prefix, vars).ViaField("image"))
errs = errs.Also(substitution.ValidateNoReferencesToEntireProhibitedVariables(step.WorkingDir, prefix, vars).ViaField("workingDir"))
errs = errs.Also(substitution.ValidateNoReferencesToEntireProhibitedVariables(step.Script, prefix, vars).ViaField("script"))
for i, cmd := range step.Command {
errs = errs.Also(substitution.ValidateNoReferencesToEntireProhibitedVariables(cmd, prefix, vars).ViaFieldIndex("command", i))
}
for i, arg := range step.Args {
errs = errs.Also(substitution.ValidateNoReferencesToEntireProhibitedVariables(arg, prefix, vars).ViaFieldIndex("args", i))
}
for _, env := range step.Env {
errs = errs.Also(substitution.ValidateNoReferencesToEntireProhibitedVariables(env.Value, prefix, vars).ViaFieldKey("env", env.Name))
}
for i, v := range step.VolumeMounts {
errs = errs.Also(substitution.ValidateNoReferencesToEntireProhibitedVariables(v.Name, prefix, vars).ViaField("name").ViaFieldIndex("volumeMount", i))
errs = errs.Also(substitution.ValidateNoReferencesToEntireProhibitedVariables(v.MountPath, prefix, vars).ViaField("mountPath").ViaFieldIndex("volumeMount", i))
errs = errs.Also(substitution.ValidateNoReferencesToEntireProhibitedVariables(v.SubPath, prefix, vars).ViaField("subPath").ViaFieldIndex("volumeMount", i))
}
return errs
}
// validateArrayUsage returns an error if the Steps contain references to the input array params in fields where these references are prohibited
func validateArrayUsage(steps []Step, prefix string, arrayParamNames sets.String) (errs *apis.FieldError) {
for idx, step := range steps {
errs = errs.Also(validateStepArrayUsage(step, prefix, arrayParamNames)).ViaFieldIndex("steps", idx)
}
return errs
}
// validateStepArrayUsage returns an error if the Step contains references to the input array params in fields where these references are prohibited
func validateStepArrayUsage(step Step, prefix string, arrayParamNames sets.String) *apis.FieldError {
errs := substitution.ValidateNoReferencesToProhibitedVariables(step.Name, prefix, arrayParamNames).ViaField("name")
errs = errs.Also(substitution.ValidateNoReferencesToProhibitedVariables(step.Image, prefix, arrayParamNames).ViaField("image"))
errs = errs.Also(substitution.ValidateNoReferencesToProhibitedVariables(step.WorkingDir, prefix, arrayParamNames).ViaField("workingDir"))
errs = errs.Also(substitution.ValidateNoReferencesToProhibitedVariables(step.Script, prefix, arrayParamNames).ViaField("script"))
for i, cmd := range step.Command {
errs = errs.Also(substitution.ValidateVariableReferenceIsIsolated(cmd, prefix, arrayParamNames).ViaFieldIndex("command", i))
}
for i, arg := range step.Args {
errs = errs.Also(substitution.ValidateVariableReferenceIsIsolated(arg, prefix, arrayParamNames).ViaFieldIndex("args", i))
}
for _, env := range step.Env {
errs = errs.Also(substitution.ValidateNoReferencesToProhibitedVariables(env.Value, prefix, arrayParamNames).ViaFieldKey("env", env.Name))
}
for i, v := range step.VolumeMounts {
errs = errs.Also(substitution.ValidateNoReferencesToProhibitedVariables(v.Name, prefix, arrayParamNames).ViaField("name").ViaFieldIndex("volumeMount", i))
errs = errs.Also(substitution.ValidateNoReferencesToProhibitedVariables(v.MountPath, prefix, arrayParamNames).ViaField("mountPath").ViaFieldIndex("volumeMount", i))
errs = errs.Also(substitution.ValidateNoReferencesToProhibitedVariables(v.SubPath, prefix, arrayParamNames).ViaField("subPath").ViaFieldIndex("volumeMount", i))
}
return errs
}
// validateVariables returns an error if the Steps contain references to any unknown variables
func validateVariables(ctx context.Context, steps []Step, prefix string, vars sets.String) (errs *apis.FieldError) {
for idx, step := range steps {
errs = errs.Also(validateStepVariables(ctx, step, prefix, vars).ViaFieldIndex("steps", idx))
}
return errs
}
// ValidateNameFormat validates that the name format of all param types follows the rules
func ValidateNameFormat(stringAndArrayParams sets.String, objectParams []ParamSpec) (errs *apis.FieldError) {
// checking string or array name format
// ----
invalidStringAndArrayNames := []string{}
// Converting to sorted list here rather than just looping map keys
// because we want the order of items in vars to be deterministic for purpose of unit testing
for _, name := range stringAndArrayParams.List() {
if !stringAndArrayVariableNameFormatRegex.MatchString(name) {
invalidStringAndArrayNames = append(invalidStringAndArrayNames, name)
}
}
if len(invalidStringAndArrayNames) != 0 {
errs = errs.Also(&apis.FieldError{
Message: fmt.Sprintf("The format of following array and string variable names is invalid: %s", invalidStringAndArrayNames),
Paths: []string{"params"},
Details: "String/Array Names: \nMust only contain alphanumeric characters, hyphens (-), underscores (_), and dots (.)\nMust begin with a letter or an underscore (_)",
})
}
// checking object name and key name format
// -----
invalidObjectNames := map[string][]string{}
for _, obj := range objectParams {
// check object param name
if !objectVariableNameFormatRegex.MatchString(obj.Name) {
invalidObjectNames[obj.Name] = []string{}
}
// check key names
for k := range obj.Properties {
if !objectVariableNameFormatRegex.MatchString(k) {
invalidObjectNames[obj.Name] = append(invalidObjectNames[obj.Name], k)
}
}
}
if len(invalidObjectNames) != 0 {
errs = errs.Also(&apis.FieldError{
Message: fmt.Sprintf("Object param name and key name format is invalid: %s", invalidObjectNames),
Paths: []string{"params"},
Details: "Object Names: \nMust only contain alphanumeric characters, hyphens (-), underscores (_) \nMust begin with a letter or an underscore (_)",
})
}
return errs
}
// validateStepVariables returns an error if the Step contains references to any unknown variables
func validateStepVariables(ctx context.Context, step Step, prefix string, vars sets.String) *apis.FieldError {
errs := substitution.ValidateNoReferencesToUnknownVariables(step.Name, prefix, vars).ViaField("name")
errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(step.Image, prefix, vars).ViaField("image"))
errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(step.WorkingDir, prefix, vars).ViaField("workingDir"))
errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(step.Script, prefix, vars).ViaField("script"))
for i, cmd := range step.Command {
errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(cmd, prefix, vars).ViaFieldIndex("command", i))
}
for i, arg := range step.Args {
errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(arg, prefix, vars).ViaFieldIndex("args", i))
}
for _, env := range step.Env {
errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(env.Value, prefix, vars).ViaFieldKey("env", env.Name))
}
for i, v := range step.VolumeMounts {
errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(v.Name, prefix, vars).ViaField("name").ViaFieldIndex("volumeMount", i))
errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(v.MountPath, prefix, vars).ViaField("MountPath").ViaFieldIndex("volumeMount", i))
errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(v.SubPath, prefix, vars).ViaField("SubPath").ViaFieldIndex("volumeMount", i))
}
errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(string(step.OnError), prefix, vars).ViaField("onError"))
return errs
}
// isParamRefs attempts to check if a specified string looks like it contains any parameter reference
// This is useful to make sure the specified value looks like a Parameter Reference before performing any strict validation
func isParamRefs(s string) bool {
return strings.HasPrefix(s, "$("+ParamsPrefix)
}
// GetIndexingReferencesToArrayParams returns all strings referencing indices of TaskRun array parameters
// from parameters, workspaces, and when expressions defined in the Task.
// For example, if a Task has a parameter with a value "$(params.array-param-name[1])",
// this would be one of the strings returned.
func (ts *TaskSpec) GetIndexingReferencesToArrayParams() sets.String {
// collect all the possible places to use param references
paramsRefs := []string{}
paramsRefs = append(paramsRefs, extractParamRefsFromSteps(ts.Steps)...)
paramsRefs = append(paramsRefs, extractParamRefsFromStepTemplate(ts.StepTemplate)...)
paramsRefs = append(paramsRefs, extractParamRefsFromVolumes(ts.Volumes)...)
for _, v := range ts.Workspaces {
paramsRefs = append(paramsRefs, v.MountPath)
}
paramsRefs = append(paramsRefs, extractParamRefsFromSidecars(ts.Sidecars)...)
// extract all array indexing references, for example []{"$(params.array-params[1])"}
arrayIndexParamRefs := []string{}
for _, p := range paramsRefs {
arrayIndexParamRefs = append(arrayIndexParamRefs, extractArrayIndexingParamRefs(p)...)
}
return sets.NewString(arrayIndexParamRefs...)
}