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

kubernetes-sigs / blob-csi-driver / 14841670227

05 May 2025 04:49PM UTC coverage: 79.72%. Remained the same
14841670227

Pull #1984

github

web-flow
chore(deps): bump golang.org/x/sync from 0.13.0 to 0.14.0

Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.13.0 to 0.14.0.
- [Commits](https://github.com/golang/sync/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #1984: chore(deps): bump golang.org/x/sync from 0.13.0 to 0.14.0

2390 of 2998 relevant lines covered (79.72%)

7.92 hits per line

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

86.54
/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
        requireInfraEncryptionField    = "requireinfraencryption"
98
        ephemeralField                 = "csi.storage.k8s.io/ephemeral"
99
        podNamespaceField              = "csi.storage.k8s.io/pod.namespace"
100
        serviceAccountTokenField       = "csi.storage.k8s.io/serviceAccount.tokens"
101
        clientIDField                  = "clientid"
102
        tenantIDField                  = "tenantid"
103
        mountOptionsField              = "mountoptions"
104
        falseValue                     = "false"
105
        trueValue                      = "true"
106
        defaultSecretAccountName       = "azurestorageaccountname"
107
        defaultSecretAccountKey        = "azurestorageaccountkey"
108
        accountSasTokenField           = "azurestorageaccountsastoken"
109
        msiSecretField                 = "msisecret"
110
        storageSPNClientSecretField    = "azurestoragespnclientsecret"
111
        Fuse                           = "fuse"
112
        Fuse2                          = "fuse2"
113
        NFS                            = "nfs"
114
        AZNFS                          = "aznfs"
115
        NFSv3                          = "nfsv3"
116
        vnetResourceGroupField         = "vnetresourcegroup"
117
        vnetNameField                  = "vnetname"
118
        subnetNameField                = "subnetname"
119
        accessTierField                = "accesstier"
120
        networkEndpointTypeField       = "networkendpointtype"
121
        mountPermissionsField          = "mountpermissions"
122
        fsGroupChangePolicyField       = "fsgroupchangepolicy"
123
        useDataPlaneAPIField           = "usedataplaneapi"
124

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

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

137
        // 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
138
        containerMaxSize = 100 * util.TiB
139

140
        subnetTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s"
141

142
        defaultNamespace = "default"
143

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

151
        VolumeID = "volumeid"
152

153
        defaultStorageEndPointSuffix = "core.windows.net"
154

155
        FSGroupChangeNone = "None"
156
        // define tag value delimiter and default is comma
157
        tagValueDelimiterField = "tagvaluedelimiter"
158

159
        DefaultTokenAudience = "api://AzureADTokenExchange" //nolint:gosec // G101 ignore this!
160

161
)
162

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

168
        // 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,
169
        // set --s2s-preserve-access-tier=false to avoid BlobAccessTierNotSupportedForAccountType error in azcopy
170
        azcopyCloneVolumeOptions = []string{"--recursive", "--check-length=false", "--s2s-preserve-access-tier=false", "--log-level=ERROR"}
171
)
172

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

