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

kubernetes-sigs / blob-csi-driver / 10512035464

22 Aug 2024 04:32PM CUT coverage: 73.88%. Remained the same
10512035464

Pull #1553

github

web-flow
chore(deps): bump github.com/onsi/ginkgo/v2 from 2.19.1 to 2.20.1

Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.19.1 to 2.20.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.19.1...v2.20.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #1553: chore(deps): bump github.com/onsi/ginkgo/v2 from 2.19.1 to 2.20.1

2260 of 3059 relevant lines covered (73.88%)

7.09 hits per line

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

84.82
/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
        k8sutil "k8s.io/kubernetes/pkg/volume/util"
43
        mount "k8s.io/mount-utils"
44
        utilexec "k8s.io/utils/exec"
45

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

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

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

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

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

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

144
        defaultNamespace = "default"
145

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

153
        VolumeID = "volumeid"
154

155
        defaultStorageEndPointSuffix = "core.windows.net"
156

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

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

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

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

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

213
// Driver implements all interfaces of CSI drivers
214
type Driver struct {
215
        csicommon.CSIDriver
216

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

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

293
        var err error
136✔
294
        getter := func(key string) (interface{}, error) { return nil, nil }
162✔
295
        if d.accountSearchCache, err = azcache.NewTimedCache(time.Minute, getter, false); err != nil {
136✔
296
                klog.Fatalf("%v", err)
×
297
        }
×
298
        if d.dataPlaneAPIVolCache, err = azcache.NewTimedCache(24*30*time.Hour, getter, false); err != nil {
136✔
299
                klog.Fatalf("%v", err)
×
300
        }
×
301
        if d.azcopySasTokenCache, err = azcache.NewTimedCache(15*time.Minute, getter, false); err != nil {
136✔
302
                klog.Fatalf("%v", err)
×
303
        }
×
304

305
        if options.VolStatsCacheExpireInMinutes <= 0 {
272✔
306
                options.VolStatsCacheExpireInMinutes = 10 // default expire in 10 minutes
136✔
307
        }
136✔
308
        if d.volStatsCache, err = azcache.NewTimedCache(time.Duration(options.VolStatsCacheExpireInMinutes)*time.Minute, getter, false); err != nil {
136✔
309
                klog.Fatalf("%v", err)
×
310
        }
×
311
        if d.subnetCache, err = azcache.NewTimedCache(10*time.Minute, getter, false); err != nil {
136✔
312
                klog.Fatalf("%v", err)
×
313
        }
×
314

315
        d.mounter = &mount.SafeFormatAndMount{
136✔
316
                Interface: mount.New(""),
136✔
317
                Exec:      utilexec.New(),
136✔
318
        }
136✔
319

136✔
320
        // Initialize default library driver
136✔
321
        d.AddControllerServiceCapabilities(
136✔
322
                []csi.ControllerServiceCapability_RPC_Type{
136✔
323
                        csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
136✔
324
                        //csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
136✔
325
                        //csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS,
136✔
326
                        csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
136✔
327
                        csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
136✔
328
                        csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
136✔
329
                })
136✔
330
        d.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
136✔
331
                csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
136✔
332
                csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY,
136✔
333
                csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
136✔
334
                csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
136✔
335
                csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
136✔
336
                csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER,
136✔
337
                csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
136✔
338
        })
136✔
339

136✔
340
        nodeCap := []csi.NodeServiceCapability_RPC_Type{
136✔
341
                csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
136✔
342
                csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
136✔
343
        }
136✔
344
        if d.enableGetVolumeStats {
136✔
345
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_GET_VOLUME_STATS)
×
346
        }
×
347
        if d.enableVolumeMountGroup {
136✔
348
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_VOLUME_MOUNT_GROUP)
×
349
        }
×
350
        d.AddNodeServiceCapabilities(nodeCap)
136✔
351

136✔
352
        return &d
136✔
353
}
354

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

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

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

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

