Skip to content

Commit e750511

Browse files
committed
Add secret and configmap support as workspaces
- Handle multiple types of workspaces - Handle Secret and ConfigMap volumes with workspaces - New `files` package to move all "on-the-fly" file creation in one place. Signed-off-by: Vincent Demeester <[email protected]>
1 parent ec8d10d commit e750511

File tree

11 files changed

+487
-45
lines changed

11 files changed

+487
-45
lines changed

cmd/tkn-local/run.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ func run(opts *runOption) error {
112112
"context": dir,
113113
"dockerfile": dir,
114114
},
115-
// FrontAttrs is handled after
116115
Session: attachable,
117116
// CacheExports: c.cfg.CacheExports,
118117
// CacheImports: c.cfg.CacheImports,
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# In this contrived example 3 different kinds of workspace volume are used to thread
2+
# data through a pipeline's tasks.
3+
# 1. A ConfigMap is used as source of recipe data.
4+
# 2. A Secret is used to store a password.
5+
# 3. A PVC is used to share data from one task to the next.
6+
#
7+
# The end result is a pipeline that first checks if the password is correct and, if so,
8+
# copies data out of a recipe store onto a shared volume. The recipe data is then read
9+
# by a subsequent task and printed to screen.
10+
apiVersion: v1
11+
kind: ConfigMap
12+
metadata:
13+
name: sensitive-recipe-storage
14+
data:
15+
brownies: |
16+
1. Heat oven to 325 degrees F
17+
2. Melt 1/2 cup butter w/ 1/2 cup cocoa, stirring smooth.
18+
3. Remove from heat, allow to cool for a few minutes.
19+
4. Transfer to bowl.
20+
5. Whisk in 2 eggs, one at a time.
21+
6. Stir in vanilla.
22+
7. Separately combine 1 cup sugar, 1/4 cup flour, 1 cup chopped
23+
walnuts and pinch of salt
24+
8. Combine mixtures.
25+
9. Bake in greased pan for 30 minutes. Watch carefully for
26+
appropriate level of gooeyness.
27+
---
28+
apiVersion: v1
29+
kind: Secret
30+
metadata:
31+
name: secret-password
32+
type: Opaque
33+
data:
34+
password: aHVudGVyMg==
35+
---
36+
apiVersion: v1
37+
kind: PersistentVolumeClaim
38+
metadata:
39+
name: shared-task-storage
40+
spec:
41+
resources:
42+
requests:
43+
storage: 16Mi
44+
volumeMode: Filesystem
45+
accessModes:
46+
- ReadWriteOnce
47+
---
48+
apiVersion: tekton.dev/v1beta1
49+
kind: Task
50+
metadata:
51+
name: fetch-secure-data
52+
spec:
53+
workspaces:
54+
- name: super-secret-password
55+
- name: secure-store
56+
- name: filedrop
57+
steps:
58+
- name: fetch-and-write
59+
image: ubuntu
60+
script: |
61+
if [ "hunter2" = "$(cat $(workspaces.super-secret-password.path)/password)" ]; then
62+
cp $(workspaces.secure-store.path)/recipe.txt $(workspaces.filedrop.path)
63+
else
64+
echo "wrong password!"
65+
exit 1
66+
fi
67+
---
68+
apiVersion: tekton.dev/v1beta1
69+
kind: Task
70+
metadata:
71+
name: print-data
72+
spec:
73+
workspaces:
74+
- name: storage
75+
readOnly: true
76+
params:
77+
- name: filename
78+
steps:
79+
- name: print-secrets
80+
image: ubuntu
81+
script: cat $(workspaces.storage.path)/$(params.filename)
82+
---
83+
apiVersion: tekton.dev/v1beta1
84+
kind: Pipeline
85+
metadata:
86+
name: fetch-and-print-recipe
87+
spec:
88+
workspaces:
89+
- name: password-vault
90+
- name: recipe-store
91+
- name: shared-data
92+
tasks:
93+
- name: fetch-the-recipe
94+
taskRef:
95+
name: fetch-secure-data
96+
workspaces:
97+
- name: super-secret-password
98+
workspace: password-vault
99+
- name: secure-store
100+
workspace: recipe-store
101+
- name: filedrop
102+
workspace: shared-data
103+
- name: print-the-recipe
104+
taskRef:
105+
name: print-data
106+
# Note: this is currently required to ensure order of write / read on PVC is correct.
107+
runAfter:
108+
- fetch-the-recipe
109+
params:
110+
- name: filename
111+
value: recipe.txt
112+
workspaces:
113+
- name: storage
114+
workspace: shared-data
115+
---
116+
apiVersion: tekton.dev/v1beta1
117+
kind: PipelineRun
118+
metadata:
119+
generateName: recipe-time-
120+
spec:
121+
pipelineRef:
122+
name: fetch-and-print-recipe
123+
workspaces:
124+
- name: password-vault
125+
secret:
126+
secretName: secret-password
127+
- name: recipe-store
128+
configMap:
129+
name: sensitive-recipe-storage
130+
items:
131+
- key: brownies
132+
path: recipe.txt
133+
- name: shared-data
134+
persistentVolumeClaim:
135+
claimName: shared-task-storage

