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

kubernetes-sigs / blob-csi-driver / 7198555417

13 Dec 2023 05:01PM UTC coverage: 78.227%. Remained the same
7198555417

Pull #1171

github

web-flow
chore(deps): bump github/codeql-action from 2 to 3

Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #1171: chore(deps): bump github/codeql-action from 2 to 3

1933 of 2471 relevant lines covered (78.23%)

6.93 hits per line

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

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

29
        "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-09-01/storage"
30
        azstorage "github.com/Azure/azure-sdk-for-go/storage"
31
        az "github.com/Azure/go-autorest/autorest/azure"
32
        "github.com/container-storage-interface/spec/lib/go/csi"
33
        "github.com/pborman/uuid"
34
        v1 "k8s.io/api/core/v1"
35
        "k8s.io/apimachinery/pkg/api/errors"
36
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37
        "k8s.io/client-go/kubernetes"
38
        "k8s.io/klog/v2"
39
        k8sutil "k8s.io/kubernetes/pkg/volume/util"
40
        mount "k8s.io/mount-utils"
41
        utilexec "k8s.io/utils/exec"
42

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

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

117
        // See https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
118
        containerNameMinLength = 3
119
        containerNameMaxLength = 63
120

121
        accountNotProvisioned                   = "StorageAccountIsNotProvisioned"
122
        tooManyRequests                         = "TooManyRequests"
123
        clientThrottled                         = "client throttled"
124
        containerBeingDeletedDataplaneAPIError  = "ContainerBeingDeleted"
125
        containerBeingDeletedManagementAPIError = "container is being deleted"
126
        statusCodeNotFound                      = "StatusCode=404"
127
        httpCodeNotFound                        = "HTTPStatusCode: 404"
128

129
        // 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
130
        containerMaxSize = 100 * util.TiB
131

132
        subnetTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s"
133

134
        defaultNamespace = "default"
135

136
        pvcNameKey           = "csi.storage.k8s.io/pvc/name"
137
        pvcNamespaceKey      = "csi.storage.k8s.io/pvc/namespace"
138
        pvNameKey            = "csi.storage.k8s.io/pv/name"
139
        pvcNameMetadata      = "${pvc.metadata.name}"
140
        pvcNamespaceMetadata = "${pvc.metadata.namespace}"
141
        pvNameMetadata       = "${pv.metadata.name}"
142

143
        VolumeID = "volumeid"
144

145
        defaultStorageEndPointSuffix = "core.windows.net"
146
)
147

148
var (
149
        supportedProtocolList = []string{Fuse, Fuse2, NFS}
150
        retriableErrors       = []string{accountNotProvisioned, tooManyRequests, statusCodeNotFound, containerBeingDeletedDataplaneAPIError, containerBeingDeletedManagementAPIError, clientThrottled}
151
)
152

153
// DriverOptions defines driver parameters specified in driver deployment
154
type DriverOptions struct {
155
        NodeID                                 string
156
        DriverName                             string
157
        BlobfuseProxyEndpoint                  string
158
        EnableBlobfuseProxy                    bool
159
        BlobfuseProxyConnTimout                int
160
        EnableBlobMockMount                    bool
161
        AllowInlineVolumeKeyAccessWithIdentity bool
162
        EnableGetVolumeStats                   bool
163
        AppendTimeStampInCacheDir              bool
164
        AppendMountErrorHelpLink               bool
165
        MountPermissions                       uint64
166
        EnableAznfsMount                       bool
167
        VolStatsCacheExpireInMinutes           int
168
        SasTokenExpirationMinutes              int
169
}
170

171
func (option *DriverOptions) AddFlags() {
1✔
172
        flag.StringVar(&option.BlobfuseProxyEndpoint, "blobfuse-proxy-endpoint", "unix://tmp/blobfuse-proxy.sock", "blobfuse-proxy endpoint")
1✔
173
        flag.StringVar(&option.NodeID, "nodeid", "", "node id")
1✔
174
        flag.StringVar(&option.DriverName, "drivername", DefaultDriverName, "name of the driver")
1✔
175
        flag.BoolVar(&option.EnableBlobfuseProxy, "enable-blobfuse-proxy", false, "using blobfuse proxy for mounts")
1✔
176
        flag.IntVar(&option.BlobfuseProxyConnTimout, "blobfuse-proxy-connect-timeout", 5, "blobfuse proxy connection timeout(seconds)")
1✔
177
        flag.BoolVar(&option.EnableBlobMockMount, "enable-blob-mock-mount", false, "enable mock mount(only for testing)")
1✔
178
        flag.BoolVar(&option.EnableGetVolumeStats, "enable-get-volume-stats", false, "allow GET_VOLUME_STATS on agent node")
1✔
179
        flag.BoolVar(&option.AppendTimeStampInCacheDir, "append-timestamp-cache-dir", false, "append timestamp into cache directory on agent node")
1✔
180
        flag.Uint64Var(&option.MountPermissions, "mount-permissions", 0777, "mounted folder permissions")
1✔
181
        flag.BoolVar(&option.AllowInlineVolumeKeyAccessWithIdentity, "allow-inline-volume-key-access-with-idenitity", false, "allow accessing storage account key using cluster identity for inline volume")
1✔
182
        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✔
183
        flag.BoolVar(&option.EnableAznfsMount, "enable-aznfs-mount", false, "replace nfs mount with aznfs mount")
1✔
184
        flag.IntVar(&option.VolStatsCacheExpireInMinutes, "vol-stats-cache-expire-in-minutes", 10, "The cache expire time in minutes for volume stats cache")
1✔
185
        flag.IntVar(&option.SasTokenExpirationMinutes, "sas-token-expiration-minutes", 1440, "sas token expiration minutes during volume cloning")
1✔
186
}
1✔
187