437
func checkContainerNameBeginAndEnd(containerName string) bool {
12✔
438
        length := len(containerName)
12✔
439
        if (('a' <= containerName[0] && containerName[0] <= 'z') ||
12✔
440
                ('0' <= containerName[0] && containerName[0] <= '9')) &&
12✔
441
                (('a' <= containerName[length-1] && containerName[length-1] <= 'z') ||
12✔
442
                        ('0' <= containerName[length-1] && containerName[length-1] <= '9')) {
22✔
443
                return true
10✔
444
        }
10✔
445

446
        return false
2✔
447
}
448

449
// isSASToken checks if the key contains the patterns.
450
// SAS token format could refer to https://docs.microsoft.com/en-us/rest/api/eventhub/generate-sas-token
451
func isSASToken(key string) bool {
3✔
452
        return strings.HasPrefix(key, "?")
3✔
453
}
3✔
454

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

464
        var (
10✔
465
                subsID                  string
10✔
466
                accountKey              string
10✔
467
                accountSasToken         string
10✔
468
                msiSecret               string
10✔
469
                storageSPNClientSecret  string
10✔
470
                storageSPNClientID      string
10✔
471
                storageSPNTenantID      string
10✔
472
                secretName              string
10✔
473
                pvcNamespace            string
10✔
474
                keyVaultURL             string
10✔
475
                keyVaultSecretName      string
10✔
476
                keyVaultSecretVersion   string
10✔
477
                azureStorageAuthType    string
10✔
478
                authEnv                 []string
10✔
479
                getAccountKeyFromSecret bool
10✔
480
                getLatestAccountKey     bool
10✔
481
                clientID                string
10✔
482
                tenantID                string
10✔
483
                serviceAccountToken     string
10✔
484
        )
10✔
485

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

9✔
543
        if protocol == NFS {
11✔
544
                // nfs protocol does not need account key, return directly
2✔
545
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
546
        }
2✔
547

548
        if secretNamespace == "" {
13✔
549
                if pvcNamespace == "" {
12✔
550
                        secretNamespace = defaultNamespace
6✔
551
                } else {
6✔
552
                        secretNamespace = pvcNamespace
×
553
                }
×
554
        }
555

556
        if rgName == "" {
8✔
557
                rgName = d.cloud.ResourceGroup
1✔
558
        }
1✔
559

560
        if tenantID == "" {
14✔
561
                tenantID = d.cloud.TenantID
7✔
562
        }
7✔
563

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

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

646
        if containerName == "" {
4✔
647
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
648
        }
×
649

650
        if accountKey != "" {
8✔
651
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
652
        }
4✔
653

654
        if accountSasToken != "" {
5✔
655
                klog.V(2).Infof("accountSasToken is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
656
                authEnv = append(authEnv, "AZURE_STORAGE_SAS_TOKEN="+accountSasToken)
1✔
657
        }
1✔
658

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

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

669
        if storageSPNClientID != "" {
4✔
670
                klog.V(2).Infof("storageSPNClientID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNClientID, accountName, containerName)
×
671
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+storageSPNClientID)
×
672
        }
×
673

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

679
        return rgName, accountName, accountKey, containerName, authEnv, err
4✔
680
}
681

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

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

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

743
                        if rgName == "" {
2✔
744
                                rgName = d.cloud.ResourceGroup
×
745
                        }
×
746

747
                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName, getLatestAccountKey)
2✔
748
                        if err != nil {
3✔
749
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
750
                        }
1✔
751
                }
752
        }
753

754
        if containerName == "" {
1✔
755
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
756
        }
×
757

758
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
759
}
760

761
func IsCorruptedDir(dir string) bool {
4✔
762
        _, pathErr := mount.PathExists(dir)
4✔
763
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
764
}
4✔
765

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

777
func isSupportedProtocol(protocol string) bool {
19✔
778
        if protocol == "" {
20✔
779
                return true
1✔
780
        }
1✔
781
        for _, v := range supportedProtocolList {
46✔
782
                if protocol == v || protocol == NFSv3 {
44✔
783
                        return true
16✔
784
                }
16✔
785
        }
786
        return false
2✔
787
}
788

789
func isSupportedAccessTier(accessTier string) bool {
22✔
790
        if accessTier == "" {
37✔
791
                return true
15✔
792
        }
15✔
793
        for _, tier := range armstorage.PossibleAccessTierValues() {
32✔
794
                if accessTier == string(tier) {
28✔
795
                        return true
3✔
796
                }
3✔
797
        }
798
        return false
4✔
799
}
800

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

