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

Handle Secrets in CreateSnapshot #8

Merged
merged 1 commit into from
Aug 10, 2018
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
6 changes: 3 additions & 3 deletions pkg/connection/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ type CSIConnection interface {
SupportsControllerListSnapshots(ctx context.Context) (bool, error)

// CreateSnapshot creates a snapshot for a volume
CreateSnapshot(ctx context.Context, snapshotName string, snapshot *crdv1.VolumeSnapshot, volume *v1.PersistentVolume, parameters map[string]string) (driverName string, snapshotId string, timestamp int64, status *csi.SnapshotStatus, err error)
CreateSnapshot(ctx context.Context, snapshotName string, snapshot *crdv1.VolumeSnapshot, volume *v1.PersistentVolume, parameters map[string]string, snapshotterCredentials map[string]string) (driverName string, snapshotId string, timestamp int64, status *csi.SnapshotStatus, err error)

// DeleteSnapshot deletes a snapshot from a volume
DeleteSnapshot(ctx context.Context, snapshotID string) (err error)
Expand Down Expand Up @@ -189,7 +189,7 @@ func (c *csiConnection) SupportsControllerListSnapshots(ctx context.Context) (bo
return false, nil
}

func (c *csiConnection) CreateSnapshot(ctx context.Context, snapshotName string, snapshot *crdv1.VolumeSnapshot, volume *v1.PersistentVolume, parameters map[string]string) (string, string, int64, *csi.SnapshotStatus, error) {
func (c *csiConnection) CreateSnapshot(ctx context.Context, snapshotName string, snapshot *crdv1.VolumeSnapshot, volume *v1.PersistentVolume, parameters map[string]string, snapshotterCredentials map[string]string) (string, string, int64, *csi.SnapshotStatus, error) {
glog.V(5).Infof("CSI CreateSnapshot: %s", snapshot.Name)
if volume.Spec.CSI == nil {
return "", "", 0, nil, fmt.Errorf("CSIPersistentVolumeSource not defined in spec")
Expand All @@ -206,7 +206,7 @@ func (c *csiConnection) CreateSnapshot(ctx context.Context, snapshotName string,
SourceVolumeId: volume.Spec.CSI.VolumeHandle,
Name: snapshotName,
Parameters: parameters,
CreateSnapshotSecrets: nil,
CreateSnapshotSecrets: snapshotterCredentials,
}

rsp, err := client.CreateSnapshot(ctx, &req)
Expand Down
6 changes: 3 additions & 3 deletions pkg/controller/csi_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (

// Handler is responsible for handling VolumeSnapshot events from informer.
type Handler interface {
CreateSnapshot(snapshot *crdv1.VolumeSnapshot, volume *v1.PersistentVolume, parameters map[string]string) (string, string, int64, *csi.SnapshotStatus, error)
CreateSnapshot(snapshot *crdv1.VolumeSnapshot, volume *v1.PersistentVolume, parameters map[string]string, snapshotterCredentials map[string]string) (string, string, int64, *csi.SnapshotStatus, error)
DeleteSnapshot(content *crdv1.VolumeSnapshotContent) error
GetSnapshotStatus(content *crdv1.VolumeSnapshotContent) (*csi.SnapshotStatus, int64, error)
}
Expand All @@ -57,7 +57,7 @@ func NewCSIHandler(
}
}

func (handler *csiHandler) CreateSnapshot(snapshot *crdv1.VolumeSnapshot, volume *v1.PersistentVolume, parameters map[string]string) (string, string, int64, *csi.SnapshotStatus, error) {
func (handler *csiHandler) CreateSnapshot(snapshot *crdv1.VolumeSnapshot, volume *v1.PersistentVolume, parameters map[string]string, snapshotterCredentials map[string]string) (string, string, int64, *csi.SnapshotStatus, error) {

ctx, cancel := context.WithTimeout(context.Background(), handler.timeout)
defer cancel()
Expand All @@ -66,7 +66,7 @@ func (handler *csiHandler) CreateSnapshot(snapshot *crdv1.VolumeSnapshot, volume
if err != nil {
return "", "", 0, nil, err
}
return handler.csiConnection.CreateSnapshot(ctx, snapshotName, snapshot, volume, parameters)
return handler.csiConnection.CreateSnapshot(ctx, snapshotName, snapshot, volume, parameters, snapshotterCredentials)
}

func (handler *csiHandler) DeleteSnapshot(content *crdv1.VolumeSnapshotContent) error {
Expand Down
19 changes: 17 additions & 2 deletions pkg/controller/csi_snapshot_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ const pvcKind = "PersistentVolumeClaim"

const IsDefaultSnapshotClassAnnotation = "snapshot.storage.kubernetes.io/is-default-class"

const snapshotterSecretNameKey = "csiSnapshotterSecretName"
const snapshotterSecretNamespaceKey = "csiSnapshotterSecretNamespace"

// syncContent deals with one key off the queue. It returns false when it's time to quit.
func (ctrl *CSISnapshotController) syncContent(content *crdv1.VolumeSnapshotContent) error {
glog.V(4).Infof("synchronizing VolumeSnapshotContent[%s]", content.Name)
Expand Down Expand Up @@ -476,7 +479,20 @@ func (ctrl *CSISnapshotController) createSnapshotOperation(snapshot *crdv1.Volum
return nil, err
}

driverName, snapshotID, timestamp, csiSnapshotStatus, err := ctrl.handler.CreateSnapshot(snapshot, volume, class.Parameters)
// Create VolumeSnapshotContent name
contentName := GetSnapshotContentNameForSnapshot(snapshot)

// Resolve snapshotting secret credentials.
snapshotterSecretRef, err := GetSecretReference(snapshotterSecretNameKey, snapshotterSecretNamespaceKey, class.Parameters, contentName, nil)
if err != nil {
return nil, err
}
snapshotterCredentials, err := GetCredentials(ctrl.client, snapshotterSecretRef)
if err != nil {
return nil, err
}

driverName, snapshotID, timestamp, csiSnapshotStatus, err := ctrl.handler.CreateSnapshot(snapshot, volume, class.Parameters, snapshotterCredentials)
if err != nil {
return nil, fmt.Errorf("Failed to take snapshot of the volume, %s: %q", volume.Name, err)
}
Expand All @@ -489,7 +505,6 @@ func (ctrl *CSISnapshotController) createSnapshotOperation(snapshot *crdv1.Volum
}

// Create VolumeSnapshotContent in the database
contentName := GetSnapshotContentNameForSnapshot(snapshot)
volumeRef, err := ref.GetReference(scheme.Scheme, volume)

snapshotContent := &crdv1.VolumeSnapshotContent{
Expand Down
119 changes: 119 additions & 0 deletions pkg/controller/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ import (
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"os"
"strconv"
"strings"
)
Expand Down Expand Up @@ -121,3 +125,118 @@ func IsDefaultAnnotation(obj metav1.ObjectMeta) bool {

return false
}

// GetSecretReference returns a reference to the secret specified in the given nameKey and namespaceKey parameters, or an error if the parameters are not specified correctly.
// if neither the name or namespace parameter are set, a nil reference and no error is returned.
// no lookup of the referenced secret is performed, and the secret may or may not exist.
//
// supported tokens for name resolution:
// - ${volumesnapshotcontent.name}
// - ${volumesnapshot.namespace}
// - ${volumesnapshot.name}
// - ${volumesnapshot.annotations['ANNOTATION_KEY']} (e.g. ${pvc.annotations['example.com/snapshot-create-secret-name']})
//
// supported tokens for namespace resolution:
// - ${volumesnapshotcontent.name}
// - ${volumesnapshot.namespace}
//
// an error is returned in the following situations:
// - only one of name or namespace is provided
// - the name or namespace parameter contains a token that cannot be resolved
// - the resolved name is not a valid secret name
// - the resolved namespace is not a valid namespace name
func GetSecretReference(nameKey, namespaceKey string, snapshotClassParams map[string]string, snapContentName string, snapshot *crdv1.VolumeSnapshot) (*v1.SecretReference, error) {
nameTemplate, hasName := snapshotClassParams[nameKey]
namespaceTemplate, hasNamespace := snapshotClassParams[namespaceKey]

if !hasName && !hasNamespace {
Copy link
Collaborator

Choose a reason for hiding this comment

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

just use !hasName || !hasNamespace to return ?

return nil, nil
}

if len(nameTemplate) == 0 || len(namespaceTemplate) == 0 {
return nil, fmt.Errorf("%s and %s parameters must be specified together", nameKey, namespaceKey)
}

ref := &v1.SecretReference{}

{
Copy link
Collaborator

Choose a reason for hiding this comment

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

what is { for?

// Secret namespace template can make use of the VolumeSnapshotContent name or the VolumeSnapshot namespace.
// Note that neither of those things are under the control of the VolumeSnapshot user.
namespaceParams := map[string]string{"volumesnapshotcontent.name": snapContentName}
if snapshot != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

what if snapshot == nil?

namespaceParams["volumesnapshot.namespace"] = snapshot.Namespace
}

resolvedNamespace, err := resolveTemplate(namespaceTemplate, namespaceParams)
if err != nil {
return nil, fmt.Errorf("error resolving %s value %q: %v", namespaceKey, namespaceTemplate, err)
}
if len(validation.IsDNS1123Label(resolvedNamespace)) > 0 {
if namespaceTemplate != resolvedNamespace {
return nil, fmt.Errorf("%s parameter %q resolved to %q which is not a valid namespace name", namespaceKey, namespaceTemplate, resolvedNamespace)
}
return nil, fmt.Errorf("%s parameter %q is not a valid namespace name", namespaceKey, namespaceTemplate)
}
ref.Namespace = resolvedNamespace
}

{
// Secret name template can make use of the VolumeSnapshotContent name, VolumeSnapshot name or namespace,
// or a VolumeSnapshot annotation.
// Note that VolumeSnapshot name and annotations are under the VolumeSnapshot user's control.
nameParams := map[string]string{"volumesnapshotcontent.name": snapContentName}
if snapshot != nil {
nameParams["volumesnapshot.name"] = snapshot.Name
nameParams["volumesnapshot.namespace"] = snapshot.Namespace
for k, v := range snapshot.Annotations {
nameParams["volumesnapshot.annotations['"+k+"']"] = v
}
}
resolvedName, err := resolveTemplate(nameTemplate, nameParams)
if err != nil {
return nil, fmt.Errorf("error resolving %s value %q: %v", nameKey, nameTemplate, err)
}
if len(validation.IsDNS1123Subdomain(resolvedName)) > 0 {
if nameTemplate != resolvedName {
return nil, fmt.Errorf("%s parameter %q resolved to %q which is not a valid secret name", nameKey, nameTemplate, resolvedName)
}
return nil, fmt.Errorf("%s parameter %q is not a valid secret name", nameKey, nameTemplate)
}
ref.Name = resolvedName
}

glog.V(4).Infof("GetSecretReference validated Secret: %+v", ref)
return ref, nil
}

func resolveTemplate(template string, params map[string]string) (string, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

add comments?

missingParams := sets.NewString()
resolved := os.Expand(template, func(k string) string {
v, ok := params[k]
if !ok {
missingParams.Insert(k)
}
return v
})
if missingParams.Len() > 0 {
return "", fmt.Errorf("invalid tokens: %q", missingParams.List())
}
return resolved, nil
}

func GetCredentials(k8s kubernetes.Interface, ref *v1.SecretReference) (map[string]string, error) {
if ref == nil {
return nil, nil
}

secret, err := k8s.CoreV1().Secrets(ref.Namespace).Get(ref.Name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error getting secret %s in namespace %s: %v", ref.Name, ref.Namespace, err)
}

credentials := map[string]string{}
for key, value := range secret.Data {
credentials[key] = string(value)
}
return credentials, nil
}