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

kubernetes-sigs / blob-csi-driver / 6075716175

04 Sep 2023 04:12PM UTC coverage: 80.476%. Remained the same
6075716175

Pull #1003

github

web-flow
chore(deps): bump github.com/pborman/uuid from 1.2.0 to 1.2.1

Bumps [github.com/pborman/uuid](https://github.com/pborman/uuid) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/pborman/uuid/releases)
- [Commits](https://github.com/pborman/uuid/compare/v1.2...v1.2.1)

---
updated-dependencies:
- dependency-name: github.com/pborman/uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #1003: chore(deps): bump github.com/pborman/uuid from 1.2.0 to 1.2.1

1859 of 2310 relevant lines covered (80.48%)

5.35 hits per line

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

86.28
/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
        AZNFS                          = "aznfs"
108
        vnetResourceGroupField         = "vnetresourcegroup"
109
        vnetNameField                  = "vnetname"
110
        subnetNameField                = "subnetname"
111
        accessTierField                = "accesstier"
112
        networkEndpointTypeField       = "networkendpointtype"
113
        mountPermissionsField          = "mountpermissions"
114
        useDataPlaneAPIField           = "usedataplaneapi"
115

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

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

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

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

133
        defaultNamespace = "default"
134

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

142
        VolumeID = "volumeid"
143

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

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

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

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

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

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

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

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

2✔
260
        userAgent := GetUserAgent(d.Name, d.customUserAgent, d.userAgentSuffix)
2✔
261
        klog.V(2).Infof("driver userAgent: %s", userAgent)
2✔
262
        d.cloud, err = getCloudProvider(kubeconfig, d.NodeID, d.cloudConfigSecretName, d.cloudConfigSecretNamespace, userAgent, d.allowEmptyCloudConfig, d.kubeAPIQPS, d.kubeAPIBurst)
2✔
263
        if err != nil {
2✔
264
                klog.Fatalf("failed to get Azure Cloud Provider, error: %v", err)
×
265
        }
×
266
        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✔
267

2✔
268
        d.mounter = &mount.SafeFormatAndMount{
2✔
269
                Interface: mount.New(""),
2✔
270
                Exec:      utilexec.New(),
2✔
271
        }
2✔
272

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

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

2✔
301
        s := csicommon.NewNonBlockingGRPCServer()
2✔
302
        // Driver d act as IdentityServer, ControllerServer and NodeServer
2✔
303
        s.Start(endpoint, d, d, d, testBool)
2✔
304
        s.Wait()
2✔
305
}
306

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

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

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

362
        return false
2✔
363
}
364

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

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

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

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

9✔
450
        if protocol == NFS {
11✔
451
                // nfs protocol does not need account key, return directly
2✔
452
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
453
        }
2✔
454

455
        if secretNamespace == "" {
13✔
456
                if pvcNamespace == "" {
12✔
457
                        secretNamespace = defaultNamespace
6✔
458
                } else {
6✔
459
                        secretNamespace = pvcNamespace
×
460
                }
×
461
        }
462

463
        if rgName == "" {
8✔
464
                rgName = d.cloud.ResourceGroup
1✔
465
        }
1✔
466

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

538
        if containerName == "" {
4✔
539
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
540
        }
×
541

542
        if accountKey != "" {
8✔
543
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
544
        }
4✔
545

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

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

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

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

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

571
        return rgName, accountName, accountKey, containerName, authEnv, err
4✔
572
}
573

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

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

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

635
                        if rgName == "" {
2✔
636
                                rgName = d.cloud.ResourceGroup
×
637
                        }
×
638

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

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

650
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
651
}
652

653
func IsCorruptedDir(dir string) bool {
4✔
654
        _, pathErr := mount.PathExists(dir)
4✔
655
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
656
}
4✔
657

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

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

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

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

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

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

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

741
        accountName = strings.TrimSpace(accountName)
10✔
742
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
10✔
743
        return accountName, accountKey, nil
10✔
744
}
745

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

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

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

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

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

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

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

4✔
834
        klog.V(4).Infof("got storage account(%s) from secret(%s) namespace(%s)", accountName, secretName, secretNamespace)
4✔
835
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
836
}
837

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

845
        if len(vnetResourceGroup) == 0 {
11✔
846
                vnetResourceGroup = d.cloud.ResourceGroup
5✔
847
                if len(d.cloud.VnetResourceGroup) > 0 {
8✔
848
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
849
                }
3✔
850
        }
851

852
        if len(vnetName) == 0 {
11✔
853
                vnetName = d.cloud.VnetName
5✔
854
        }
5✔
855

856
        if len(subnetName) == 0 {
11✔
857
                subnetName = d.cloud.SubnetName
5✔
858
        }
5✔
859
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
860
}
861

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

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

4✔
893
        // stores the mount options already included in mountOptions
4✔
894
        included := make(map[string]bool)
4✔
895

4✔
896
        for _, mountOption := range mountOptions {
11✔
897
                for k := range defaultMountOptions {
49✔
898
                        if strings.HasPrefix(mountOption, k) {
46✔
899
                                included[k] = true
4✔
900
                        }
4✔
901
                }
902
        }
903

904
        allMountOptions := mountOptions
4✔
905

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

916
        return allMountOptions
4✔
917
}
918

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

937
func createStorageAccountSecret(account, key string) map[string]string {
1✔
938
        secret := make(map[string]string)
1✔
939
        secret[defaultSecretAccountName] = account
1✔
940
        secret[defaultSecretAccountKey] = key
1✔
941
        return secret
1✔
942
}
1✔
943

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

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