821
// isNFSProtocol checks if the protocol is NFS or AZNFS
822
func isNFSProtocol(protocol string) bool {
20✔
823
        protocol = strings.ToLower(protocol)
20✔
824
        return protocol == NFS || protocol == AZNFS || protocol == NFSv3
20✔
825
}
20✔
826

827
// get storage account from secrets map
828
func getStorageAccount(secrets map[string]string) (string, string, error) {
22✔
829
        if secrets == nil {
23✔
830
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
831
        }
1✔
832

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

848
        if accountName == "" {
25✔
849
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountNameField, defaultSecretAccountName)
4✔
850
        }
4✔
851
        if accountKey == "" {
21✔
852
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountKeyField, defaultSecretAccountKey)
4✔
853
        }
4✔
854

855
        accountName = strings.TrimSpace(accountName)
13✔
856
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
13✔
857
        return accountName, accountKey, nil
13✔
858
}
859

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

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

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

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

928
// GetInfoFromSecret get info from k8s secret
929
// return <accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, error>
930
func (d *Driver) GetInfoFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, string, string, string, string, error) {
16✔
931
        if d.KubeClient == nil {
26✔
932
                return "", "", "", "", "", "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
10✔
933
        }
10✔
934

935
        secret, err := d.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
936
        if err != nil {
8✔
937
                return "", "", "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
938
        }
2✔
939

940
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
941
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
942
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
943
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
944
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
945
        spnClientID := strings.TrimSpace(string(secret.Data[storageSPNClientIDField][:]))
4✔
946
        spnTenantID := strings.TrimSpace(string(secret.Data[storageSPNTenantIDField][:]))
4✔
947

4✔
948
        klog.V(4).Infof("got storage account(%s) from secret(%s) namespace(%s)", accountName, secretName, secretNamespace)
4✔
949
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
950
}
951

952
// getSubnetResourceID get default subnet resource ID from cloud provider config
953
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
954
        subsID := d.cloud.SubscriptionID
6✔
955
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
956
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
957
        }
4✔
958

959
        if len(vnetResourceGroup) == 0 {
10✔
960
                vnetResourceGroup = d.cloud.ResourceGroup
4✔
961
                if len(d.cloud.VnetResourceGroup) > 0 {
7✔
962
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
963
                }
3✔
964
        }
965

966
        if len(vnetName) == 0 {
10✔
967
                vnetName = d.cloud.VnetName
4✔
968
        }
4✔
969

970
        if len(subnetName) == 0 {
10✔
971
                subnetName = d.cloud.SubnetName
4✔
972
        }
4✔
973
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
974
}
975

976
func (d *Driver) useDataPlaneAPI(volumeID, accountName string) bool {
10✔
977
        cache, err := d.dataPlaneAPIVolCache.Get(volumeID, azcache.CacheReadTypeDefault)
10✔
978
        if err != nil {
10✔
979
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
980
        }
×
981
        if cache != nil {
13✔
982
                return true
3✔
983
        }
3✔
984
        cache, err = d.dataPlaneAPIVolCache.Get(accountName, azcache.CacheReadTypeDefault)
7✔
985
        if err != nil {
7✔
986
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
987
        }
×
988
        if cache != nil {
7✔
989
                return true
×
990
        }
×
991
        return false
7✔
992
}
993

