Skip to content

Commit b7b5fa9

Browse files
authored
Merge pull request #69 from liggitt/node-publish-secret
Update provision and publish secret handling
2 parents 254aff7 + b4cd6df commit b7b5fa9

File tree

2 files changed

+286
-53
lines changed

2 files changed

+286
-53
lines changed

pkg/controller/controller.go

Lines changed: 162 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222
"math"
2323
"net"
24+
"os"
2425
"strings"
2526
"time"
2627

@@ -33,6 +34,8 @@ import (
3334

3435
"k8s.io/api/core/v1"
3536
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37+
"k8s.io/apimachinery/pkg/util/sets"
38+
"k8s.io/apimachinery/pkg/util/validation"
3639
"k8s.io/apimachinery/pkg/util/wait"
3740
"k8s.io/client-go/kubernetes"
3841
"k8s.io/client-go/rest"
@@ -46,8 +49,18 @@ import (
4649
)
4750

4851
const (
49-
secretNameKey = "csiProvisionerSecretName"
50-
secretNamespaceKey = "csiProvisionerSecretNamespace"
52+
provisionerSecretNameKey = "csiProvisionerSecretName"
53+
provisionerSecretNamespaceKey = "csiProvisionerSecretNamespace"
54+
55+
controllerPublishSecretNameKey = "csiControllerPublishSecretName"
56+
controllerPublishSecretNamespaceKey = "csiControllerPublishSecretNamespace"
57+
58+
nodeStageSecretNameKey = "csiNodeStageSecretName"
59+
nodeStageSecretNamespaceKey = "csiNodeStageSecretNamespace"
60+
61+
nodePublishSecretNameKey = "csiNodePublishSecretName"
62+
nodePublishSecretNamespaceKey = "csiNodePublishSecretNamespace"
63+
5164
// Defines parameters for ExponentialBackoff used for executing
5265
// CSI CreateVolume API call, it gives approx 4 minutes for the CSI
5366
// driver to complete a volume creation.
@@ -272,7 +285,7 @@ func (p *csiProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis
272285
return nil, err
273286
}
274287

275-
share, err := makeVolumeName(p.volumeNamePrefix, fmt.Sprintf("%s", options.PVC.ObjectMeta.UID), p.volumeNameUUIDLength)
288+
pvName, err := makeVolumeName(p.volumeNamePrefix, fmt.Sprintf("%s", options.PVC.ObjectMeta.UID), p.volumeNameUUIDLength)
276289
if err != nil {
277290
return nil, err
278291
}
@@ -283,7 +296,7 @@ func (p *csiProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis
283296
// Create a CSI CreateVolumeRequest and Response
284297
req := csi.CreateVolumeRequest{
285298

286-
Name: share,
299+
Name: pvName,
287300
Parameters: options.Parameters,
288301
VolumeCapabilities: []*csi.VolumeCapability{
289302
{
@@ -297,14 +310,30 @@ func (p *csiProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis
297310
}
298311
rep := &csi.CreateVolumeResponse{}
299312

300-
secret := v1.SecretReference{}
301-
if options.Parameters != nil {
302-
credentials, err := getCredentialsFromParameters(p.client, options.Parameters)
303-
if err != nil {
304-
return nil, err
305-
}
306-
req.ControllerCreateSecrets = credentials
307-
secret.Name, secret.Namespace, _ = getSecretAndNamespaceFromParameters(options.Parameters)
313+
// Resolve provision secret credentials.
314+
// No PVC is provided when resolving provision/delete secret names, since the PVC may or may not exist at delete time.
315+
provisionerSecretRef, err := getSecretReference(provisionerSecretNameKey, provisionerSecretNamespaceKey, options.Parameters, pvName, nil)
316+
if err != nil {
317+
return nil, err
318+
}
319+
provisionerCredentials, err := getCredentials(p.client, provisionerSecretRef)
320+
if err != nil {
321+
return nil, err
322+
}
323+
req.ControllerCreateSecrets = provisionerCredentials
324+
325+
// Resolve controller publish, node stage, node publish secret references
326+
controllerPublishSecretRef, err := getSecretReference(controllerPublishSecretNameKey, controllerPublishSecretNamespaceKey, options.Parameters, pvName, options.PVC)
327+
if err != nil {
328+
return nil, err
329+
}
330+
nodeStageSecretRef, err := getSecretReference(nodeStageSecretNameKey, nodeStageSecretNamespaceKey, options.Parameters, pvName, options.PVC)
331+
if err != nil {
332+
return nil, err
333+
}
334+
nodePublishSecretRef, err := getSecretReference(nodePublishSecretNameKey, nodePublishSecretNamespaceKey, options.Parameters, pvName, options.PVC)
335+
if err != nil {
336+
return nil, err
308337
}
309338

310339
opts := wait.Backoff{Duration: backoffDuration, Factor: backoffFactor, Steps: backoffSteps}
@@ -345,24 +374,18 @@ func (p *csiProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis
345374
delReq := &csi.DeleteVolumeRequest{
346375
VolumeId: rep.GetVolume().GetId(),
347376
}
348-
if options.Parameters != nil {
349-
credentials, err := getCredentialsFromParameters(p.client, options.Parameters)
350-
if err != nil {
351-
return nil, err
352-
}
353-
delReq.ControllerDeleteSecrets = credentials
354-
}
377+
delReq.ControllerDeleteSecrets = provisionerCredentials
355378
ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
356379
defer cancel()
357380
_, err := p.csiClient.DeleteVolume(ctx, delReq)
358381
if err != nil {
359-
capErr = fmt.Errorf("%v. Cleanup of volume %s failed, volume is orphaned: %v", capErr, share, err)
382+
capErr = fmt.Errorf("%v. Cleanup of volume %s failed, volume is orphaned: %v", capErr, pvName, err)
360383
}
361384
return nil, capErr
362385
}
363386
pv := &v1.PersistentVolume{
364387
ObjectMeta: metav1.ObjectMeta{
365-
Name: share,
388+
Name: pvName,
366389
},
367390
Spec: v1.PersistentVolumeSpec{
368391
PersistentVolumeReclaimPolicy: options.PersistentVolumeReclaimPolicy,
@@ -373,16 +396,17 @@ func (p *csiProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis
373396
// TODO wait for CSI VolumeSource API
374397
PersistentVolumeSource: v1.PersistentVolumeSource{
375398
CSI: &v1.CSIPersistentVolumeSource{
376-
Driver: driverName,
377-
VolumeHandle: p.volumeIdToHandle(rep.Volume.Id),
378-
VolumeAttributes: volumeAttributes,
399+
Driver: driverName,
400+
VolumeHandle: p.volumeIdToHandle(rep.Volume.Id),
401+
VolumeAttributes: volumeAttributes,
402+
ControllerPublishSecretRef: controllerPublishSecretRef,
403+
NodeStageSecretRef: nodeStageSecretRef,
404+
NodePublishSecretRef: nodePublishSecretRef,
379405
},
380406
},
381407
},
382408
}
383-
if len(secret.Name) != 0 {
384-
pv.Spec.PersistentVolumeSource.CSI.NodePublishSecretRef = &secret
385-
}
409+
386410
glog.Infof("successfully created PV %+v", pv.Spec.PersistentVolumeSource)
387411

388412
return pv, nil
@@ -406,7 +430,13 @@ func (p *csiProvisioner) Delete(volume *v1.PersistentVolume) error {
406430
storageClassName := volume.Spec.StorageClassName
407431
if len(storageClassName) != 0 {
408432
if storageClass, err := p.client.StorageV1().StorageClasses().Get(storageClassName, metav1.GetOptions{}); err == nil {
409-
credentials, err := getCredentialsFromParameters(p.client, storageClass.Parameters)
433+
// Resolve provision secret credentials.
434+
// No PVC is provided when resolving provision/delete secret names, since the PVC may or may not exist at delete time.
435+
provisionerSecretRef, err := getSecretReference(provisionerSecretNameKey, provisionerSecretNamespaceKey, storageClass.Parameters, volume.Name, nil)
436+
if err != nil {
437+
return err
438+
}
439+
credentials, err := getCredentials(p.client, provisionerSecretRef)
410440
if err != nil {
411441
return err
412442
}
@@ -422,24 +452,6 @@ func (p *csiProvisioner) Delete(volume *v1.PersistentVolume) error {
422452
return err
423453
}
424454

425-
func getSecretAndNamespaceFromParameters(parameters map[string]string) (string, string, bool) {
426-
if secretName, ok := parameters[secretNameKey]; ok {
427-
namespace := "default"
428-
if len(parameters[secretNamespaceKey]) != 0 {
429-
namespace = parameters[secretNamespaceKey]
430-
}
431-
return secretName, namespace, true
432-
}
433-
return "", "", false
434-
}
435-
436-
func getCredentialsFromParameters(k8s kubernetes.Interface, parameters map[string]string) (map[string]string, error) {
437-
if secretName, namespace, found := getSecretAndNamespaceFromParameters(parameters); found {
438-
return getCredentialsFromSecret(k8s, secretName, namespace)
439-
}
440-
return map[string]string{}, nil
441-
}
442-
443455
//TODO use a unique volume handle from and to Id
444456
func (p *csiProvisioner) volumeIdToHandle(id string) string {
445457
return id
@@ -449,19 +461,116 @@ func (p *csiProvisioner) volumeHandleToId(handle string) string {
449461
return handle
450462
}
451463

452-
func getCredentialsFromSecret(k8s kubernetes.Interface, secretName, nameSpace string) (map[string]string, error) {
453-
credentials := map[string]string{}
454-
if len(secretName) == 0 {
455-
return credentials, nil
464+
// 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.
465+
// if neither the name or namespace parameter are set, a nil reference and no error is returned.
466+
// no lookup of the referenced secret is performed, and the secret may or may not exist.
467+
//
468+
// supported tokens for name resolution:
469+
// - ${pv.name}
470+
// - ${pvc.namespace}
471+
// - ${pvc.name}
472+
// - ${pvc.annotations['ANNOTATION_KEY']} (e.g. ${pvc.annotations['example.com/node-publish-secret-name']})
473+
//
474+
// supported tokens for namespace resolution:
475+
// - ${pv.name}
476+
// - ${pvc.namespace}
477+
//
478+
// an error is returned in the following situations:
479+
// - only one of name or namespace is provided
480+
// - the name or namespace parameter contains a token that cannot be resolved
481+
// - the resolved name is not a valid secret name
482+
// - the resolved namespace is not a valid namespace name
483+
func getSecretReference(nameKey, namespaceKey string, storageClassParams map[string]string, pvName string, pvc *v1.PersistentVolumeClaim) (*v1.SecretReference, error) {
484+
nameTemplate, hasName := storageClassParams[nameKey]
485+
namespaceTemplate, hasNamespace := storageClassParams[namespaceKey]
486+
487+
if !hasName && !hasNamespace {
488+
return nil, nil
489+
}
490+
491+
if len(nameTemplate) == 0 || len(namespaceTemplate) == 0 {
492+
return nil, fmt.Errorf("%s and %s parameters must be specified together", nameKey, namespaceKey)
493+
}
494+
495+
ref := &v1.SecretReference{}
496+
497+
{
498+
// Secret namespace template can make use of the PV name or the PVC namespace.
499+
// Note that neither of those things are under the control of the PVC user.
500+
namespaceParams := map[string]string{"pv.name": pvName}
501+
if pvc != nil {
502+
namespaceParams["pvc.namespace"] = pvc.Namespace
503+
}
504+
505+
resolvedNamespace, err := resolveTemplate(namespaceTemplate, namespaceParams)
506+
if err != nil {
507+
return nil, fmt.Errorf("error resolving %s value %q: %v", namespaceKey, namespaceTemplate, err)
508+
}
509+
if len(validation.IsDNS1123Label(resolvedNamespace)) > 0 {
510+
if namespaceTemplate != resolvedNamespace {
511+
return nil, fmt.Errorf("%s parameter %q resolved to %q which is not a valid namespace name", namespaceKey, namespaceTemplate, resolvedNamespace)
512+
}
513+
return nil, fmt.Errorf("%s parameter %q is not a valid namespace name", namespaceKey, namespaceTemplate)
514+
}
515+
ref.Namespace = resolvedNamespace
516+
}
517+
518+
{
519+
// Secret name template can make use of the PV name, PVC name or namespace, or a PVC annotation.
520+
// Note that PVC name and annotations are under the PVC user's control.
521+
nameParams := map[string]string{"pv.name": pvName}
522+
if pvc != nil {
523+
nameParams["pvc.name"] = pvc.Name
524+
nameParams["pvc.namespace"] = pvc.Namespace
525+
for k, v := range pvc.Annotations {
526+
nameParams["pvc.annotations['"+k+"']"] = v
527+
}
528+
}
529+
resolvedName, err := resolveTemplate(nameTemplate, nameParams)
530+
if err != nil {
531+
return nil, fmt.Errorf("error resolving %s value %q: %v", nameKey, nameTemplate, err)
532+
}
533+
if len(validation.IsDNS1123Subdomain(resolvedName)) > 0 {
534+
if nameTemplate != resolvedName {
535+
return nil, fmt.Errorf("%s parameter %q resolved to %q which is not a valid secret name", nameKey, nameTemplate, resolvedName)
536+
}
537+
return nil, fmt.Errorf("%s parameter %q is not a valid secret name", nameKey, nameTemplate)
538+
}
539+
ref.Name = resolvedName
540+
}
541+
542+
return ref, nil
543+
}
544+
545+
func resolveTemplate(template string, params map[string]string) (string, error) {
546+
missingParams := sets.NewString()
547+
resolved := os.Expand(template, func(k string) string {
548+
v, ok := params[k]
549+
if !ok {
550+
missingParams.Insert(k)
551+
}
552+
return v
553+
})
554+
if missingParams.Len() > 0 {
555+
return "", fmt.Errorf("invalid tokens: %q", missingParams.List())
456556
}
457-
secret, err := k8s.CoreV1().Secrets(nameSpace).Get(secretName, metav1.GetOptions{})
557+
return resolved, nil
558+
}
559+
560+
func getCredentials(k8s kubernetes.Interface, ref *v1.SecretReference) (map[string]string, error) {
561+
if ref == nil {
562+
return nil, nil
563+
}
564+
565+
secret, err := k8s.CoreV1().Secrets(ref.Namespace).Get(ref.Name, metav1.GetOptions{})
458566
if err != nil {
459-
return credentials, fmt.Errorf("failed to find the secret %s in the namespace %s with error: %v\n", secretName, nameSpace, err)
567+
return nil, fmt.Errorf("error getting secret %s in namespace %s: %v", ref.Name, ref.Namespace, err)
460568
}
569+
570+
credentials := map[string]string{}
461571
for key, value := range secret.Data {
462572
credentials[key] = string(value)
463573
}
464-
465574
return credentials, nil
466575
}
467576

0 commit comments

Comments
 (0)