Handle Secrets in CreateSnapshot
This PR adds handling for Secrets in CreateSnapshot.
This commit is contained in:
@@ -47,7 +47,7 @@ type CSIConnection interface {
|
|||||||
SupportsControllerListSnapshots(ctx context.Context) (bool, error)
|
SupportsControllerListSnapshots(ctx context.Context) (bool, error)
|
||||||
|
|
||||||
// CreateSnapshot creates a snapshot for a volume
|
// 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 deletes a snapshot from a volume
|
||||||
DeleteSnapshot(ctx context.Context, snapshotID string) (err error)
|
DeleteSnapshot(ctx context.Context, snapshotID string) (err error)
|
||||||
@@ -189,7 +189,7 @@ func (c *csiConnection) SupportsControllerListSnapshots(ctx context.Context) (bo
|
|||||||
return false, nil
|
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)
|
glog.V(5).Infof("CSI CreateSnapshot: %s", snapshot.Name)
|
||||||
if volume.Spec.CSI == nil {
|
if volume.Spec.CSI == nil {
|
||||||
return "", "", 0, nil, fmt.Errorf("CSIPersistentVolumeSource not defined in spec")
|
return "", "", 0, nil, fmt.Errorf("CSIPersistentVolumeSource not defined in spec")
|
||||||
@@ -206,7 +206,7 @@ func (c *csiConnection) CreateSnapshot(ctx context.Context, snapshotName string,
|
|||||||
SourceVolumeId: volume.Spec.CSI.VolumeHandle,
|
SourceVolumeId: volume.Spec.CSI.VolumeHandle,
|
||||||
Name: snapshotName,
|
Name: snapshotName,
|
||||||
Parameters: parameters,
|
Parameters: parameters,
|
||||||
CreateSnapshotSecrets: nil,
|
CreateSnapshotSecrets: snapshotterCredentials,
|
||||||
}
|
}
|
||||||
|
|
||||||
rsp, err := client.CreateSnapshot(ctx, &req)
|
rsp, err := client.CreateSnapshot(ctx, &req)
|
||||||
|
@@ -30,7 +30,7 @@ import (
|
|||||||
|
|
||||||
// Handler is responsible for handling VolumeSnapshot events from informer.
|
// Handler is responsible for handling VolumeSnapshot events from informer.
|
||||||
type Handler interface {
|
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
|
DeleteSnapshot(content *crdv1.VolumeSnapshotContent) error
|
||||||
GetSnapshotStatus(content *crdv1.VolumeSnapshotContent) (*csi.SnapshotStatus, int64, error)
|
GetSnapshotStatus(content *crdv1.VolumeSnapshotContent) (*csi.SnapshotStatus, int64, error)
|
||||||
}
|
}
|
||||||
@@ -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)
|
ctx, cancel := context.WithTimeout(context.Background(), handler.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -66,7 +66,7 @@ func (handler *csiHandler) CreateSnapshot(snapshot *crdv1.VolumeSnapshot, volume
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", 0, nil, err
|
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 {
|
func (handler *csiHandler) DeleteSnapshot(content *crdv1.VolumeSnapshotContent) error {
|
||||||
|
@@ -77,6 +77,9 @@ const pvcKind = "PersistentVolumeClaim"
|
|||||||
|
|
||||||
const IsDefaultSnapshotClassAnnotation = "snapshot.storage.kubernetes.io/is-default-class"
|
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.
|
// 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 {
|
func (ctrl *CSISnapshotController) syncContent(content *crdv1.VolumeSnapshotContent) error {
|
||||||
glog.V(4).Infof("synchronizing VolumeSnapshotContent[%s]", content.Name)
|
glog.V(4).Infof("synchronizing VolumeSnapshotContent[%s]", content.Name)
|
||||||
@@ -476,7 +479,20 @@ func (ctrl *CSISnapshotController) createSnapshotOperation(snapshot *crdv1.Volum
|
|||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to take snapshot of the volume, %s: %q", volume.Name, err)
|
return nil, fmt.Errorf("Failed to take snapshot of the volume, %s: %q", volume.Name, err)
|
||||||
}
|
}
|
||||||
@@ -489,7 +505,6 @@ func (ctrl *CSISnapshotController) createSnapshotOperation(snapshot *crdv1.Volum
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create VolumeSnapshotContent in the database
|
// Create VolumeSnapshotContent in the database
|
||||||
contentName := GetSnapshotContentNameForSnapshot(snapshot)
|
|
||||||
volumeRef, err := ref.GetReference(scheme.Scheme, volume)
|
volumeRef, err := ref.GetReference(scheme.Scheme, volume)
|
||||||
|
|
||||||
snapshotContent := &crdv1.VolumeSnapshotContent{
|
snapshotContent := &crdv1.VolumeSnapshotContent{
|
||||||
|
@@ -23,7 +23,11 @@ import (
|
|||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
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"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -121,3 +125,118 @@ func IsDefaultAnnotation(obj metav1.ObjectMeta) bool {
|
|||||||
|
|
||||||
return false
|
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
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user