Skip to content

Commit

Permalink
Add Secret volume source support to workspaces
Browse files Browse the repository at this point in the history
Fixes #1438

The final of the original feature requests related to workspaces
was to include support for Secrets as the source of a volume mounted
into Task containers.

This PR introduces support for Secrets as workspaces in a TaskRun
definition.
  • Loading branch information
Scott authored and tekton-robot committed Jan 16, 2020
1 parent 23a9554 commit 19a23d7
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 27 deletions.
18 changes: 18 additions & 0 deletions docs/taskruns.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,24 @@ workspaces:
name: my-configmap
```

A Secret can also be used as a workspace with the following caveats:

1. Secret volume sources are always mounted as read-only inside a task's
containers - tasks cannot write content to them and a step may error out
and fail the task if a write is attempted.
2. The Secret you want to use as a workspace must already exist prior
to the TaskRun being submitted.

To use a [`secret`](https://kubernetes.io/docs/concepts/storage/volumes/#secret)
as a `workspace`:

```yaml
workspaces:
- name: myworkspace
secret:
secretName: my-secret
```

_For a complete example see [workspace.yaml](../examples/taskruns/workspace.yaml)._

## Status
Expand Down
22 changes: 22 additions & 0 deletions examples/taskruns/workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ metadata:
data:
message: hello world
---
apiVersion: v1
kind: Secret
metadata:
name: my-secret
type: Opaque
stringData:
username: user
data:
message: aGVsbG8gc2VjcmV0
---
apiVersion: tekton.dev/v1alpha1
kind: TaskRun
metadata:
Expand All @@ -39,6 +49,9 @@ spec:
items:
- key: message
path: my-message.txt
- name: custom5
secret:
secretName: my-secret
taskSpec:
steps:
- name: write
Expand All @@ -62,10 +75,19 @@ spec:
- name: readconfigmap
image: ubuntu
script: cat $(workspaces.custom4.path)/my-message.txt | grep "hello world"
- name: readsecret
image: ubuntu
script: |
#!/usr/bin/env bash
set -xe
cat $(workspaces.custom5.path)/username | grep "user"
cat $(workspaces.custom5.path)/message | grep "hello secret"
workspaces:
- name: custom
- name: custom2
mountPath: /foo/bar/baz
- name: custom3
- name: custom4
mountPath: /baz/bar/quux
- name: custom5
mountPath: /my/secret/volume
1 change: 0 additions & 1 deletion pkg/apis/pipeline/v1alpha1/workspace_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,4 @@ import (
type WorkspaceDeclaration = v1alpha2.WorkspaceDeclaration

// WorkspaceBinding maps a Task's declared workspace to a Volume.
// Currently we only support PersistentVolumeClaims, EmptyDir and ConfigMap.
type WorkspaceBinding = v1alpha2.WorkspaceBinding
38 changes: 38 additions & 0 deletions pkg/apis/pipeline/v1alpha1/workspace_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ func TestWorkspaceBindingValidateValid(t *testing.T) {
},
},
},
}, {
name: "Valid secret",
binding: &WorkspaceBinding{
Name: "beth",
Secret: &corev1.SecretVolumeSource{
SecretName: "my-secret",
},
},
}} {
t.Run(tc.name, func(t *testing.T) {
if err := tc.binding.Validate(context.Background()); err != nil {
Expand All @@ -77,6 +85,30 @@ func TestWorkspaceBindingValidateInvalid(t *testing.T) {
ClaimName: "pool-party",
},
},
}, {
name: "Provided both emptydir and configmap",
binding: &WorkspaceBinding{
Name: "beth",
EmptyDir: &corev1.EmptyDirVolumeSource{},
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "foo-configmap",
},
},
},
}, {
name: "Provided both configmap and secret",
binding: &WorkspaceBinding{
Name: "beth",
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "my-configmap",
},
},
Secret: &corev1.SecretVolumeSource{
SecretName: "my-secret",
},
},
}, {
name: "Provided neither pvc nor emptydir",
binding: &WorkspaceBinding{
Expand All @@ -94,6 +126,12 @@ func TestWorkspaceBindingValidateInvalid(t *testing.T) {
Name: "beth",
ConfigMap: &corev1.ConfigMapVolumeSource{},
},
}, {
name: "Provide secret without a secretName",
binding: &WorkspaceBinding{
Name: "beth",
Secret: &corev1.SecretVolumeSource{},
},
}} {
t.Run(tc.name, func(t *testing.T) {
if err := tc.binding.Validate(context.Background()); err == nil {
Expand Down
4 changes: 3 additions & 1 deletion pkg/apis/pipeline/v1alpha2/workspace_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ func (w *WorkspaceDeclaration) GetMountPath() string {
}

// WorkspaceBinding maps a Task's declared workspace to a Volume.
// Currently we only support PersistentVolumeClaims and EmptyDir.
type WorkspaceBinding struct {
// Name is the name of the workspace populated by the volume.
Name string `json:"name"`
Expand All @@ -68,4 +67,7 @@ type WorkspaceBinding struct {
// ConfigMap represents a configMap that should populate this workspace.
// +optional
ConfigMap *corev1.ConfigMapVolumeSource `json:"configMap,omitempty"`
// Secret represents a secret that should populate this workspace.
// +optional
Secret *corev1.SecretVolumeSource `json:"secret,omitempty"`
}
9 changes: 9 additions & 0 deletions pkg/apis/pipeline/v1alpha2/workspace_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var allVolumeSourceFields []string = []string{
"workspace.persistentvolumeclaim",
"workspace.emptydir",
"workspace.configmap",
"workspace.secret",
}

// Validate looks at the Volume provided in wb and makes sure that it is valid.
Expand Down Expand Up @@ -59,6 +60,11 @@ func (b *WorkspaceBinding) Validate(ctx context.Context) *apis.FieldError {
return apis.ErrMissingField("workspace.configmap.name")
}

// For a Secret to work, you must provide the name of the Secret to use.
if b.Secret != nil && b.Secret.SecretName == "" {
return apis.ErrMissingField("workspace.secret.secretName")
}

return nil
}

Expand All @@ -75,5 +81,8 @@ func (b *WorkspaceBinding) numSources() int {
if b.ConfigMap != nil {
n++
}
if b.Secret != nil {
n++
}
return n
}
14 changes: 14 additions & 0 deletions pkg/apis/pipeline/v1alpha2/workspace_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ func TestWorkspaceBindingValidateValid(t *testing.T) {
},
},
},
}, {
name: "Valid secret",
binding: &WorkspaceBinding{
Name: "beth",
Secret: &corev1.SecretVolumeSource{
SecretName: "my-secret",
},
},
}} {
t.Run(tc.name, func(t *testing.T) {
if err := tc.binding.Validate(context.Background()); err != nil {
Expand Down Expand Up @@ -94,6 +102,12 @@ func TestWorkspaceBindingValidateInvalid(t *testing.T) {
Name: "beth",
ConfigMap: &corev1.ConfigMapVolumeSource{},
},
}, {
name: "Provide secret without a secretName",
binding: &WorkspaceBinding{
Name: "beth",
Secret: &corev1.SecretVolumeSource{},
},
}} {
t.Run(tc.name, func(t *testing.T) {
if err := tc.binding.Validate(context.Background()); err == nil {
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/pipeline/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 20 additions & 21 deletions pkg/workspace/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,23 @@ const (
volumeNameBase = "ws"
)

// GetVolumes will return a dictionary where the keys are the names fo the workspaces bound in
// nameVolumeMap is a map from a workspace's name to its Volume.
type nameVolumeMap map[string]corev1.Volume

// setVolumeSource assigns a volume to a workspace's name.
func (nvm nameVolumeMap) setVolumeSource(workspaceName string, volumeName string, source corev1.VolumeSource) {
nvm[workspaceName] = corev1.Volume{
Name: volumeName,
VolumeSource: source,
}
}

// GetVolumes will return a dictionary where the keys are the names of the workspaces bound in
// wb and the value is the Volume to use. If the same Volume is bound twice, the resulting volumes
// will both have the same name to prevent the same Volume from being attached to pod twice.
// will both have the same name to prevent the same Volume from being attached to a pod twice.
func GetVolumes(wb []v1alpha1.WorkspaceBinding) map[string]corev1.Volume {
pvcs := map[string]corev1.Volume{}
v := map[string]corev1.Volume{}
v := make(nameVolumeMap)
for _, w := range wb {
name := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix(volumeNameBase)
switch {
Expand All @@ -27,30 +38,18 @@ func GetVolumes(wb []v1alpha1.WorkspaceBinding) map[string]corev1.Volume {
v[w.Name] = vv
} else {
pvc := *w.PersistentVolumeClaim
v[w.Name] = corev1.Volume{
Name: name,
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &pvc,
},
}
v.setVolumeSource(w.Name, name, corev1.VolumeSource{PersistentVolumeClaim: &pvc})
pvcs[pvc.ClaimName] = v[w.Name]
}
case w.EmptyDir != nil:
ed := *w.EmptyDir
v[w.Name] = corev1.Volume{
Name: name,
VolumeSource: corev1.VolumeSource{
EmptyDir: &ed,
},
}
v.setVolumeSource(w.Name, name, corev1.VolumeSource{EmptyDir: &ed})
case w.ConfigMap != nil:
cm := *w.ConfigMap
v[w.Name] = corev1.Volume{
Name: name,
VolumeSource: corev1.VolumeSource{
ConfigMap: &cm,
},
}
v.setVolumeSource(w.Name, name, corev1.VolumeSource{ConfigMap: &cm})
case w.Secret != nil:
s := *w.Secret
v.setVolumeSource(w.Name, name, corev1.VolumeSource{Secret: &s})
}
}
return v
Expand Down
35 changes: 31 additions & 4 deletions pkg/workspace/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,33 @@ func TestGetVolumes(t *testing.T) {
},
},
},
}, {
name: "binding a single workspace with secret",
workspaces: []v1alpha1.WorkspaceBinding{{
Name: "custom",
Secret: &corev1.SecretVolumeSource{
SecretName: "foobarsecret",
Items: []corev1.KeyToPath{{
Key: "foobar",
Path: "foobar.txt",
}},
},
SubPath: "/foo/bar/baz",
}},
expectedVolumes: map[string]corev1.Volume{
"custom": {
Name: "ws-78c5n",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: "foobarsecret",
Items: []corev1.KeyToPath{{
Key: "foobar",
Path: "foobar.txt",
}},
},
},
},
},
}, {
name: "0 workspace bindings",
workspaces: []v1alpha1.WorkspaceBinding{},
Expand All @@ -105,15 +132,15 @@ func TestGetVolumes(t *testing.T) {
}},
expectedVolumes: map[string]corev1.Volume{
"custom": {
Name: "ws-78c5n",
Name: "ws-6nl7g",
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: "mypvc",
},
},
},
"even-more-custom": {
Name: "ws-6nl7g",
Name: "ws-j2tds",
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: "myotherpvc",
Expand All @@ -138,7 +165,7 @@ func TestGetVolumes(t *testing.T) {
}},
expectedVolumes: map[string]corev1.Volume{
"custom": {
Name: "ws-j2tds",
Name: "ws-vr6ds",
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: "mypvc",
Expand All @@ -147,7 +174,7 @@ func TestGetVolumes(t *testing.T) {
},
"custom2": {
// Since it is the same PVC source, it can't be added twice with two different names
Name: "ws-j2tds",
Name: "ws-vr6ds",
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: "mypvc",
Expand Down

0 comments on commit 19a23d7

Please sign in to comment.