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

kubernetes-sigs / blob-csi-driver / 16064476868

04 Jul 2025 02:25AM UTC coverage: 78.36%. Remained the same
16064476868

Pull #2058

github

andyzhangx
chore: fix helm chart index on release-1.26
Pull Request #2058: [release-1.26] chore: fix helm chart index on release-1.26

2379 of 3036 relevant lines covered (78.36%)

7.65 hits per line

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

85.14
/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
        "context"
21
        "encoding/json"
22
        "errors"
23
        "flag"
24
        "fmt"
25
        "os"
26
        "strconv"
27
        "strings"
28
        "sync"
29
        "time"
30

31
        "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage"
32
        azstorage "github.com/Azure/azure-sdk-for-go/storage"
33
        "github.com/container-storage-interface/spec/lib/go/csi"
34
        grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
35
        "github.com/pborman/uuid"
36
        "google.golang.org/grpc"
37
        v1 "k8s.io/api/core/v1"
38
        apierror "k8s.io/apimachinery/pkg/api/errors"
39
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
40
        "k8s.io/client-go/kubernetes"
41
        "k8s.io/klog/v2"
42
        mount "k8s.io/mount-utils"
43
        utilexec "k8s.io/utils/exec"
44

45
        csicommon "sigs.k8s.io/blob-csi-driver/pkg/csi-common"
46
        "sigs.k8s.io/blob-csi-driver/pkg/util"
47
        "sigs.k8s.io/cloud-provider-azure/pkg/azclient"
48
        azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
49
        "sigs.k8s.io/cloud-provider-azure/pkg/provider/storage"
50
)
51

52
const (
53
        // DefaultDriverName holds the name of the csi-driver
54
        DefaultDriverName              = "blob.csi.azure.com"
55
        blobCSIDriverName              = "blob_csi_driver"
56
        separator                      = "#"
57
        volumeIDTemplate               = "%s#%s#%s#%s#%s#%s"
58
        secretNameTemplate             = "azure-storage-account-%s-secret"
59
        serverNameField                = "server"
60
        storageEndpointSuffixField     = "storageendpointsuffix"
61
        tagsField                      = "tags"
62
        matchTagsField                 = "matchtags"
63
        protocolField                  = "protocol"
64
        accountNameField               = "accountname"
65
        accountKeyField                = "accountkey"
66
        storageAccountField            = "storageaccount"
67
        storageAccountTypeField        = "storageaccounttype"
68
        skuNameField                   = "skuname"
69
        subscriptionIDField            = "subscriptionid"
70
        resourceGroupField             = "resourcegroup"
71
        locationField                  = "location"
72
        secretNameField                = "secretname"
73
        secretNamespaceField           = "secretnamespace"
74
        containerNameField             = "containername"
75
        containerNamePrefixField       = "containernameprefix"
76
        storeAccountKeyField           = "storeaccountkey"
77
        getLatestAccountKeyField       = "getlatestaccountkey"
78
        isHnsEnabledField              = "ishnsenabled"
79
        softDeleteBlobsField           = "softdeleteblobs"
80
        softDeleteContainersField      = "softdeletecontainers"
81
        enableBlobVersioningField      = "enableblobversioning"
82
        getAccountKeyFromSecretField   = "getaccountkeyfromsecret"
83
        storageSPNClientIDField        = "azurestoragespnclientid"
84
        storageSPNTenantIDField        = "azurestoragespntenantid"
85
        storageAuthTypeField           = "azurestorageauthtype"
86
        storageIdentityClientIDField   = "azurestorageidentityclientid"
87
        storageIdentityObjectIDField   = "azurestorageidentityobjectid"
88
        storageIdentityResourceIDField = "azurestorageidentityresourceid"
89
        msiEndpointField               = "msiendpoint"
90
        storageAADEndpointField        = "azurestorageaadendpoint"
91
        keyVaultURLField               = "keyvaulturl"
92
        keyVaultSecretNameField        = "keyvaultsecretname"
93
        keyVaultSecretVersionField     = "keyvaultsecretversion"
94
        storageAccountNameField        = "storageaccountname"
95
        allowBlobPublicAccessField     = "allowblobpublicaccess"
96
        allowSharedKeyAccessField      = "allowsharedkeyaccess"
97
        publicNetworkAccessField       = "publicnetworkaccess"
98
        requireInfraEncryptionField    = "requireinfraencryption"
99
        ephemeralField                 = "csi.storage.k8s.io/ephemeral"
100
        podNamespaceField              = "csi.storage.k8s.io/pod.namespace"
101
        serviceAccountTokenField       = "csi.storage.k8s.io/serviceAccount.tokens"
102
        clientIDField                  = "clientid"
103
        mountWithWITokenField          = "mountwithworkloadidentitytoken"
104
        tenantIDField                  = "tenantid"
105
        mountOptionsField              = "mountoptions"
106
        falseValue                     = "false"
107
        trueValue                      = "true"
108
        defaultSecretAccountName       = "azurestorageaccountname"
109
        defaultSecretAccountKey        = "azurestorageaccountkey"
110
        accountSasTokenField           = "azurestorageaccountsastoken"
111
        msiSecretField                 = "msisecret"
112
        storageSPNClientSecretField    = "azurestoragespnclientsecret"
113
        Fuse                           = "fuse"
114
        Fuse2                          = "fuse2"
115
        NFS                            = "nfs"
116
        AZNFS                          = "aznfs"
117
        NFSv3                          = "nfsv3"
118
        vnetResourceGroupField         = "vnetresourcegroup"
119
        vnetNameField                  = "vnetname"
120
        vnetLinkNameField              = "vnetlinkname"
121
        subnetNameField                = "subnetname"
122
        accessTierField                = "accesstier"
123
        networkEndpointTypeField       = "networkendpointtype"
124
        mountPermissionsField          = "mountpermissions"
125
        fsGroupChangePolicyField       = "fsgroupchangepolicy"
126
        useDataPlaneAPIField           = "usedataplaneapi"
127

128
        // See https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
129
        containerNameMinLength = 3
130
        containerNameMaxLength = 63
131

132
        accountNotProvisioned                   = "StorageAccountIsNotProvisioned"
133
        tooManyRequests                         = "TooManyRequests"
134
        clientThrottled                         = "client throttled"
135
        containerBeingDeletedDataplaneAPIError  = "ContainerBeingDeleted"
136
        containerBeingDeletedManagementAPIError = "container is being deleted"
137
        statusCodeNotFound                      = "StatusCode=404"
138
        httpCodeNotFound                        = "HTTPStatusCode: 404"
139

140
        // 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
141
        containerMaxSize = 100 * util.TiB
142

143
        subnetTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s"
144

145
        defaultNamespace = "default"
146

147
        pvcNameKey           = "csi.storage.k8s.io/pvc/name"
148
        pvcNamespaceKey      = "csi.storage.k8s.io/pvc/namespace"
149
        pvNameKey            = "csi.storage.k8s.io/pv/name"
150
        pvcNameMetadata      = "${pvc.metadata.name}"
151
        pvcNamespaceMetadata = "${pvc.metadata.namespace}"
152
        pvNameMetadata       = "${pv.metadata.name}"
153

154
        VolumeID = "volumeid"
155

156
        defaultStorageEndPointSuffix = "core.windows.net"
157

158
        FSGroupChangeNone = "None"
159
        // define tag value delimiter and default is comma
160
        tagValueDelimiterField = "tagvaluedelimiter"
161

162
        DefaultTokenAudience = "api://AzureADTokenExchange" //nolint:gosec // G101 ignore this!
163

164
)
165

