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

kubernetes-sigs / blob-csi-driver / 5624779439

21 Jul 2023 05:02PM CUT coverage: 80.434%. Remained the same
5624779439

Pull #984

github

web-flow
chore(deps): bump github.com/Azure/azure-sdk-for-go/sdk/azcore

Bumps [github.com/Azure/azure-sdk-for-go/sdk/azcore](https://github.com/Azure/azure-sdk-for-go) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/Azure/azure-sdk-for-go/releases)
- [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md)
- [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.6.0...sdk/azcore/v1.7.0)

---
updated-dependencies:
- dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azcore
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #984: chore(deps): bump github.com/Azure/azure-sdk-for-go/sdk/azcore from 1.6.0 to 1.7.0

1854 of 2305 relevant lines covered (80.43%)

5.3 hits per line

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

86.26
/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
        "fmt"
21
        "os"
22
        "strconv"
23
        "strings"
24
        "sync"
25
        "time"
26

27
        "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-09-01/storage"
28
        azstorage "github.com/Azure/azure-sdk-for-go/storage"
29
        az "github.com/Azure/go-autorest/autorest/azure"
30
        "github.com/container-storage-interface/spec/lib/go/csi"
31
        "github.com/pborman/uuid"
32
        "golang.org/x/net/context"
33

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
        azure "sigs.k8s.io/cloud-provider-azure/pkg/provider"
47
)
48

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

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

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

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

130
        subnetTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s"
131

132
        defaultNamespace = "default"
133

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

141
        VolumeID = "volumeid"
142

143
        defaultStorageEndPointSuffix = "core.windows.net"
144
)
145

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

151
// DriverOptions defines driver parameters specified in driver deployment
152
type DriverOptions struct {
153
        NodeID                                 string
154
        DriverName                             string
155
        CloudConfigSecretName                  string
156
        CloudConfigSecretNamespace             string
157
        CustomUserAgent                        string
158
        UserAgentSuffix                        string
159
        BlobfuseProxyEndpoint                  string
160
        EnableBlobfuseProxy                    bool
161
        BlobfuseProxyConnTimout                int
162
        EnableBlobMockMount                    bool
163
        AllowEmptyCloudConfig                  bool
164
        AllowInlineVolumeKeyAccessWithIdentity bool
165
        EnableGetVolumeStats                   bool
166
        AppendTimeStampInCacheDir              bool
167
        AppendMountErrorHelpLink               bool
168
        MountPermissions                       uint64
169
        KubeAPIQPS                             float64
170
        KubeAPIBurst                           int
171
}
172

173
// Driver implements all interfaces of CSI drivers
174
type Driver struct {
175
        csicommon.CSIDriver
176

177
        cloud                      *azure.Cloud
178
        cloudConfigSecretName      string
179
        cloudConfigSecretNamespace string
180
        customUserAgent            string
181
        userAgentSuffix            string
182
        blobfuseProxyEndpoint      string
183
        // enableBlobMockMount is only for testing, DO NOT set as true in non-testing scenario
184
        enableBlobMockMount                    bool
185
        enableBlobfuseProxy                    bool
186
        allowEmptyCloudConfig                  bool
187
        enableGetVolumeStats                   bool
188
        allowInlineVolumeKeyAccessWithIdentity bool
189
        appendTimeStampInCacheDir              bool
190
        appendMountErrorHelpLink               bool
191
        blobfuseProxyConnTimout                int
192
        mountPermissions                       uint64
193
        kubeAPIQPS                             float64
194
        kubeAPIBurst                           int
195
        mounter                                *mount.SafeFormatAndMount
196
        volLockMap                             *util.LockMap
197
        // A map storing all volumes with ongoing operations so that additional operations
198
        // for that same volume (as defined by VolumeID) return an Aborted error
199
        volumeLocks *volumeLocks
200
        // only for nfs feature
201
        subnetLockMap *util.LockMap
202
        // a map storing all volumes created by this driver <volumeName, accountName>
203
        volMap sync.Map
204
        // a timed cache storing all volumeIDs and storage accounts that are using data plane API
205
        dataPlaneAPIVolCache azcache.Resource
206
        // a timed cache storing account search history (solve account list throttling issue)
207
        accountSearchCache azcache.Resource
208
}
209