194
func (option *DriverOptions) AddFlags() {
1✔
195
        flag.StringVar(&option.BlobfuseProxyEndpoint, "blobfuse-proxy-endpoint", "unix://tmp/blobfuse-proxy.sock", "blobfuse-proxy endpoint")
1✔
196
        flag.StringVar(&option.NodeID, "nodeid", "", "node id")
1✔
197
        flag.StringVar(&option.DriverName, "drivername", DefaultDriverName, "name of the driver")
1✔
198
        flag.BoolVar(&option.EnableBlobfuseProxy, "enable-blobfuse-proxy", false, "using blobfuse proxy for mounts")
1✔
199
        flag.IntVar(&option.BlobfuseProxyConnTimout, "blobfuse-proxy-connect-timeout", 5, "blobfuse proxy connection timeout(seconds)")
1✔
200
        flag.BoolVar(&option.EnableBlobMockMount, "enable-blob-mock-mount", false, "enable mock mount(only for testing)")
1✔
201
        flag.BoolVar(&option.EnableGetVolumeStats, "enable-get-volume-stats", false, "allow GET_VOLUME_STATS on agent node")
1✔
202
        flag.BoolVar(&option.AppendTimeStampInCacheDir, "append-timestamp-cache-dir", false, "append timestamp into cache directory on agent node")
1✔
203
        flag.Uint64Var(&option.MountPermissions, "mount-permissions", 0777, "mounted folder permissions")
1✔
204
        flag.BoolVar(&option.AllowInlineVolumeKeyAccessWithIdentity, "allow-inline-volume-key-access-with-idenitity", false, "allow accessing storage account key using cluster identity for inline volume")
1✔
205
        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✔
206
        flag.BoolVar(&option.EnableAznfsMount, "enable-aznfs-mount", false, "replace nfs mount with aznfs mount")
1✔
207
        flag.IntVar(&option.VolStatsCacheExpireInMinutes, "vol-stats-cache-expire-in-minutes", 10, "The cache expire time in minutes for volume stats cache")
1✔
208
        flag.IntVar(&option.SasTokenExpirationMinutes, "sas-token-expiration-minutes", 1440, "sas token expiration minutes during volume cloning")
1✔
209
        flag.IntVar(&option.WaitForAzCopyTimeoutMinutes, "wait-for-azcopy-timeout-minutes", 18, "timeout in minutes for waiting for azcopy to finish")
1✔
210
        flag.BoolVar(&option.EnableVolumeMountGroup, "enable-volume-mount-group", true, "indicates whether enabling VOLUME_MOUNT_GROUP")
1✔
211
        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✔
212
}
1✔
213

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

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

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

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

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

324
        d.mounter = &mount.SafeFormatAndMount{
138✔
325
                Interface: mount.New(""),
138✔
326
                Exec:      utilexec.New(),
138✔
327
        }
138✔
328

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

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

138✔
361
        return &d
138✔
362
}
363

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

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

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

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

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

455
        return false
2✔
456
}
457

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

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

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

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

11✔
552
        if protocol == NFS {
13✔
553
                // nfs protocol does not need account key, return directly
2✔
554
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
555
        }
2✔
556

557
        if secretNamespace == "" {
16✔
558
                if pvcNamespace == "" {
14✔
559
                        secretNamespace = defaultNamespace
7✔
560
                } else {
7✔
561
                        secretNamespace = pvcNamespace
×
562
                }
×
563
        }
564

565
        if rgName == "" {
12✔
566
                rgName = d.cloud.ResourceGroup
3✔
567
        }
3✔
568

569
        if tenantID == "" {
18✔
570
                tenantID = d.cloud.TenantID
9✔
571
        }
9✔
572

573
        // if client id is specified, we only use workload identity for blobfuse auth
574
        if clientID != "" {
10✔
575
                klog.V(2).Infof("clientID(%s) is specified, use workload identity for blobfuse auth", clientID)
1✔
576

1✔
577
                workloadIdentityToken, err := parseServiceAccountToken(serviceAccountToken)
1✔
578
                if err != nil {
1✔
579
                        return rgName, accountName, accountKey, containerName, authEnv, err
×
580
                }
×
581

582
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+clientID)
1✔
583
                if tenantID != "" {
1✔
584
                        authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+tenantID)
×
585
                }
×
586
                authEnv = append(authEnv, "WORKLOAD_IDENTITY_TOKEN="+workloadIdentityToken)
1✔
587

1✔
588
                return rgName, accountName, accountKey, containerName, authEnv, err
1✔
589
        }
590

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

662
        if containerName == "" {
5✔
663
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
664
        }
×
665

666
        if accountKey != "" {
9✔
667
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
668
        }
4✔
669