166
var (
167
        supportedProtocolList            = []string{Fuse, Fuse2, NFS, AZNFS}
168
        retriableErrors                  = []string{accountNotProvisioned, tooManyRequests, statusCodeNotFound, containerBeingDeletedDataplaneAPIError, containerBeingDeletedManagementAPIError, clientThrottled}
169
        supportedFSGroupChangePolicyList = []string{FSGroupChangeNone, string(v1.FSGroupChangeAlways), string(v1.FSGroupChangeOnRootMismatch)}
170

171
        // azcopyCloneVolumeOptions used in volume cloning between different storage account and --check-length to false because volume data may be in changing state, copy volume is not same as current source volume,
172
        // set --s2s-preserve-access-tier=false to avoid BlobAccessTierNotSupportedForAccountType error in azcopy
173
        azcopyCloneVolumeOptions = []string{"--recursive", "--check-length=false", "--s2s-preserve-access-tier=false", "--log-level=ERROR"}
174
)
175

176
// DriverOptions defines driver parameters specified in driver deployment
177
type DriverOptions struct {
178
        NodeID                                 string
179
        DriverName                             string
180
        BlobfuseProxyEndpoint                  string
181
        EnableBlobfuseProxy                    bool
182
        BlobfuseProxyConnTimout                int
183
        EnableBlobMockMount                    bool
184
        AllowInlineVolumeKeyAccessWithIdentity bool
185
        EnableGetVolumeStats                   bool
186
        AppendTimeStampInCacheDir              bool
187
        AppendMountErrorHelpLink               bool
188
        MountPermissions                       uint64
189
        EnableAznfsMount                       bool
190
        VolStatsCacheExpireInMinutes           int
191
        SasTokenExpirationMinutes              int
192
        WaitForAzCopyTimeoutMinutes            int
193
        EnableVolumeMountGroup                 bool
194
        FSGroupChangePolicy                    string
195
}
196

197
func (option *DriverOptions) AddFlags() {
1✔
198
        flag.StringVar(&option.BlobfuseProxyEndpoint, "blobfuse-proxy-endpoint", "unix://tmp/blobfuse-proxy.sock", "blobfuse-proxy endpoint")
1✔
199
        flag.StringVar(&option.NodeID, "nodeid", "", "node id")
1✔
200
        flag.StringVar(&option.DriverName, "drivername", DefaultDriverName, "name of the driver")
1✔
201
        flag.BoolVar(&option.EnableBlobfuseProxy, "enable-blobfuse-proxy", false, "using blobfuse proxy for mounts")
1✔
202
        flag.IntVar(&option.BlobfuseProxyConnTimout, "blobfuse-proxy-connect-timeout", 5, "blobfuse proxy connection timeout(seconds)")
1✔
203
        flag.BoolVar(&option.EnableBlobMockMount, "enable-blob-mock-mount", false, "enable mock mount(only for testing)")
1✔
204
        flag.BoolVar(&option.EnableGetVolumeStats, "enable-get-volume-stats", false, "allow GET_VOLUME_STATS on agent node")
1✔
205
        flag.BoolVar(&option.AppendTimeStampInCacheDir, "append-timestamp-cache-dir", false, "append timestamp into cache directory on agent node")
1✔
206
        flag.Uint64Var(&option.MountPermissions, "mount-permissions", 0777, "mounted folder permissions")
1✔
207
        flag.BoolVar(&option.AllowInlineVolumeKeyAccessWithIdentity, "allow-inline-volume-key-access-with-idenitity", false, "allow accessing storage account key using cluster identity for inline volume")
1✔
208
        flag.BoolVar(&option.AppendMountErrorHelpLink, "append-mount-error-help-link", true, "Whether to include a link for help with mount errors when a mount error occurs.")
1✔
209
        flag.BoolVar(&option.EnableAznfsMount, "enable-aznfs-mount", false, "replace nfs mount with aznfs mount")
1✔
210
        flag.IntVar(&option.VolStatsCacheExpireInMinutes, "vol-stats-cache-expire-in-minutes", 10, "The cache expire time in minutes for volume stats cache")
1✔
211
        flag.IntVar(&option.SasTokenExpirationMinutes, "sas-token-expiration-minutes", 1440, "sas token expiration minutes during volume cloning")
1✔
212
        flag.IntVar(&option.WaitForAzCopyTimeoutMinutes, "wait-for-azcopy-timeout-minutes", 18, "timeout in minutes for waiting for azcopy to finish")
1✔
213
        flag.BoolVar(&option.EnableVolumeMountGroup, "enable-volume-mount-group", true, "indicates whether enabling VOLUME_MOUNT_GROUP")
1✔
214
        flag.StringVar(&option.FSGroupChangePolicy, "fsgroup-change-policy", "", "indicates how the volume's ownership will be changed by the driver, OnRootMismatch is the default value")
1✔
215
}
1✔
216

217
// Driver implements all interfaces of CSI drivers
218
type Driver struct {
219
        csicommon.CSIDriver
220
        // Embed UnimplementedXXXServer to ensure the driver returns Unimplemented for any
221
        // new RPC methods that might be introduced in future versions of the spec.
222
        csi.UnimplementedControllerServer
223
        csi.UnimplementedIdentityServer
224
        csi.UnimplementedNodeServer
225

226
        cloud                 *storage.AccountRepo
227
        clientFactory         azclient.ClientFactory
228
        networkClientFactory  azclient.ClientFactory
229
        KubeClient            kubernetes.Interface
230
        blobfuseProxyEndpoint string
231
        // enableBlobMockMount is only for testing, DO NOT set as true in non-testing scenario
232
        enableBlobMockMount                    bool
233
        enableBlobfuseProxy                    bool
234
        enableGetVolumeStats                   bool
235
        allowInlineVolumeKeyAccessWithIdentity bool
236
        appendTimeStampInCacheDir              bool
237
        appendMountErrorHelpLink               bool
238
        blobfuseProxyConnTimout                int
239
        mountPermissions                       uint64
240
        enableAznfsMount                       bool
241
        enableVolumeMountGroup                 bool
242
        fsGroupChangePolicy                    string
243
        mounter                                *mount.SafeFormatAndMount
244
        volLockMap                             *util.LockMap
245
        // A map storing all volumes with ongoing operations so that additional operations
246
        // for that same volume (as defined by VolumeID) return an Aborted error
247
        volumeLocks *volumeLocks
248
        // only for nfs feature
249
        subnetLockMap *util.LockMap
250
        // a map storing all volumes created by this driver <volumeName, accountName>
251
        volMap sync.Map
252
        // a timed cache storing all volumeIDs and storage accounts that are using data plane API
253
        dataPlaneAPIVolCache azcache.Resource
254
        // a timed cache storing account search history (solve account list throttling issue)
255
        accountSearchCache azcache.Resource
256
        // a timed cache storing volume stats <volumeID, volumeStats>
257
        volStatsCache azcache.Resource
258
        // a timed cache storing account which should use sastoken for azcopy based volume cloning
259
        azcopySasTokenCache azcache.Resource
260
        // a timed cache storing subnet operations
261
        subnetCache azcache.Resource
262
        // sas expiry time for azcopy in volume clone
263
        sasTokenExpirationMinutes int
264
        // timeout in minutes for waiting for azcopy to finish
265
        waitForAzCopyTimeoutMinutes int
266
        // azcopy for provide exec mock for ut
267
        azcopy *util.Azcopy
268
}
269

