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

kubernetes-sigs / blob-csi-driver / 13680967726

05 Mar 2025 04:40PM UTC coverage: 78.276%. Remained the same
13680967726

Pull #1863

github

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

Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.11.0 to 0.12.0.
- [Commits](https://github.com/golang/sync/compare/v0.11.0...v0.12.0)

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

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

2324 of 2969 relevant lines covered (78.28%)

7.65 hits per line

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

84.97
/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
        "errors"
22
        "flag"
23
        "fmt"
24
        "os"
25
        "strconv"
26
        "strings"
27
        "sync"
28
        "time"
29

30
        "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage"
31
        azstorage "github.com/Azure/azure-sdk-for-go/storage"
32
        az "github.com/Azure/go-autorest/autorest/azure"
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

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

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

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

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

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

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

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

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

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

321
        d.mounter = &mount.SafeFormatAndMount{
137✔
322
                Interface: mount.New(""),
137✔
323
                Exec:      utilexec.New(),
137✔
324
        }
137✔
325

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

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

137✔
358
        return &d
137✔
359
}
360

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

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

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

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

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

452
        return false
2✔
453
}
454

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

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

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

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

9✔
549
        if protocol == NFS {
11✔
550
                // nfs protocol does not need account key, return directly
2✔
551
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
552
        }
2✔
553

554
        if secretNamespace == "" {
13✔
555
                if pvcNamespace == "" {
12✔
556
                        secretNamespace = defaultNamespace
6✔
557
                } else {
6✔
558
                        secretNamespace = pvcNamespace
×
559
                }
×
560
        }
561

562
        if rgName == "" {
8✔
563
                rgName = d.cloud.ResourceGroup
1✔
564
        }
1✔
565

566
        if tenantID == "" {
14✔
567
                tenantID = d.cloud.TenantID
7✔
568
        }
7✔
569

570
        // if client id is specified, we only use service account token to get account key
571
        if clientID != "" {
7✔
572
                klog.V(2).Infof("clientID(%s) is specified, use service account token to get account key", clientID)
×
573
                if subsID == "" {
×
574
                        subsID = d.cloud.SubscriptionID
×
575
                }
×
576
                accountKey, err := d.cloud.GetStorageAccesskeyFromServiceAccountToken(ctx, subsID, accountName, rgName, clientID, tenantID, serviceAccountToken)
×
577
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
×
578
                return rgName, accountName, accountKey, containerName, authEnv, err
×
579
        }
580

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

652
        if containerName == "" {
4✔
653
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
654
        }
×
655

656
        if accountKey != "" {
8✔
657
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
658
        }
4✔
659