670
        if accountSasToken != "" {
6✔
671
                klog.V(2).Infof("accountSasToken is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
672
                authEnv = append(authEnv, "AZURE_STORAGE_SAS_TOKEN="+accountSasToken)
1✔
673
        }
1✔
674

675
        if msiSecret != "" {
6✔
676
                klog.V(2).Infof("msiSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
677
                authEnv = append(authEnv, "MSI_SECRET="+msiSecret)
1✔
678
        }
1✔
679

680
        if storageSPNClientSecret != "" {
6✔
681
                klog.V(2).Infof("storageSPNClientSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
682
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_SECRET="+storageSPNClientSecret)
1✔
683
        }
1✔
684

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

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

695
        return rgName, accountName, accountKey, containerName, authEnv, err
5✔
696
}
697

698
// GetStorageAccountAndContainer get storage account and container info
699
// returns <accountName, accountKey, accountSasToken, containerName>
700
// only for e2e testing
701
func (d *Driver) GetStorageAccountAndContainer(ctx context.Context, volumeID string, attrib, secrets map[string]string) (string, string, string, string, error) {
3✔
702
        var (
3✔
703
                subsID                string
3✔
704
                accountName           string
3✔
705
                accountKey            string
3✔
706
                accountSasToken       string
3✔
707
                containerName         string
3✔
708
                keyVaultURL           string
3✔
709
                keyVaultSecretName    string
3✔
710
                keyVaultSecretVersion string
3✔
711
                getLatestAccountKey   bool
3✔
712
                err                   error
3✔
713
        )
3✔
714

3✔
715
        for k, v := range attrib {
8✔
716
                switch strings.ToLower(k) {
5✔
717
                case subscriptionIDField:
×
718
                        subsID = v
×
719
                case containerNameField:
1✔
720
                        containerName = v
1✔
721
                case keyVaultURLField:
×
722
                        keyVaultURL = v
×
723
                case keyVaultSecretNameField:
1✔
724
                        keyVaultSecretName = v
1✔
725
                case keyVaultSecretVersionField:
1✔
726
                        keyVaultSecretVersion = v
1✔
727
                case storageAccountField:
×
728
                        accountName = v
×
729
                case storageAccountNameField: // for compatibility
1✔
730
                        accountName = v
1✔
731
                case getLatestAccountKeyField:
1✔
732
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
2✔
733
                                return "", "", "", "", fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
734
                        }
1✔
735
                }
736
        }
737

738
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
739
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
740
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
741
        if keyVaultURL != "" {
2✔
742
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
×
743
                if err != nil {
×
744
                        return "", "", "", "", err
×
745
                }
×
746
                if isSASToken(key) {
×
747
                        accountSasToken = key
×
748
                } else {
×
749
                        accountKey = key
×
750
                }
×
751
        } else {
2✔
752
                if len(secrets) == 0 {
4✔
753
                        var rgName string
2✔
754
                        rgName, accountName, containerName, _, _, err = GetContainerInfo(volumeID)
2✔
755
                        if err != nil {
2✔
756
                                return "", "", "", "", err
×
757
                        }
×
758

759
                        if rgName == "" {
2✔
760
                                rgName = d.cloud.ResourceGroup
×
761
                        }
×
762
                        accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, subsID, accountName, rgName, getLatestAccountKey)
2✔
763
                        if err != nil {
3✔
764
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
765
                        }
1✔
766
                }
767
        }
768

769
        if containerName == "" {
1✔
770
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
771
        }
×
772

773
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
774
}
775

776
func IsCorruptedDir(dir string) bool {
4✔
777
        _, pathErr := mount.PathExists(dir)
4✔
778
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
779
}
4✔
780

781
func isRetriableError(err error) bool {
5✔
782
        if err != nil {
9✔
783
                for _, v := range retriableErrors {
19✔
784
                        if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(v)) {
18✔
785
                                return true
3✔
786
                        }
3✔
787
                }
788
        }
789
        return false
2✔
790
}
791

792
func isSupportedProtocol(protocol string) bool {
19✔
793
        if protocol == "" {
20✔
794
                return true
1✔
795
        }
1✔
796
        for _, v := range supportedProtocolList {
46✔
797
                if protocol == v || protocol == NFSv3 {
44✔
798
                        return true
16✔
799
                }
16✔
800
        }
801
        return false
2✔
802
}
803

804
func isSupportedAccessTier(accessTier string) bool {
22✔
805
        if accessTier == "" {
37✔
806
                return true
15✔
807
        }
15✔
808
        for _, tier := range armstorage.PossibleAccessTierValues() {
32✔
809
                if accessTier == string(tier) {
28✔
810
                        return true
3✔
811
                }
3✔
812
        }
813
        return false
4✔
814
}
815