pkg/tekton/files/config.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package files
2+
3+
import (
4+
"github.com/moby/buildkit/client/llb"
5+
"github.com/pkg/errors"
6+
corev1 "k8s.io/api/core/v1"
7+
)
8+
9+
func ConfigMap(configmap *corev1.ConfigMap, configmapSource *corev1.ConfigMapVolumeSource) (llb.State, error) {
10+
state := llb.Scratch().Dir("/")
11+
if len(configmapSource.Items) == 0 {
12+
for name, value := range configmap.Data {
13+
state = addConfigMap(state, configmap.Name, name, value)
14+
}
15+
} else {
16+
for _, item := range configmapSource.Items {
17+
value, ok := configmap.Data[item.Key]
18+
if !ok {
19+
return llb.State{}, errors.Errorf("key %s from configmap %s not found in context", item.Key, configmap.Name)
20+
}
21+
state = addConfigMap(state, configmap.Name, item.Path, value)
22+
}
23+
}
24+
return state, nil
25+
}
26+
27+
func addConfigMap(state llb.State, configmapName, name, value string) llb.State {
28+
return state.File(
29+
llb.Mkfile(name, 0755, []byte(value)),
30+
llb.WithCustomName("[tekton] configmap "+configmapName+"/"+name+": preparing file"),
31+
)
32+
}

pkg/tekton/files/config_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package files_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/vdemeester/buildkit-tekton/pkg/tekton/files"
7+
corev1 "k8s.io/api/core/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
)
10+
11+
func TestConfigMapMissingItem(t *testing.T) {
12+
configmap := &corev1.ConfigMap{
13+
Data: map[string]string{
14+
"configmap": "value",
15+
},
16+
}
17+
configmapSource := &corev1.ConfigMapVolumeSource{
18+
Items: []corev1.KeyToPath{{
19+
Key: "notfound",
20+
Path: "foo.txt",
21+
}},
22+
}
23+
_, err := files.ConfigMap(configmap, configmapSource)
24+
if err == nil {
25+
t.Fatalf("expected an error, got nothing")
26+
} else {
27+
}
28+
}
29+
30+
func TestConfigMap(t *testing.T) {
31+
tests := []struct {
32+
name string
33+
configmap *corev1.ConfigMap
34+
configmapSource *corev1.ConfigMapVolumeSource
35+
}{{
36+
name: "all-keys",
37+
configmap: &corev1.ConfigMap{
38+
ObjectMeta: metav1.ObjectMeta{Name: "myconfigmap"},
39+
Data: map[string]string{
40+
"configmap1": "value1",
41+
"configmap2": "value2",
42+
},
43+
},
44+
configmapSource: &corev1.ConfigMapVolumeSource{},
45+
}, {
46+
name: "with-items",
47+
configmap: &corev1.ConfigMap{
48+
Data: map[string]string{
49+
"configmap1": "value1",
50+
"configmap2": "value2",
51+
},
52+
},
53+
configmapSource: &corev1.ConfigMapVolumeSource{
54+
Items: []corev1.KeyToPath{{
55+
Key: "configmap1",
56+
Path: "foo.txt",
57+
}},
58+
},
59+
}}
60+
for _, tc := range tests {
61+
tc := tc
62+
t.Run(tc.name, func(t *testing.T) {
63+
_, err := files.ConfigMap(tc.configmap, tc.configmapSource)
64+
if err != nil {
65+
t.Fatal(err)
66+
}
67+
// FIXME(vdemeester) exercise this better, most likely using buildkit testutil (integration)
68+
})
69+
}
70+
}