188
// Driver implements all interfaces of CSI drivers
189
type Driver struct {
190
        csicommon.CSIDriver
191

192
        cloud                 *azure.Cloud
193
        KubeClient            kubernetes.Interface
194
        blobfuseProxyEndpoint string
195
        // enableBlobMockMount is only for testing, DO NOT set as true in non-testing scenario
196
        enableBlobMockMount                    bool
197
        enableBlobfuseProxy                    bool
198
        enableGetVolumeStats                   bool
199
        allowInlineVolumeKeyAccessWithIdentity bool
200
        appendTimeStampInCacheDir              bool
201
        appendMountErrorHelpLink               bool
202
        blobfuseProxyConnTimout                int
203
        mountPermissions                       uint64
204
        enableAznfsMount                       bool
205
        mounter                                *mount.SafeFormatAndMount
206
        volLockMap                             *util.LockMap
207
        // A map storing all volumes with ongoing operations so that additional operations
208
        // for that same volume (as defined by VolumeID) return an Aborted error
209
        volumeLocks *volumeLocks
210
        // only for nfs feature
211
        subnetLockMap *util.LockMap
212
        // a map storing all volumes created by this driver <volumeName, accountName>
213
        volMap sync.Map
214
        // a timed cache storing all volumeIDs and storage accounts that are using data plane API
215
        dataPlaneAPIVolCache azcache.Resource
216
        // a timed cache storing account search history (solve account list throttling issue)
217
        accountSearchCache azcache.Resource
218
        // a timed cache storing volume stats <volumeID, volumeStats>
219
        volStatsCache azcache.Resource
220
        // sas expiry time for azcopy in volume clone
221
        sasTokenExpirationMinutes int
222
        // azcopy for provide exec mock for ut
223
        azcopy *util.Azcopy
224
}
225

226
// NewDriver Creates a NewCSIDriver object. Assumes vendor version is equal to driver version &
227
// does not support optional driver plugin info manifest field. Refer to CSI spec for more details.
228
func NewDriver(options *DriverOptions, kubeClient kubernetes.Interface, cloud *provider.Cloud) *Driver {
113✔
229
        var err error
113✔
230
        d := Driver{
113✔
231
                volLockMap:                             util.NewLockMap(),
113✔
232
                subnetLockMap:                          util.NewLockMap(),
113✔
233
                volumeLocks:                            newVolumeLocks(),
113✔
234
                blobfuseProxyEndpoint:                  options.BlobfuseProxyEndpoint,
113✔
235
                enableBlobfuseProxy:                    options.EnableBlobfuseProxy,
113✔
236
                allowInlineVolumeKeyAccessWithIdentity: options.AllowInlineVolumeKeyAccessWithIdentity,
113✔
237
                blobfuseProxyConnTimout:                options.BlobfuseProxyConnTimout,
113✔
238
                enableBlobMockMount:                    options.EnableBlobMockMount,
113✔
239
                enableGetVolumeStats:                   options.EnableGetVolumeStats,
113✔
240
                appendMountErrorHelpLink:               options.AppendMountErrorHelpLink,
113✔
241
                mountPermissions:                       options.MountPermissions,
113✔
242
                enableAznfsMount:                       options.EnableAznfsMount,
113✔
243
                sasTokenExpirationMinutes:              options.SasTokenExpirationMinutes,
113✔
244
                azcopy:                                 &util.Azcopy{},
113✔
245
                KubeClient:                             kubeClient,
113✔
246
                cloud:                                  cloud,
113✔
247
        }
113✔
248
        d.Name = options.DriverName
113✔
249
        d.Version = driverVersion
113✔
250
        d.NodeID = options.NodeID
113✔
251

113✔
252
        getter := func(key string) (interface{}, error) { return nil, nil }
118✔
253
        if d.accountSearchCache, err = azcache.NewTimedCache(time.Minute, getter, false); err != nil {
113✔
254
                klog.Fatalf("%v", err)
×
255
        }
×
256
        if d.dataPlaneAPIVolCache, err = azcache.NewTimedCache(10*time.Minute, getter, false); err != nil {
113✔
257
                klog.Fatalf("%v", err)
×
258
        }
×
259

260
        if options.VolStatsCacheExpireInMinutes <= 0 {
226✔
261
                options.VolStatsCacheExpireInMinutes = 10 // default expire in 10 minutes
113✔
262
        }
113✔
263
        if d.volStatsCache, err = azcache.NewTimedCache(time.Duration(options.VolStatsCacheExpireInMinutes)*time.Minute, getter, false); err != nil {
113✔
264
                klog.Fatalf("%v", err)
×
265
        }
×
266
        d.mounter = &mount.SafeFormatAndMount{
113✔
267
                Interface: mount.New(""),
113✔
268
                Exec:      utilexec.New(),
113✔
269
        }
113✔
270

113✔
271
        // Initialize default library driver
113✔
272
        d.AddControllerServiceCapabilities(
113✔
273
                []csi.ControllerServiceCapability_RPC_Type{
113✔
274
                        csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
113✔
275
                        //csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
113✔
276
                        //csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS,
113✔
277
                        csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
113✔
278
                        csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
113✔
279
                        csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
113✔
280
                })
113✔
281
        d.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
113✔
282
                csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
113✔
283
                csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY,
113✔
284
                csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
113✔
285
                csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
113✔
286
                csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
113✔
287
                csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER,
113✔
288
                csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
113✔
289
        })
