From 1d9bcd0abfc12ce8160e01660a2b3cce28bef0c0 Mon Sep 17 00:00:00 2001 From: Chuang Wang Date: Mon, 23 May 2022 22:31:05 -0700 Subject: [PATCH] TEP-0075: Implement object var replacement on task&taskrun level Implement variable replacement for object's individual attributes on task&taskrun level. [According to TEP-0075, when providing values for strings, Task and Pipeline authors can access individual attributes of an object param; they cannot access the object as whole.] --- pkg/reconciler/taskrun/resources/apply.go | 20 +- .../taskrun/resources/apply_test.go | 320 ++++++++++++++++++ 2 files changed, 336 insertions(+), 4 deletions(-) diff --git a/pkg/reconciler/taskrun/resources/apply.go b/pkg/reconciler/taskrun/resources/apply.go index c038fbc3915..5559dc672da 100644 --- a/pkg/reconciler/taskrun/resources/apply.go +++ b/pkg/reconciler/taskrun/resources/apply.go @@ -49,30 +49,42 @@ func ApplyParameters(spec *v1beta1.TaskSpec, tr *v1beta1.TaskRun, defaults ...v1 "inputs.params.%s", } + objectIndividualVariablePattern := "params.%s.%s" + // Set all the default stringReplacements for _, p := range defaults { if p.Default != nil { - if p.Default.Type == v1beta1.ParamTypeString { + switch p.Default.Type { + case v1beta1.ParamTypeString: for _, pattern := range patterns { stringReplacements[fmt.Sprintf(pattern, p.Name)] = p.Default.StringVal } - } else { + case v1beta1.ParamTypeArray: for _, pattern := range patterns { arrayReplacements[fmt.Sprintf(pattern, p.Name)] = p.Default.ArrayVal } + default: + for k, v := range p.Default.ObjectVal { + stringReplacements[fmt.Sprintf(objectIndividualVariablePattern, p.Name, k)] = v + } } } } // Set and overwrite params with the ones from the TaskRun for _, p := range tr.Spec.Params { - if p.Value.Type == v1beta1.ParamTypeString { + switch p.Value.Type { + case v1beta1.ParamTypeString: for _, pattern := range patterns { stringReplacements[fmt.Sprintf(pattern, p.Name)] = p.Value.StringVal } - } else { + case v1beta1.ParamTypeArray: for _, pattern := range patterns { arrayReplacements[fmt.Sprintf(pattern, p.Name)] = p.Value.ArrayVal } + default: + for k, v := range p.Value.ObjectVal { + stringReplacements[fmt.Sprintf(objectIndividualVariablePattern, p.Name, k)] = v + } } } return ApplyReplacements(spec, stringReplacements, arrayReplacements) diff --git a/pkg/reconciler/taskrun/resources/apply_test.go b/pkg/reconciler/taskrun/resources/apply_test.go index d7e3636e752..b631df860ba 100644 --- a/pkg/reconciler/taskrun/resources/apply_test.go +++ b/pkg/reconciler/taskrun/resources/apply_test.go @@ -222,6 +222,180 @@ var ( }, } + simpleTaskSpecUsingObjectVar = &v1beta1.TaskSpec{ + Sidecars: []v1beta1.Sidecar{{ + Name: "foo", + Image: `$(params.myObject.myimage)`, + Env: []corev1.EnvVar{{ + Name: "foo", + Value: "$(params.myObject.FOO)", + }}, + }}, + StepTemplate: &v1beta1.StepTemplate{ + Env: []corev1.EnvVar{{ + Name: "template-var", + Value: `$(params.myObject.FOO)`, + }}, + Image: "$(params.myObject.myimage)", + }, + Steps: []v1beta1.Step{{ + Name: "foo", + Image: "$(params.myObject.myimage)", + }, { + Name: "baz", + Image: "bat", + WorkingDir: "$(inputs.resources.workspace.path)", + Args: []string{"$(inputs.resources.workspace.url)"}, + }, { + Name: "qux", + Image: "$(params.something)", + Args: []string{"$(outputs.resources.imageToUse.url)"}, + }, { + Name: "foo", + Image: `$(params.myObject.myimage)`, + }, { + 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.myObject.FOO)", + VolumeMounts: []corev1.VolumeMount{{ + Name: "$(params.myObject.FOO)", + MountPath: "path/to/$(params.myObject.FOO)", + SubPath: "sub/$(params.myObject.FOO)/path", + }}, + }, { + Name: "foo", + Image: "busybox:$(params.myObject.FOO)", + Env: []corev1.EnvVar{{ + Name: "foo", + Value: "value-$(params.myObject.FOO)", + }, { + Name: "bar", + ValueFrom: &corev1.EnvVarSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: "config-$(params.myObject.FOO)"}, + Key: "config-key-$(params.myObject.FOO)", + }, + }, + }, { + Name: "baz", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: "secret-$(params.myObject.FOO)"}, + Key: "secret-key-$(params.myObject.FOO)", + }, + }, + }}, + EnvFrom: []corev1.EnvFromSource{{ + Prefix: "prefix-0-$(params.myObject.FOO)", + ConfigMapRef: &corev1.ConfigMapEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{Name: "config-$(params.myObject.FOO)"}, + }, + }, { + Prefix: "prefix-1-$(params.myObject.FOO)", + SecretRef: &corev1.SecretEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{Name: "secret-$(params.myObject.FOO)"}, + }, + }}, + }, { + 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.myObject.FOO)", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "$(params.myObject.FOO)", + }, + Items: []corev1.KeyToPath{{ + Key: "$(params.myObject.FOO)", + Path: "$(params.myObject.FOO)", + }}, + }, + }, + }, { + Name: "some-secret", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "$(params.myObject.FOO)", + Items: []corev1.KeyToPath{{ + Key: "$(params.myObject.FOO)", + Path: "$(params.myObject.FOO)", + }}, + }, + }, + }, { + Name: "some-pvc", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "$(params.myObject.FOO)", + }, + }, + }, { + Name: "some-projected-volumes", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{{ + ConfigMap: &corev1.ConfigMapProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "$(params.myObject.FOO)", + }, + }, + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "$(params.myObject.FOO)", + }, + }, + ServiceAccountToken: &corev1.ServiceAccountTokenProjection{ + Audience: "$(params.myObject.FOO)", + }, + }}, + }, + }, + }, { + Name: "some-csi", + VolumeSource: corev1.VolumeSource{ + CSI: &corev1.CSIVolumeSource{ + VolumeAttributes: map[string]string{ + "secretProviderClass": "$(params.myObject.FOO)", + }, + NodePublishSecretRef: &corev1.LocalObjectReference{ + Name: "$(params.myObject.FOO)", + }, + }, + }, + }}, + 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", @@ -261,6 +435,18 @@ var ( }}, } + arrayAndObjectParamTaskSpec = &v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Name: "simple-image", + Image: "some-image", + }, { + Name: "image-with-c-specified", + Image: "some-other-image", + Command: []string{"echo"}, + Args: []string{"$(params.myObject.key1)", "$(params.myObject.key2)", "$(params.array-param)", "last"}, + }}, + } + multipleArrayParamsTaskSpec = &v1beta1.TaskSpec{ Steps: []v1beta1.Step{{ Name: "simple-image", @@ -285,6 +471,18 @@ var ( }}, } + multipleArrayAndObjectParamsTaskSpec = &v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Name: "simple-image", + Image: "image-$(params.myObject.key1)", + }, { + Name: "image-with-c-specified", + Image: "some-other-image", + Command: []string{"cmd", "$(params.array-param1)"}, + Args: []string{"$(params.array-param2)", "second", "$(params.array-param1)", "$(params.myObject.key2)", "last"}, + }}, + } + arrayTaskRun0Elements = &v1beta1.TaskRun{ Spec: v1beta1.TaskRunSpec{ Params: []v1beta1.Param{{ @@ -339,6 +537,21 @@ var ( }, } + arrayTaskRunWith1ObjectParam = &v1beta1.TaskRun{ + Spec: v1beta1.TaskRunSpec{ + Params: []v1beta1.Param{{ + Name: "array-param", + Value: *v1beta1.NewArrayOrString("middlefirst", "middlesecond"), + }, { + Name: "myObject", + Value: *v1beta1.NewObject(map[string]string{ + "key1": "object value1", + "key2": "object value2", + }), + }}, + }, + } + arrayTaskRunMultipleArraysAndStrings = &v1beta1.TaskRun{ Spec: v1beta1.TaskRunSpec{ Params: []v1beta1.Param{{ @@ -357,6 +570,24 @@ var ( }, } + arrayTaskRunMultipleArraysAndObject = &v1beta1.TaskRun{ + Spec: v1beta1.TaskRunSpec{ + Params: []v1beta1.Param{{ + Name: "array-param1", + Value: *v1beta1.NewArrayOrString("1-param1", "2-param1", "3-param1", "4-param1"), + }, { + Name: "array-param2", + Value: *v1beta1.NewArrayOrString("1-param2", "2-param2", "3-param3"), + }, { + Name: "myObject", + Value: *v1beta1.NewObject(map[string]string{ + "key1": "value1", + "key2": "value2", + }), + }}, + }, + } + inputs = map[string]v1beta1.PipelineResourceInterface{ "workspace": gitResource, } @@ -482,6 +713,26 @@ func TestApplyArrayParameters(t *testing.T) { spec.Steps[1].Command = []string{"cmd", "1-param1", "2-param1", "3-param1", "4-param1"} spec.Steps[1].Args = []string{"1-param2", "2-param2", "2-param3", "second", "1-param1", "2-param1", "3-param1", "4-param1", "foo", "last"} }), + }, { + name: "array and object parameter", + args: args{ + ts: arrayAndObjectParamTaskSpec, + tr: arrayTaskRunWith1ObjectParam, + }, + want: applyMutation(arrayAndObjectParamTaskSpec, func(spec *v1beta1.TaskSpec) { + spec.Steps[1].Args = []string{"object value1", "object value2", "middlefirst", "middlesecond", "last"} + }), + }, { + name: "several arrays and objects", + args: args{ + ts: multipleArrayAndObjectParamsTaskSpec, + tr: arrayTaskRunMultipleArraysAndObject, + }, + want: applyMutation(multipleArrayAndObjectParamsTaskSpec, func(spec *v1beta1.TaskSpec) { + spec.Steps[0].Image = "image-value1" + spec.Steps[1].Command = []string{"cmd", "1-param1", "2-param1", "3-param1", "4-param1"} + spec.Steps[1].Args = []string{"1-param2", "2-param2", "3-param3", "second", "1-param1", "2-param1", "3-param1", "4-param1", "value2", "last"} + }), }, { name: "default array parameter", args: args{ @@ -575,6 +826,75 @@ func TestApplyParameters(t *testing.T) { } } +func TestApplyObjectParameters(t *testing.T) { + tr := &v1beta1.TaskRun{ + Spec: v1beta1.TaskRunSpec{ + Params: []v1beta1.Param{{ + Name: "myObject", + Value: *v1beta1.NewObject(map[string]string{ + "myimage": "bar", + "FOO": "world", + }), + }}, + }, + } + dp := []v1beta1.ParamSpec{{ + Name: "something", + Default: v1beta1.NewArrayOrString("mydefault"), + }, { + 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" + }) + got := resources.ApplyParameters(simpleTaskSpecUsingObjectVar, tr, dp...) + if d := cmp.Diff(want, got); d != "" { + t.Errorf("ApplyParameters() got diff %s", diff.PrintWantGot(d)) + } +} + func TestApplyResources(t *testing.T) { tests := []struct { name string