pkg/tekton/files/script.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package files
2+
3+
import (
4+
"strings"
5+
6+
"github.com/moby/buildkit/client/llb"
7+
"github.com/tektoncd/pipeline/pkg/names"
8+
)
9+
10+
const (
11+
defaultScriptPreamble = "#!/bin/sh\nset -e\n"
12+
)
13+
14+
func Script(stepName, scriptName, script string) (string, llb.State) {
15+
// Check for a shebang, and add a default if it's not set.
16+
// The shebang must be the first non-empty line.
17+
cleaned := strings.TrimSpace(script)
18+
hasShebang := strings.HasPrefix(cleaned, "#!")
19+
20+
if !hasShebang {
21+
script = defaultScriptPreamble + script
22+
}
23+
filename := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix(scriptName)
24+
data := script
25+
scriptSt := llb.Scratch().Dir("/").File(
26+
llb.Mkfile(filename, 0755, []byte(data)),
27+
llb.WithCustomName("[tekton] "+stepName+": preparing script"),
28+
)
29+
return filename, scriptSt
30+
}

pkg/tekton/files/script_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package files_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/vdemeester/buildkit-tekton/pkg/tekton/files"
7+
)
8+
9+
func TestScript(t *testing.T) {
10+
tests := []struct {
11+
name, script, expected string
12+
}{{
13+
name: "no-shebang",
14+
script: `echo hello world
15+
cat foo`,
16+
expected: `#!/bin/sh
17+
set -e
18+
echo hello world
19+
cat foo`,
20+
}, {
21+
name: "with shebang",
22+
script: `#!/usr/bin/env bash
23+
echo foo`,
24+
expected: `#!/usr/bin/env bash
25+
echo foo`,
26+
}}
27+
for _, tc := range tests {
28+
tc := tc
29+
t.Run(tc.name, func(t *testing.T) {
30+
filename, state := files.Script("stepName", "scriptName", tc.script)
31+
// FIXME(vdemeester) exercise this better, most likely using buildkit testutil (integration)
32+
t.Logf("%s: %+v", filename, state)
33+
})
34+
}
35+
}

pkg/tekton/files/secret.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package files
2+
3+
import (
4+
"github.com/moby/buildkit/client/llb"
5+
"github.com/pkg/errors"
6+
corev1 "k8s.io/api/core/v1"
7+
)
8+
9+
func Secret(secret *corev1.Secret, secretSource *corev1.SecretVolumeSource) (llb.State, error) {
10+
state := llb.Scratch().Dir("/")
11+
if len(secretSource.Items) == 0 {
12+
for name, value := range secret.Data {
13+
state = addSecret(state, secret.Name, name, value)
14+
}
15+
} else {
16+
for _, item := range secretSource.Items {
17+
value, ok := secret.Data[item.Key]
18+
if !ok {
19+
return llb.State{}, errors.Errorf("key %s from secret %s not found in context", item.Key, secret.Name)
20+
}
21+
state = addSecret(state, secret.Name, item.Path, value)
22+
}
23+
}
24+
return state, nil
25+
}
26+
27+
func addSecret(state llb.State, secretName, name string, value []byte) llb.State {
28+
return state.File(
29+
llb.Mkfile(name, 0755, value),
30+
llb.WithCustomName("[tekton] secret "+secretName+"/"+name+": preparing file"),
31+
)
32+
}

0 commit comments

Comments
 (0)