113✔
290

113✔
291
        nodeCap := []csi.NodeServiceCapability_RPC_Type{
113✔
292
                csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
113✔
293
                csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
113✔
294
        }
113✔
295
        if d.enableGetVolumeStats {
113✔
296
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_GET_VOLUME_STATS)
×
297
        }
×
298
        d.AddNodeServiceCapabilities(nodeCap)
113✔
299

113✔
300
        return &d
113✔
301
}
302

303
// Run driver initialization
304
func (d *Driver) Run(endpoint string, testBool bool) {
2✔
305
        versionMeta, err := GetVersionYAML(d.Name)
2✔
306
        if err != nil {
2✔
307
                klog.Fatalf("%v", err)
×
308
        }
×
309
        klog.Infof("\nDRIVER INFORMATION:\n-------------------\n%s\n\nStreaming logs below:", versionMeta)
2✔
310

2✔
311
        s := csicommon.NewNonBlockingGRPCServer()
2✔
312
        // Driver d act as IdentityServer, ControllerServer and NodeServer
2✔
313
        s.Start(endpoint, d, d, d, testBool)
2✔
314
        s.Wait()
2✔
315
}
316

317
// GetContainerInfo get container info according to volume id
318
// the format of VolumeId is: rg#accountName#containerName#uuid#secretNamespace#subsID
319
//
320
// e.g.
321
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#"
322
// output: rg, f5713de20cde511e8ba4900, containerName, "" , ""
323
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#"
324
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, ""
325
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#subsID"
326
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, subsID
327
func GetContainerInfo(id string) (string, string, string, string, string, error) {
39✔
328
        segments := strings.Split(id, separator)
39✔
329
        if len(segments) < 3 {
48✔
330
                return "", "", "", "", "", fmt.Errorf("error parsing volume id: %q, should at least contain two #", id)
9✔
331
        }
9✔
332
        var secretNamespace, subsID string
30✔
333
        if len(segments) > 4 {
36✔
334
                secretNamespace = segments[4]
6✔
335
        }
6✔
336
        if len(segments) > 5 {
33✔
337
                subsID = segments[5]
3✔
338
        }
3✔
339
        return segments[0], segments[1], segments[2], secretNamespace, subsID, nil
30✔
340
}
341

342
// A container name must be a valid DNS name, conforming to the following naming rules:
343
//  1. Container names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character.
344
//  2. Every dash (-) character must be immediately preceded and followed by a letter or number; consecutive dashes are not permitted in container names.
345
//  3. All letters in a container name must be lowercase.
346
//  4. Container names must be from 3 through 63 characters long.
347
//
348
// See https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
349
func getValidContainerName(volumeName, protocol string) string {
5✔
350
        containerName := strings.ToLower(volumeName)
5✔
351
        if len(containerName) > containerNameMaxLength {
6✔
352
                containerName = containerName[0:containerNameMaxLength]
1✔
353
        }
1✔
354
        if !checkContainerNameBeginAndEnd(containerName) || len(containerName) < containerNameMinLength {
5✔
355
                // now we set as 63 for maximum container name length
×
356
                // todo: get cluster name
×
357
                containerName = k8sutil.GenerateVolumeName(fmt.Sprintf("pvc-%s", protocol), uuid.NewUUID().String(), 63)
×
358
                klog.Warningf("requested volume name (%s) is invalid, regenerated as (%q)", volumeName, containerName)
×
359
        }
×
360
        return strings.Replace(containerName, "--", "-", -1)
5✔
361
}
362

363
func checkContainerNameBeginAndEnd(containerName string) bool {
11✔
364
        length := len(containerName)
11✔
365
        if (('a' <= containerName[0] && containerName[0] <= 'z') ||
11✔
366
                ('0' <= containerName[0] && containerName[0] <= '9')) &&
11✔
367
                (('a' <= containerName[length-1] && containerName[length-1] <= 'z') ||
11✔
368
                        ('0' <= containerName[length-1] && containerName[length-1] <= '9')) {
20✔
369
                return true
9✔
370
        }
9✔
371

372
        return false
2✔
373
}
374