210
// NewDriver Creates a NewCSIDriver object. Assumes vendor version is equal to driver version &
211
// does not support optional driver plugin info manifest field. Refer to CSI spec for more details.
212
func NewDriver(options *DriverOptions) *Driver {
108✔
213
        d := Driver{
108✔
214
                volLockMap:                             util.NewLockMap(),
108✔
215
                subnetLockMap:                          util.NewLockMap(),
108✔
216
                volumeLocks:                            newVolumeLocks(),
108✔
217
                cloudConfigSecretName:                  options.CloudConfigSecretName,
108✔
218
                cloudConfigSecretNamespace:             options.CloudConfigSecretNamespace,
108✔
219
                customUserAgent:                        options.CustomUserAgent,
108✔
220
                userAgentSuffix:                        options.UserAgentSuffix,
108✔
221
                blobfuseProxyEndpoint:                  options.BlobfuseProxyEndpoint,
108✔
222
                enableBlobfuseProxy:                    options.EnableBlobfuseProxy,
108✔
223
                allowInlineVolumeKeyAccessWithIdentity: options.AllowInlineVolumeKeyAccessWithIdentity,
108✔
224
                blobfuseProxyConnTimout:                options.BlobfuseProxyConnTimout,
108✔
225
                enableBlobMockMount:                    options.EnableBlobMockMount,
108✔
226
                allowEmptyCloudConfig:                  options.AllowEmptyCloudConfig,
108✔
227
                enableGetVolumeStats:                   options.EnableGetVolumeStats,
108✔
228
                appendMountErrorHelpLink:               options.AppendMountErrorHelpLink,
108✔
229
                mountPermissions:                       options.MountPermissions,
108✔
230
                kubeAPIQPS:                             options.KubeAPIQPS,
108✔
231
                kubeAPIBurst:                           options.KubeAPIBurst,
108✔
232
        }
108✔
233
        d.Name = options.DriverName
108✔
234
        d.Version = driverVersion
108✔
235
        d.NodeID = options.NodeID
108✔
236

108✔
237
        var err error
108✔
238
        getter := func(key string) (interface{}, error) { return nil, nil }
111✔
239
        if d.accountSearchCache, err = azcache.NewTimedCache(time.Minute, getter, false); err != nil {
108✔
240
                klog.Fatalf("%v", err)
×
241
        }
×
242
        if d.dataPlaneAPIVolCache, err = azcache.NewTimedCache(10*time.Minute, getter, false); err != nil {
108✔
243
                klog.Fatalf("%v", err)
×
244
        }
×
245
        return &d
108✔
246
}
247

248
// Run driver initialization
249
func (d *Driver) Run(endpoint, kubeconfig string, testBool bool) {
2✔
250
        versionMeta, err := GetVersionYAML(d.Name)
2✔
251
        if err != nil {
2✔
252
                klog.Fatalf("%v", err)
×
253
        }
×
254
        klog.Infof("\nDRIVER INFORMATION:\n-------------------\n%s\n\nStreaming logs below:", versionMeta)
2✔
255

2✔
256
        userAgent := GetUserAgent(d.Name, d.customUserAgent, d.userAgentSuffix)
2✔
257
        klog.V(2).Infof("driver userAgent: %s", userAgent)
2✔
258
        d.cloud, err = getCloudProvider(kubeconfig, d.NodeID, d.cloudConfigSecretName, d.cloudConfigSecretNamespace, userAgent, d.allowEmptyCloudConfig, d.kubeAPIQPS, d.kubeAPIBurst)
2✔
259
        if err != nil {
2✔
260
                klog.Fatalf("failed to get Azure Cloud Provider, error: %v", err)
×
261
        }
×
262
        klog.V(2).Infof("cloud: %s, location: %s, rg: %s, VnetName: %s, VnetResourceGroup: %s, SubnetName: %s", d.cloud.Cloud, d.cloud.Location, d.cloud.ResourceGroup, d.cloud.VnetName, d.cloud.VnetResourceGroup, d.cloud.SubnetName)
2✔
263

2✔
264
        d.mounter = &mount.SafeFormatAndMount{
2✔
265
                Interface: mount.New(""),
2✔
266
                Exec:      utilexec.New(),
2✔
267
        }
2✔
268

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

2✔
288
        nodeCap := []csi.NodeServiceCapability_RPC_Type{
2✔
289
                csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
2✔
290
                csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
2✔
291
        }
2✔
292
        if d.enableGetVolumeStats {
2✔
293
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_GET_VOLUME_STATS)
×
294
        }
