Skip to content

Commit 4e834f8

Browse files
committed
Implement reading secrets from PV source
Add some tests for reading secrets
1 parent 705008a commit 4e834f8

File tree

4 files changed

+105
-109
lines changed

4 files changed

+105
-109
lines changed

deploy/kubernetes/rbac.yaml

-3
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ rules:
3636
- apiGroups: [""]
3737
resources: ["persistentvolumeclaims/status"]
3838
verbs: ["update", "patch"]
39-
- apiGroups: ["storage.k8s.io"]
40-
resources: ["storageclasses"]
41-
verbs: ["get", "list", "watch"]
4239
- apiGroups: [""]
4340
resources: ["events"]
4441
verbs: ["list", "watch", "create", "update", "patch"]

pkg/csi/mock_client.go

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type MockClient struct {
1919
supportsNodeResize bool
2020
supportsControllerResize bool
2121
supportsPluginControllerService bool
22+
usedSecrets map[string]string
2223
}
2324

2425
func (c *MockClient) GetDriverName(context.Context) (string, error) {
@@ -43,5 +44,11 @@ func (c *MockClient) Expand(
4344
requestBytes int64,
4445
secrets map[string]string) (int64, bool, error) {
4546
// TODO: Determine whether the operation succeeds or fails by parameters.
47+
c.usedSecrets = secrets
4648
return requestBytes, c.supportsNodeResize, nil
4749
}
50+
51+
// GetSecrets returns secrets used for volume expansion
52+
func (c *MockClient) GetSecrets() map[string]string {
53+
return c.usedSecrets
54+
}

pkg/resizer/csi_resizer.go

+4-106
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,18 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23-
"os"
2423
"time"
2524

2625
"github.com/kubernetes-csi/external-resizer/pkg/csi"
2726

2827
"k8s.io/api/core/v1"
2928
"k8s.io/apimachinery/pkg/api/resource"
3029
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31-
"k8s.io/apimachinery/pkg/util/sets"
32-
"k8s.io/apimachinery/pkg/util/validation"
3330
"k8s.io/client-go/informers"
3431
"k8s.io/client-go/kubernetes"
35-
storagev1listers "k8s.io/client-go/listers/storage/v1"
3632
"k8s.io/klog"
3733
)
3834

39-
const (
40-
resizerSecretNameKey = "csi.storage.k8s.io/resizer-secret-name"
41-
resizerSecretNamespaceKey = "csi.storage.k8s.io/resizer-secret-namespace"
42-
)
43-
4435
var (
4536
controllerServiceNotSupportErr = errors.New("CSI driver does not support controller service")
4637
resizeNotSupportErr = errors.New("CSI driver neither supports controller resize nor node resize")
@@ -101,7 +92,6 @@ func NewResizerFromClient(
10192
timeout: timeout,
10293

10394
k8sClient: k8sClient,
104-
scLister: informerFactory.Storage().V1().StorageClasses().Lister(),
10595
}, nil
10696
}
10797

@@ -111,7 +101,6 @@ type csiResizer struct {
111101
timeout time.Duration
112102

113103
k8sClient kubernetes.Interface
114-
scLister storagev1listers.StorageClassLister
115104
}
116105

