Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TEP-0076] Add indexing into array for taskrun params reference #5132

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ For instructions on using variable substitutions see the relevant section of [th
| `params.<param name>` | The value of the parameter at runtime. |
| `params['<param name>']` | (see above) |
| `params["<param name>"]` | (see above) |
| `params.<param name>[i]` | Get the i-th element of param array. This is alpha feature, set `enable-api-fields` to `alpha` to use it.|
| `params['<param name>'][i]` | (see above) |
| `params["<param name>"][i]` | (see above) |
| `resources.inputs.<resourceName>.path` | The path to the input resource's directory. |
| `resources.outputs.<resourceName>.path` | The path to the output resource's directory. |
| `results.<resultName>.path` | The path to the file where the `Task` writes its results data. |
Expand Down
37 changes: 37 additions & 0 deletions examples/v1beta1/taskruns/alpha/param_array_indexing.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: params-array-indexing
spec:
params:
- name: array-to-echo
value:
- "foo"
- "bar"
taskSpec:
params:
- name: array-to-echo
type: array
steps:
# this step should echo "foo"
- name: echo-params-1
image: bash:3.2
args: [
"echo",
"$(params.array-to-echo[0])",
]
# this step should echo "bar"
- name: echo-params-2
image: ubuntu
script: |
#!/bin/bash
VALUE=$(params.array-to-echo[1])
EXPECTED="bar"
diff=$(diff <(printf "%s\n" "${VALUE[@]}") <(printf "%s\n" "${EXPECTED[@]}"))
if [[ -z "$diff" ]]; then
echo "Get expected: ${VALUE}"
exit 0
else
echo "Want: ${EXPECTED} Got: ${VALUE}"
exit 1
fi
15 changes: 14 additions & 1 deletion pkg/reconciler/taskrun/resources/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ import (
)

// ApplyParameters applies the params from a TaskRun.Input.Parameters to a TaskSpec
func ApplyParameters(spec *v1beta1.TaskSpec, tr *v1beta1.TaskRun, defaults ...v1beta1.ParamSpec) *v1beta1.TaskSpec {
func ApplyParameters(ctx context.Context, spec *v1beta1.TaskSpec, tr *v1beta1.TaskRun, defaults ...v1beta1.ParamSpec) *v1beta1.TaskSpec {
// This assumes that the TaskRun inputs have been validated against what the Task requests.

// stringReplacements is used for standard single-string stringReplacements, while arrayReplacements contains arrays
// that need to be further processed.
stringReplacements := map[string]string{}
arrayReplacements := map[string][]string{}
cfg := config.FromContextOrDefaults(ctx)

patterns := []string{
"params.%s",
Expand All @@ -58,6 +59,12 @@ func ApplyParameters(spec *v1beta1.TaskSpec, tr *v1beta1.TaskRun, defaults ...v1
switch p.Default.Type {
case v1beta1.ParamTypeArray:
for _, pattern := range patterns {
// array indexing for param is alpha feature
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of scope for this PR, but these two for loops have a lot of duplicate code that could be consolidated.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, we can also refactor the code in pipelinerun as well

if cfg.FeatureFlags.EnableAPIFields == config.AlphaAPIFields {
for i := 0; i < len(p.Default.ArrayVal); i++ {
stringReplacements[fmt.Sprintf(pattern+"[%d]", p.Name, i)] = p.Default.ArrayVal[i]
}
}
arrayReplacements[fmt.Sprintf(pattern, p.Name)] = p.Default.ArrayVal
}
case v1beta1.ParamTypeObject:
Expand All @@ -76,6 +83,12 @@ func ApplyParameters(spec *v1beta1.TaskSpec, tr *v1beta1.TaskRun, defaults ...v1
switch p.Value.Type {
case v1beta1.ParamTypeArray:
for _, pattern := range patterns {
// array indexing for param is alpha feature
if cfg.FeatureFlags.EnableAPIFields == config.AlphaAPIFields {
for i := 0; i < len(p.Value.ArrayVal); i++ {
stringReplacements[fmt.Sprintf(pattern+"[%d]", p.Name, i)] = p.Value.ArrayVal[i]
}
}
arrayReplacements[fmt.Sprintf(pattern, p.Name)] = p.Value.ArrayVal
}
case v1beta1.ParamTypeObject:
Expand Down
253 changes: 250 additions & 3 deletions pkg/reconciler/taskrun/resources/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,180 @@ var (
}},
}

simpleTaskSpecArrayIndexing = &v1beta1.TaskSpec{
Sidecars: []v1beta1.Sidecar{{
Name: "foo",
Image: `$(params["myimage"][0])`,
Env: []corev1.EnvVar{{
Name: "foo",
Value: "$(params['FOO'][1])",
}},
}},
StepTemplate: &v1beta1.StepTemplate{
Env: []corev1.EnvVar{{
Name: "template-var",
Value: `$(params["FOO"][1])`,
}},
Image: "$(params.myimage[0])",
},
Steps: []v1beta1.Step{{
Name: "foo",
Image: "$(params.myimage[0])",
}, {
Name: "baz",
Image: "bat",
WorkingDir: "$(inputs.resources.workspace.path)",
Args: []string{"$(inputs.resources.workspace.url)"},
}, {
Name: "qux",
Image: "$(params.something[0])",
Args: []string{"$(outputs.resources.imageToUse.url)"},
}, {
Name: "foo",
Image: `$(params["myimage"][0])`,
}, {
Name: "baz",
Image: "$(params.somethingelse)",
WorkingDir: "$(inputs.resources.workspace.path)",
Args: []string{"$(inputs.resources.workspace.url)"},
}, {
Name: "qux",
Image: "quux",
Args: []string{"$(outputs.resources.imageToUse.url)"},
}, {
Name: "foo",
Image: "busybox:$(params.FOO[1])",
VolumeMounts: []corev1.VolumeMount{{
Name: "$(params.FOO[1])",
MountPath: "path/to/$(params.FOO[1])",
SubPath: "sub/$(params.FOO[1])/path",
}},
}, {
Name: "foo",
Image: "busybox:$(params.FOO[1])",
Env: []corev1.EnvVar{{
Name: "foo",
Value: "value-$(params.FOO[1])",
}, {
Name: "bar",
ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: "config-$(params.FOO[1])"},
Key: "config-key-$(params.FOO[1])",
},
},
}, {
Name: "baz",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: "secret-$(params.FOO[1])"},
Key: "secret-key-$(params.FOO[1])",
},
},
}},
EnvFrom: []corev1.EnvFromSource{{
Prefix: "prefix-0-$(params.FOO[1])",
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{Name: "config-$(params.FOO[1])"},
},
}, {
Prefix: "prefix-1-$(params.FOO[1])",
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{Name: "secret-$(params.FOO[1])"},
},
}},
}, {
Name: "outputs-resources-path-ab",
Image: "$(outputs.resources.imageToUse-ab.path)",
}, {
Name: "outputs-resources-path-re",
Image: "$(outputs.resources.imageToUse-re.path)",
}},
Volumes: []corev1.Volume{{
Name: "$(params.FOO[1])",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "$(params.FOO[1])",
},
Items: []corev1.KeyToPath{{
Key: "$(params.FOO[1])",
Path: "$(params.FOO[1])",
}},
},
},
}, {
Name: "some-secret",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: "$(params.FOO[1])",
Items: []corev1.KeyToPath{{
Key: "$(params.FOO[1])",
Path: "$(params.FOO[1])",
}},
},
},
}, {
Name: "some-pvc",
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: "$(params.FOO[1])",
},
},
}, {
Name: "some-projected-volumes",
VolumeSource: corev1.VolumeSource{
Projected: &corev1.ProjectedVolumeSource{
Sources: []corev1.VolumeProjection{{
ConfigMap: &corev1.ConfigMapProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: "$(params.FOO[1])",
},
},
Secret: &corev1.SecretProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: "$(params.FOO[1])",
},
},
ServiceAccountToken: &corev1.ServiceAccountTokenProjection{
Audience: "$(params.FOO[1])",
},
}},
},
},
}, {
Name: "some-csi",
VolumeSource: corev1.VolumeSource{
CSI: &corev1.CSIVolumeSource{
VolumeAttributes: map[string]string{
"secretProviderClass": "$(params.FOO[1])",
},
NodePublishSecretRef: &corev1.LocalObjectReference{
Name: "$(params.FOO[1])",
},
},
},
}},
Resources: &v1beta1.TaskResources{
Inputs: []v1beta1.TaskResource{{
ResourceDeclaration: v1beta1.ResourceDeclaration{
Name: "workspace",
},
}},
Outputs: []v1beta1.TaskResource{{
ResourceDeclaration: v1beta1.ResourceDeclaration{
Name: "imageToUse-ab",
TargetPath: "/foo/builtImage",
},
}, {
ResourceDeclaration: v1beta1.ResourceDeclaration{
Name: "imageToUse-re",
TargetPath: "foo/builtImage",
},
}},
},
}