×
295
        d.AddNodeServiceCapabilities(nodeCap)
2✔
296

2✔
297
        s := csicommon.NewNonBlockingGRPCServer()
2✔
298
        // Driver d act as IdentityServer, ControllerServer and NodeServer
2✔
299
        s.Start(endpoint, d, d, d, testBool)
2✔
300
        s.Wait()
2✔
301
}
302

303
// GetContainerInfo get container info according to volume id
304
// the format of VolumeId is: rg#accountName#containerName#uuid#secretNamespace#subsID
305
//
306
// e.g.
307
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#"
308
// output: rg, f5713de20cde511e8ba4900, containerName, "" , ""
309
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#"
310
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, ""
311
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#subsID"
312
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, subsID
313
func GetContainerInfo(id string) (string, string, string, string, string, error) {
33✔
314
        segments := strings.Split(id, separator)
33✔
315
        if len(segments) < 3 {
40✔
316
                return "", "", "", "", "", fmt.Errorf("error parsing volume id: %q, should at least contain two #", id)
7✔
317
        }
7✔
318
        var secretNamespace, subsID string
26✔
319
        if len(segments) > 4 {
32✔
320
                secretNamespace = segments[4]
6✔
321
        }
6✔
322
        if len(segments) > 5 {
29✔
323
                subsID = segments[5]
3✔
324
        }
3✔
325
        return segments[0], segments[1], segments[2], secretNamespace, subsID, nil
26✔
326
}
327

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

349
func checkContainerNameBeginAndEnd(containerName string) bool {
11✔
350
        length := len(containerName)
11✔
351
        if (('a' <= containerName[0] && containerName[0] <= 'z') ||
11✔
352
                ('0' <= containerName[0] && containerName[0] <= '9')) &&
11✔
353
                (('a' <= containerName[length-1] && containerName[length-1] <= 'z') ||
11✔
354
                        ('0' <= containerName[length-1] && containerName[length-1] <= '9')) {
20✔
355
                return true
9✔
356
        }
9✔
357

358
        return false
2✔
359
}
360

361
// isSASToken checks if the key contains the patterns.
362
// SAS token format could refer to https://docs.microsoft.com/en-us/rest/api/eventhub/generate-sas-token
363
func isSASToken(key string) bool {
3✔
364
        return strings.HasPrefix(key, "?")
3✔
365
}
3✔
366