660
        if accountSasToken != "" {
5✔
661
                klog.V(2).Infof("accountSasToken is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
662
                authEnv = append(authEnv, "AZURE_STORAGE_SAS_TOKEN="+accountSasToken)
1✔
663
        }
1✔
664

665
        if msiSecret != "" {
5✔
666
                klog.V(2).Infof("msiSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
667
                authEnv = append(authEnv, "MSI_SECRET="+msiSecret)
1✔
668
        }
1✔
669

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

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

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

685
        return rgName, accountName, accountKey, containerName, authEnv, err
4✔
686
}
687

688
// GetStorageAccountAndContainer get storage account and container info
689
// returns <accountName, accountKey, accountSasToken, containerName>
690
// only for e2e testing
691
func (d *Driver) GetStorageAccountAndContainer(ctx context.Context, volumeID string, attrib, secrets map[string]string) (string, string, string, string, error) {
3✔
692
        var (
3✔
693
                subsID                string
3✔
694
                accountName           string
3✔
695
                accountKey            string
3✔
696
                accountSasToken       string
3✔
697
                containerName         string
3✔
698
                keyVaultURL           string
3✔
699
                keyVaultSecretName    string
3✔
700
                keyVaultSecretVersion string
3✔
701
                getLatestAccountKey   bool
3✔
702
                err                   error
3✔
703
        )
3✔
704

3✔
705
        for k, v := range attrib {
8✔
706
                switch strings.ToLower(k) {
5✔
707
                case subscriptionIDField:
×
708
                        subsID = v
×
709
                case containerNameField:
1✔
710
                        containerName = v
1✔
711
                case keyVaultURLField:
×
712
                        keyVaultURL = v
×
713
                case keyVaultSecretNameField:
1✔
714
                        keyVaultSecretName = v
1✔
715
                case keyVaultSecretVersionField:
1✔
716
                        keyVaultSecretVersion = v
1✔
717
                case storageAccountField:
×
718
                        accountName = v
×
719
                case storageAccountNameField: // for compatibility
1✔
720
                        accountName = v
1✔
721
                case getLatestAccountKeyField:
1✔
722
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
2✔
723
                                return "", "", "", "", fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
724
                        }
1✔
725
                }
726
        }
727

728
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
729
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
730
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
731
        if keyVaultURL != "" {
2✔
732
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
×
733
                if err != nil {
×
734
                        return "", "", "", "", err
×
735
                }
×
736
                if isSASToken(key) {
×
737
                        accountSasToken = key
×
738
                } else {
×
739
                        accountKey = key
×
740
                }
×
741
        } else {
2✔
742
                if len(secrets) == 0 {
4✔
743
                        var rgName string
2✔
744
                        rgName, accountName, containerName, _, _, err = GetContainerInfo(volumeID)
2✔
745
                        if err != nil {
2✔
746
                                return "", "", "", "", err
×
747
                        }
×
748

749
                        if rgName == "" {
2✔
750
                                rgName = d.cloud.ResourceGroup
×
751
                        }
×
752
                        accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, subsID, accountName, rgName, getLatestAccountKey)
2✔
753
                        if err != nil {
3✔
754
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
755
                        }
1✔
756
                }
757
        }
758

759
        if containerName == "" {
1✔
760
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
761
        }
×
762

763
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
764
}
765

766
func IsCorruptedDir(dir string) bool {
4✔
767
        _, pathErr := mount.PathExists(dir)
4✔
768
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
769
}
4✔
770

771
func isRetriableError(err error) bool {
5✔
772
        if err != nil {
9✔
773
                for _, v := range retriableErrors {
19✔
774
                        if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(v)) {
18✔
775
                                return true
3✔
776
                        }
3✔
777
                }
778
        }
779
        return false
2✔
780
}
781

782
func isSupportedProtocol(protocol string) bool {
19✔
783
        if protocol == "" {
20✔
784
                return true
1✔
785
        }
1✔
786
        for _, v := range supportedProtocolList {
46✔
787
                if protocol == v || protocol == NFSv3 {
44✔
788
                        return true
16✔
789
                }
16✔
790
        }
791
        return false
2✔
792
}
793

794
func isSupportedAccessTier(accessTier string) bool {
22✔
795
        if accessTier == "" {
37✔
796
                return true
15✔
797
        }
15✔
798
        for _, tier := range armstorage.PossibleAccessTierValues() {
32✔
799
                if accessTier == string(tier) {
28✔
800
                        return true
3✔
801
                }
3✔
802
        }
803
        return false
4✔
804
}
805

806
// container names can contain only lowercase letters, numbers, and hyphens,
807
// and must begin and end with a letter or a number
808
func isSupportedContainerNamePrefix(prefix string) bool {
21✔
809
        if prefix == "" {
34✔
810
                return true
13✔
811
        }
13✔
812
        if len(prefix) > 20 {
9✔
813
                return false
1✔
814
        }
1✔
815
        if prefix[0] == '-' {
8✔
816
                return false
1✔
817
        }
1✔
818
        for _, v := range prefix {
19✔
819
                if v != '-' && (v < '0' || v > '9') && (v < 'a' || v > 'z') {
17✔
820
                        return false
4✔
821
                }
4✔
822
        }
823
        return true
2✔
824
}
825

826
// isNFSProtocol checks if the protocol is NFS or AZNFS
827
func isNFSProtocol(protocol string) bool {
20✔
828
        protocol = strings.ToLower(protocol)
20✔
829
        return protocol == NFS || protocol == AZNFS || protocol == NFSv3
20✔
830
}
20✔
831