375
// isSASToken checks if the key contains the patterns.
376
// SAS token format could refer to https://docs.microsoft.com/en-us/rest/api/eventhub/generate-sas-token
377
func isSASToken(key string) bool {
3✔
378
        return strings.HasPrefix(key, "?")
3✔
379
}
3✔
380

381
// GetAuthEnv return <accountName, containerName, authEnv, error>
382
func (d *Driver) GetAuthEnv(ctx context.Context, volumeID, protocol string, attrib, secrets map[string]string) (string, string, string, string, []string, error) {
10✔
383
        rgName, accountName, containerName, secretNamespace, _, err := GetContainerInfo(volumeID)
10✔
384
        if err != nil {
12✔
385
                // ignore volumeID parsing error
2✔
386
                klog.V(2).Infof("parsing volumeID(%s) return with error: %v", volumeID, err)
2✔
387
                err = nil
2✔
388
        }
2✔
389

390
        var (
10✔
391
                subsID                  string
10✔
392
                accountKey              string
10✔
393
                accountSasToken         string
10✔
394
                msiSecret               string
10✔
395
                storageSPNClientSecret  string
10✔
396
                storageSPNClientID      string
10✔
397
                storageSPNTenantID      string
10✔
398
                secretName              string
10✔
399
                pvcNamespace            string
10✔
400
                keyVaultURL             string
10✔
401
                keyVaultSecretName      string
10✔
402
                keyVaultSecretVersion   string
10✔
403
                azureStorageAuthType    string
10✔
404
                authEnv                 []string
10✔
405
                getAccountKeyFromSecret bool
10✔
406
                getLatestAccountKey     bool
10✔
407
        )
10✔
408

10✔
409
        for k, v := range attrib {
36✔
410
                switch strings.ToLower(k) {
26✔
411
                case subscriptionIDField:
1✔
412
                        subsID = v
1✔
413
                case resourceGroupField:
×
414
                        rgName = v
×
415
                case containerNameField:
3✔
416
                        containerName = v
3✔
417
                case keyVaultURLField:
1✔
418
                        keyVaultURL = v
1✔
419
                case keyVaultSecretNameField:
1✔
420
                        keyVaultSecretName = v
1✔
421
                case keyVaultSecretVersionField:
1✔
422
                        keyVaultSecretVersion = v
1✔
423
                case storageAccountField:
2✔
424
                        accountName = v
2✔
425
                case storageAccountNameField: // for compatibility
1✔
426
                        accountName = v
1✔
427
                case secretNameField:
1✔
428
                        secretName = v
1✔
429
                case secretNamespaceField:
1✔
430
                        secretNamespace = v
1✔
431
                case pvcNamespaceKey:
1✔
432
                        pvcNamespace = v
1✔
433
                case getAccountKeyFromSecretField:
1✔
434
                        getAccountKeyFromSecret = strings.EqualFold(v, trueValue)
1✔
435
                case storageAuthTypeField:
×
436
                        azureStorageAuthType = v
×
437
                        authEnv = append(authEnv, "AZURE_STORAGE_AUTH_TYPE="+v)
×
438
                case storageIdentityClientIDField:
1✔
439
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_CLIENT_ID="+v)
1✔
440
                case storageIdentityObjectIDField:
1✔
441
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_OBJECT_ID="+v)
1✔
442
                case storageIdentityResourceIDField:
1✔
443
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_RESOURCE_ID="+v)
1✔
444
                case msiEndpointField:
1✔
445
                        authEnv = append(authEnv, "MSI_ENDPOINT="+v)
1✔
446
                case storageSPNClientIDField:
1✔
447
                        storageSPNClientID = v
1✔
448
                case storageSPNTenantIDField:
1✔
449
                        storageSPNTenantID = v
1✔
450
                case storageAADEndpointField:
1✔
451
                        authEnv = append(authEnv, "AZURE_STORAGE_AAD_ENDPOINT="+v)
1✔
452
                case getLatestAccountKeyField:
1✔
453
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
2✔
454
                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
455
                        }
1✔
456
                }
457
        }
458
        klog.V(2).Infof("volumeID(%s) authEnv: %s", volumeID, authEnv)
9✔
459

9✔
460
        if protocol == NFS {
11✔
461
                // nfs protocol does not need account key, return directly
2✔
462
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
463
        }
2✔
464

465
        if secretNamespace == "" {
13✔
466
                if pvcNamespace == "" {
12✔
467
                        secretNamespace = defaultNamespace
6✔
468
                } else {
6✔
469
                        secretNamespace = pvcNamespace
×
470
                }
×
471
        }
472

473
        if rgName == "" {
8✔
474
                rgName = d.cloud.ResourceGroup
1✔
475
        }
1✔
476