367
// GetAuthEnv return <accountName, containerName, authEnv, error>
368
func (d *Driver) GetAuthEnv(ctx context.Context, volumeID, protocol string, attrib, secrets map[string]string) (string, string, string, string, []string, error) {
10✔
369
        rgName, accountName, containerName, secretNamespace, _, err := GetContainerInfo(volumeID)
10✔
370
        if err != nil {
12✔
371
                // ignore volumeID parsing error
2✔
372
                klog.V(2).Infof("parsing volumeID(%s) return with error: %v", volumeID, err)
2✔
373
                err = nil
2✔
374
        }
2✔
375

376
        var (
10✔
377
                subsID                  string
10✔
378
                accountKey              string
10✔
379
                accountSasToken         string
10✔
380
                msiSecret               string
10✔
381
                storageSPNClientSecret  string
10✔
382
                storageSPNClientID      string
10✔
383
                storageSPNTenantID      string
10✔
384
                secretName              string
10✔
385
                pvcNamespace            string
10✔
386
                keyVaultURL             string
10✔
387
                keyVaultSecretName      string
10✔
388
                keyVaultSecretVersion   string
10✔
389
                azureStorageAuthType    string
10✔
390
                authEnv                 []string
10✔
391
                getAccountKeyFromSecret bool
10✔
392
                getLatestAccountKey     bool
10✔
393
        )
10✔
394

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

9✔
446
        if protocol == NFS {
11✔
447
                // nfs protocol does not need account key, return directly
2✔
448
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
449
        }
2✔
450

451
        if secretNamespace == "" {
13✔
452
                if pvcNamespace == "" {
12✔
453
                        secretNamespace = defaultNamespace
6✔
454
                } else {
6✔
455
                        secretNamespace = pvcNamespace
×
456
                }
×
457
        }
458

459
        if rgName == "" {
8✔
460
                rgName = d.cloud.ResourceGroup
1✔
461
        }
1✔
462

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

534
        if containerName == "" {
4✔
535
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
536
        }
×
537

538
        if accountKey != "" {
8✔
539
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
540
        }
4✔
541

542
        if accountSasToken != "" {
5✔
543
                klog.V(2).Infof("accountSasToken is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
544
                authEnv = append(authEnv, "AZURE_STORAGE_SAS_TOKEN="+accountSasToken)
1✔
545
        }
1✔
546

547
        if msiSecret != "" {
5✔
548
                klog.V(2).Infof("msiSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
549
                authEnv = append(authEnv, "MSI_SECRET="+msiSecret)
1✔
550
        }
1✔
551

552
        if storageSPNClientSecret != "" {
5✔
553
                klog.V(2).Infof("storageSPNClientSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
554
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_SECRET="+storageSPNClientSecret)
1✔
555
        }
1✔
556

557
        if storageSPNClientID != "" {
4✔
558
                klog.V(2).Infof("storageSPNClientID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNClientID, accountName, containerName)
×
559
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+storageSPNClientID)
×
560
        }
×
561

562
        if storageSPNTenantID != "" {
4✔
563
                klog.V(2).Infof("storageSPNTenantID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNTenantID, accountName, containerName)
×
564
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+storageSPNTenantID)
×
565
        }
×
566

567
        return rgName, accountName, accountKey, containerName, authEnv, err
4✔
568
}
569

570
// GetStorageAccountAndContainer get storage account and container info
571
// returns <accountName, accountKey, accountSasToken, containerName>
572
// only for e2e testing
573
func (d *Driver) GetStorageAccountAndContainer(ctx context.Context, volumeID string, attrib, secrets map[string]string) (string, string, string, string, error) {
3✔
574
        var (
3✔
575
                subsID                string
3✔
576
                accountName           string
3✔
577
                accountKey            string
3✔
578
                accountSasToken       string
3✔
579
                containerName         string
3✔
580
                keyVaultURL           string
3✔
581
                keyVaultSecretName    string
3✔
582
                keyVaultSecretVersion string
3✔
583
                getLatestAccountKey   bool
3✔
584
                err                   error
3✔
585
        )
3✔
586

3✔
587
        for k, v := range attrib {
8✔
588
                switch strings.ToLower(k) {
5✔
589
                case subscriptionIDField:
×
590
                        subsID = v
×
591
                case containerNameField:
1✔
592
                        containerName = v
1✔
593
                case keyVaultURLField:
×
594
                        keyVaultURL = v
×
595
                case keyVaultSecretNameField:
1✔
596
                        keyVaultSecretName = v
1✔
597
                case keyVaultSecretVersionField:
1✔
598
                        keyVaultSecretVersion = v
1✔
599
                case storageAccountField:
×
600
                        accountName = v
×
601
                case storageAccountNameField: // for compatibility
1✔
602
                        accountName = v
1✔
603
                case getLatestAccountKeyField:
1✔
604
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
2✔
605
                                return "", "", "", "", fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
606
                        }
1✔
607
                }
608
        }
609

610
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
611
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
612
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
613
        if keyVaultURL != "" {
2✔
614
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
×
615
                if err != nil {
×
616
                        return "", "", "", "", err
×
617
                }
×
618
                if isSASToken(key) {
×
619
                        accountSasToken = key
×
620
                } else {
×
621
                        accountKey = key
×
622
                }
×
623
        } else {
2✔
624
                if len(secrets) == 0 {
4✔
625
                        var rgName string
2✔
626
                        rgName, accountName, containerName, _, _, err = GetContainerInfo(volumeID)
2✔
627
                        if err != nil {
2✔
628
                                return "", "", "", "", err
×
629
                        }
×
630

631
                        if rgName == "" {
2✔
632
                                rgName = d.cloud.ResourceGroup
×
633
                        }
×
634

635
                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName, getLatestAccountKey)
2✔
636
                        if err != nil {
3✔
637
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
638
                        }
1✔
639
                }
640
        }
641

642
        if containerName == "" {
1✔
643
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
644
        }
×
645

646
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
647
}
648

649
func IsCorruptedDir(dir string) bool {
4✔
650
        _, pathErr := mount.PathExists(dir)
4✔
651
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
652
}
4✔
653

654
func isRetriableError(err error) bool {
5✔
655
        if err != nil {
9✔
656
                for _, v := range retriableErrors {
19✔
657
                        if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(v)) {
18✔
658
                                return true
3✔
659
                        }
3✔
660
                }
661
        }
662
        return false
2✔
663
}
664