816
// container names can contain only lowercase letters, numbers, and hyphens,
817
// and must begin and end with a letter or a number
818
func isSupportedContainerNamePrefix(prefix string) bool {
21✔
819
        if prefix == "" {
34✔
820
                return true
13✔
821
        }
13✔
822
        if len(prefix) > 20 {
9✔
823
                return false
1✔
824
        }
1✔
825
        if prefix[0] == '-' {
8✔
826
                return false
1✔
827
        }
1✔
828
        for _, v := range prefix {
19✔
829
                if v != '-' && (v < '0' || v > '9') && (v < 'a' || v > 'z') {
17✔
830
                        return false
4✔
831
                }
4✔
832
        }
833
        return true
2✔
834
}
835

836
// isNFSProtocol checks if the protocol is NFS or AZNFS
837
func isNFSProtocol(protocol string) bool {
22✔
838
        protocol = strings.ToLower(protocol)
22✔
839
        return protocol == NFS || protocol == AZNFS || protocol == NFSv3
22✔
840
}
22✔
841

842
// get storage account from secrets map
843
func getStorageAccount(secrets map[string]string) (string, string, error) {
22✔
844
        if secrets == nil {
23✔
845
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
846
        }
1✔
847

848
        var accountName, accountKey string
21✔
849
        for k, v := range secrets {
64✔
850
                v = strings.TrimSpace(v)
43✔
851
                switch strings.ToLower(k) {
43✔
852
                case accountNameField:
7✔
853
                        accountName = v
7✔
854
                case defaultSecretAccountName: // for compatibility with built-in azurefile plugin
13✔
855
                        accountName = v
13✔
856
                case accountKeyField:
7✔
857
                        accountKey = v
7✔
858
                case defaultSecretAccountKey: // for compatibility with built-in azurefile plugin
12✔
859
                        accountKey = v
12✔
860
                }
861
        }
862

863
        if accountName == "" {
25✔
864
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountNameField, defaultSecretAccountName)
4✔
865
        }
4✔
866
        if accountKey == "" {
21✔
867
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountKeyField, defaultSecretAccountKey)
4✔
868
        }
4✔
869

870
        accountName = strings.TrimSpace(accountName)
13✔
871
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
13✔
872
        return accountName, accountKey, nil
13✔
873
}
874

875
func getContainerReference(containerName string, secrets map[string]string, storageEndpointSuffix string) (*azstorage.Container, error) {
9✔
876
        accountName, accountKey, rerr := getStorageAccount(secrets)
9✔
877
        if rerr != nil {
11✔
878
                return nil, rerr
2✔
879
        }
2✔
880
        client, err := azstorage.NewClient(accountName, accountKey, storageEndpointSuffix, azstorage.DefaultAPIVersion, true)
7✔
881
        if err != nil {
13✔
882
                return nil, err
6✔
883
        }
6✔
884
        blobClient := client.GetBlobService()
1✔
885
        container := blobClient.GetContainerReference(containerName)
1✔
886
        if container == nil {
1✔
887
                return nil, fmt.Errorf("ContainerReference of %s is nil", containerName)
×
888
        }
×
889
        return container, nil
1✔
890
}
891

892
func setAzureCredentials(ctx context.Context, kubeClient kubernetes.Interface, accountName, accountKey, secretNamespace string) (string, error) {
6✔
893
        if kubeClient == nil {
8✔
894
                klog.Warningf("could not create secret: kubeClient is nil")
2✔
895
                return "", nil
2✔
896
        }
2✔
897
        if accountName == "" || accountKey == "" {
6✔
898
                return "", fmt.Errorf("the account info is not enough, accountName(%v), accountKey(%v)", accountName, accountKey)
2✔
899
        }
2✔
900
        secretName := fmt.Sprintf(secretNameTemplate, accountName)
2✔
901
        secret := &v1.Secret{
2✔
902
                ObjectMeta: metav1.ObjectMeta{
2✔
903
                        Namespace: secretNamespace,
2✔
904
                        Name:      secretName,
2✔
905
                },
2✔
906
                Data: map[string][]byte{
2✔
907
                        defaultSecretAccountName: []byte(accountName),
2✔
908
                        defaultSecretAccountKey:  []byte(accountKey),
2✔
909
                },
2✔
910
                Type: "Opaque",
2✔
911
        }
2✔
912
        _, err := kubeClient.CoreV1().Secrets(secretNamespace).Create(ctx, secret, metav1.CreateOptions{})
2✔
913
        if apierror.IsAlreadyExists(err) {
3✔
914
                err = nil
1✔
915
        }
1✔
916
        if err != nil {
2✔
917
                return "", fmt.Errorf("couldn't create secret %w", err)
×
918
        }
×
919
        return secretName, err
2✔
920
}
921