994
// appendDefaultMountOptions return mount options combined with mountOptions and defaultMountOptions
995
func appendDefaultMountOptions(mountOptions []string, tmpPath, containerName string) []string {
4✔
996
        var defaultMountOptions = map[string]string{
4✔
997
                "--pre-mount-validate": "true",
4✔
998
                "--use-https":          "true",
4✔
999
                "--tmp-path":           tmpPath,
4✔
1000
                "--container-name":     containerName,
4✔
1001
                // prevent billing charges on mounting
4✔
1002
                "--cancel-list-on-mount-seconds": "10",
4✔
1003
                // allow remounting using a non-empty tmp-path
4✔
1004
                "--empty-dir-check": "false",
4✔
1005
        }
4✔
1006

4✔
1007
        // stores the mount options already included in mountOptions
4✔
1008
        included := make(map[string]bool)
4✔
1009

4✔
1010
        for _, mountOption := range mountOptions {
11✔
1011
                for k := range defaultMountOptions {
49✔
1012
                        if strings.HasPrefix(mountOption, k) {
46✔
1013
                                included[k] = true
4✔
1014
                        }
4✔
1015
                }
1016
        }
1017

1018
        allMountOptions := mountOptions
4✔
1019

4✔
1020
        for k, v := range defaultMountOptions {
28✔
1021
                if _, isIncluded := included[k]; !isIncluded {
44✔
1022
                        if v != "" {
40✔
1023
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
20✔
1024
                        } else {
20✔
1025
                                allMountOptions = append(allMountOptions, k)
×
1026
                        }
×
1027
                }
1028
        }
1029

1030
        return allMountOptions
4✔
1031
}
1032

1033
// chmodIfPermissionMismatch only perform chmod when permission mismatches
1034
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
3✔
1035
        info, err := os.Lstat(targetPath)
3✔
1036
        if err != nil {
4✔
1037
                return err
1✔
1038
        }
1✔
1039
        perm := info.Mode() & os.ModePerm
2✔
1040
        if perm != mode {
3✔
1041
                klog.V(2).Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), mode)
1✔
1042
                if err := os.Chmod(targetPath, mode); err != nil {
1✔
1043
                        return err
×
1044
                }
×
1045
        } else {
1✔
1046
                klog.V(2).Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
1✔
1047
        }
1✔
1048
        return nil
2✔
1049
}
1050

1051
func createStorageAccountSecret(account, key string) map[string]string {
1✔
1052
        secret := make(map[string]string)
1✔
1053
        secret[defaultSecretAccountName] = account
1✔
1054
        secret[defaultSecretAccountKey] = key
1✔
1055
        return secret
1✔
1056
}
1✔
1057

1058
// setKeyValueInMap set key/value pair in map
1059
// key in the map is case insensitive, if key already exists, overwrite existing value
1060
func setKeyValueInMap(m map[string]string, key, value string) {
7✔
1061
        if m == nil {
8✔
1062
                return
1✔
1063
        }
1✔
1064
        for k := range m {
17✔
1065
                if strings.EqualFold(k, key) {
13✔
1066
                        m[k] = value
2✔
1067
                        return
2✔
1068
                }
2✔
1069
        }
1070
        m[key] = value
4✔
1071
}
1072

1073
// getValueInMap get value from map by key
1074
// key in the map is case insensitive
1075
func getValueInMap(m map[string]string, key string) string {
12✔
1076
        if m == nil {
13✔
1077
                return ""
1✔
1078
        }
1✔
1079
        for k, v := range m {
23✔
1080
                if strings.EqualFold(k, key) {
16✔
1081
                        return v
4✔
1082
                }
4✔
1083
        }
1084
        return ""
7✔
1085
}
1086

1087
// replaceWithMap replace key with value for str
1088
func replaceWithMap(str string, m map[string]string) string {
15✔
1089
        for k, v := range m {
20✔
1090
                if k != "" {
9✔
1091
                        str = strings.ReplaceAll(str, k, v)
4✔
1092
                }
4✔
1093
        }
1094
        return str
15✔
1095
}
1096

1097
func isSupportedFSGroupChangePolicy(policy string) bool {
27✔
1098
        if policy == "" {
47✔
1099
                return true
20✔
1100
        }
20✔
1101
        for _, v := range supportedFSGroupChangePolicyList {
25✔
1102
                if policy == v {
21✔
1103
                        return true
3✔
1104
                }
3✔
1105
        }
1106
        return false
4✔
1107
}
1108

1109
func isReadOnlyFromCapability(vc *csi.VolumeCapability) bool {
7✔
1110
        if vc.GetAccessMode() == nil {
9✔
1111
                return false
2✔
1112
        }
2✔
1113
        mode := vc.GetAccessMode().GetMode()
5✔
1114
        return (mode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY ||
5✔
1115
                mode == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY)
5✔
1116
}
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