477
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
478
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
479
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
480
        if keyVaultURL != "" {
8✔
481
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
1✔
482
                if err != nil {
2✔
483
                        return rgName, accountName, accountKey, containerName, authEnv, err
1✔
484
                }
1✔
485
                if isSASToken(key) {
×
486
                        accountSasToken = key
×
487
                } else {
×
488
                        accountKey = key
×
489
                }
×
490
        } else {
6✔
491
                if len(secrets) == 0 {
11✔
492
                        if secretName == "" && accountName != "" {
9✔
493
                                secretName = fmt.Sprintf(secretNameTemplate, accountName)
4✔
494
                        }
4✔
495
                        if secretName != "" {
10✔
496
                                // read from k8s secret first
5✔
497
                                var name, spnClientID, spnTenantID string
5✔
498
                                name, accountKey, accountSasToken, msiSecret, storageSPNClientSecret, spnClientID, spnTenantID, err = d.GetInfoFromSecret(ctx, secretName, secretNamespace)
5✔
499
                                if name != "" {
5✔
500
                                        accountName = name
×
501
                                }
×
502
                                if spnClientID != "" {
5✔
503
                                        storageSPNClientID = spnClientID
×
504
                                }
×
505
                                if spnTenantID != "" {
5✔
506
                                        storageSPNTenantID = spnTenantID
×
507
                                }
×
508
                                if err != nil && strings.EqualFold(azureStorageAuthType, "msi") {
5✔
509
                                        klog.V(2).Infof("ignore error(%v) since secret is optional for auth type(%s)", err, azureStorageAuthType)
×
510
                                        err = nil
×
511
                                }
×
512
                                if err != nil && !getAccountKeyFromSecret && (azureStorageAuthType == "" || strings.EqualFold(azureStorageAuthType, "key")) {
10✔
513
                                        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✔
514
                                                accountName, secretNamespace, secretName, err)
5✔
515
                                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName, getLatestAccountKey)
5✔
516
                                        if err != nil {
7✔
517
                                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
2✔
518
                                        }
2✔
519
                                }
520
                        }
521
                } else {
1✔
522
                        for k, v := range secrets {
8✔
523
                                v = strings.TrimSpace(v)
7✔
524
                                switch strings.ToLower(k) {
7✔
525
                                case accountNameField:
1✔
526
                                        accountName = v
1✔
527
                                case defaultSecretAccountName: // for compatibility with built-in blobfuse plugin
1✔
528
                                        accountName = v
1✔
529
                                case accountKeyField:
1✔
530
                                        accountKey = v
1✔
531
                                case defaultSecretAccountKey: // for compatibility with built-in blobfuse plugin
1✔
532
                                        accountKey = v
1✔
533
                                case accountSasTokenField:
1✔
534
                                        accountSasToken = v
1✔
535
                                case msiSecretField:
1✔
536
                                        msiSecret = v
1✔
537
                                case storageSPNClientSecretField:
1✔
538
                                        storageSPNClientSecret = v
1✔
539
                                case storageSPNClientIDField:
×
540
                                        storageSPNClientID = v
×
541
                                case storageSPNTenantIDField:
×
542
                                        storageSPNTenantID = v
×
543
                                }
544
                        }
545
                }
546
        }
547

548
        if containerName == "" {
4✔
549
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
550
        }
×
551

552
        if accountKey != "" {
8✔
553
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
554
        }
4✔
555

