Skip to content

Commit

Permalink
TEP-0075: Implement object var replacement on task&taskrun level
Browse files Browse the repository at this point in the history
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.]
  • Loading branch information
chuangw6 authored and tekton-robot committed Jul 4, 2022
1 parent 0d1d87a commit 380dbd0
Show file tree
Hide file tree
Showing 2 changed files with 286 additions and 8 deletions.
29 changes: 21 additions & 8 deletions pkg/reconciler/taskrun/resources/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,29 +49,42 @@ func ApplyParameters(spec *v1beta1.TaskSpec, tr *v1beta1.TaskRun, defaults ...v1
"inputs.params.%s",
}

// reference pattern for object individual keys params.<object_param_name>.<key_name>
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.ParamTypeArray:
for _, pattern := range patterns {
stringReplacements[fmt.Sprintf(pattern, p.Name)] = p.Default.StringVal
arrayReplacements[fmt.Sprintf(pattern, p.Name)] = p.Default.ArrayVal
}
} else {
case v1beta1.ParamTypeObject:
for k, v := range p.Default.ObjectVal {
stringReplacements[fmt.Sprintf(objectIndividualVariablePattern, p.Name, k)] = v
}
default:
for _, pattern := range patterns {
arrayReplacements[fmt.Sprintf(pattern, p.Name)] = p.Default.ArrayVal
stringReplacements[fmt.Sprintf(pattern, p.Name)] = p.Default.StringVal
}
}
}
}
// 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.ParamTypeArray:
for _, pattern := range patterns {
stringReplacements[fmt.Sprintf(pattern, p.Name)] = p.Value.StringVal
arrayReplacements[fmt.Sprintf(pattern, p.Name)] = p.Value.ArrayVal
}
} else {
case v1beta1.ParamTypeObject:
for k, v := range p.Value.ObjectVal {
stringReplacements[fmt.Sprintf(objectIndividualVariablePattern, p.Name, k)] = v
}
default:
for _, pattern := range patterns {
arrayReplacements[fmt.Sprintf(pattern, p.Name)] = p.Value.ArrayVal
stringReplacements[fmt.Sprintf(pattern, p.Name)] = p.Value.StringVal
}
}
}
Expand Down
265 changes: 265 additions & 0 deletions pkg/reconciler/taskrun/resources/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,128 @@ var (
},
}

// a taskspec for testing object var in all places i.e. Sidecars, StepTemplate, Steps and Volumns
objectParamTaskSpec = &v1beta1.TaskSpec{
Sidecars: []v1beta1.Sidecar{{
Name: "foo",
Image: `$(params.myObject.key1)`,
Env: []corev1.EnvVar{{
Name: "foo",
Value: "$(params.myObject.key2)",
}},
}},
StepTemplate: &v1beta1.StepTemplate{
Image: "$(params.myObject.key1)",
Env: []corev1.EnvVar{{
Name: "template-var",
Value: `$(params.myObject.key2)`,
}},
},
Steps: []v1beta1.Step{{
Name: "foo",
Image: "$(params.myObject.key1)",
WorkingDir: "path/to/$(params.myObject.key2)",
Args: []string{"first $(params.myObject.key1)", "second $(params.myObject.key2)"},
VolumeMounts: []corev1.VolumeMount{{
Name: "$(params.myObject.key1)",
MountPath: "path/to/$(params.myObject.key2)",
SubPath: "sub/$(params.myObject.key2)/path",
}},
Env: []corev1.EnvVar{{
Name: "foo",
Value: "value-$(params.myObject.key1)",
}, {
Name: "bar",
ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: "config-$(params.myObject.key1)"},
Key: "config-key-$(params.myObject.key2)",
},
},
}, {
Name: "baz",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: "secret-$(params.myObject.key1)"},
Key: "secret-key-$(params.myObject.key2)",
},
},
}},
EnvFrom: []corev1.EnvFromSource{{
Prefix: "prefix-0-$(params.myObject.key1)",
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{Name: "config-$(params.myObject.key1)"},
},
}, {
Prefix: "prefix-1-$(params.myObject.key1)",
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{Name: "secret-$(params.myObject.key1)"},
},
}},
}},
Volumes: []corev1.Volume{{
Name: "$(params.myObject.key1)",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "$(params.myObject.key1)",
},
Items: []corev1.KeyToPath{{
Key: "$(params.myObject.key1)",
Path: "$(params.myObject.key2)",
}},
},
Secret: &corev1.SecretVolumeSource{
SecretName: "$(params.myObject.key1)",
Items: []corev1.KeyToPath{{
Key: "$(params.myObject.key1)",
Path: "$(params.myObject.key2)",
}},
},
},
}, {
Name: "some-pvc",
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: "$(params.myObject.key1)",
},
},
}, {
Name: "some-projected-volumes",
VolumeSource: corev1.VolumeSource{
Projected: &corev1.ProjectedVolumeSource{
Sources: []corev1.VolumeProjection{{
ConfigMap: &corev1.ConfigMapProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: "$(params.myObject.key1)",
},
},
Secret: &corev1.SecretProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: "$(params.myObject.key1)",
},
},
ServiceAccountToken: &corev1.ServiceAccountTokenProjection{
Audience: "$(params.myObject.key2)",
},
}},
},
},
}, {
Name: "some-csi",
VolumeSource: corev1.VolumeSource{
CSI: &corev1.CSIVolumeSource{
VolumeAttributes: map[string]string{
"secretProviderClass": "$(params.myObject.key1)",
},
NodePublishSecretRef: &corev1.LocalObjectReference{
Name: "$(params.myObject.key1)",
},
},
},
}},
}