665
func isSupportedProtocol(protocol string) bool {
15✔
666
        if protocol == "" {
16✔
667
                return true
1✔
668
        }
1✔
669
        for _, v := range supportedProtocolList {
36✔
670
                if protocol == v {
34✔
671
                        return true
12✔
672
                }
12✔
673
        }
674
        return false
2✔
675
}
676

677
func isSupportedAccessTier(accessTier string) bool {
18✔
678
        if accessTier == "" {
29✔
679
                return true
11✔
680
        }
11✔
681
        for _, tier := range storage.PossibleAccessTierValues() {
25✔
682
                if accessTier == string(tier) {
21✔
683
                        return true
3✔
684
                }
3✔
685
        }
686
        return false
4✔
687
}
688

689
// container names can contain only lowercase letters, numbers, and hyphens,
690
// and must begin and end with a letter or a number
691
func isSupportedContainerNamePrefix(prefix string) bool {
17✔
692
        if prefix == "" {
26✔
693
                return true
9✔
694
        }
9✔
695
        if len(prefix) > 20 {
9✔
696
                return false
1✔
697
        }
1✔
698
        if prefix[0] == '-' {
8✔
699
                return false
1✔
700
        }
1✔
701
        for _, v := range prefix {
19✔
702
                if v != '-' && (v < '0' || v > '9') && (v < 'a' || v > 'z') {
17✔
703
                        return false
4✔
704
                }
4✔
705
        }
706
        return true
2✔
707
}
708

709
// get storage account from secrets map
710
func getStorageAccount(secrets map[string]string) (string, string, error) {
18✔
711
        if secrets == nil {
19✔
712
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
713
        }
1✔
714

715
        var accountName, accountKey string
17✔
716
        for k, v := range secrets {
53✔
717
                v = strings.TrimSpace(v)
36✔
718
                switch strings.ToLower(k) {
36✔
719
                case accountNameField:
7✔
720
                        accountName = v
7✔
721
                case defaultSecretAccountName: // for compatibility with built-in azurefile plugin
9✔
722
                        accountName = v
9✔
723
                case accountKeyField:
7✔
724
                        accountKey = v
7✔
725
                case defaultSecretAccountKey: // for compatibility with built-in azurefile plugin
9✔
726
                        accountKey = v
9✔
727
                }
728
        }
729

730
        if accountName == "" {
21✔
731
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountNameField, defaultSecretAccountName)
4✔
732
        }
4✔
733
        if accountKey == "" {
16✔
734
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountKeyField, defaultSecretAccountKey)
3✔
735
        }
3✔
736

737
        accountName = strings.TrimSpace(accountName)
10✔
738
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
10✔
739
        return accountName, accountKey, nil
10✔
740
}
741

742
func getContainerReference(containerName string, secrets map[string]string, env az.Environment) (*azstorage.Container, error) {
9✔
743
        accountName, accountKey, rerr := getStorageAccount(secrets)
9✔
744
        if rerr != nil {
11✔
745
                return nil, rerr
2✔
746
        }
2✔
747
        client, err := azstorage.NewBasicClientOnSovereignCloud(accountName, accountKey, env)
7✔
748
        if err != nil {
13✔
749
                return nil, err
6✔
750
        }
6✔
751
        blobClient := client.GetBlobService()
1✔
752
        container := blobClient.GetContainerReference(containerName)
1✔
753
        if container == nil {
1✔
754
                return nil, fmt.Errorf("ContainerReference of %s is nil", containerName)
×
755
        }
×
756
        return container, nil
1✔
757
}
758