832
// get storage account from secrets map
833
func getStorageAccount(secrets map[string]string) (string, string, error) {
22✔
834
        if secrets == nil {
23✔
835
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
836
        }
1✔
837

838
        var accountName, accountKey string
21✔
839
        for k, v := range secrets {
64✔
840
                v = strings.TrimSpace(v)
43✔
841
                switch strings.ToLower(k) {
43✔
842
                case accountNameField:
7✔
843
                        accountName = v
7✔
844
                case defaultSecretAccountName: // for compatibility with built-in azurefile plugin
13✔
845
                        accountName = v
13✔
846
                case accountKeyField:
7✔
847
                        accountKey = v
7✔
848
                case defaultSecretAccountKey: // for compatibility with built-in azurefile plugin
12✔
849
                        accountKey = v
12✔
850
                }
851
        }
852

853
        if accountName == "" {
25✔
854
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountNameField, defaultSecretAccountName)
4✔
855
        }
4✔
856
        if accountKey == "" {
21✔
857
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountKeyField, defaultSecretAccountKey)
4✔
858
        }
4✔
859

860
        accountName = strings.TrimSpace(accountName)
13✔
861
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
13✔
862
        return accountName, accountKey, nil
13✔
863
}
864

865
func getContainerReference(containerName string, secrets map[string]string, env az.Environment) (*azstorage.Container, error) {
9✔
866
        accountName, accountKey, rerr := getStorageAccount(secrets)
9✔
867
        if rerr != nil {
11✔
868
                return nil, rerr
2✔
869
        }
2✔
870
        client, err := azstorage.NewBasicClientOnSovereignCloud(accountName, accountKey, env)
7✔
871
        if err != nil {
13✔
872
                return nil, err
6✔
873
        }
6✔
874
        blobClient := client.GetBlobService()
1✔
875
        container := blobClient.GetContainerReference(containerName)
1✔
876
        if container == nil {
1✔
877
                return nil, fmt.Errorf("ContainerReference of %s is nil", containerName)
×
878
        }
×
879
        return container, nil
1✔
880
}
881

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

912
// GetStorageAccesskey get Azure storage account key from
913
//  1. secrets (if not empty)
914
//  2. use k8s client identity to read from k8s secret
915
//  3. use cluster identity to get from storage account directly
916
func (d *Driver) GetStorageAccesskey(ctx context.Context, accountOptions *storage.AccountOptions, secrets map[string]string, secretName, secretNamespace string) (string, string, error) {
12✔
917
        if len(secrets) > 0 {
17✔
918
                return getStorageAccount(secrets)
5✔
919
        }
5✔
920

921
        // read from k8s secret first
922
        if secretName == "" {
12✔
923
                secretName = fmt.Sprintf(secretNameTemplate, accountOptions.Name)
5✔
924
        }
5✔
925
        _, accountKey, _, _, _, _, _, err := d.GetInfoFromSecret(ctx, secretName, secretNamespace) //nolint
7✔
926
        if err != nil && d.cloud != nil {
12✔
927
                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✔
928
                accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, accountOptions.SubscriptionID, accountOptions.Name, accountOptions.ResourceGroup, accountOptions.GetLatestAccountKey)
5✔
929
        }
5✔
930
        return accountOptions.Name, accountKey, err
7✔
931
}
932

933
// GetStorageAccesskeyWithSubsID get Azure storage account key from storage account directly
934
func (d *Driver) GetStorageAccesskeyWithSubsID(ctx context.Context, subsID, account, resourceGroup string, getLatestAccountKey bool) (string, error) {
15✔
935
        if d.cloud == nil || d.cloud.ComputeClientFactory == nil {
17✔
936
                return "", fmt.Errorf("could not get account key: cloud or ComputeClientFactory is nil")
2✔
937
        }
2✔
938
        accountClient, err := d.cloud.ComputeClientFactory.GetAccountClientForSub(subsID)
13✔
939
        if err != nil {
13✔
940
                return "", err
×
941
        }
×
942
        return d.cloud.GetStorageAccesskey(ctx, accountClient, account, resourceGroup, getLatestAccountKey)
13✔
943
}
944