gcsTaskSpec = &v1beta1.TaskSpec{
Steps: []v1beta1.Step{{
Name: "foobar",
Expand Down Expand Up @@ -261,6 +383,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",
Expand All @@ -285,6 +419,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{{
Expand Down Expand Up @@ -339,6 +485,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{{
Expand All @@ -357,6 +518,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,
}
Expand Down Expand Up @@ -482,6 +661,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{
Expand Down Expand Up @@ -575,6 +774,72 @@ func TestApplyParameters(t *testing.T) {
}
}

func TestApplyObjectParameters(t *testing.T) {
// define the taskrun to test values provided by taskrun can overwrite the values provided in spec's default
tr := &v1beta1.TaskRun{
Spec: v1beta1.TaskRunSpec{
Params: []v1beta1.Param{{
Name: "myObject",
Value: *v1beta1.NewObject(map[string]string{
"key1": "taskrun-value-for-key1",
"key2": "taskrun-value-for-key2",
}),
}},
},
}
dp := []v1beta1.ParamSpec{{
Name: "myObject",
Default: v1beta1.NewObject(map[string]string{
"key1": "default-value-for-key1",
"key2": "default-value-for-key2",
}),
}}

want := applyMutation(objectParamTaskSpec, func(spec *v1beta1.TaskSpec) {
spec.Sidecars[0].Image = "taskrun-value-for-key1"
spec.Sidecars[0].Env[0].Value = "taskrun-value-for-key2"

spec.StepTemplate.Image = "taskrun-value-for-key1"
spec.StepTemplate.Env[0].Value = "taskrun-value-for-key2"

spec.Steps[0].Image = "taskrun-value-for-key1"
spec.Steps[0].WorkingDir = "path/to/taskrun-value-for-key2"
spec.Steps[0].Args = []string{"first taskrun-value-for-key1", "second taskrun-value-for-key2"}

spec.Steps[0].VolumeMounts[0].Name = "taskrun-value-for-key1"
spec.Steps[0].VolumeMounts[0].SubPath = "sub/taskrun-value-for-key2/path"
spec.Steps[0].VolumeMounts[0].MountPath = "path/to/taskrun-value-for-key2"

spec.Steps[0].Env[0].Value = "value-taskrun-value-for-key1"
spec.Steps[0].Env[1].ValueFrom.ConfigMapKeyRef.LocalObjectReference.Name = "config-taskrun-value-for-key1"
spec.Steps[0].Env[1].ValueFrom.ConfigMapKeyRef.Key = "config-key-taskrun-value-for-key2"
spec.Steps[0].Env[2].ValueFrom.SecretKeyRef.LocalObjectReference.Name = "secret-taskrun-value-for-key1"
spec.Steps[0].Env[2].ValueFrom.SecretKeyRef.Key = "secret-key-taskrun-value-for-key2"
spec.Steps[0].EnvFrom[0].Prefix = "prefix-0-taskrun-value-for-key1"
spec.Steps[0].EnvFrom[0].ConfigMapRef.LocalObjectReference.Name = "config-taskrun-value-for-key1"
spec.Steps[0].EnvFrom[1].Prefix = "prefix-1-taskrun-value-for-key1"
spec.Steps[0].EnvFrom[1].SecretRef.LocalObjectReference.Name = "secret-taskrun-value-for-key1"

spec.Volumes[0].Name = "taskrun-value-for-key1"
spec.Volumes[0].VolumeSource.ConfigMap.LocalObjectReference.Name = "taskrun-value-for-key1"
spec.Volumes[0].VolumeSource.ConfigMap.Items[0].Key = "taskrun-value-for-key1"
spec.Volumes[0].VolumeSource.ConfigMap.Items[0].Path = "taskrun-value-for-key2"
spec.Volumes[0].VolumeSource.Secret.SecretName = "taskrun-value-for-key1"
spec.Volumes[0].VolumeSource.Secret.Items[0].Key = "taskrun-value-for-key1"
spec.Volumes[0].VolumeSource.Secret.Items[0].Path = "taskrun-value-for-key2"
spec.Volumes[1].VolumeSource.PersistentVolumeClaim.ClaimName = "taskrun-value-for-key1"
spec.Volumes[2].VolumeSource.Projected.Sources[0].ConfigMap.Name = "taskrun-value-for-key1"
spec.Volumes[2].VolumeSource.Projected.Sources[0].Secret.Name = "taskrun-value-for-key1"
spec.Volumes[2].VolumeSource.Projected.Sources[0].ServiceAccountToken.Audience = "taskrun-value-for-key2"
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...)
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
Expand Down

0 comments on commit 380dbd0

Please sign in to comment.