759
func setAzureCredentials(ctx context.Context, kubeClient kubernetes.Interface, accountName, accountKey, secretNamespace string) (string, error) {
6✔
760
        if kubeClient == nil {
8✔
761
                klog.Warningf("could not create secret: kubeClient is nil")
2✔
762
                return "", nil
2✔
763
        }
2✔
764
        if accountName == "" || accountKey == "" {
6✔
765
                return "", fmt.Errorf("the account info is not enough, accountName(%v), accountKey(%v)", accountName, accountKey)
2✔
766
        }
2✔
767
        secretName := fmt.Sprintf(secretNameTemplate, accountName)
2✔
768
        secret := &v1.Secret{
2✔
769
                ObjectMeta: metav1.ObjectMeta{
2✔
770
                        Namespace: secretNamespace,
2✔
771
                        Name:      secretName,
2✔
772
                },
2✔
773
                Data: map[string][]byte{
2✔
774
                        defaultSecretAccountName: []byte(accountName),
2✔
775
                        defaultSecretAccountKey:  []byte(accountKey),
2✔
776
                },
2✔
777
                Type: "Opaque",
2✔
778
        }
2✔
779
        _, err := kubeClient.CoreV1().Secrets(secretNamespace).Create(ctx, secret, metav1.CreateOptions{})
2✔
780
        if errors.IsAlreadyExists(err) {
3✔
781
                err = nil
1✔
782
        }
1✔
783
        if err != nil {
2✔
784
                return "", fmt.Errorf("couldn't create secret %w", err)
×
785
        }
×
786
        return secretName, err
2✔
787
}
788

789
// GetStorageAccesskey get Azure storage account key from
790
//  1. secrets (if not empty)
791
//  2. use k8s client identity to read from k8s secret
792
//  3. use cluster identity to get from storage account directly
793
func (d *Driver) GetStorageAccesskey(ctx context.Context, accountOptions *azure.AccountOptions, secrets map[string]string, secretName, secretNamespace string) (string, string, error) {
7✔
794
        if len(secrets) > 0 {
8✔
795
                return getStorageAccount(secrets)
1✔
796
        }
1✔
797

798
        // read from k8s secret first
799
        if secretName == "" {
10✔
800
                secretName = fmt.Sprintf(secretNameTemplate, accountOptions.Name)
4✔
801
        }
4✔
802
        _, accountKey, _, _, _, _, _, err := d.GetInfoFromSecret(ctx, secretName, secretNamespace) //nolint
6✔
803
        if err != nil {
10✔
804
                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)
4✔
805
                accountKey, err = d.cloud.GetStorageAccesskey(ctx, accountOptions.SubscriptionID, accountOptions.Name, accountOptions.ResourceGroup, accountOptions.GetLatestAccountKey)
4✔
806
        }
4✔
807
        return accountOptions.Name, accountKey, err
6✔
808
}
809

810
// GetInfoFromSecret get info from k8s secret
811
// return <accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, error>
812
func (d *Driver) GetInfoFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, string, string, string, string, error) {
15✔
813
        if d.cloud.KubeClient == nil {
24✔
814
                return "", "", "", "", "", "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
9✔
815
        }
9✔
816

817
        secret, err := d.cloud.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
818
        if err != nil {
8✔
819
                return "", "", "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
820
        }
2✔
821

822
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
823
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
824
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
825
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
826
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
827
        spnClientID := strings.TrimSpace(string(secret.Data[storageSPNClientIDField][:]))
4✔
828
        spnTenantID := strings.TrimSpace(string(secret.Data[storageSPNTenantIDField][:]))
4✔
829

4✔
830
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
4✔
831
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
832
}
833

834
// getSubnetResourceID get default subnet resource ID from cloud provider config
835
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
836
        subsID := d.cloud.SubscriptionID
6✔
837
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
838
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
839
        }
4✔
840

841
        if len(vnetResourceGroup) == 0 {
11✔
842
                vnetResourceGroup = d.cloud.ResourceGroup
5✔
843
                if len(d.cloud.VnetResourceGroup) > 0 {
8✔
844
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
845
                }
3✔
846
        }
847

848
        if len(vnetName) == 0 {
11✔
849
                vnetName = d.cloud.VnetName
5✔
850
        }
5✔
851