945
// GetInfoFromSecret get info from k8s secret
946
// return <accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, error>
947
func (d *Driver) GetInfoFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, string, string, string, string, error) {
16✔
948
        if d.KubeClient == nil {
26✔
949
                return "", "", "", "", "", "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
10✔
950
        }
10✔
951

952
        secret, err := d.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
953
        if err != nil {
8✔
954
                return "", "", "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
955
        }
2✔
956

957
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
958
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
959
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
960
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
961
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
962
        spnClientID := strings.TrimSpace(string(secret.Data[storageSPNClientIDField][:]))
4✔
963
        spnTenantID := strings.TrimSpace(string(secret.Data[storageSPNTenantIDField][:]))
4✔
964

4✔
965
        klog.V(4).Infof("got storage account(%s) from secret(%s) namespace(%s)", accountName, secretName, secretNamespace)
4✔
966
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
967
}
968

969
// getSubnetResourceID get default subnet resource ID from cloud provider config
970
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
971
        subsID := d.cloud.SubscriptionID
6✔
972
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
973
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
974
        }
4✔
975

976
        if len(vnetResourceGroup) == 0 {
10✔
977
                vnetResourceGroup = d.cloud.ResourceGroup
4✔
978
                if len(d.cloud.VnetResourceGroup) > 0 {
7✔
979
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
980
                }
3✔
981
        }
982

983
        if len(vnetName) == 0 {
10✔
984
                vnetName = d.cloud.VnetName
4✔
985
        }
4✔
986

987
        if len(subnetName) == 0 {
10✔
988
                subnetName = d.cloud.SubnetName
4✔
989
        }
4✔
990
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
991
}
992

993
func (d *Driver) useDataPlaneAPI(ctx context.Context, volumeID, accountName string) bool {
9✔
994
        cache, err := d.dataPlaneAPIVolCache.Get(ctx, volumeID, azcache.CacheReadTypeDefault)
9✔
995
        if err != nil {
9✔
996
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
997
        }
×
998
        if cache != nil {
12✔
999
                return true
3✔
1000
        }
3✔
1001
        cache, err = d.dataPlaneAPIVolCache.Get(ctx, accountName, azcache.CacheReadTypeDefault)
6✔
1002
        if err != nil {
6✔
1003
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
1004
        }
×
1005
        if cache != nil {
6✔
1006
                return true
×
1007
        }
×
1008
        return false
6✔
1009
}
1010

1011
// appendDefaultMountOptions return mount options combined with mountOptions and defaultMountOptions
1012
func appendDefaultMountOptions(mountOptions []string, tmpPath, containerName string) []string {
4✔
1013
        var defaultMountOptions = map[string]string{
4✔
1014
                "--pre-mount-validate": "true",
4✔
1015
                "--use-https":          "true",
4✔
1016
                "--tmp-path":           tmpPath,
4✔
1017
                "--container-name":     containerName,
4✔
1018
                // prevent billing charges on mounting
4✔
1019
                "--cancel-list-on-mount-seconds": "10",
4✔
1020
                // allow remounting using a non-empty tmp-path
4✔
1021
                "--empty-dir-check": "false",
4✔
1022
        }
4✔
1023

4✔
1024
        // stores the mount options already included in mountOptions
4✔
1025
        included := make(map[string]bool)
4✔
1026

4✔
1027
        for _, mountOption := range mountOptions {
11✔
1028
                for k := range defaultMountOptions {
49✔
1029
                        if strings.HasPrefix(mountOption, k) {
46✔
1030
                                included[k] = true
4✔
1031
                        }
4✔
1032
                }
1033
        }
1034

1035
        allMountOptions := mountOptions
4✔
1036

4✔
1037
        for k, v := range defaultMountOptions {
28✔
1038
                if _, isIncluded := included[k]; !isIncluded {
44✔
1039
                        if v != "" {
40✔
1040
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
20✔
1041
                        } else {
20✔
1042
                                allMountOptions = append(allMountOptions, k)
×
1043
                        }
×
1044
                }
1045
        }
1046

1047
        return allMountOptions
4✔
1048
}
1049