270
// NewDriver Creates a NewCSIDriver object. Assumes vendor version is equal to driver version &
271
// does not support optional driver plugin info manifest field. Refer to CSI spec for more details.
272
func NewDriver(options *DriverOptions, kubeClient kubernetes.Interface, cloud *storage.AccountRepo) *Driver {
139✔
273
        d := Driver{
139✔
274
                volLockMap:                             util.NewLockMap(),
139✔
275
                subnetLockMap:                          util.NewLockMap(),
139✔
276
                volumeLocks:                            newVolumeLocks(),
139✔
277
                blobfuseProxyEndpoint:                  options.BlobfuseProxyEndpoint,
139✔
278
                enableBlobfuseProxy:                    options.EnableBlobfuseProxy,
139✔
279
                allowInlineVolumeKeyAccessWithIdentity: options.AllowInlineVolumeKeyAccessWithIdentity,
139✔
280
                blobfuseProxyConnTimout:                options.BlobfuseProxyConnTimout,
139✔
281
                enableBlobMockMount:                    options.EnableBlobMockMount,
139✔
282
                enableGetVolumeStats:                   options.EnableGetVolumeStats,
139✔
283
                enableVolumeMountGroup:                 options.EnableVolumeMountGroup,
139✔
284
                appendMountErrorHelpLink:               options.AppendMountErrorHelpLink,
139✔
285
                mountPermissions:                       options.MountPermissions,
139✔
286
                enableAznfsMount:                       options.EnableAznfsMount,
139✔
287
                sasTokenExpirationMinutes:              options.SasTokenExpirationMinutes,
139✔
288
                waitForAzCopyTimeoutMinutes:            options.WaitForAzCopyTimeoutMinutes,
139✔
289
                fsGroupChangePolicy:                    options.FSGroupChangePolicy,
139✔
290
                azcopy:                                 &util.Azcopy{ExecCmd: &util.ExecCommand{}},
139✔
291
                KubeClient:                             kubeClient,
139✔
292
                cloud:                                  cloud,
139✔
293
        }
139✔
294
        d.Name = options.DriverName
139✔
295
        d.Version = driverVersion
139✔
296
        d.NodeID = options.NodeID
139✔
297
        if d.cloud != nil {
278✔
298
                d.clientFactory = d.cloud.ComputeClientFactory
139✔
299
                d.networkClientFactory = d.cloud.NetworkClientFactory
139✔
300
                if d.networkClientFactory == nil {
278✔
301
                        d.networkClientFactory = d.cloud.ComputeClientFactory
139✔
302
                }
139✔
303
        }
304

305
        var err error
139✔
306
        getter := func(_ context.Context, _ string) (interface{}, error) { return nil, nil }
162✔
307
        if d.accountSearchCache, err = azcache.NewTimedCache(time.Minute, getter, false); err != nil {
139✔
308
                klog.Fatalf("%v", err)
×
309
        }
×
310
        if d.dataPlaneAPIVolCache, err = azcache.NewTimedCache(24*30*time.Hour, getter, false); err != nil {
139✔
311
                klog.Fatalf("%v", err)
×
312
        }
×
313
        if d.azcopySasTokenCache, err = azcache.NewTimedCache(15*time.Minute, getter, false); err != nil {
139✔
314
                klog.Fatalf("%v", err)
×
315
        }
×
316

317
        if options.VolStatsCacheExpireInMinutes <= 0 {
278✔
318
                options.VolStatsCacheExpireInMinutes = 10 // default expire in 10 minutes
139✔
319
        }
139✔
320
        if d.volStatsCache, err = azcache.NewTimedCache(time.Duration(options.VolStatsCacheExpireInMinutes)*time.Minute, getter, false); err != nil {
139✔
321
                klog.Fatalf("%v", err)
×
322
        }
×
323
        if d.subnetCache, err = azcache.NewTimedCache(10*time.Minute, getter, false); err != nil {
139✔
324
                klog.Fatalf("%v", err)
×
325
        }
×
326

327
        d.mounter = &mount.SafeFormatAndMount{
139✔
328
                Interface: mount.New(""),
139✔
329
                Exec:      utilexec.New(),
139✔
330
        }
139✔
331

139✔
332
        // Initialize default library driver
139✔
333
        d.AddControllerServiceCapabilities(
139✔
334
                []csi.ControllerServiceCapability_RPC_Type{
139✔
335
                        csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
139✔
336
                        //csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
139✔
337
                        //csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS,
139✔
338
                        csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
139✔
339
                        csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
139✔
340
                        csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
139✔
341
                })
139✔
342
        d.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
139✔
343
                csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
139✔
344
                csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY,
139✔
345
                csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
139✔
346
                csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
139✔
347
                csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
139✔
348
                csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER,
139✔
349
                csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
139✔
350
        })
139✔
351

139✔
352
        nodeCap := []csi.NodeServiceCapability_RPC_Type{
139✔
353
                csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
139✔
354
                csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
139✔
355
        }
139✔
356
        if d.enableGetVolumeStats {
139✔
357
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_GET_VOLUME_STATS)
×
358
        }
×
359
        if d.enableVolumeMountGroup {
139✔
360
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_VOLUME_MOUNT_GROUP)
×
361
        }
×
362
        d.AddNodeServiceCapabilities(nodeCap)
139✔
363

139✔
364
        return &d
139✔
365
}
366

367
// Run driver initialization
368
func (d *Driver) Run(ctx context.Context, endpoint string) error {
2✔
369
        versionMeta, err := GetVersionYAML(d.Name)
2✔
370
        if err != nil {
2✔
371
                klog.Fatalf("%v", err)
×
372
        }
×
373
        klog.Infof("\nDRIVER INFORMATION:\n-------------------\n%s\n\nStreaming logs below:", versionMeta)
2✔
374
        opts := []grpc.ServerOption{
2✔
375
                grpc.ChainUnaryInterceptor(
2✔
376
                        grpcprom.NewServerMetrics().UnaryServerInterceptor(),
2✔
377
                        csicommon.LogGRPC,
2✔
378
                ),
2✔
379
        }
2✔
380
        s := grpc.NewServer(opts...)
2✔
381
        csi.RegisterIdentityServer(s, d)
2✔
382
        csi.RegisterControllerServer(s, d)
2✔
383
        csi.RegisterNodeServer(s, d)
2✔
384

2✔
385
        go func() {
4✔
386
                //graceful shutdown
2✔
387
                <-ctx.Done()
2✔
388
                s.GracefulStop()
2✔
389
        }()
2✔
390
        // Driver d act as IdentityServer, ControllerServer and NodeServer
391
        listener, err := csicommon.Listen(ctx, endpoint)
2✔
392
        if err != nil {
2✔
393
                klog.Fatalf("failed to listen to endpoint, error: %v", err)
×
394
        }
×
395
        err = s.Serve(listener)
2✔
396
        if errors.Is(err, grpc.ErrServerStopped) {
2✔
397
                klog.Infof("gRPC server stopped serving")
×
398
                return nil
×
399
        }
×
400
        return err
2✔
401
}
402

403
// GetContainerInfo get container info according to volume id
404
// the format of VolumeId is: rg#accountName#containerName#uuid#secretNamespace#subsID
405
//
406
// e.g.
407
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#"
408
// output: rg, f5713de20cde511e8ba4900, containerName, "" , ""
409
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#"
410
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, ""
411
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#subsID"
412
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, subsID
413
func GetContainerInfo(id string) (string, string, string, string, string, error) {
42✔
414
        segments := strings.Split(id, separator)
42✔
415
        if len(segments) < 3 {
52✔
416
                return "", "", "", "", "", fmt.Errorf("error parsing volume id: %q, should at least contain two #", id)
10✔
417
        }
10✔
418
        var secretNamespace, subsID string
32✔
419
        if len(segments) > 4 {
38✔
420
                secretNamespace = segments[4]
6✔
421
        }
6✔
422
        if len(segments) > 5 {
35✔
423
                subsID = segments[5]
3✔
424
        }
3✔
425
        return segments[0], segments[1], segments[2], secretNamespace, subsID, nil
32✔
426
}
427

428
// A container name must be a valid DNS name, conforming to the following naming rules:
429
//  1. Container names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character.
430
//  2. Every dash (-) character must be immediately preceded and followed by a letter or number; consecutive dashes are not permitted in container names.
431
//  3. All letters in a container name must be lowercase.
432
//  4. Container names must be from 3 through 63 characters long.
433
//
434
// See https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
435
func getValidContainerName(volumeName, protocol string) string {
6✔
436
        containerName := strings.ToLower(volumeName)
6✔
437
        if len(containerName) > containerNameMaxLength {
7✔
438
                containerName = containerName[0:containerNameMaxLength]
1✔
439
        }
1✔
440
        if !checkContainerNameBeginAndEnd(containerName) || len(containerName) < containerNameMinLength {
6✔
441
                // now we set as 63 for maximum container name length
×
442
                // todo: get cluster name
×
443
                containerName = generateVolumeName(fmt.Sprintf("pvc-%s", protocol), uuid.NewUUID().String(), 63)
×
444
                klog.Warningf("requested volume name (%s) is invalid, regenerated as (%q)", volumeName, containerName)
×
445
        }
×
446
        return strings.Replace(containerName, "--", "-", -1)
6✔
447
}
448

449
func checkContainerNameBeginAndEnd(containerName string) bool {
12✔
450
        length := len(containerName)
12✔
451
        if (('a' <= containerName[0] && containerName[0] <= 'z') ||
12✔
452
                ('0' <= containerName[0] && containerName[0] <= '9')) &&
12✔
453
                (('a' <= containerName[length-1] && containerName[length-1] <= 'z') ||
12✔
454
                        ('0' <= containerName[length-1] && containerName[length-1] <= '9')) {
22✔
455
                return true
10✔
456
        }
10✔
457

458
        return false
2✔
459
}
460

461
// isSASToken checks if the key contains the patterns.
462
// SAS token format could refer to https://docs.microsoft.com/en-us/rest/api/eventhub/generate-sas-token
463
func isSASToken(key string) bool {
3✔
464
        return strings.HasPrefix(key, "?")
3✔
465
}
3✔
466

467
// GetAuthEnv return <accountName, containerName, authEnv, error>
468
func (d *Driver) GetAuthEnv(ctx context.Context, volumeID, protocol string, attrib, secrets map[string]string) (string, string, string, string, []string, error) {
11✔
469
        rgName, accountName, containerName, secretNamespace, _, err := GetContainerInfo(volumeID)
11✔
470
        if err != nil {
13✔
471
                // ignore volumeID parsing error
2✔
472
                klog.V(2).Infof("parsing volumeID(%s) return with error: %v", volumeID, err)
2✔
473
                err = nil
2✔
474
        }
2✔
475

476
        var (
11✔
477
                subsID                  string
11✔
478
                accountKey              string
11✔
479
                accountSasToken         string
11✔
480
                msiSecret               string
11✔
481
                storageSPNClientSecret  string
11✔
482
                storageSPNClientID      string
11✔
483
                storageSPNTenantID      string
11✔
484
                secretName              string
11✔
485
                pvcNamespace            string
11✔
486
                keyVaultURL             string
11✔
487
                keyVaultSecretName      string
11✔
488
                keyVaultSecretVersion   string
11✔
489
                azureStorageAuthType    string
11✔
490
                authEnv                 []string
11✔
491
                getAccountKeyFromSecret bool
11✔
492
                getLatestAccountKey     bool
11✔
493
                clientID                string
11✔
494
                mountWithWIToken        bool
11✔
495
                tenantID                string
11✔
496
                serviceAccountToken     string
11✔
497
        )
11✔
498

11✔
499
        for k, v := range attrib {
53✔
500
                switch strings.ToLower(k) {
42✔
501
                case subscriptionIDField:
2✔
502
                        subsID = v
2✔
503
                case resourceGroupField:
1✔
504
                        rgName = v
1✔
505
                case containerNameField:
4✔
506
                        containerName = v
4✔
507
                case keyVaultURLField:
1✔
508
                        keyVaultURL = v
1✔
509
                case keyVaultSecretNameField:
1✔
510
                        keyVaultSecretName = v
1✔
511
                case keyVaultSecretVersionField:
1✔
512
                        keyVaultSecretVersion = v
1✔
513
                case storageAccountField:
3✔
514
                        accountName = v
3✔
515
                case storageAccountNameField: // for compatibility
2✔
516
                        accountName = v
2✔
517
                case secretNameField:
2✔
518
                        secretName = v
2✔
519
                case secretNamespaceField:
2✔
520
                        secretNamespace = v
2✔
521
                case pvcNamespaceKey:
2✔
522
                        pvcNamespace = v
2✔
523
                case getAccountKeyFromSecretField:
2✔
524
                        getAccountKeyFromSecret = strings.EqualFold(v, trueValue)
2✔
525
                case storageAuthTypeField:
1✔
526
                        azureStorageAuthType = v
1✔
527
                        authEnv = append(authEnv, "AZURE_STORAGE_AUTH_TYPE="+v)
1✔
528
                case storageIdentityClientIDField:
1✔
529
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_CLIENT_ID="+v)
1✔
530
                case storageIdentityObjectIDField:
1✔
531
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_OBJECT_ID="+v)
1✔
532
                case storageIdentityResourceIDField:
1✔
533
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_RESOURCE_ID="+v)
1✔
534
                case msiEndpointField:
2✔
535
                        authEnv = append(authEnv, "MSI_ENDPOINT="+v)
2✔
536
                case storageSPNClientIDField:
1✔
537
                        storageSPNClientID = v
1✔
538
                case storageSPNTenantIDField:
1✔
539
                        storageSPNTenantID = v
1✔
540
                case storageAADEndpointField:
1✔
541
                        authEnv = append(authEnv, "AZURE_STORAGE_AAD_ENDPOINT="+v)
1✔
542
                case getLatestAccountKeyField:
2✔
543
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
3✔
544
                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
545
                        }
1✔
546
                case clientIDField:
×
547
                        clientID = v
×
548
                case mountWithWITokenField:
2✔
549
                        if mountWithWIToken, err = strconv.ParseBool(v); err != nil {
3✔
550
                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("invalid %s: %s in volume context", mountWithWITokenField, v)
1✔
551
                        }
1✔
552
                case tenantIDField:
1✔
553
                        tenantID = v
1✔
554
                case strings.ToLower(serviceAccountTokenField):
1✔
555
                        serviceAccountToken = v
1✔
556
                }
557
        }
558
        klog.V(2).Infof("volumeID(%s) authEnv: %s", volumeID, authEnv)
9✔
559

9✔
560
        if protocol == NFS {
11✔
561
                // nfs protocol does not need account key, return directly
2✔
562
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
563
        }
2✔
564

565
        if secretNamespace == "" {
12✔
566
                if pvcNamespace == "" {
10✔
567
                        secretNamespace = defaultNamespace
5✔
568
                } else {
5✔
569
                        secretNamespace = pvcNamespace
×
570
                }
×
571
        }
572

573
        if rgName == "" {
8✔
574
                rgName = d.cloud.ResourceGroup
1✔
575
        }
1✔
576

577
        if tenantID == "" {
13✔
578
                tenantID = d.cloud.TenantID
6✔
579
        }
6✔
580

581
        if clientID != "" {
7✔
582
                if mountWithWIToken {
×
583
                        klog.V(2).Infof("clientID(%s) is specified, use workload identity for blobfuse auth", clientID)
×
584

×
585
                        workloadIdentityToken, err := parseServiceAccountToken(serviceAccountToken)
×
586
                        if err != nil {
×
587
                                return rgName, accountName, accountKey, containerName, authEnv, err
×
588
                        }
×
589

590
                        authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+clientID)
×
591
                        if tenantID != "" {
×
592
                                authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+tenantID)
×
593
                        }
×
594
                        authEnv = append(authEnv, "WORKLOAD_IDENTITY_TOKEN="+workloadIdentityToken)
×
595

×
596
                        return rgName, accountName, accountKey, containerName, authEnv, err
×
597
                }
598
                klog.V(2).Infof("clientID(%s) is specified, use service account token to get account key", clientID)
×
599
                if subsID == "" {
×
600
                        subsID = d.cloud.SubscriptionID
×
601
                }
×
602
                accountKey, err := d.cloud.GetStorageAccesskeyFromServiceAccountToken(ctx, subsID, accountName, rgName, clientID, tenantID, serviceAccountToken)
×
603
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
×
604
                return rgName, accountName, accountKey, containerName, authEnv, err
×
605
        }
606

607
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
608
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
609
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
610
        if keyVaultURL != "" {
8✔
611
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
1✔
612
                if err != nil {
2✔
613
                        return rgName, accountName, accountKey, containerName, authEnv, err
1✔
614
                }
1✔
615
                if isSASToken(key) {
×
616
                        accountSasToken = key
×
617
                } else {
×
618
                        accountKey = key
×
619
                }
×
620
        } else {
6✔
621
                if len(secrets) == 0 {
11✔
622
                        if secretName == "" && accountName != "" {
8✔
623
                                secretName = fmt.Sprintf(secretNameTemplate, accountName)
3✔
624
                        }
3✔
625
                        if secretName != "" {
10✔
626
                                // read from k8s secret first
5✔
627
                                var name, spnClientID, spnTenantID string
5✔
628
                                name, accountKey, accountSasToken, msiSecret, storageSPNClientSecret, spnClientID, spnTenantID, err = d.GetInfoFromSecret(ctx, secretName, secretNamespace)
5✔
629
                                if name != "" {
5✔
630
                                        accountName = name
×
631
                                }
×
632
                                if spnClientID != "" {
5✔
633
                                        storageSPNClientID = spnClientID
×
634
                                }
×
635
                                if spnTenantID != "" {
5✔
636
                                        storageSPNTenantID = spnTenantID
×
637
                                }
×
638
                                if err != nil && strings.EqualFold(azureStorageAuthType, "msi") {
5✔
639
                                        klog.V(2).Infof("ignore error(%v) since secret is optional for auth type(%s)", err, azureStorageAuthType)
×
640
                                        err = nil
×
641
                                }
×
642
                                if err != nil && !getAccountKeyFromSecret && (azureStorageAuthType == "" || strings.EqualFold(azureStorageAuthType, "key")) {
10✔
643
                                        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✔
644
                                                accountName, secretNamespace, secretName, err)
5✔
645
                                        accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, subsID, accountName, rgName, getLatestAccountKey)
5✔
646
                                        if err != nil {
7✔
647
                                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
2✔
648
                                        }
2✔
649
                                }
650
                        }
651
                } else {
1✔
652
                        for k, v := range secrets {
8✔
653
                                v = strings.TrimSpace(v)
7✔
654
                                switch strings.ToLower(k) {
7✔
655
                                case accountNameField:
1✔
656
                                        accountName = v
1✔
657
                                case defaultSecretAccountName: // for compatibility with built-in blobfuse plugin
1✔
658
                                        accountName = v
1✔
659
                                case accountKeyField:
1✔
660
                                        accountKey = v
1✔
661
                                case defaultSecretAccountKey: // for compatibility with built-in blobfuse plugin
1✔
662
                                        accountKey = v
1✔
663
                                case accountSasTokenField:
1✔
664
                                        accountSasToken = v
1✔
665
                                case msiSecretField:
1✔
666
                                        msiSecret = v
1✔
667
                                case storageSPNClientSecretField:
1✔
668
                                        storageSPNClientSecret = v
1✔
669
                                case storageSPNClientIDField:
×
670
                                        storageSPNClientID = v
×
671
                                case storageSPNTenantIDField:
×
672
                                        storageSPNTenantID = v
×
673
                                }
674
                        }
675
                }
676
        }
677

678
        if containerName == "" {
4✔
679
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
680
        }
×
681

682
        if accountKey != "" {
8✔
683
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
684
        }
4✔
685

686
        if accountSasToken != "" {
5✔
687
                klog.V(2).Infof("accountSasToken is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
688
                authEnv = append(authEnv, "AZURE_STORAGE_SAS_TOKEN="+accountSasToken)
1✔
689
        }
1✔
690

691
        if msiSecret != "" {
5✔
692
                klog.V(2).Infof("msiSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
693
                authEnv = append(authEnv, "MSI_SECRET="+msiSecret)
1✔
694
        }
1✔
695

696
        if storageSPNClientSecret != "" {
5✔
697
                klog.V(2).Infof("storageSPNClientSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
698
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_SECRET="+storageSPNClientSecret)
1✔
699
        }
1✔
700

701
        if storageSPNClientID != "" {
4✔
702
                klog.V(2).Infof("storageSPNClientID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNClientID, accountName, containerName)
×
703
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+storageSPNClientID)
×
704
        }
×
705

706
        if storageSPNTenantID != "" {
4✔
707
                klog.V(2).Infof("storageSPNTenantID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNTenantID, accountName, containerName)
×
708
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+storageSPNTenantID)
×
709
        }
×
710

711
        return rgName, accountName, accountKey, containerName, authEnv, err
4✔
712
}
713

714
// GetStorageAccountAndContainer get storage account and container info
715
// returns <accountName, accountKey, accountSasToken, containerName>
716
// only for e2e testing
717
func (d *Driver) GetStorageAccountAndContainer(ctx context.Context, volumeID string, attrib, secrets map[string]string) (string, string, string, string, error) {
3✔
718
        var (
3✔
719
                subsID                string
3✔
720
                accountName           string
3✔
721
                accountKey            string
3✔
722
                accountSasToken       string
3✔
723
                containerName         string
3✔
724
                keyVaultURL           string
3✔
725
                keyVaultSecretName    string
3✔
726
                keyVaultSecretVersion string
3✔
727
                getLatestAccountKey   bool
3✔
728
                err                   error
3✔
729
        )
3✔
730

3✔
731
        for k, v := range attrib {
8✔
732
                switch strings.ToLower(k) {
5✔
733
                case subscriptionIDField:
×
734
                        subsID = v
×
735
                case containerNameField:
1✔
736
                        containerName = v
1✔
737
                case keyVaultURLField:
×
738
                        keyVaultURL = v
×
739
                case keyVaultSecretNameField:
1✔
740
                        keyVaultSecretName = v
1✔
741
                case keyVaultSecretVersionField:
1✔
742
                        keyVaultSecretVersion = v
1✔
743
                case storageAccountField:
×
744
                        accountName = v
×
745
                case storageAccountNameField: // for compatibility
1✔
746
                        accountName = v
1✔
747
                case getLatestAccountKeyField:
1✔
748
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
2✔
749
                                return "", "", "", "", fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
750
                        }
1✔
751
                }
752
        }
753

754
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
755
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
756
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
757
        if keyVaultURL != "" {
2✔
758
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
×
759
                if err != nil {
×
760
                        return "", "", "", "", err
×
761
                }
×
762
                if isSASToken(key) {
×
763
                        accountSasToken = key
×
764
                } else {
×
765
                        accountKey = key
×
766
                }
×
767
        } else {
2✔
768
                if len(secrets) == 0 {
4✔
769
                        var rgName string
2✔
770
                        rgName, accountName, containerName, _, _, err = GetContainerInfo(volumeID)
2✔
771
                        if err != nil {
2✔
772
                                return "", "", "", "", err
×
773
                        }
×
774

775
                        if rgName == "" {
2✔
776
                                rgName = d.cloud.ResourceGroup
×
777
                        }
×
778
                        accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, subsID, accountName, rgName, getLatestAccountKey)
2✔
779
                        if err != nil {
3✔
780
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
781
                        }
1✔
782
                }
783
        }
784

785
        if containerName == "" {
1✔
786
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
787
        }
×
788

789
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
790
}
791

792
func IsCorruptedDir(dir string) bool {
4✔
793
        _, pathErr := mount.PathExists(dir)
4✔
794
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
795
}
4✔
796

797
func isRetriableError(err error) bool {
5✔
798
        if err != nil {
9✔
799
                for _, v := range retriableErrors {
19✔
800
                        if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(v)) {
18✔
801
                                return true
3✔
802
                        }
3✔
803
                }
804
        }
805
        return false
2✔
806
}
807

808
func isSupportedProtocol(protocol string) bool {
20✔
809
        if protocol == "" {
21✔
810
                return true
1✔
811
        }
1✔
812
        for _, v := range supportedProtocolList {
48✔
813
                if protocol == v || protocol == NFSv3 {
46✔
814
                        return true
17✔
815
                }
17✔
816
        }
817
        return false
2✔
818
}
819

820
func isSupportedAccessTier(accessTier string) bool {
23✔
821
        if accessTier == "" {
39✔
822
                return true
16✔
823
        }
16✔
824
        for _, tier := range armstorage.PossibleAccessTierValues() {
32✔
825
                if accessTier == string(tier) {
28✔
826
                        return true
3✔
827
                }
3✔
828
        }
829
        return false
4✔
830
}
831

832
func isSupportedPublicNetworkAccess(publicNetworkAccess string) bool {
19✔
833
        if publicNetworkAccess == "" {
34✔
834
                return true
15✔
835
        }
15✔
836
        for _, tier := range armstorage.PossiblePublicNetworkAccessValues() {
13✔
837
                if publicNetworkAccess == string(tier) {
11✔
838
                        return true
2✔
839
                }
2✔
840
        }
841
        return false
2✔
842
}
843

844
// container names can contain only lowercase letters, numbers, and hyphens,
845
// and must begin and end with a letter or a number
846
func isSupportedContainerNamePrefix(prefix string) bool {
21✔
847
        if prefix == "" {
34✔
848
                return true
13✔
849
        }
13✔
850
        if len(prefix) > 20 {
9✔
851
                return false
1✔
852
        }
1✔
853
        if prefix[0] == '-' {
8✔
854
                return false
1✔
855
        }
1✔
856
        for _, v := range prefix {
19✔
857
                if v != '-' && (v < '0' || v > '9') && (v < 'a' || v > 'z') {
17✔
858
                        return false
4✔
859
                }
4✔
860
        }
861
        return true
2✔
862
}
863

864
// isNFSProtocol checks if the protocol is NFS or AZNFS
865
func isNFSProtocol(protocol string) bool {
20✔
866
        protocol = strings.ToLower(protocol)
20✔
867
        return protocol == NFS || protocol == AZNFS || protocol == NFSv3
20✔
868
}
20✔
869

870
// get storage account from secrets map
871
func getStorageAccount(secrets map[string]string) (string, string, error) {
22✔
872
        if secrets == nil {
23✔
873
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
874
        }
1✔
875

876
        var accountName, accountKey string
21✔
877
        for k, v := range secrets {
64✔
878
                v = strings.TrimSpace(v)
43✔
879
                switch strings.ToLower(k) {
43✔
880
                case accountNameField:
7✔
881
                        accountName = v
7✔
882
                case defaultSecretAccountName: // for compatibility with built-in azurefile plugin
13✔
883
                        accountName = v
13✔
884
                case accountKeyField:
7✔
885
                        accountKey = v
7✔
886
                case defaultSecretAccountKey: // for compatibility with built-in azurefile plugin
12✔
887
                        accountKey = v
12✔
888
                }
889
        }
890

891
        if accountName == "" {
25✔
892
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountNameField, defaultSecretAccountName)
4✔
893
        }
4✔
894
        if accountKey == "" {
21✔
895
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountKeyField, defaultSecretAccountKey)
4✔
896
        }
4✔
897

898
        accountName = strings.TrimSpace(accountName)
13✔
899
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
13✔
900
        return accountName, accountKey, nil
13✔
901
}
902

903
func getContainerReference(containerName string, secrets map[string]string, storageEndpointSuffix string) (*azstorage.Container, error) {
9✔
904
        accountName, accountKey, rerr := getStorageAccount(secrets)
9✔
905
        if rerr != nil {
11✔
906
                return nil, rerr
2✔
907
        }
2✔
908
        client, err := azstorage.NewClient(accountName, accountKey, storageEndpointSuffix, azstorage.DefaultAPIVersion, true)
7✔
909
        if err != nil {
13✔
910
                return nil, err
6✔
911
        }
6✔
912
        blobClient := client.GetBlobService()
1✔
913
        container := blobClient.GetContainerReference(containerName)
1✔
914
        if container == nil {
1✔
915
                return nil, fmt.Errorf("ContainerReference of %s is nil", containerName)
×
916
        }
×
917
        return container, nil
1✔
918
}
919

920
func setAzureCredentials(ctx context.Context, kubeClient kubernetes.Interface, accountName, accountKey, secretNamespace string) (string, error) {
6✔
921
        if kubeClient == nil {
8✔
922
                klog.Warningf("could not create secret: kubeClient is nil")
2✔
923
                return "", nil
2✔
924
        }
2✔
925
        if accountName == "" || accountKey == "" {
6✔
926
                return "", fmt.Errorf("the account info is not enough, accountName(%v), accountKey(%v)", accountName, accountKey)
2✔
927
        }
2✔
928
        secretName := fmt.Sprintf(secretNameTemplate, accountName)
2✔
929
        secret := &v1.Secret{
2✔
930
                ObjectMeta: metav1.ObjectMeta{
2✔
931
                        Namespace: secretNamespace,
2✔
932
                        Name:      secretName,
2✔
933
                },
2✔
934
                Data: map[string][]byte{
2✔
935
                        defaultSecretAccountName: []byte(accountName),
2✔
936
                        defaultSecretAccountKey:  []byte(accountKey),
2✔
937
                },
2✔
938
                Type: "Opaque",
2✔
939
        }
2✔
940
        _, err := kubeClient.CoreV1().Secrets(secretNamespace).Create(ctx, secret, metav1.CreateOptions{})
2✔
941
        if apierror.IsAlreadyExists(err) {
3✔
942
                err = nil
1✔
943
        }
1✔
944
        if err != nil {
2✔
945
                return "", fmt.Errorf("couldn't create secret %w", err)
×
946
        }
×
947
        return secretName, err
2✔
948
}
949

950
// GetStorageAccesskey get Azure storage account key from
951
//  1. secrets (if not empty)
952
//  2. use k8s client identity to read from k8s secret
953
//  3. use cluster identity to get from storage account directly
954
func (d *Driver) GetStorageAccesskey(ctx context.Context, accountOptions *storage.AccountOptions, secrets map[string]string, secretName, secretNamespace string) (string, string, error) {
12✔
955
        if len(secrets) > 0 {
17✔
956
                return getStorageAccount(secrets)
5✔
957
        }
5✔
958

959
        // read from k8s secret first
960
        if secretName == "" {
12✔
961
                secretName = fmt.Sprintf(secretNameTemplate, accountOptions.Name)
5✔
962
        }
5✔
963
        _, accountKey, _, _, _, _, _, err := d.GetInfoFromSecret(ctx, secretName, secretNamespace) //nolint
7✔
964
        if err != nil && d.cloud != nil {
12✔
965
                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)
5✔
966
                accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, accountOptions.SubscriptionID, accountOptions.Name, accountOptions.ResourceGroup, accountOptions.GetLatestAccountKey)
5✔
967
        }
5✔
968
        return accountOptions.Name, accountKey, err
7✔
969
}
970

971
// GetStorageAccesskeyWithSubsID get Azure storage account key from storage account directly
972
func (d *Driver) GetStorageAccesskeyWithSubsID(ctx context.Context, subsID, account, resourceGroup string, getLatestAccountKey bool) (string, error) {
15✔
973
        if d.cloud == nil || d.cloud.ComputeClientFactory == nil {
17✔
974
                return "", fmt.Errorf("could not get account key: cloud or ComputeClientFactory is nil")
2✔
975
        }
2✔
976
        accountClient, err := d.cloud.ComputeClientFactory.GetAccountClientForSub(subsID)
13✔
977
        if err != nil {
13✔
978
                return "", err
×
979
        }
×
980
        return d.cloud.GetStorageAccesskey(ctx, accountClient, account, resourceGroup, getLatestAccountKey)
13✔
981
}
982

983
// GetInfoFromSecret get info from k8s secret
984
// return <accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, error>
985
func (d *Driver) GetInfoFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, string, string, string, string, error) {
16✔
986
        if d.KubeClient == nil {
26✔
987
                return "", "", "", "", "", "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
10✔
988
        }
10✔
989

990
        secret, err := d.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
991
        if err != nil {
8✔
992
                return "", "", "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
993
        }
2✔
994

995
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
996
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
997
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
998
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
999
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
1000
        spnClientID := strings.TrimSpace(string(secret.Data[storageSPNClientIDField][:]))
4✔
1001
        spnTenantID := strings.TrimSpace(string(secret.Data[storageSPNTenantIDField][:]))
4✔
1002

4✔
1003
        klog.V(4).Infof("got storage account(%s) from secret(%s) namespace(%s)", accountName, secretName, secretNamespace)
4✔
1004
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
1005
}
1006

1007
// getSubnetResourceID get default subnet resource ID from cloud provider config
1008
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
1009
        subsID := d.cloud.SubscriptionID
6✔
1010
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
1011
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
1012
        }
4✔
1013

1014
        if len(vnetResourceGroup) == 0 {
10✔
1015
                vnetResourceGroup = d.cloud.ResourceGroup
4✔
1016
                if len(d.cloud.VnetResourceGroup) > 0 {
7✔
1017
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
1018
                }
3✔
1019
        }
1020

1021
        if len(vnetName) == 0 {
10✔
1022
                vnetName = d.cloud.VnetName
4✔
1023
        }
4✔
1024

1025
        if len(subnetName) == 0 {
10✔
1026
                subnetName = d.cloud.SubnetName
4✔
1027
        }
4✔
1028
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
1029
}
1030

1031
func (d *Driver) useDataPlaneAPI(ctx context.Context, volumeID, accountName string) bool {
9✔
1032
        cache, err := d.dataPlaneAPIVolCache.Get(ctx, volumeID, azcache.CacheReadTypeDefault)
9✔
1033
        if err != nil {
9✔
1034
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
1035
        }
×
1036
        if cache != nil {
12✔
1037
                return true
3✔
1038
        }
3✔
1039
        cache, err = d.dataPlaneAPIVolCache.Get(ctx, accountName, azcache.CacheReadTypeDefault)
6✔
1040
        if err != nil {
6✔
1041
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
1042
        }
×
1043
        if cache != nil {
6✔
1044
                return true
×
1045
        }
×
1046
        return false
6✔
1047
}
1048