556
        if accountSasToken != "" {
5✔
557
                klog.V(2).Infof("accountSasToken is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
558
                authEnv = append(authEnv, "AZURE_STORAGE_SAS_TOKEN="+accountSasToken)
1✔
559
        }
1✔
560

561
        if msiSecret != "" {
5✔
562
                klog.V(2).Infof("msiSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
563
                authEnv = append(authEnv, "MSI_SECRET="+msiSecret)
1✔
564
        }
1✔
565

566
        if storageSPNClientSecret != "" {
5✔
567
                klog.V(2).Infof("storageSPNClientSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
568
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_SECRET="+storageSPNClientSecret)
1✔
569
        }
1✔
570

571
        if storageSPNClientID != "" {
4✔
572
                klog.V(2).Infof("storageSPNClientID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNClientID, accountName, containerName)
×
573
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+storageSPNClientID)
×
574
        }
×
575

576
        if storageSPNTenantID != "" {
4✔
577
                klog.V(2).Infof("storageSPNTenantID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNTenantID, accountName, containerName)
×
578
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+storageSPNTenantID)
×
579
        }
×
580

581
        return rgName, accountName, accountKey, containerName, authEnv, err
4✔
582
}
583

584
// GetStorageAccountAndContainer get storage account and container info
585
// returns <accountName, accountKey, accountSasToken, containerName>
586
// only for e2e testing
587
func (d *Driver) GetStorageAccountAndContainer(ctx context.Context, volumeID string, attrib, secrets map[string]string) (string, string, string, string, error) {
3✔
588
        var (
3✔
589
                subsID                string
3✔
590
                accountName           string
3✔
591
                accountKey            string
3✔
592
                accountSasToken       string
3✔
593
                containerName         string
3✔
594
                keyVaultURL           string
3✔
595
                keyVaultSecretName    string
3✔
596
                keyVaultSecretVersion string
3✔
597
                getLatestAccountKey   bool
3✔
598
                err                   error
3✔
599
        )
3✔
600

3✔
601
        for k, v := range attrib {
8✔
602
                switch strings.ToLower(k) {
5✔
603
                case subscriptionIDField:
×
604
                        subsID = v
×
605
                case containerNameField:
1✔
606
                        containerName = v
1✔
607
                case keyVaultURLField:
×
608
                        keyVaultURL = v
×
609
                case keyVaultSecretNameField:
1✔
610
                        keyVaultSecretName = v
1✔
611
                case keyVaultSecretVersionField:
1✔
612
                        keyVaultSecretVersion = v
1✔
613
                case storageAccountField:
×
614
                        accountName = v
×
615
                case storageAccountNameField: // for compatibility
1✔
616
                        accountName = v
1✔
617
                case getLatestAccountKeyField:
1✔
618
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
2✔
619
                                return "", "", "", "", fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
620
                        }
1✔
621
                }
622
        }
623

624
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
625
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
626
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
627
        if keyVaultURL != "" {
2✔
628
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
×
629
                if err != nil {
×
630
                        return "", "", "", "", err
×
631
                }
×
632
                if isSASToken(key) {
×
633
                        accountSasToken = key
×
634
                } else {
×
635
                        accountKey = key
×
636
                }
×
637
        } else {
2✔
638
                if len(secrets) == 0 {
4✔
639
                        var rgName string
2✔
640
                        rgName, accountName, containerName, _, _, err = GetContainerInfo(volumeID)
2✔
641
                        if err != nil {
2✔
642
                                return "", "", "", "", err
×
643
                        }
×
644

645
                        if rgName == "" {
2✔
646
                                rgName = d.cloud.ResourceGroup
×
647
                        }
×
648

649
                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName, getLatestAccountKey)
2✔
650
                        if err != nil {
3✔
651
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
652
                        }
1✔
653
                }
654
        }
655

656
        if containerName == "" {
1✔
657
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
658
        }
×
659

660
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
661
}
662

663
func IsCorruptedDir(dir string) bool {
4✔
664
        _, pathErr := mount.PathExists(dir)
4✔
665
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
666
}
4✔
667

668
func isRetriableError(err error) bool {
5✔
669
        if err != nil {
9✔
670
                for _, v := range retriableErrors {
19✔
671
                        if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(v)) {
18✔
672
                                return true
3✔
673
                        }
3✔
674
                }
675
        }
676
        return false
2✔
677
}
678

679
func isSupportedProtocol(protocol string) bool {
17✔
680
        if protocol == "" {
18✔
681
                return true
1✔
682
        }
1✔
683
        for _, v := range supportedProtocolList {
40✔
684
                if protocol == v {
38✔
685
                        return true
14✔
686
                }
14✔
687
        }
688
        return false
2✔
689
}
690

691
func isSupportedAccessTier(accessTier string) bool {
20✔
692
        if accessTier == "" {
33✔
693
                return true
13✔
694
        }
13✔
695
        for _, tier := range storage.PossibleAccessTierValues() {
25✔
696
                if accessTier == string(tier) {
21✔
697
                        return true
3✔
698
                }
3✔
699
        }
700
        return false
4✔
701
}
702

703
// container names can contain only lowercase letters, numbers, and hyphens,
704
// and must begin and end with a letter or a number
705
func isSupportedContainerNamePrefix(prefix string) bool {
19✔
706
        if prefix == "" {
30✔
707
                return true
11✔
708
        }
11✔
709
        if len(prefix) > 20 {
9✔
710
                return false
1✔
711
        }
1✔
712
        if prefix[0] == '-' {
8✔
713
                return false
1✔
714
        }
1✔
715
        for _, v := range prefix {
19✔
716
                if v != '-' && (v < '0' || v > '9') && (v < 'a' || v > 'z') {
17✔
717
                        return false
4✔
718
                }
4✔
719
        }
720
        return true
2✔
721
}
722

723
// isNFSProtocol checks if the protocol is NFS or AZNFS
724
func isNFSProtocol(protocol string) bool {
21✔
725
        protocol = strings.ToLower(protocol)
21✔
726
        return protocol == NFS || protocol == AZNFS
21✔
727
}
21✔
728

729
// get storage account from secrets map
730
func getStorageAccount(secrets map[string]string) (string, string, error) {
18✔
731
        if secrets == nil {
19✔
732
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
733
        }
1✔
734

735
        var accountName, accountKey string
17✔
736
        for k, v := range secrets {
53✔
737
                v = strings.TrimSpace(v)
36✔
738
                switch strings.ToLower(k) {
36✔
739
                case accountNameField:
7✔
740
                        accountName = v
7✔
741
                case defaultSecretAccountName: // for compatibility with built-in azurefile plugin
9✔
742
                        accountName = v
9✔
743
                case accountKeyField:
7✔
744
                        accountKey = v
7✔
745
                case defaultSecretAccountKey: // for compatibility with built-in azurefile plugin
9✔
746
                        accountKey = v
9✔
747
                }
748
        }
749

750
        if accountName == "" {
21✔
751
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountNameField, defaultSecretAccountName)
4✔
752
        }