1050
// chmodIfPermissionMismatch only perform chmod when permission mismatches
1051
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
3✔
1052
        info, err := os.Lstat(targetPath)
3✔
1053
        if err != nil {
4✔
1054
                return err
1✔
1055
        }
1✔
1056
        perm := info.Mode() & os.ModePerm
2✔
1057
        if perm != mode {
3✔
1058
                klog.V(2).Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), mode)
1✔
1059
                if err := os.Chmod(targetPath, mode); err != nil {
1✔
1060
                        return err
×
1061
                }
×
1062
        } else {
1✔
1063
                klog.V(2).Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
1✔
1064
        }
1✔
1065
        return nil
2✔
1066
}
1067

1068
func createStorageAccountSecret(account, key string) map[string]string {
1✔
1069
        secret := make(map[string]string)
1✔
1070
        secret[defaultSecretAccountName] = account
1✔
1071
        secret[defaultSecretAccountKey] = key
1✔
1072
        return secret
1✔
1073
}
1✔
1074

1075
// setKeyValueInMap set key/value pair in map
1076
// key in the map is case insensitive, if key already exists, overwrite existing value
1077
func setKeyValueInMap(m map[string]string, key, value string) {
7✔
1078
        if m == nil {
8✔
1079
                return
1✔
1080
        }
1✔
1081
        for k := range m {
17✔
1082
                if strings.EqualFold(k, key) {
13✔
1083
                        m[k] = value
2✔
1084
                        return
2✔
1085
                }
2✔
1086
        }
1087
        m[key] = value
4✔
1088
}
1089

1090
// getValueInMap get value from map by key
1091
// key in the map is case insensitive
1092
func getValueInMap(m map[string]string, key string) string {
12✔
1093
        if m == nil {
13✔
1094
                return ""
1✔
1095
        }
1✔
1096
        for k, v := range m {
23✔
1097
                if strings.EqualFold(k, key) {
16✔
1098
                        return v
4✔
1099
                }
4✔
1100
        }
1101
        return ""
7✔
1102
}
1103

1104
// replaceWithMap replace key with value for str
1105
func replaceWithMap(str string, m map[string]string) string {
14✔
1106
        for k, v := range m {
19✔
1107
                if k != "" {
9✔
1108
                        str = strings.ReplaceAll(str, k, v)
4✔
1109
                }
4✔
1110
        }
1111
        return str
14✔
1112
}
1113

1114
func isSupportedFSGroupChangePolicy(policy string) bool {
27✔
1115
        if policy == "" {
47✔
1116
                return true
20✔
1117
        }
20✔
1118
        for _, v := range supportedFSGroupChangePolicyList {
25✔
1119
                if policy == v {
21✔
1120
                        return true
3✔
1121
                }
3✔
1122
        }
1123
        return false
4✔
1124
}
1125

1126
func isReadOnlyFromCapability(vc *csi.VolumeCapability) bool {
7✔
1127
        if vc.GetAccessMode() == nil {
9✔
1128
                return false
2✔
1129
        }
2✔
1130
        mode := vc.GetAccessMode().GetMode()
5✔
1131
        return (mode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY ||
5✔
1132
                mode == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY)
5✔
1133
}
1134

1135
// generateVolumeName returns a PV name with clusterName prefix. The function
1136
// should be used to generate a name of GCE PD or Cinder volume. It basically
1137
// adds "<clusterName>-dynamic-" before the PV name, making sure the resulting
1138
// string fits given length and cuts "dynamic" if not.
1139
func generateVolumeName(clusterName, pvName string, maxLength int) string {
3✔
1140
        prefix := clusterName + "-dynamic"
3✔
1141
        pvLen := len(pvName)
3✔
1142
        // cut the "<clusterName>-dynamic" to fit full pvName into maxLength
3✔
1143
        // +1 for the '-' dash
3✔
1144
        if pvLen+1+len(prefix) > maxLength {
5✔
1145
                prefix = prefix[:maxLength-pvLen-1]
2✔
1146
        }
2✔
1147
        return prefix + "-" + pvName
3✔
1148
}
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