922
// GetStorageAccesskey get Azure storage account key from
923
//  1. secrets (if not empty)
924
//  2. use k8s client identity to read from k8s secret
925
//  3. use cluster identity to get from storage account directly
926
func (d *Driver) GetStorageAccesskey(ctx context.Context, accountOptions *storage.AccountOptions, secrets map[string]string, secretName, secretNamespace string) (string, string, error) {
12✔
927
        if len(secrets) > 0 {
17✔
928
                return getStorageAccount(secrets)
5✔
929
        }
5✔
930

931
        // read from k8s secret first
932
        if secretName == "" {
12✔
933
                secretName = fmt.Sprintf(secretNameTemplate, accountOptions.Name)
5✔
934
        }
5✔
935
        _, accountKey, _, _, _, _, _, err := d.GetInfoFromSecret(ctx, secretName, secretNamespace) //nolint
7✔
936
        if err != nil && d.cloud != nil {
12✔
937
                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✔
938
                accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, accountOptions.SubscriptionID, accountOptions.Name, accountOptions.ResourceGroup, accountOptions.GetLatestAccountKey)
5✔
939
        }
5✔
940
        return accountOptions.Name, accountKey, err
7✔
941
}
942

943
// GetStorageAccesskeyWithSubsID get Azure storage account key from storage account directly
944
func (d *Driver) GetStorageAccesskeyWithSubsID(ctx context.Context, subsID, account, resourceGroup string, getLatestAccountKey bool) (string, error) {
15✔
945
        if d.cloud == nil || d.cloud.ComputeClientFactory == nil {
17✔
946
                return "", fmt.Errorf("could not get account key: cloud or ComputeClientFactory is nil")
2✔
947
        }
2✔
948
        accountClient, err := d.cloud.ComputeClientFactory.GetAccountClientForSub(subsID)
13✔
949
        if err != nil {
13✔
950
                return "", err
×
951
        }
×
952
        return d.cloud.GetStorageAccesskey(ctx, accountClient, account, resourceGroup, getLatestAccountKey)
13✔
953
}
954

955
// GetInfoFromSecret get info from k8s secret
956
// return <accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, error>
957
func (d *Driver) GetInfoFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, string, string, string, string, error) {
16✔
958
        if d.KubeClient == nil {
26✔
959
                return "", "", "", "", "", "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
10✔
960
        }
10✔
961

962
        secret, err := d.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
963
        if err != nil {
8✔
964
                return "", "", "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
965
        }
2✔
966

967
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
968
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
969
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
970
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
971
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
972
        spnClientID := strings.TrimSpace(string(secret.Data[storageSPNClientIDField][:]))
4✔
973
        spnTenantID := strings.TrimSpace(string(secret.Data[storageSPNTenantIDField][:]))
4✔
974

4✔
975
        klog.V(4).Infof("got storage account(%s) from secret(%s) namespace(%s)", accountName, secretName, secretNamespace)
4✔
976
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
977
}
978

979
// getSubnetResourceID get default subnet resource ID from cloud provider config
980
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
981
        subsID := d.cloud.SubscriptionID
6✔
982
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
983
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
984
        }
4✔
985

986
        if len(vnetResourceGroup) == 0 {
10✔
987
                vnetResourceGroup = d.cloud.ResourceGroup
4✔
988
                if len(d.cloud.VnetResourceGroup) > 0 {
7✔
989
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
990
                }
3✔
991
        }
992

