Skip to content

Commit

Permalink
Handle Secrets in CreateSnapshot
Browse files Browse the repository at this point in the history
This PR adds handling for Secrets in CreateSnapshot.
  • Loading branch information
xing-yang committed Aug 10, 2018
1 parent 7fa276f commit dd820b5
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 8 deletions.
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 {
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{}

{
// 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 {
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) {
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
}

0 comments on commit dd820b5

Please sign in to comment.