gcsTaskSpec = &v1beta1.TaskSpec{
Steps: []v1beta1.Step{{
Name: "foobar",
Expand Down Expand Up @@ -697,7 +871,7 @@ func TestApplyArrayParameters(t *testing.T) {
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := resources.ApplyParameters(tt.args.ts, tt.args.tr, tt.args.dp...)
got := resources.ApplyParameters(context.Background(), tt.args.ts, tt.args.tr, tt.args.dp...)
lbernick marked this conversation as resolved.
Show resolved Hide resolved
if d := cmp.Diff(tt.want, got); d != "" {
t.Errorf("ApplyParameters() got diff %s", diff.PrintWantGot(d))
}
Expand Down Expand Up @@ -768,7 +942,80 @@ func TestApplyParameters(t *testing.T) {
spec.Sidecars[0].Image = "bar"
spec.Sidecars[0].Env[0].Value = "world"
})
got := resources.ApplyParameters(simpleTaskSpec, tr, dp...)
got := resources.ApplyParameters(context.Background(), simpleTaskSpec, tr, dp...)
if d := cmp.Diff(want, got); d != "" {
t.Errorf("ApplyParameters() got diff %s", diff.PrintWantGot(d))
}
}

func TestApplyParameters_ArrayIndexing(t *testing.T) {
tr := &v1beta1.TaskRun{
Spec: v1beta1.TaskRunSpec{
Params: []v1beta1.Param{{
Name: "myimage",
Value: *v1beta1.NewArrayOrString("bar", "foo"),
}, {
Name: "FOO",
Value: *v1beta1.NewArrayOrString("hello", "world"),
}},
},
}
dp := []v1beta1.ParamSpec{{
Name: "something",
Default: v1beta1.NewArrayOrString("mydefault", "mydefault2"),
}, {
Name: "somethingelse",
Default: v1beta1.NewArrayOrString(""),
}}
want := applyMutation(simpleTaskSpec, func(spec *v1beta1.TaskSpec) {
spec.StepTemplate.Env[0].Value = "world"
spec.StepTemplate.Image = "bar"

spec.Steps[0].Image = "bar"
spec.Steps[2].Image = "mydefault"
spec.Steps[3].Image = "bar"
spec.Steps[4].Image = ""

spec.Steps[6].VolumeMounts[0].Name = "world"
spec.Steps[6].VolumeMounts[0].SubPath = "sub/world/path"
spec.Steps[6].VolumeMounts[0].MountPath = "path/to/world"
spec.Steps[6].Image = "busybox:world"

spec.Steps[7].Env[0].Value = "value-world"
spec.Steps[7].Env[1].ValueFrom.ConfigMapKeyRef.LocalObjectReference.Name = "config-world"
spec.Steps[7].Env[1].ValueFrom.ConfigMapKeyRef.Key = "config-key-world"
spec.Steps[7].Env[2].ValueFrom.SecretKeyRef.LocalObjectReference.Name = "secret-world"
spec.Steps[7].Env[2].ValueFrom.SecretKeyRef.Key = "secret-key-world"
spec.Steps[7].EnvFrom[0].Prefix = "prefix-0-world"
spec.Steps[7].EnvFrom[0].ConfigMapRef.LocalObjectReference.Name = "config-world"
spec.Steps[7].EnvFrom[1].Prefix = "prefix-1-world"
spec.Steps[7].EnvFrom[1].SecretRef.LocalObjectReference.Name = "secret-world"
spec.Steps[7].Image = "busybox:world"
spec.Steps[8].Image = "$(outputs.resources.imageToUse-ab.path)"
spec.Steps[9].Image = "$(outputs.resources.imageToUse-re.path)"

spec.Volumes[0].Name = "world"
spec.Volumes[0].VolumeSource.ConfigMap.LocalObjectReference.Name = "world"
spec.Volumes[0].VolumeSource.ConfigMap.Items[0].Key = "world"
spec.Volumes[0].VolumeSource.ConfigMap.Items[0].Path = "world"
spec.Volumes[1].VolumeSource.Secret.SecretName = "world"
spec.Volumes[1].VolumeSource.Secret.Items[0].Key = "world"
spec.Volumes[1].VolumeSource.Secret.Items[0].Path = "world"
spec.Volumes[2].VolumeSource.PersistentVolumeClaim.ClaimName = "world"
spec.Volumes[3].VolumeSource.Projected.Sources[0].ConfigMap.Name = "world"
spec.Volumes[3].VolumeSource.Projected.Sources[0].Secret.Name = "world"
spec.Volumes[3].VolumeSource.Projected.Sources[0].ServiceAccountToken.Audience = "world"
spec.Volumes[4].VolumeSource.CSI.VolumeAttributes["secretProviderClass"] = "world"
spec.Volumes[4].VolumeSource.CSI.NodePublishSecretRef.Name = "world"

spec.Sidecars[0].Image = "bar"
spec.Sidecars[0].Env[0].Value = "world"
})
ctx := context.Background()
cfg := config.FromContextOrDefaults(ctx)
cfg.FeatureFlags.EnableAPIFields = config.AlphaAPIFields
ctx = config.ToContext(ctx, cfg)
got := resources.ApplyParameters(ctx, simpleTaskSpecArrayIndexing, tr, dp...)
if d := cmp.Diff(want, got); d != "" {
t.Errorf("ApplyParameters() got diff %s", diff.PrintWantGot(d))
}
Expand Down Expand Up @@ -834,7 +1081,7 @@ func TestApplyObjectParameters(t *testing.T) {
spec.Volumes[3].VolumeSource.CSI.VolumeAttributes["secretProviderClass"] = "taskrun-value-for-key1"
spec.Volumes[3].VolumeSource.CSI.NodePublishSecretRef.Name = "taskrun-value-for-key1"
})
got := resources.ApplyParameters(objectParamTaskSpec, tr, dp...)
got := resources.ApplyParameters(context.Background(), objectParamTaskSpec, tr, dp...)
if d := cmp.Diff(want, got); d != "" {
t.Errorf("ApplyParameters() got diff %s", diff.PrintWantGot(d))
}
Expand Down
Loading