993
        if len(vnetName) == 0 {
10✔
994
                vnetName = d.cloud.VnetName
4✔
995
        }
4✔
996

997
        if len(subnetName) == 0 {
10✔
998
                subnetName = d.cloud.SubnetName
4✔
999
        }
4✔
1000
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
1001
}
1002

1003
func (d *Driver) useDataPlaneAPI(ctx context.Context, volumeID, accountName string) bool {
9✔
1004
        cache, err := d.dataPlaneAPIVolCache.Get(ctx, volumeID, azcache.CacheReadTypeDefault)
9✔
1005
        if err != nil {
9✔
1006
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
1007
        }
×
1008
        if cache != nil {
12✔
1009
                return true
3✔
1010
        }
3✔
1011
        cache, err = d.dataPlaneAPIVolCache.Get(ctx, accountName, azcache.CacheReadTypeDefault)
6✔
1012
        if err != nil {
6✔
1013
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
1014
        }
×
1015
        if cache != nil {
6✔
1016
                return true
×
1017
        }
×
1018
        return false
6✔
1019
}
1020

1021
// appendDefaultMountOptions return mount options combined with mountOptions and defaultMountOptions
1022
func appendDefaultMountOptions(mountOptions []string, tmpPath, containerName string) []string {
6✔
1023
        var defaultMountOptions = map[string]string{
6✔
1024
                "--pre-mount-validate": "true",
6✔
1025
                "--use-https":          "true",
6✔
1026
                "--tmp-path":           tmpPath,
6✔
1027
                "--container-name":     containerName,
6✔
1028
                // prevent billing charges on mounting
6✔
1029
                "--cancel-list-on-mount-seconds": "10",
6✔
1030
                // allow remounting using a non-empty tmp-path
6✔
1031
                "--empty-dir-check": "false",
6✔
1032
        }
6✔
1033

6✔
1034
        // stores the mount options already included in mountOptions
6✔
1035
        included := make(map[string]bool)
6✔
1036

6✔
1037
        for _, mountOption := range mountOptions {
14✔
1038
                for k := range defaultMountOptions {
56✔
1039
                        if strings.HasPrefix(mountOption, k) {
52✔
1040
                                included[k] = true
4✔
1041
                        }
4✔
1042
                }
1043
        }
1044

1045
        allMountOptions := mountOptions
6✔
1046

6✔
1047
        for k, v := range defaultMountOptions {
42✔
1048
                if _, isIncluded := included[k]; !isIncluded {
68✔
1049
                        if v != "" {
63✔
1050
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
31✔
1051
                        } else {
32✔
1052
                                allMountOptions = append(allMountOptions, k)
1✔
1053
                        }
1✔
1054
                }
1055
        }
1056

1057
        return allMountOptions
6✔
1058
}
1059

1060
// chmodIfPermissionMismatch only perform chmod when permission mismatches
1061
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
3✔
1062
        info, err := os.Lstat(targetPath)
3✔
1063
        if err != nil {
4✔
1064
                return err
1✔
1065
        }
1✔
1066
        perm := info.Mode() & os.ModePerm
2✔
1067
        if perm != mode {
3✔
1068
                klog.V(2).Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), mode)
1✔
1069
                if err := os.Chmod(targetPath, mode); err != nil {
1✔
1070
                        return err
×
1071
                }
×
1072
        } else {
1✔
1073
                klog.V(2).Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
1✔
1074
        }
1✔
1075
        return nil
2✔
1076
}
1077

1078
func createStorageAccountSecret(account, key string) map[string]string {
1✔
1079
        secret := make(map[string]string)
1✔
1080
        secret[defaultSecretAccountName] = account
1✔
1081
        secret[defaultSecretAccountKey] = key
1✔
1082
        return secret
1✔
1083
}
1✔
1084

1085
// setKeyValueInMap set key/value pair in map
1086
// key in the map is case insensitive, if key already exists, overwrite existing value
1087
func setKeyValueInMap(m map[string]string, key, value string) {
10✔
1088
        if m == nil {
11✔
1089
                return
1✔
1090
        }
1✔
1091
        for k := range m {
32✔
1092
                if strings.EqualFold(k, key) {
25✔
1093
                        m[k] = value
2✔
1094
                        return
2✔
1095
                }
2✔
1096
        }
1097
        m[key] = value
7✔
1098
}
1099

