• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubernetes-sigs / blob-csi-driver / 4994757254

16 May 2023 05:23PM UTC coverage: 80.672%. Remained the same
4994757254

Pull #939

github

GitHub
chore(deps): bump github.com/onsi/ginkgo/v2 from 2.9.2 to 2.9.5
Pull Request #939: chore(deps): bump github.com/onsi/ginkgo/v2 from 2.9.2 to 2.9.5

1824 of 2261 relevant lines covered (80.67%)

5.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.94
/pkg/blob/blob.go
1
/*
2
Copyright 2019 The Kubernetes Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package blob
18

19
import (
20
        "fmt"
21
        "os"
22
        "strings"
23
        "sync"
24
        "time"
25

26
        "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-09-01/storage"
27
        azstorage "github.com/Azure/azure-sdk-for-go/storage"
28
        az "github.com/Azure/go-autorest/autorest/azure"
29
        "github.com/container-storage-interface/spec/lib/go/csi"
30
        "github.com/pborman/uuid"
31
        "golang.org/x/net/context"
32

33
        v1 "k8s.io/api/core/v1"
34
        "k8s.io/apimachinery/pkg/api/errors"
35
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36
        "k8s.io/client-go/kubernetes"
37
        "k8s.io/klog/v2"
38
        k8sutil "k8s.io/kubernetes/pkg/volume/util"
39
        mount "k8s.io/mount-utils"
40
        utilexec "k8s.io/utils/exec"
41

42
        csicommon "sigs.k8s.io/blob-csi-driver/pkg/csi-common"
43
        "sigs.k8s.io/blob-csi-driver/pkg/util"
44
        azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
45
        azure "sigs.k8s.io/cloud-provider-azure/pkg/provider"
46
)
47

48
const (
49
        // DefaultDriverName holds the name of the csi-driver
50
        DefaultDriverName            = "blob.csi.azure.com"
51
        blobCSIDriverName            = "blob_csi_driver"
52
        separator                    = "#"
53
        volumeIDTemplate             = "%s#%s#%s#%s#%s#%s"
54
        secretNameTemplate           = "azure-storage-account-%s-secret"
55
        serverNameField              = "server"
56
        storageEndpointSuffixField   = "storageendpointsuffix"
57
        tagsField                    = "tags"
58
        matchTagsField               = "matchtags"
59
        protocolField                = "protocol"
60
        accountNameField             = "accountname"
61
        accountKeyField              = "accountkey"
62
        storageAccountField          = "storageaccount"
63
        storageAccountTypeField      = "storageaccounttype"
64
        skuNameField                 = "skuname"
65
        subscriptionIDField          = "subscriptionid"
66
        resourceGroupField           = "resourcegroup"
67
        locationField                = "location"
68
        secretNameField              = "secretname"
69
        secretNamespaceField         = "secretnamespace"
70
        containerNameField           = "containername"
71
        containerNamePrefixField     = "containernameprefix"
72
        storeAccountKeyField         = "storeaccountkey"
73
        isHnsEnabledField            = "ishnsenabled"
74
        softDeleteBlobsField         = "softdeleteblobs"
75
        softDeleteContainersField    = "softdeletecontainers"
76
        enableBlobVersioningField    = "enableblobversioning"
77
        getAccountKeyFromSecretField = "getaccountkeyfromsecret"
78
        keyVaultURLField             = "keyvaulturl"
79
        keyVaultSecretNameField      = "keyvaultsecretname"
80
        keyVaultSecretVersionField   = "keyvaultsecretversion"
81
        storageAccountNameField      = "storageaccountname"
82
        allowBlobPublicAccessField   = "allowblobpublicaccess"
83
        requireInfraEncryptionField  = "requireinfraencryption"
84
        ephemeralField               = "csi.storage.k8s.io/ephemeral"
85
        podNamespaceField            = "csi.storage.k8s.io/pod.namespace"
86
        mountOptionsField            = "mountoptions"
87
        falseValue                   = "false"
88
        trueValue                    = "true"
89
        defaultSecretAccountName     = "azurestorageaccountname"
90
        defaultSecretAccountKey      = "azurestorageaccountkey"
91
        accountSasTokenField         = "azurestorageaccountsastoken"
92
        msiSecretField               = "msisecret"
93
        storageSPNClientSecretField  = "azurestoragespnclientsecret"
94
        Fuse                         = "fuse"
95
        Fuse2                        = "fuse2"
96
        NFS                          = "nfs"
97
        vnetResourceGroupField       = "vnetresourcegroup"
98
        vnetNameField                = "vnetname"
99
        subnetNameField              = "subnetname"
100
        accessTierField              = "accesstier"
101
        networkEndpointTypeField     = "networkendpointtype"
102
        mountPermissionsField        = "mountpermissions"
103
        useDataPlaneAPIField         = "usedataplaneapi"
104

105
        // See https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
106
        containerNameMinLength = 3
107
        containerNameMaxLength = 63
108

109
        accountNotProvisioned                   = "StorageAccountIsNotProvisioned"
110
        tooManyRequests                         = "TooManyRequests"
111
        clientThrottled                         = "client throttled"
112
        containerBeingDeletedDataplaneAPIError  = "ContainerBeingDeleted"
113
        containerBeingDeletedManagementAPIError = "container is being deleted"
114
        statusCodeNotFound                      = "StatusCode=404"
115
        httpCodeNotFound                        = "HTTPStatusCode: 404"
116

117
        // containerMaxSize is the max size of the blob container. See https://docs.microsoft.com/en-us/azure/storage/blobs/scalability-targets#scale-targets-for-blob-storage
118
        containerMaxSize = 100 * util.TiB
119

120
        subnetTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s"
121

122
        defaultNamespace = "default"
123

124
        pvcNameKey           = "csi.storage.k8s.io/pvc/name"
125
        pvcNamespaceKey      = "csi.storage.k8s.io/pvc/namespace"
126
        pvNameKey            = "csi.storage.k8s.io/pv/name"
127
        pvcNameMetadata      = "${pvc.metadata.name}"
128
        pvcNamespaceMetadata = "${pvc.metadata.namespace}"
129
        pvNameMetadata       = "${pv.metadata.name}"
130

131
        VolumeID = "volumeid"
132

133
        defaultStorageEndPointSuffix = "core.windows.net"
134
)
135

136
var (
137
        supportedProtocolList = []string{Fuse, Fuse2, NFS}
138
        retriableErrors       = []string{accountNotProvisioned, tooManyRequests, statusCodeNotFound, containerBeingDeletedDataplaneAPIError, containerBeingDeletedManagementAPIError, clientThrottled}
139
)
140

141
// DriverOptions defines driver parameters specified in driver deployment
142
type DriverOptions struct {
143
        NodeID                                 string
144
        DriverName                             string
145
        CloudConfigSecretName                  string
146
        CloudConfigSecretNamespace             string
147
        CustomUserAgent                        string
148
        UserAgentSuffix                        string
149
        BlobfuseProxyEndpoint                  string
150
        EnableBlobfuseProxy                    bool
151
        BlobfuseProxyConnTimout                int
152
        EnableBlobMockMount                    bool
153
        AllowEmptyCloudConfig                  bool
154
        AllowInlineVolumeKeyAccessWithIdentity bool
155
        EnableGetVolumeStats                   bool
156
        AppendTimeStampInCacheDir              bool
157
        AppendMountErrorHelpLink               bool
158
        MountPermissions                       uint64
159
        KubeAPIQPS                             float64
160
        KubeAPIBurst                           int
161
}
162

163
// Driver implements all interfaces of CSI drivers
164
type Driver struct {
165
        csicommon.CSIDriver
166

167
        cloud                      *azure.Cloud
168
        cloudConfigSecretName      string
169
        cloudConfigSecretNamespace string
170
        customUserAgent            string
171
        userAgentSuffix            string
172
        blobfuseProxyEndpoint      string
173
        // enableBlobMockMount is only for testing, DO NOT set as true in non-testing scenario
174
        enableBlobMockMount                    bool
175
        enableBlobfuseProxy                    bool
176
        allowEmptyCloudConfig                  bool
177
        enableGetVolumeStats                   bool
178
        allowInlineVolumeKeyAccessWithIdentity bool
179
        appendTimeStampInCacheDir              bool
180
        appendMountErrorHelpLink               bool
181
        blobfuseProxyConnTimout                int
182
        mountPermissions                       uint64
183
        kubeAPIQPS                             float64
184
        kubeAPIBurst                           int
185
        mounter                                *mount.SafeFormatAndMount
186
        volLockMap                             *util.LockMap
187
        // A map storing all volumes with ongoing operations so that additional operations
188
        // for that same volume (as defined by VolumeID) return an Aborted error
189
        volumeLocks *volumeLocks
190
        // only for nfs feature
191
        subnetLockMap *util.LockMap
192
        // a map storing all volumes created by this driver <volumeName, accountName>
193
        volMap sync.Map
194
        // a timed cache storing all volumeIDs and storage accounts that are using data plane API
195
        dataPlaneAPIVolCache *azcache.TimedCache
196
        // a timed cache storing account search history (solve account list throttling issue)
197
        accountSearchCache *azcache.TimedCache
198
}
199

200
// NewDriver Creates a NewCSIDriver object. Assumes vendor version is equal to driver version &
201
// does not support optional driver plugin info manifest field. Refer to CSI spec for more details.
202
func NewDriver(options *DriverOptions) *Driver {
105✔
203
        d := Driver{
105✔
204
                volLockMap:                             util.NewLockMap(),
105✔
205
                subnetLockMap:                          util.NewLockMap(),
105✔
206
                volumeLocks:                            newVolumeLocks(),
105✔
207
                cloudConfigSecretName:                  options.CloudConfigSecretName,
105✔
208
                cloudConfigSecretNamespace:             options.CloudConfigSecretNamespace,
105✔
209
                customUserAgent:                        options.CustomUserAgent,
105✔
210
                userAgentSuffix:                        options.UserAgentSuffix,
105✔
211
                blobfuseProxyEndpoint:                  options.BlobfuseProxyEndpoint,
105✔
212
                enableBlobfuseProxy:                    options.EnableBlobfuseProxy,
105✔
213
                allowInlineVolumeKeyAccessWithIdentity: options.AllowInlineVolumeKeyAccessWithIdentity,
105✔
214
                blobfuseProxyConnTimout:                options.BlobfuseProxyConnTimout,
105✔
215
                enableBlobMockMount:                    options.EnableBlobMockMount,
105✔
216
                allowEmptyCloudConfig:                  options.AllowEmptyCloudConfig,
105✔
217
                enableGetVolumeStats:                   options.EnableGetVolumeStats,
105✔
218
                appendMountErrorHelpLink:               options.AppendMountErrorHelpLink,
105✔
219
                mountPermissions:                       options.MountPermissions,
105✔
220
                kubeAPIQPS:                             options.KubeAPIQPS,
105✔
221
                kubeAPIBurst:                           options.KubeAPIBurst,
105✔
222
        }
105✔
223
        d.Name = options.DriverName
105✔
224
        d.Version = driverVersion
105✔
225
        d.NodeID = options.NodeID
105✔
226

105✔
227
        var err error
105✔
228
        getter := func(key string) (interface{}, error) { return nil, nil }
108✔
229
        if d.accountSearchCache, err = azcache.NewTimedcache(time.Minute, getter); err != nil {
105✔
230
                klog.Fatalf("%v", err)
×
231
        }
×
232
        if d.dataPlaneAPIVolCache, err = azcache.NewTimedcache(10*time.Minute, getter); err != nil {
105✔
233
                klog.Fatalf("%v", err)
×
234
        }
×
235
        return &d
105✔
236
}
237

238
// Run driver initialization
239
func (d *Driver) Run(endpoint, kubeconfig string, testBool bool) {
2✔
240
        versionMeta, err := GetVersionYAML(d.Name)
2✔
241
        if err != nil {
2✔
242
                klog.Fatalf("%v", err)
×
243
        }
×
244
        klog.Infof("\nDRIVER INFORMATION:\n-------------------\n%s\n\nStreaming logs below:", versionMeta)
2✔
245

2✔
246
        userAgent := GetUserAgent(d.Name, d.customUserAgent, d.userAgentSuffix)
2✔
247
        klog.V(2).Infof("driver userAgent: %s", userAgent)
2✔
248
        d.cloud, err = getCloudProvider(kubeconfig, d.NodeID, d.cloudConfigSecretName, d.cloudConfigSecretNamespace, userAgent, d.allowEmptyCloudConfig, d.kubeAPIQPS, d.kubeAPIBurst)
2✔
249
        if err != nil {
2✔
250
                klog.Fatalf("failed to get Azure Cloud Provider, error: %v", err)
×
251
        }
×
252
        klog.V(2).Infof("cloud: %s, location: %s, rg: %s, VnetName: %s, VnetResourceGroup: %s, SubnetName: %s", d.cloud.Cloud, d.cloud.Location, d.cloud.ResourceGroup, d.cloud.VnetName, d.cloud.VnetResourceGroup, d.cloud.SubnetName)
2✔
253

2✔
254
        d.mounter = &mount.SafeFormatAndMount{
2✔
255
                Interface: mount.New(""),
2✔
256
                Exec:      utilexec.New(),
2✔
257
        }
2✔
258

2✔
259
        // Initialize default library driver
2✔
260
        d.AddControllerServiceCapabilities(
2✔
261
                []csi.ControllerServiceCapability_RPC_Type{
2✔
262
                        csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
2✔
263
                        //csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
2✔
264
                        //csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS,
2✔
265
                        csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
2✔
266
                        csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
2✔
267
                })
2✔
268
        d.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
2✔
269
                csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
2✔
270
                csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY,
2✔
271
                csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
2✔
272
                csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
2✔
273
                csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
2✔
274
                csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER,
2✔
275
                csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
2✔
276
        })
2✔
277

2✔
278
        nodeCap := []csi.NodeServiceCapability_RPC_Type{
2✔
279
                csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
2✔
280
                csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
2✔
281
        }
2✔
282
        if d.enableGetVolumeStats {
2✔
283
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_GET_VOLUME_STATS)
×
284
        }
×
285
        d.AddNodeServiceCapabilities(nodeCap)
2✔
286

2✔
287
        s := csicommon.NewNonBlockingGRPCServer()
2✔
288
        // Driver d act as IdentityServer, ControllerServer and NodeServer
2✔
289
        s.Start(endpoint, d, d, d, testBool)
2✔
290
        s.Wait()
2✔
291
}
292

293
// GetContainerInfo get container info according to volume id
294
// the format of VolumeId is: rg#accountName#containerName#uuid#secretNamespace#subsID
295
//
296
// e.g.
297
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#"
298
// output: rg, f5713de20cde511e8ba4900, containerName, "" , ""
299
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#"
300
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, ""
301
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#subsID"
302
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, subsID
303
func GetContainerInfo(id string) (string, string, string, string, string, error) {
32✔
304
        segments := strings.Split(id, separator)
32✔
305
        if len(segments) < 3 {
39✔
306
                return "", "", "", "", "", fmt.Errorf("error parsing volume id: %q, should at least contain two #", id)
7✔
307
        }
7✔
308
        var secretNamespace, subsID string
25✔
309
        if len(segments) > 4 {
31✔
310
                secretNamespace = segments[4]
6✔
311
        }
6✔
312
        if len(segments) > 5 {
28✔
313
                subsID = segments[5]
3✔
314
        }
3✔
315
        return segments[0], segments[1], segments[2], secretNamespace, subsID, nil
25✔
316
}
317

318
// A container name must be a valid DNS name, conforming to the following naming rules:
319
//  1. Container names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character.
320
//  2. Every dash (-) character must be immediately preceded and followed by a letter or number; consecutive dashes are not permitted in container names.
321
//  3. All letters in a container name must be lowercase.
322
//  4. Container names must be from 3 through 63 characters long.
323
//
324
// See https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
325
func getValidContainerName(volumeName, protocol string) string {
5✔
326
        containerName := strings.ToLower(volumeName)
5✔
327
        if len(containerName) > containerNameMaxLength {
6✔
328
                containerName = containerName[0:containerNameMaxLength]
1✔
329
        }
1✔
330
        if !checkContainerNameBeginAndEnd(containerName) || len(containerName) < containerNameMinLength {
5✔
331
                // now we set as 63 for maximum container name length
×
332
                // todo: get cluster name
×
333
                containerName = k8sutil.GenerateVolumeName(fmt.Sprintf("pvc-%s", protocol), uuid.NewUUID().String(), 63)
×
334
                klog.Warningf("requested volume name (%s) is invalid, regenerated as (%q)", volumeName, containerName)
×
335
        }
×
336
        return strings.Replace(containerName, "--", "-", -1)
5✔
337
}
338

339
func checkContainerNameBeginAndEnd(containerName string) bool {
11✔
340
        length := len(containerName)
11✔
341
        if (('a' <= containerName[0] && containerName[0] <= 'z') ||
11✔
342
                ('0' <= containerName[0] && containerName[0] <= '9')) &&
11✔
343
                (('a' <= containerName[length-1] && containerName[length-1] <= 'z') ||
11✔
344
                        ('0' <= containerName[length-1] && containerName[length-1] <= '9')) {
20✔
345
                return true
9✔
346
        }
9✔
347

348
        return false
2✔
349
}
350

351
// isSASToken checks if the key contains the patterns.
352
// SAS token format could refer to https://docs.microsoft.com/en-us/rest/api/eventhub/generate-sas-token
353
func isSASToken(key string) bool {
3✔
354
        return strings.HasPrefix(key, "?")
3✔
355
}
3✔
356

357
// GetAuthEnv return <accountName, containerName, authEnv, error>
358
func (d *Driver) GetAuthEnv(ctx context.Context, volumeID, protocol string, attrib, secrets map[string]string) (string, string, string, string, []string, error) {
9✔
359
        rgName, accountName, containerName, secretNamespace, _, err := GetContainerInfo(volumeID)
9✔
360
        if err != nil {
11✔
361
                // ignore volumeID parsing error
2✔
362
                klog.V(2).Infof("parsing volumeID(%s) return with error: %v", volumeID, err)
2✔
363
                err = nil
2✔
364
        }
2✔
365

366
        var (
9✔
367
                subsID                  string
9✔
368
                accountKey              string
9✔
369
                accountSasToken         string
9✔
370
                msiSecret               string
9✔
371
                storageSPNClientSecret  string
9✔
372
                secretName              string
9✔
373
                pvcNamespace            string
9✔
374
                keyVaultURL             string
9✔
375
                keyVaultSecretName      string
9✔
376
                keyVaultSecretVersion   string
9✔
377
                azureStorageAuthType    string
9✔
378
                authEnv                 []string
9✔
379
                getAccountKeyFromSecret bool
9✔
380
        )
9✔
381

9✔
382
        for k, v := range attrib {
34✔
383
                switch strings.ToLower(k) {
25✔
384
                case subscriptionIDField:
1✔
385
                        subsID = v
1✔
386
                case resourceGroupField:
×
387
                        rgName = v
×
388
                case containerNameField:
3✔
389
                        containerName = v
3✔
390
                case keyVaultURLField:
1✔
391
                        keyVaultURL = v
1✔
392
                case keyVaultSecretNameField:
1✔
393
                        keyVaultSecretName = v
1✔
394
                case keyVaultSecretVersionField:
1✔
395
                        keyVaultSecretVersion = v
1✔
396
                case storageAccountField:
2✔
397
                        accountName = v
2✔
398
                case storageAccountNameField: // for compatibility
1✔
399
                        accountName = v
1✔
400
                case secretNameField:
1✔
401
                        secretName = v
1✔
402
                case secretNamespaceField:
1✔
403
                        secretNamespace = v
1✔
404
                case pvcNamespaceKey:
1✔
405
                        pvcNamespace = v
1✔
406
                case getAccountKeyFromSecretField:
1✔
407
                        getAccountKeyFromSecret = strings.EqualFold(v, trueValue)
1✔
408
                case "azurestorageauthtype":
×
409
                        azureStorageAuthType = v
×
410
                        authEnv = append(authEnv, "AZURE_STORAGE_AUTH_TYPE="+v)
×
411
                case "azurestorageidentityclientid":
1✔
412
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_CLIENT_ID="+v)
1✔
413
                case "azurestorageidentityobjectid":
1✔
414
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_OBJECT_ID="+v)
1✔
415
                case "azurestorageidentityresourceid":
1✔
416
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_RESOURCE_ID="+v)
1✔
417
                case "msiendpoint":
1✔
418
                        authEnv = append(authEnv, "MSI_ENDPOINT="+v)
1✔
419
                case "azurestoragespnclientid":
1✔
420
                        authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+v)
1✔
421
                case "azurestoragespntenantid":
1✔
422
                        authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+v)
1✔
423
                case "azurestorageaadendpoint":
1✔
424
                        authEnv = append(authEnv, "AZURE_STORAGE_AAD_ENDPOINT="+v)
1✔
425
                }
426
        }
427
        klog.V(2).Infof("volumeID(%s) authEnv: %s", volumeID, authEnv)
9✔
428

9✔
429
        if protocol == NFS {
11✔
430
                // nfs protocol does not need account key, return directly
2✔
431
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
432
        }
2✔
433

434
        if secretNamespace == "" {
13✔
435
                if pvcNamespace == "" {
12✔
436
                        secretNamespace = defaultNamespace
6✔
437
                } else {
6✔
438
                        secretNamespace = pvcNamespace
×
439
                }
×
440
        }
441

442
        if rgName == "" {
8✔
443
                rgName = d.cloud.ResourceGroup
1✔
444
        }
1✔
445

446
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
447
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
448
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
449
        if keyVaultURL != "" {
8✔
450
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
1✔
451
                if err != nil {
2✔
452
                        return rgName, accountName, accountKey, containerName, authEnv, err
1✔
453
                }
1✔
454
                if isSASToken(key) {
×
455
                        accountSasToken = key
×
456
                } else {
×
457
                        accountKey = key
×
458
                }
×
459
        } else {
6✔
460
                if len(secrets) == 0 {
11✔
461
                        if secretName == "" && accountName != "" {
9✔
462
                                secretName = fmt.Sprintf(secretNameTemplate, accountName)
4✔
463
                        }
4✔
464
                        if secretName != "" {
10✔
465
                                // read from k8s secret first
5✔
466
                                var name string
5✔
467
                                name, accountKey, accountSasToken, msiSecret, storageSPNClientSecret, err = d.GetInfoFromSecret(ctx, secretName, secretNamespace)
5✔
468
                                if name != "" {
5✔
469
                                        accountName = name
×
470
                                }
×
471
                                if err != nil && strings.EqualFold(azureStorageAuthType, "msi") {
5✔
472
                                        klog.V(2).Infof("ignore error(%v) since secret is optional for auth type(%s)", err, azureStorageAuthType)
×
473
                                        err = nil
×
474
                                }
×
475
                                if err != nil && !getAccountKeyFromSecret && (azureStorageAuthType == "" || strings.EqualFold(azureStorageAuthType, "key")) {
10✔
476
                                        klog.V(2).Infof("get account(%s) key from secret(%s, %s) failed with error: %v, use cluster identity to get account key instead",
5✔
477
                                                accountName, secretNamespace, secretName, err)
5✔
478
                                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName)
5✔
479
                                        if err != nil {
7✔
480
                                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
2✔
481
                                        }
2✔
482
                                }
483
                        }
484
                } else {
1✔
485
                        for k, v := range secrets {
8✔
486
                                v = strings.TrimSpace(v)
7✔
487
                                switch strings.ToLower(k) {
7✔
488
                                case accountNameField:
1✔
489
                                        accountName = v
1✔
490
                                case defaultSecretAccountName: // for compatibility with built-in blobfuse plugin
1✔
491
                                        accountName = v
1✔
492
                                case accountKeyField:
1✔
493
                                        accountKey = v
1✔
494
                                case defaultSecretAccountKey: // for compatibility with built-in blobfuse plugin
1✔
495
                                        accountKey = v
1✔
496
                                case accountSasTokenField:
1✔
497
                                        accountSasToken = v
1✔
498
                                case msiSecretField:
1✔
499
                                        msiSecret = v
1✔
500
                                case storageSPNClientSecretField:
1✔
501
                                        storageSPNClientSecret = v
1✔
502
                                }
503
                        }
504
                }
505
        }
506

507
        if containerName == "" {
4✔
508
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
509
        }
×
510

511
        if accountKey != "" {
8✔
512
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
513
        }
4✔
514

515
        if accountSasToken != "" {
5✔
516
                klog.V(2).Infof("accountSasToken is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
517
                authEnv = append(authEnv, "AZURE_STORAGE_SAS_TOKEN="+accountSasToken)
1✔
518
        }
1✔
519

520
        if msiSecret != "" {
5✔
521
                klog.V(2).Infof("msiSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
522
                authEnv = append(authEnv, "MSI_SECRET="+msiSecret)
1✔
523
        }
1✔
524

525
        if storageSPNClientSecret != "" {
5✔
526
                klog.V(2).Infof("storageSPNClientSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
527
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_SECRET="+storageSPNClientSecret)
1✔
528
        }
1✔
529

530
        return rgName, accountName, accountKey, containerName, authEnv, err
4✔
531
}
532

533
// GetStorageAccountAndContainer get storage account and container info
534
// returns <accountName, accountKey, accountSasToken, containerName>
535
// only for e2e testing
536
func (d *Driver) GetStorageAccountAndContainer(ctx context.Context, volumeID string, attrib, secrets map[string]string) (string, string, string, string, error) {
2✔
537
        var (
2✔
538
                subsID                string
2✔
539
                accountName           string
2✔
540
                accountKey            string
2✔
541
                accountSasToken       string
2✔
542
                containerName         string
2✔
543
                keyVaultURL           string
2✔
544
                keyVaultSecretName    string
2✔
545
                keyVaultSecretVersion string
2✔
546
                err                   error
2✔
547
        )
2✔
548

2✔
549
        for k, v := range attrib {
6✔
550
                switch strings.ToLower(k) {
4✔
551
                case subscriptionIDField:
×
552
                        subsID = v
×
553
                case containerNameField:
1✔
554
                        containerName = v
1✔
555
                case keyVaultURLField:
×
556
                        keyVaultURL = v
×
557
                case keyVaultSecretNameField:
1✔
558
                        keyVaultSecretName = v
1✔
559
                case keyVaultSecretVersionField:
1✔
560
                        keyVaultSecretVersion = v
1✔
561
                case storageAccountField:
×
562
                        accountName = v
×
563
                case storageAccountNameField: // for compatibility
1✔
564
                        accountName = v
1✔
565
                }
566
        }
567

568
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
569
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
570
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
571
        if keyVaultURL != "" {
2✔
572
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
×
573
                if err != nil {
×
574
                        return "", "", "", "", err
×
575
                }
×
576
                if isSASToken(key) {
×
577
                        accountSasToken = key
×
578
                } else {
×
579
                        accountKey = key
×
580
                }
×
581
        } else {
2✔
582
                if len(secrets) == 0 {
4✔
583
                        var rgName string
2✔
584
                        rgName, accountName, containerName, _, _, err = GetContainerInfo(volumeID)
2✔
585
                        if err != nil {
2✔
586
                                return "", "", "", "", err
×
587
                        }
×
588

589
                        if rgName == "" {
2✔
590
                                rgName = d.cloud.ResourceGroup
×
591
                        }
×
592

593
                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName)
2✔
594
                        if err != nil {
3✔
595
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
596
                        }
1✔
597
                }
598
        }
599

600
        if containerName == "" {
1✔
601
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
602
        }
×
603

604
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
605
}
606

607
func IsCorruptedDir(dir string) bool {
4✔
608
        _, pathErr := mount.PathExists(dir)
4✔
609
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
610
}
4✔
611

612
func isRetriableError(err error) bool {
5✔
613
        if err != nil {
9✔
614
                for _, v := range retriableErrors {
19✔
615
                        if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(v)) {
18✔
616
                                return true
3✔
617
                        }
3✔
618
                }
619
        }
620
        return false
2✔
621
}
622

623
func isSupportedProtocol(protocol string) bool {
15✔
624
        if protocol == "" {
16✔
625
                return true
1✔
626
        }
1✔
627
        for _, v := range supportedProtocolList {
36✔
628
                if protocol == v {
34✔
629
                        return true
12✔
630
                }
12✔
631
        }
632
        return false
2✔
633
}
634

635
func isSupportedAccessTier(accessTier string) bool {
18✔
636
        if accessTier == "" {
29✔
637
                return true
11✔
638
        }
11✔
639
        for _, tier := range storage.PossibleAccessTierValues() {
25✔
640
                if accessTier == string(tier) {
21✔
641
                        return true
3✔
642
                }
3✔
643
        }
644
        return false
4✔
645
}
646

647
// container names can contain only lowercase letters, numbers, and hyphens,
648
// and must begin and end with a letter or a number
649
func isSupportedContainerNamePrefix(prefix string) bool {
17✔
650
        if prefix == "" {
26✔
651
                return true
9✔
652
        }
9✔
653
        if len(prefix) > 20 {
9✔
654
                return false
1✔
655
        }
1✔
656
        if prefix[0] == '-' {
8✔
657
                return false
1✔
658
        }
1✔
659
        for _, v := range prefix {
19✔
660
                if v != '-' && (v < '0' || v > '9') && (v < 'a' || v > 'z') {
17✔
661
                        return false
4✔
662
                }
4✔
663
        }
664
        return true
2✔
665
}
666

667
// get storage account from secrets map
668
func getStorageAccount(secrets map[string]string) (string, string, error) {
18✔
669
        if secrets == nil {
19✔
670
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
671
        }
1✔
672

673
        var accountName, accountKey string
17✔
674
        for k, v := range secrets {
53✔
675
                v = strings.TrimSpace(v)
36✔
676
                switch strings.ToLower(k) {
36✔
677
                case accountNameField:
7✔
678
                        accountName = v
7✔
679
                case defaultSecretAccountName: // for compatibility with built-in azurefile plugin
9✔
680
                        accountName = v
9✔
681
                case accountKeyField:
7✔
682
                        accountKey = v
7✔
683
                case defaultSecretAccountKey: // for compatibility with built-in azurefile plugin
9✔
684
                        accountKey = v
9✔
685
                }
686
        }
687

688
        if accountName == "" {
21✔
689
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field secrets(%v)", accountNameField, defaultSecretAccountName, secrets)
4✔
690
        }
4✔
691
        if accountKey == "" {
16✔
692
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets(%v)", accountKeyField, defaultSecretAccountKey, secrets)
3✔
693
        }
3✔
694

695
        accountName = strings.TrimSpace(accountName)
10✔
696
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
10✔
697
        return accountName, accountKey, nil
10✔
698
}
699

700
func getContainerReference(containerName string, secrets map[string]string, env az.Environment) (*azstorage.Container, error) {
9✔
701
        accountName, accountKey, rerr := getStorageAccount(secrets)
9✔
702
        if rerr != nil {
11✔
703
                return nil, rerr
2✔
704
        }
2✔
705
        client, err := azstorage.NewBasicClientOnSovereignCloud(accountName, accountKey, env)
7✔
706
        if err != nil {
13✔
707
                return nil, err
6✔
708
        }
6✔
709
        blobClient := client.GetBlobService()
1✔
710
        container := blobClient.GetContainerReference(containerName)
1✔
711
        if container == nil {
1✔
712
                return nil, fmt.Errorf("ContainerReference of %s is nil", containerName)
×
713
        }
×
714
        return container, nil
1✔
715
}
716

717
func setAzureCredentials(ctx context.Context, kubeClient kubernetes.Interface, accountName, accountKey, secretNamespace string) (string, error) {
6✔
718
        if kubeClient == nil {
8✔
719
                klog.Warningf("could not create secret: kubeClient is nil")
2✔
720
                return "", nil
2✔
721
        }
2✔
722
        if accountName == "" || accountKey == "" {
6✔
723
                return "", fmt.Errorf("the account info is not enough, accountName(%v), accountKey(%v)", accountName, accountKey)
2✔
724
        }
2✔
725
        secretName := fmt.Sprintf(secretNameTemplate, accountName)
2✔
726
        secret := &v1.Secret{
2✔
727
                ObjectMeta: metav1.ObjectMeta{
2✔
728
                        Namespace: secretNamespace,
2✔
729
                        Name:      secretName,
2✔
730
                },
2✔
731
                Data: map[string][]byte{
2✔
732
                        defaultSecretAccountName: []byte(accountName),
2✔
733
                        defaultSecretAccountKey:  []byte(accountKey),
2✔
734
                },
2✔
735
                Type: "Opaque",
2✔
736
        }
2✔
737
        _, err := kubeClient.CoreV1().Secrets(secretNamespace).Create(ctx, secret, metav1.CreateOptions{})
2✔
738
        if errors.IsAlreadyExists(err) {
3✔
739
                err = nil
1✔
740
        }
1✔
741
        if err != nil {
2✔
742
                return "", fmt.Errorf("couldn't create secret %w", err)
×
743
        }
×
744
        return secretName, err
2✔
745
}
746

747
// GetStorageAccesskey get Azure storage account key from
748
//  1. secrets (if not empty)
749
//  2. use k8s client identity to read from k8s secret
750
//  3. use cluster identity to get from storage account directly
751
func (d *Driver) GetStorageAccesskey(ctx context.Context, accountOptions *azure.AccountOptions, secrets map[string]string, secretName, secretNamespace string) (string, string, error) {
7✔
752
        if len(secrets) > 0 {
8✔
753
                return getStorageAccount(secrets)
1✔
754
        }
1✔
755

756
        // read from k8s secret first
757
        if secretName == "" {
10✔
758
                secretName = fmt.Sprintf(secretNameTemplate, accountOptions.Name)
4✔
759
        }
4✔
760
        _, accountKey, _, _, _, err := d.GetInfoFromSecret(ctx, secretName, secretNamespace) //nolint
6✔
761
        if err != nil {
10✔
762
                klog.V(2).Infof("could not get account(%s) key from secret(%s) namespace(%s), error: %v, use cluster identity to get account key instead", accountOptions.Name, secretName, secretNamespace, err)
4✔
763
                accountKey, err = d.cloud.GetStorageAccesskey(ctx, accountOptions.SubscriptionID, accountOptions.Name, accountOptions.ResourceGroup)
4✔
764
        }
4✔
765
        return accountOptions.Name, accountKey, err
6✔
766
}
767

768
// GetInfoFromSecret get info from k8s secret
769
// return <accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, error>
770
func (d *Driver) GetInfoFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, string, string, error) {
15✔
771
        if d.cloud.KubeClient == nil {
24✔
772
                return "", "", "", "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
9✔
773
        }
9✔
774

775
        secret, err := d.cloud.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
776
        if err != nil {
8✔
777
                return "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
778
        }
2✔
779

780
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
781
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
782
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
783
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
784
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
785

4✔
786
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
4✔
787
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, nil
4✔
788
}
789

790
// getSubnetResourceID get default subnet resource ID from cloud provider config
791
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
792
        subsID := d.cloud.SubscriptionID
6✔
793
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
794
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
795
        }
4✔
796

797
        if len(vnetResourceGroup) == 0 {
11✔
798
                vnetResourceGroup = d.cloud.ResourceGroup
5✔
799
                if len(d.cloud.VnetResourceGroup) > 0 {
8✔
800
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
801
                }
3✔
802
        }
803

804
        if len(vnetName) == 0 {
11✔
805
                vnetName = d.cloud.VnetName
5✔
806
        }
5✔
807

808
        if len(subnetName) == 0 {
11✔
809
                subnetName = d.cloud.SubnetName
5✔
810
        }
5✔
811
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
812
}
813

814
func (d *Driver) useDataPlaneAPI(volumeID, accountName string) bool {
4✔
815
        cache, err := d.dataPlaneAPIVolCache.Get(volumeID, azcache.CacheReadTypeDefault)
4✔
816
        if err != nil {
4✔
817
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
818
        }
×
819
        if cache != nil {
7✔
820
                return true
3✔
821
        }
3✔
822
        cache, err = d.dataPlaneAPIVolCache.Get(accountName, azcache.CacheReadTypeDefault)
1✔
823
        if err != nil {
1✔
824
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
825
        }
×
826
        if cache != nil {
1✔
827
                return true
×
828
        }
×
829
        return false
1✔
830
}
831

832
// appendDefaultMountOptions return mount options combined with mountOptions and defaultMountOptions
833
func appendDefaultMountOptions(mountOptions []string, tmpPath, containerName string) []string {
4✔
834
        var defaultMountOptions = map[string]string{
4✔
835
                "--pre-mount-validate": "true",
4✔
836
                "--use-https":          "true",
4✔
837
                "--tmp-path":           tmpPath,
4✔
838
                "--container-name":     containerName,
4✔
839
                // prevent billing charges on mounting
4✔
840
                "--cancel-list-on-mount-seconds": "10",
4✔
841
                // allow remounting using a non-empty tmp-path
4✔
842
                "--empty-dir-check": "false",
4✔
843
        }
4✔
844

4✔
845
        // stores the mount options already included in mountOptions
4✔
846
        included := make(map[string]bool)
4✔
847

4✔
848
        for _, mountOption := range mountOptions {
11✔
849
                for k := range defaultMountOptions {
49✔
850
                        if strings.HasPrefix(mountOption, k) {
46✔
851
                                included[k] = true
4✔
852
                        }
4✔
853
                }
854
        }
855

856
        allMountOptions := mountOptions
4✔
857

4✔
858
        for k, v := range defaultMountOptions {
28✔
859
                if _, isIncluded := included[k]; !isIncluded {
44✔
860
                        if v != "" {
40✔
861
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
20✔
862
                        } else {
20✔
863
                                allMountOptions = append(allMountOptions, k)
×
864
                        }
×
865
                }
866
        }
867

868
        return allMountOptions
4✔
869
}
870

871
// chmodIfPermissionMismatch only perform chmod when permission mismatches
872
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
3✔
873
        info, err := os.Lstat(targetPath)
3✔
874
        if err != nil {
4✔
875
                return err
1✔
876
        }
1✔
877
        perm := info.Mode() & os.ModePerm
2✔
878
        if perm != mode {
3✔
879
                klog.V(2).Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), mode)
1✔
880
                if err := os.Chmod(targetPath, mode); err != nil {
1✔
881
                        return err
×
882
                }
×
883
        } else {
1✔
884
                klog.V(2).Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
1✔
885
        }
1✔
886
        return nil
2✔
887
}
888

889
func createStorageAccountSecret(account, key string) map[string]string {
1✔
890
        secret := make(map[string]string)
1✔
891
        secret[defaultSecretAccountName] = account
1✔
892
        secret[defaultSecretAccountKey] = key
1✔
893
        return secret
1✔
894
}
1✔
895

896
// setKeyValueInMap set key/value pair in map
897
// key in the map is case insensitive, if key already exists, overwrite existing value
898
func setKeyValueInMap(m map[string]string, key, value string) {
6✔
899
        if m == nil {
7✔
900
                return
1✔
901
        }
1✔
902
        for k := range m {
16✔
903
                if strings.EqualFold(k, key) {
13✔
904
                        m[k] = value
2✔
905
                        return
2✔
906
                }
2✔
907
        }
908
        m[key] = value
3✔
909
}
910

911
// replaceWithMap replace key with value for str
912
func replaceWithMap(str string, m map[string]string) string {
11✔
913
        for k, v := range m {
16✔
914
                if k != "" {
9✔
915
                        str = strings.ReplaceAll(str, k, v)
4✔
916
                }
4✔
917
        }
918
        return str
11✔
919
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc