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

Add Secret volume source support to workspaces #1801

Merged
merged 1 commit into from Jan 16, 2020
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
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.
Copy link
Author

Choose a reason for hiding this comment

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

Removed as suggested in #1800 (comment)

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",
Copy link
Author

Choose a reason for hiding this comment

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

Added in response to #1800 (comment)

We could write a thing that tests every permutation but I'm not super compelled to do so with this PR.

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