1100
// getValueInMap get value from map by key
1101
// key in the map is case insensitive
1102
func getValueInMap(m map[string]string, key string) string {
18✔
1103
        if m == nil {
19✔
1104
                return ""
1✔
1105
        }
1✔
1106
        for k, v := range m {
43✔
1107
                if strings.EqualFold(k, key) {
35✔
1108
                        return v
9✔
1109
                }
9✔
1110
        }
1111
        return ""
8✔
1112
}
1113

1114
// replaceWithMap replace key with value for str
1115
func replaceWithMap(str string, m map[string]string) string {
16✔
1116
        for k, v := range m {
21✔
1117
                if k != "" {
9✔
1118
                        str = strings.ReplaceAll(str, k, v)
4✔
1119
                }
4✔
1120
        }
1121
        return str
16✔
1122
}
1123

1124
func isSupportedFSGroupChangePolicy(policy string) bool {
29✔
1125
        if policy == "" {
51✔
1126
                return true
22✔
1127
        }
22✔
1128
        for _, v := range supportedFSGroupChangePolicyList {
25✔
1129
                if policy == v {
21✔
1130
                        return true
3✔
1131
                }
3✔
1132
        }
1133
        return false
4✔
1134
}
1135

1136
func isReadOnlyFromCapability(vc *csi.VolumeCapability) bool {
9✔
1137
        if vc.GetAccessMode() == nil {
11✔
1138
                return false
2✔
1139
        }
2✔
1140
        mode := vc.GetAccessMode().GetMode()
7✔
1141
        return (mode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY ||
7✔
1142
                mode == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY)
7✔
1143
}
1144

1145
// generateVolumeName returns a PV name with clusterName prefix. The function
1146
// should be used to generate a name of GCE PD or Cinder volume. It basically
1147
// adds "<clusterName>-dynamic-" before the PV name, making sure the resulting
1148
// string fits given length and cuts "dynamic" if not.
1149
func generateVolumeName(clusterName, pvName string, maxLength int) string {
3✔
1150
        prefix := clusterName + "-dynamic"
3✔
1151
        pvLen := len(pvName)
3✔
1152
        // cut the "<clusterName>-dynamic" to fit full pvName into maxLength
3✔
1153
        // +1 for the '-' dash
3✔
1154
        if pvLen+1+len(prefix) > maxLength {
5✔
1155
                prefix = prefix[:maxLength-pvLen-1]
2✔
1156
        }
2✔
1157
        return prefix + "-" + pvName
3✔
1158
}
1159

1160
// serviceAccountToken represents the service account token sent from NodePublishVolume Request.
1161
// ref: https://kubernetes-csi.github.io/docs/token-requests.html
1162
type serviceAccountToken struct {
1163
        APIAzureADTokenExchange struct {
1164
                Token               string    `json:"token"`
1165
                ExpirationTimestamp time.Time `json:"expirationTimestamp"`
1166
        } `json:"api://AzureADTokenExchange"`
1167
}
1168

1169
// parseServiceAccountToken parses the bound service account token from the token passed from NodePublishVolume Request.
1170
// ref: https://kubernetes-csi.github.io/docs/token-requests.html
1171
func parseServiceAccountToken(tokenStr string) (string, error) {
6✔
1172
        if len(tokenStr) == 0 {
7✔
1173
                return "", fmt.Errorf("service account token is empty")
1✔
1174
        }
1✔
1175
        token := serviceAccountToken{}
5✔
1176
        if err := json.Unmarshal([]byte(tokenStr), &token); err != nil {
6✔
1177
                return "", fmt.Errorf("failed to unmarshal service account tokens, error: %w", err)
1✔
1178
        }
1✔
1179
        if token.APIAzureADTokenExchange.Token == "" {
6✔
1180
                return "", fmt.Errorf("token for audience %s not found", DefaultTokenAudience)
2✔
1181
        }
2✔
1182
        return token.APIAzureADTokenExchange.Token, nil
2✔
1183
}
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