117106
func (r *csiResizer) Name() string {
@@ -144,18 +133,10 @@ func (r *csiResizer) Resize(pv *v1.PersistentVolume, requestSize resource.Quanti
144133
}
145134

146135
var secrets map[string]string
147-
// Get expand secrets from StorageClass parameters.
148-
scName := pv.Spec.StorageClassName
149-
if len(scName) > 0 {
150-
storageClass, err := r.scLister.Get(scName)
151-
if err != nil {
152-
return oldSize, false, fmt.Errorf("get StorageClass %s failed: %v", scName, err)
153-
}
154-
expandSecretRef, err := getSecretReference(storageClass.Parameters, pv.Name)
155-
if err != nil {
156-
return oldSize, false, err
157-
}
158-
secrets, err = getCredentials(r.k8sClient, expandSecretRef)
136+
secreRef := source.ControllerExpandSecretRef
137+
if secreRef != nil {
138+
var err error
139+
secrets, err = getCredentials(r.k8sClient, secreRef)
159140
if err != nil {
160141
return oldSize, false, err
161142
}
@@ -198,89 +179,6 @@ func timeoutCtx(timeout time.Duration) (context.Context, context.CancelFunc) {
198179
return context.WithTimeout(context.Background(), timeout)
199180
}
200181

201-
// verifyAndGetSecretNameAndNamespaceTemplate gets the values (templates) associated
202-
// with the parameters specified in "secret" and verifies that they are specified correctly.
203-
func verifyAndGetSecretNameAndNamespaceTemplate(storageClassParams map[string]string) (string, string, error) {
204-
nameTemplate := storageClassParams[resizerSecretNameKey]
205-
namespaceTemplate := storageClassParams[resizerSecretNamespaceKey]
206-
207-
// Name and namespaces are both specified.
208-
if nameTemplate != "" && namespaceTemplate != "" {
209-
return nameTemplate, namespaceTemplate, nil
210-
}
211-
212-
// No secrets specified
213-
if nameTemplate == "" && namespaceTemplate == "" {
214-
return "", "", nil
215-
}
216-
217-
// Only one of the names and namespaces is set.
218-
return "", "", errors.New("resizer secrets specified in parameters but value of either namespace or name is empty")
219-
}
220-
221-
// getSecretReference returns a reference to the secret specified in the given nameTemplate
222-
// and namespaceTemplate, or an error if the templates are not specified correctly.
223-
// no lookup of the referenced secret is performed, and the secret may or may not exist.
224-
//
225-
// supported tokens for name resolution:
226-
// - ${pv.name}
227-
// - ${pvc.namespace}
228-
// - ${pvc.name}
229-
// - ${pvc.annotations['ANNOTATION_KEY']} (e.g. ${pvc.annotations['example.com/node-publish-secret-name']})
230-
//
231-
// supported tokens for namespace resolution:
232-
// - ${pv.name}
233-
// - ${pvc.namespace}
234-
//
235-
// an error is returned in the following situations:
236-
// - the nameTemplate or namespaceTemplate contains a token that cannot be resolved
237-
// - the resolved name is not a valid secret name
238-
// - the resolved namespace is not a valid namespace name
239-
func getSecretReference(storageClassParams map[string]string, pvName string) (*v1.SecretReference, error) {
240-
nameTemplate, namespaceTemplate, err := verifyAndGetSecretNameAndNamespaceTemplate(storageClassParams)
241-
if err != nil {
242-
return nil, fmt.Errorf("failed to get name and namespace template from params: %v", err)
243-
}
244-
if nameTemplate == "" && namespaceTemplate == "" {
245-
return nil, nil
246-
}
247-
248-
// Secret name and namespace template can make use of the PV name.
249-
// Note that neither of those things are under the control of the user.
250-
params := map[string]string{"pv.name": pvName}
251-
resolvedNamespace, err := resolveTemplate("namespace", namespaceTemplate, params)
252-
if err != nil {
253-
return nil, fmt.Errorf("error resolving secret namespace %q: %v", namespaceTemplate, err)
254-
}
255-
resolvedName, err := resolveTemplate("name", nameTemplate, params)
256-
if err != nil {
257-
return nil, fmt.Errorf("error resolving value %q: %v", nameTemplate, err)
258-
}
259-
260-
return &v1.SecretReference{Name: resolvedName, Namespace: resolvedNamespace}, nil
261-
}
262-
263-
func resolveTemplate(field, template string, params map[string]string) (string, error) {
264-
missingParams := sets.NewString()
265-
resolved := os.Expand(template, func(k string) string {
266-
v, ok := params[k]
267-
if !ok {
268-
missingParams.Insert(k)
269-
}
270-
return v
271-
})
272-
if missingParams.Len() > 0 {
273-
return "", fmt.Errorf("invalid tokens: %q", missingParams.List())
274-
}
275-
if len(validation.IsDNS1123Label(resolved)) > 0 {
276-
if template != resolved {
277-
return "", fmt.Errorf("%q resolved to %q which is not a valid %s name", template, resolved, field)
278-
}
279-
return "", fmt.Errorf("%q is not a valid %s name", template, field)
280-
}
281-
return resolved, nil
282-
}
283-
284182
func getCredentials(k8sClient kubernetes.Interface, ref *v1.SecretReference) (map[string]string, error) {
285183
if ref == nil {
286184
return nil, nil

pkg/resizer/csi_resizer_test.go

+94
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package resizer
22

33
import (
4+
"fmt"
45
"testing"
6+
"time"
57

68
"github.com/kubernetes-csi/external-resizer/pkg/csi"
9+
"k8s.io/api/core/v1"
10+
"k8s.io/apimachinery/pkg/api/resource"
11+
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
712
"k8s.io/client-go/informers"
813
"k8s.io/client-go/kubernetes"
914
"k8s.io/client-go/kubernetes/fake"
@@ -66,6 +71,95 @@ func TestNewResizer(t *testing.T) {
6671
}
6772
}
6873

74+
func TestResizeWithSecret(t *testing.T) {
75+
tests := []struct {
76+
name string
77+
hasExpansionSecret bool
78+
expectSecrets bool
79+
}{
80+
{
81+
name: "when CSI source has expansion secret",
82+
hasExpansionSecret: true,
83+
expectSecrets: true,
84+
},
85+
{
86+
name: "when CSI source has no secret",
87+
hasExpansionSecret: false,
88+
expectSecrets: false,
89+
},
90+
}
91+
for _, tc := range tests {
92+
client := csi.NewMockClient(true, true, true)
93+
secret := makeSecret("some-secret", "secret-namespace")
94+
k8sClient := fake.NewSimpleClientset(secret)
95+
pv := makeTestPV("test-csi", 2, "ebs-csi", "vol-abcde", tc.hasExpansionSecret)
96+
csiResizer := &csiResizer{
97+
name: "ebs-csi",
98+
client: client,
99+
timeout: 10 * time.Second,
100+
k8sClient: k8sClient,
101+
}
102+
_, _, err := csiResizer.Resize(pv, resource.MustParse("10Gi"))
103+
if err != nil {
104+
t.Errorf("unexpected error while expansion : %v", err)
105+
}
106+
usedSecrets := client.GetSecrets()
107+
if !tc.expectSecrets && len(usedSecrets) > 0 {
108+
t.Errorf("expected no secrets, got : %+v", usedSecrets)
109+
}
110+
111+
if tc.expectSecrets && len(usedSecrets) == 0 {
112+
t.Errorf("expected secrets got none")
113+
}
114+
}
115+
116+
}
117+
118+
func makeSecret(name string, namespace string) *v1.Secret {
119+
return &v1.Secret{
120+
ObjectMeta: meta.ObjectMeta{
121+
Name: name,
122+
Namespace: namespace,
123+
UID: "23456",
124+
ResourceVersion: "1",
125+
},
126+
Type: "Opaque",
127+
Data: map[string][]byte{
128+
"mykey": []byte("mydata"),
129+
},
130+
}
131+
}
132+
133+
func makeTestPV(name string, sizeGig int, driverName, volID string, withSecret bool) *v1.PersistentVolume {
134+
pv := &v1.PersistentVolume{
135+
ObjectMeta: meta.ObjectMeta{
136+
Name: name,
137+
},
138+
Spec: v1.PersistentVolumeSpec{
139+
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
140+
Capacity: v1.ResourceList{
141+
v1.ResourceName(v1.ResourceStorage): resource.MustParse(
142+
fmt.Sprintf("%dGi", sizeGig),
143+
),
144+
},
145+
PersistentVolumeSource: v1.PersistentVolumeSource{
146+
CSI: &v1.CSIPersistentVolumeSource{
147+
Driver: driverName,
148+
VolumeHandle: volID,
149+
ReadOnly: false,
150+
},
151+
},
152+
},
153+
}
154+
if withSecret {
155+
pv.Spec.CSI.ControllerExpandSecretRef = &v1.SecretReference{
156+
Name: "some-secret",
157+
Namespace: "secret-namespace",
158+
}
159+
}
160+
return pv
161+
}
162+
69163
func fakeK8s() (kubernetes.Interface, informers.SharedInformerFactory) {
70164
client := fake.NewSimpleClientset()
71165
informerFactory := informers.NewSharedInformerFactory(client, 0)

0 commit comments

Comments
 (0)