4✔
753
        if accountKey == "" {
16✔
754
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountKeyField, defaultSecretAccountKey)
3✔
755
        }
3✔
756

757
        accountName = strings.TrimSpace(accountName)
10✔
758
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
10✔
759
        return accountName, accountKey, nil
10✔
760
}
761

762
func getContainerReference(containerName string, secrets map[string]string, env az.Environment) (*azstorage.Container, error) {
9✔
763
        accountName, accountKey, rerr := getStorageAccount(secrets)
9✔
764
        if rerr != nil {
11✔
765
                return nil, rerr
2✔
766
        }
2✔
767
        client, err := azstorage.NewBasicClientOnSovereignCloud(accountName, accountKey, env)
7✔
768
        if err != nil {
13✔
769
                return nil, err
6✔
770
        }
6✔
771
        blobClient := client.GetBlobService()
1✔
772
        container := blobClient.GetContainerReference(containerName)
1✔
773
        if container == nil {
1✔
774
                return nil, fmt.Errorf("ContainerReference of %s is nil", containerName)
×
775
        }
×
776
        return container, nil
1✔
777
}
778

779
func setAzureCredentials(ctx context.Context, kubeClient kubernetes.Interface, accountName, accountKey, secretNamespace string) (string, error) {
6✔
780
        if kubeClient == nil {
8✔
781
                klog.Warningf("could not create secret: kubeClient is nil")
2✔
782
                return "", nil
2✔
783
        }
2✔
784
        if accountName == "" || accountKey == "" {
6✔
785
                return "", fmt.Errorf("the account info is not enough, accountName(%v), accountKey(%v)", accountName, accountKey)
2✔
786
        }
2✔
787
        secretName := fmt.Sprintf(secretNameTemplate, accountName)
2✔
788
        secret := &v1.Secret{
2✔
789
                ObjectMeta: metav1.ObjectMeta{
2✔
790
                        Namespace: secretNamespace,
2✔
791
                        Name:      secretName,
2✔
792
                },
2✔
793
                Data: map[string][]byte{
2✔
794
                        defaultSecretAccountName: []byte(accountName),
2✔
795
                        defaultSecretAccountKey:  []byte(accountKey),
2✔
796
                },
2✔
797
                Type: "Opaque",
2✔
798
        }
2✔
799
        _, err := kubeClient.CoreV1().Secrets(secretNamespace).Create(ctx, secret, metav1.CreateOptions{})
2✔
800
        if errors.IsAlreadyExists(err) {
3✔
801
                err = nil
1✔
802
        }
1✔
803
        if err != nil {
2✔
804
                return "", fmt.Errorf("couldn't create secret %w", err)
×
805
        }
×
806
        return secretName, err
2✔
807
}
808

809
// GetStorageAccesskey get Azure storage account key from
810
//  1. secrets (if not empty)
811
//  2. use k8s client identity to read from k8s secret
812
//  3. use cluster identity to get from storage account directly
813
func (d *Driver) GetStorageAccesskey(ctx context.Context, accountOptions *azure.AccountOptions, secrets map[string]string, secretName, secretNamespace string) (string, string, error) {
9✔
814
        if len(secrets) > 0 {
10✔
815
                return getStorageAccount(secrets)
1✔
816
        }
1✔
817

818
        // read from k8s secret first
819
        if secretName == "" {
14✔
820
                secretName = fmt.Sprintf(secretNameTemplate, accountOptions.Name)
6✔
821
        }
6✔
822
        _, accountKey, _, _, _, _, _, err := d.GetInfoFromSecret(ctx, secretName, secretNamespace) //nolint
8✔
823
        if err != nil {
14✔
824
                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)
6✔
825
                accountKey, err = d.cloud.GetStorageAccesskey(ctx, accountOptions.SubscriptionID, accountOptions.Name, accountOptions.ResourceGroup, accountOptions.GetLatestAccountKey)
6✔
826
        }
6✔
827
        return accountOptions.Name, accountKey, err
8✔
828
}
829

830
// GetInfoFromSecret get info from k8s secret
831
// return <accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, error>
832
func (d *Driver) GetInfoFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, string, string, string, string, error) {
17✔
833
        if d.cloud.KubeClient == nil {
28✔
834
                return "", "", "", "", "", "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
11✔
835
        }
11✔
836

837
        secret, err := d.cloud.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
838
        if err != nil {
8✔
839
                return "", "", "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
840
        }
2✔
841

842
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
843
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
844
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
845
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
846
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
847
        spnClientID := strings.TrimSpace(string(secret.Data[storageSPNClientIDField][:]))
4✔
848
        spnTenantID := strings.TrimSpace(string(secret.Data[storageSPNTenantIDField][:]))
4✔
849

4✔
850
        klog.V(4).Infof("got storage account(%s) from secret(%s) namespace(%s)", accountName, secretName, secretNamespace)
4✔
851
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
852
}
853