852
        if len(subnetName) == 0 {
11✔
853
                subnetName = d.cloud.SubnetName
5✔
854
        }
5✔
855
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
856
}
857

858
func (d *Driver) useDataPlaneAPI(volumeID, accountName string) bool {
4✔
859
        cache, err := d.dataPlaneAPIVolCache.Get(volumeID, azcache.CacheReadTypeDefault)
4✔
860
        if err != nil {
4✔
861
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
862
        }
×
863
        if cache != nil {
7✔
864
                return true
3✔
865
        }
3✔
866
        cache, err = d.dataPlaneAPIVolCache.Get(accountName, azcache.CacheReadTypeDefault)
1✔
867
        if err != nil {
1✔
868
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
869
        }
×
870
        if cache != nil {
1✔
871
                return true
×
872
        }
×
873
        return false
1✔
874
}
875

876
// appendDefaultMountOptions return mount options combined with mountOptions and defaultMountOptions
877
func appendDefaultMountOptions(mountOptions []string, tmpPath, containerName string) []string {
4✔
878
        var defaultMountOptions = map[string]string{
4✔
879
                "--pre-mount-validate": "true",
4✔
880
                "--use-https":          "true",
4✔
881
                "--tmp-path":           tmpPath,
4✔
882
                "--container-name":     containerName,
4✔
883
                // prevent billing charges on mounting
4✔
884
                "--cancel-list-on-mount-seconds": "10",
4✔
885
                // allow remounting using a non-empty tmp-path
4✔
886
                "--empty-dir-check": "false",
4✔
887
        }
4✔
888

4✔
889
        // stores the mount options already included in mountOptions
4✔
890
        included := make(map[string]bool)
4✔
891

4✔
892
        for _, mountOption := range mountOptions {
11✔
893
                for k := range defaultMountOptions {
49✔
894
                        if strings.HasPrefix(mountOption, k) {
46✔
895
                                included[k] = true
4✔
896
                        }
4✔
897
                }
898
        }
899

900
        allMountOptions := mountOptions
4✔
901

4✔
902
        for k, v := range defaultMountOptions {
28✔
903
                if _, isIncluded := included[k]; !isIncluded {
44✔
904
                        if v != "" {
40✔
905
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
20✔
906
                        } else {
20✔
907
                                allMountOptions = append(allMountOptions, k)
×
908
                        }
×
909
                }
910
        }
911

912
        return allMountOptions
4✔
913
}
914

915
// chmodIfPermissionMismatch only perform chmod when permission mismatches
916
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
3✔
917
        info, err := os.Lstat(targetPath)
3✔
918
        if err != nil {
4✔
919
                return err
1✔
920
        }
1✔
921
        perm := info.Mode() & os.ModePerm
2✔
922
        if perm != mode {
3✔
923
                klog.V(2).Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), mode)
1✔
924
                if err := os.Chmod(targetPath, mode); err != nil {
1✔
925
                        return err
×
926
                }
×
927
        } else {
1✔
928
                klog.V(2).Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
1✔
929
        }
1✔
930
        return nil
2✔
931
}
932

933
func createStorageAccountSecret(account, key string) map[string]string {
1✔
934
        secret := make(map[string]string)
1✔
935
        secret[defaultSecretAccountName] = account
1✔
936
        secret[defaultSecretAccountKey] = key
1✔
937
        return secret
1✔
938
}
1✔
939

940
// setKeyValueInMap set key/value pair in map
941
// key in the map is case insensitive, if key already exists, overwrite existing value
942
func setKeyValueInMap(m map[string]string, key, value string) {
6✔
943
        if m == nil {
7✔
944
                return
1✔
945
        }
1✔
946
        for k := range m {
16✔
947
                if strings.EqualFold(k, key) {
13✔
948
                        m[k] = value
2✔
949
                        return
2✔
950
                }
2✔
951
        }
952
        m[key] = value
3✔
953
}
954

955
// replaceWithMap replace key with value for str
956
func replaceWithMap(str string, m map[string]string) string {
11✔
957
        for k, v := range m {
16✔
958
                if k != "" {
9✔
959
                        str = strings.ReplaceAll(str, k, v)
4✔
960
                }
4✔
961
        }
962
        return str
11✔
963
}
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