1049
// appendDefaultMountOptions return mount options combined with mountOptions and defaultMountOptions
1050
func appendDefaultMountOptions(mountOptions []string, tmpPath, containerName string) []string {
4✔
1051
        var defaultMountOptions = map[string]string{
4✔
1052
                "--pre-mount-validate": "true",
4✔
1053
                "--use-https":          "true",
4✔
1054
                "--tmp-path":           tmpPath,
4✔
1055
                "--container-name":     containerName,
4✔
1056
                // prevent billing charges on mounting
4✔
1057
                "--cancel-list-on-mount-seconds": "10",
4✔
1058
                // allow remounting using a non-empty tmp-path
4✔
1059
                "--empty-dir-check": "false",
4✔
1060
        }
4✔
1061

4✔
1062
        // stores the mount options already included in mountOptions
4✔
1063
        included := make(map[string]bool)
4✔
1064

4✔
1065
        for _, mountOption := range mountOptions {
11✔
1066
                for k := range defaultMountOptions {
49✔
1067
                        if strings.HasPrefix(mountOption, k) {
46✔
1068
                                included[k] = true
4✔
1069
                        }
4✔
1070
                }
1071
        }
1072

1073
        allMountOptions := mountOptions
4✔
1074

4✔
1075
        for k, v := range defaultMountOptions {
28✔
1076
                if _, isIncluded := included[k]; !isIncluded {
44✔
1077
                        if v != "" {
40✔
1078
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
20✔
1079
                        } else {
20✔
1080
                                allMountOptions = append(allMountOptions, k)
×
1081
                        }
×
1082
                }
1083
        }
1084

1085
        return allMountOptions
4✔
1086
}
1087

1088
// chmodIfPermissionMismatch only perform chmod when permission mismatches
1089
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
7✔
1090
        info, err := os.Lstat(targetPath)
7✔
1091
        if err != nil {
8✔
1092
                return err
1✔
1093
        }
1✔
1094
        perm := info.Mode() & os.ModePerm
6✔
1095
        expectedPerms := mode & os.ModePerm
6✔
1096
        if perm != expectedPerms {
8✔
1097
                klog.V(2).Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), expectedPerms)
2✔
1098
                // only change the permission mode bits, keep the other bits as is
2✔
1099
                if err := os.Chmod(targetPath, (info.Mode()&^os.ModePerm)|os.FileMode(expectedPerms)); err != nil {
2✔
1100
                        return err
×
1101
                }
×
1102
        } else {
4✔
1103
                klog.V(2).Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
4✔
1104
        }
4✔
1105
        return nil
6✔
1106
}
1107

1108
func createStorageAccountSecret(account, key string) map[string]string {
1✔
1109
        secret := make(map[string]string)
1✔
1110
        secret[defaultSecretAccountName] = account
1✔
1111
        secret[defaultSecretAccountKey] = key
1✔
1112
        return secret
1✔
1113
}
1✔
1114

1115
// setKeyValueInMap set key/value pair in map
1116
// key in the map is case insensitive, if key already exists, overwrite existing value
1117
func setKeyValueInMap(m map[string]string, key, value string) {
7✔
1118
        if m == nil {
8✔
1119
                return
1✔
1120
        }
1✔
1121
        for k := range m {
18✔
1122
                if strings.EqualFold(k, key) {
14✔
1123
                        m[k] = value
2✔
1124
                        return
2✔
1125
                }
2✔
1126
        }
1127
        m[key] = value
4✔
1128
}
1129

1130
// getValueInMap get value from map by key
1131
// key in the map is case insensitive
1132
func getValueInMap(m map[string]string, key string) string {
12✔
1133
        if m == nil {
13✔
1134
                return ""
1✔
1135
        }
1✔
1136
        for k, v := range m {
23✔
1137
                if strings.EqualFold(k, key) {
16✔
1138
                        return v
4✔
1139
                }
4✔
1140
        }
1141
        return ""
7✔
1142
}
1143

1144
// replaceWithMap replace key with value for str
1145
func replaceWithMap(str string, m map[string]string) string {
14✔
1146
        for k, v := range m {
19✔
1147
                if k != "" {
9✔
1148
                        str = strings.ReplaceAll(str, k, v)
4✔
1149
                }
4✔
1150
        }
1151
        return str
14✔
1152
}
1153

1154
func isSupportedFSGroupChangePolicy(policy string) bool {
28✔
1155
        if policy == "" {
49✔
1156
                return true
21✔
1157
        }
21✔
1158
        for _, v := range supportedFSGroupChangePolicyList {
25✔
1159
                if policy == v {
21✔
1160
                        return true
3✔
1161
                }
3✔
1162
        }
1163
        return false
4✔
1164
}
1165

1166
func isReadOnlyFromCapability(vc *csi.VolumeCapability) bool {
7✔
1167
        if vc.GetAccessMode() == nil {
9✔
1168
                return false
2✔
1169
        }
2✔
1170
        mode := vc.GetAccessMode().GetMode()
5✔
1171
        return (mode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY ||
5✔
1172
                mode == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY)
5✔
1173
}
1174

1175
// generateVolumeName returns a PV name with clusterName prefix. The function
1176
// should be used to generate a name of GCE PD or Cinder volume. It basically
1177
// adds "<clusterName>-dynamic-" before the PV name, making sure the resulting
1178
// string fits given length and cuts "dynamic" if not.
1179
func generateVolumeName(clusterName, pvName string, maxLength int) string {
3✔
1180
        prefix := clusterName + "-dynamic"
3✔
1181
        pvLen := len(pvName)
3✔
1182
        // cut the "<clusterName>-dynamic" to fit full pvName into maxLength
3✔
1183
        // +1 for the '-' dash
3✔
1184
        if pvLen+1+len(prefix) > maxLength {
5✔
1185
                prefix = prefix[:maxLength-pvLen-1]
2✔
1186
        }
2✔
1187
        return prefix + "-" + pvName
3✔
1188
}
1189

1190
// serviceAccountToken represents the service account token sent from NodePublishVolume Request.
1191
// ref: https://kubernetes-csi.github.io/docs/token-requests.html
1192
type serviceAccountToken struct {
1193
        APIAzureADTokenExchange struct {
1194
                Token               string    `json:"token"`
1195
                ExpirationTimestamp time.Time `json:"expirationTimestamp"`
1196
        } `json:"api://AzureADTokenExchange"`
1197
}
1198

1199
// parseServiceAccountToken parses the bound service account token from the token passed from NodePublishVolume Request.
1200
// ref: https://kubernetes-csi.github.io/docs/token-requests.html
1201
func parseServiceAccountToken(tokenStr string) (string, error) {
5✔
1202
        if len(tokenStr) == 0 {
6✔
1203
                return "", fmt.Errorf("service account token is empty")
1✔
1204
        }
1✔
1205
        token := serviceAccountToken{}
4✔
1206
        if err := json.Unmarshal([]byte(tokenStr), &token); err != nil {
5✔
1207
                return "", fmt.Errorf("failed to unmarshal service account tokens, error: %w", err)
1✔
1208
        }
1✔
1209
        if token.APIAzureADTokenExchange.Token == "" {
5✔
1210
                return "", fmt.Errorf("token for audience %s not found", DefaultTokenAudience)
2✔
1211
        }
2✔
1212
        return token.APIAzureADTokenExchange.Token, nil
1✔
1213
}
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