854
// getSubnetResourceID get default subnet resource ID from cloud provider config
855
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
856
        subsID := d.cloud.SubscriptionID
6✔
857
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
858
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
859
        }
4✔
860

861
        if len(vnetResourceGroup) == 0 {
11✔
862
                vnetResourceGroup = d.cloud.ResourceGroup
5✔
863
                if len(d.cloud.VnetResourceGroup) > 0 {
8✔
864
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
865
                }
3✔
866
        }
867

868
        if len(vnetName) == 0 {
11✔
869
                vnetName = d.cloud.VnetName
5✔
870
        }
5✔
871

872
        if len(subnetName) == 0 {
11✔
873
                subnetName = d.cloud.SubnetName
5✔
874
        }
5✔
875
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
876
}
877

878
func (d *Driver) useDataPlaneAPI(volumeID, accountName string) bool {
4✔
879
        cache, err := d.dataPlaneAPIVolCache.Get(volumeID, azcache.CacheReadTypeDefault)
4✔
880
        if err != nil {
4✔
881
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
882
        }
×
883
        if cache != nil {
7✔
884
                return true
3✔
885
        }
3✔
886
        cache, err = d.dataPlaneAPIVolCache.Get(accountName, azcache.CacheReadTypeDefault)
1✔
887
        if err != nil {
1✔
888
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
889
        }
×
890
        if cache != nil {
1✔
891
                return true
×
892
        }
×
893
        return false
1✔
894
}
895

896
// appendDefaultMountOptions return mount options combined with mountOptions and defaultMountOptions
897
func appendDefaultMountOptions(mountOptions []string, tmpPath, containerName string) []string {
4✔
898
        var defaultMountOptions = map[string]string{
4✔
899
                "--pre-mount-validate": "true",
4✔
900
                "--use-https":          "true",
4✔
901
                "--tmp-path":           tmpPath,
4✔
902
                "--container-name":     containerName,
4✔
903
                // prevent billing charges on mounting
4✔
904
                "--cancel-list-on-mount-seconds": "10",
4✔
905
                // allow remounting using a non-empty tmp-path
4✔
906
                "--empty-dir-check": "false",
4✔
907
        }
4✔
908

4✔
909
        // stores the mount options already included in mountOptions
4✔
910
        included := make(map[string]bool)
4✔
911

4✔
912
        for _, mountOption := range mountOptions {
11✔
913
                for k := range defaultMountOptions {
49✔
914
                        if strings.HasPrefix(mountOption, k) {
46✔
915
                                included[k] = true
4✔
916
                        }
4✔
917
                }
918
        }
919

920
        allMountOptions := mountOptions
4✔
921

4✔
922
        for k, v := range defaultMountOptions {
28✔
923
                if _, isIncluded := included[k]; !isIncluded {
44✔
924
                        if v != "" {
40✔
925
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
20✔
926
                        } else {
20✔
927
                                allMountOptions = append(allMountOptions, k)
×
928
                        }
×
929
                }
930
        }
931

932
        return allMountOptions
4✔
933
}
934

935
// chmodIfPermissionMismatch only perform chmod when permission mismatches
936
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
3✔
937
        info, err := os.Lstat(targetPath)
3✔
938
        if err != nil {
4✔
939
                return err
1✔
940
        }
1✔
941
        perm := info.Mode() & os.ModePerm
2✔
942
        if perm != mode {
3✔
943
                klog.V(2).Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), mode)
1✔
944
                if err := os.Chmod(targetPath, mode); err != nil {
1✔
945
                        return err
×
946
                }
×
947
        } else {
1✔
948
                klog.V(2).Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
1✔
949
        }
1✔
950
        return nil
2✔
951
}
952

953
func createStorageAccountSecret(account, key string) map[string]string {
1✔
954
        secret := make(map[string]string)
1✔
955
        secret[defaultSecretAccountName] = account
1✔
956
        secret[defaultSecretAccountKey] = key
1✔
957
        return secret
1✔
958
}
1✔
959

960
// setKeyValueInMap set key/value pair in map
961
// key in the map is case insensitive, if key already exists, overwrite existing value
962
func setKeyValueInMap(m map[string]string, key, value string) {
6✔
963
        if m == nil {
7✔
964
                return
1✔
965
        }
1✔
966
        for k := range m {
16✔
967
                if strings.EqualFold(k, key) {
13✔
968
                        m[k] = value
2✔
969
                        return
2✔
970
                }
2✔
971
        }
972
        m[key] = value
3✔
973
}
974

975
// replaceWithMap replace key with value for str
976
func replaceWithMap(str string, m map[string]string) string {
13✔
977
        for k, v := range m {
18✔
978
                if k != "" {
9✔
979
                        str = strings.ReplaceAll(str, k, v)
4✔
980
                }
4✔
981
        }
982
        return str
13✔
983
}
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