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

kubernetes-sigs / blob-csi-driver / 5273860234

15 Jun 2023 01:58AM UTC coverage: 80.383%. Remained the same
5273860234

Pull #940

github

web-flow
chore(deps): bump github.com/pelletier/go-toml from 1.9.4 to 1.9.5

Bumps [github.com/pelletier/go-toml](https://github.com/pelletier/go-toml) from 1.9.4 to 1.9.5.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v1.9.4...v1.9.5)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #940: chore(deps): bump github.com/pelletier/go-toml from 1.9.4 to 1.9.5

1848 of 2299 relevant lines covered (80.38%)

5.32 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
        keyVaultURLField             = "keyvaulturl"
83
        keyVaultSecretNameField      = "keyvaultsecretname"
84
        keyVaultSecretVersionField   = "keyvaultsecretversion"
85
        storageAccountNameField      = "storageaccountname"
86
        allowBlobPublicAccessField   = "allowblobpublicaccess"
87
        requireInfraEncryptionField  = "requireinfraencryption"
88
        ephemeralField               = "csi.storage.k8s.io/ephemeral"
89
        podNamespaceField            = "csi.storage.k8s.io/pod.namespace"
90
        mountOptionsField            = "mountoptions"
91
        falseValue                   = "false"
92
        trueValue                    = "true"
93
        defaultSecretAccountName     = "azurestorageaccountname"
94
        defaultSecretAccountKey      = "azurestorageaccountkey"
95
        accountSasTokenField         = "azurestorageaccountsastoken"
96
        msiSecretField               = "msisecret"
97
        storageSPNClientSecretField  = "azurestoragespnclientsecret"
98
        Fuse                         = "fuse"
99
        Fuse2                        = "fuse2"
100
        NFS                          = "nfs"
101
        vnetResourceGroupField       = "vnetresourcegroup"
102
        vnetNameField                = "vnetname"
103
        subnetNameField              = "subnetname"
104
        accessTierField              = "accesstier"
105
        networkEndpointTypeField     = "networkendpointtype"
106
        mountPermissionsField        = "mountpermissions"
107
        useDataPlaneAPIField         = "usedataplaneapi"
108

109
        // See https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
110
        containerNameMinLength = 3
111
        containerNameMaxLength = 63
112

113
        accountNotProvisioned                   = "StorageAccountIsNotProvisioned"
114
        tooManyRequests                         = "TooManyRequests"
115
        clientThrottled                         = "client throttled"
116
        containerBeingDeletedDataplaneAPIError  = "ContainerBeingDeleted"
117
        containerBeingDeletedManagementAPIError = "container is being deleted"
118
        statusCodeNotFound                      = "StatusCode=404"
119
        httpCodeNotFound                        = "HTTPStatusCode: 404"
120

121
        // 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
122
        containerMaxSize = 100 * util.TiB
123

124
        subnetTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s"
125

126
        defaultNamespace = "default"
127

128
        pvcNameKey           = "csi.storage.k8s.io/pvc/name"
129
        pvcNamespaceKey      = "csi.storage.k8s.io/pvc/namespace"
130
        pvNameKey            = "csi.storage.k8s.io/pv/name"
131
        pvcNameMetadata      = "${pvc.metadata.name}"
132
        pvcNamespaceMetadata = "${pvc.metadata.namespace}"
133
        pvNameMetadata       = "${pv.metadata.name}"
134

135
        VolumeID = "volumeid"
136

137
        defaultStorageEndPointSuffix = "core.windows.net"
138
)
139

140
var (
141
        supportedProtocolList = []string{Fuse, Fuse2, NFS}
142
        retriableErrors       = []string{accountNotProvisioned, tooManyRequests, statusCodeNotFound, containerBeingDeletedDataplaneAPIError, containerBeingDeletedManagementAPIError, clientThrottled}
143
)
144

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

167
// Driver implements all interfaces of CSI drivers
168
type Driver struct {
169
        csicommon.CSIDriver
170

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

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

108✔
231
        var err error
108✔
232
        getter := func(key string) (interface{}, error) { return nil, nil }
111✔
233
        if d.accountSearchCache, err = azcache.NewTimedcache(time.Minute, getter); err != nil {
108✔
234
                klog.Fatalf("%v", err)
×
235
        }
×
236
        if d.dataPlaneAPIVolCache, err = azcache.NewTimedcache(10*time.Minute, getter); err != nil {
108✔
237
                klog.Fatalf("%v", err)
×
238
        }
×
239
        return &d
108✔
240
}
241

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

2✔
250
        userAgent := GetUserAgent(d.Name, d.customUserAgent, d.userAgentSuffix)
2✔
251
        klog.V(2).Infof("driver userAgent: %s", userAgent)
2✔
252
        d.cloud, err = getCloudProvider(kubeconfig, d.NodeID, d.cloudConfigSecretName, d.cloudConfigSecretNamespace, userAgent, d.allowEmptyCloudConfig, d.kubeAPIQPS, d.kubeAPIBurst)
2✔
253
        if err != nil {
2✔
254
                klog.Fatalf("failed to get Azure Cloud Provider, error: %v", err)
×
255
        }
×
256
        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✔
257

2✔
258
        d.mounter = &mount.SafeFormatAndMount{
2✔
259
                Interface: mount.New(""),
2✔
260
                Exec:      utilexec.New(),
2✔
261
        }
2✔
262

2✔
263
        // Initialize default library driver
2✔
264
        d.AddControllerServiceCapabilities(
2✔
265
                []csi.ControllerServiceCapability_RPC_Type{
2✔
266
                        csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
2✔
267
                        //csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
2✔
268
                        //csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS,
2✔
269
                        csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
2✔
270
                        csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
2✔
271
                })
2✔
272
        d.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
2✔
273
                csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
2✔
274
                csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY,
2✔
275
                csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
2✔
276
                csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
2✔
277
                csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
2✔
278
                csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER,
2✔
279
                csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
2✔
280
        })
2✔
281

2✔
282
        nodeCap := []csi.NodeServiceCapability_RPC_Type{
2✔
283
                csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
2✔
284
                csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
2✔
285
        }
2✔
286
        if d.enableGetVolumeStats {
2✔
287
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_GET_VOLUME_STATS)
×
288
        }
×
289
        d.AddNodeServiceCapabilities(nodeCap)
2✔
290

2✔
291
        s := csicommon.NewNonBlockingGRPCServer()
2✔
292
        // Driver d act as IdentityServer, ControllerServer and NodeServer
2✔
293
        s.Start(endpoint, d, d, d, testBool)
2✔
294
        s.Wait()
2✔
295
}
296

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

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

343
func checkContainerNameBeginAndEnd(containerName string) bool {
11✔
344
        length := len(containerName)
11✔
345
        if (('a' <= containerName[0] && containerName[0] <= 'z') ||
11✔
346
                ('0' <= containerName[0] && containerName[0] <= '9')) &&
11✔
347
                (('a' <= containerName[length-1] && containerName[length-1] <= 'z') ||
11✔
348
                        ('0' <= containerName[length-1] && containerName[length-1] <= '9')) {
20✔
349
                return true
9✔
350
        }
9✔
351

352
        return false
2✔
353
}
354

355
// isSASToken checks if the key contains the patterns.
356
// SAS token format could refer to https://docs.microsoft.com/en-us/rest/api/eventhub/generate-sas-token
357
func isSASToken(key string) bool {
3✔
358
        return strings.HasPrefix(key, "?")
3✔
359
}
3✔
360

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

370
        var (
10✔
371
                subsID                  string
10✔
372
                accountKey              string
10✔
373
                accountSasToken         string
10✔
374
                msiSecret               string
10✔
375
                storageSPNClientSecret  string
10✔
376
                storageSPNClientID      string
10✔
377
                storageSPNTenantID      string
10✔
378
                secretName              string
10✔
379
                pvcNamespace            string
10✔
380
                keyVaultURL             string
10✔
381
                keyVaultSecretName      string
10✔
382
                keyVaultSecretVersion   string
10✔
383
                azureStorageAuthType    string
10✔
384
                authEnv                 []string
10✔
385
                getAccountKeyFromSecret bool
10✔
386
                getLatestAccountKey     bool
10✔
387
        )
10✔
388

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

9✔
440
        if protocol == NFS {
11✔
441
                // nfs protocol does not need account key, return directly
2✔
442
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
443
        }
2✔
444

445
        if secretNamespace == "" {
13✔
446
                if pvcNamespace == "" {
12✔
447
                        secretNamespace = defaultNamespace
6✔
448
                } else {
6✔
449
                        secretNamespace = pvcNamespace
×
450
                }
×
451
        }
452

453
        if rgName == "" {
8✔
454
                rgName = d.cloud.ResourceGroup
1✔
455
        }
1✔
456

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

528
        if containerName == "" {
4✔
529
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
530
        }
×
531

532
        if accountKey != "" {
8✔
533
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
534
        }
4✔
535

536
        if accountSasToken != "" {
5✔
537
                klog.V(2).Infof("accountSasToken is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
538
                authEnv = append(authEnv, "AZURE_STORAGE_SAS_TOKEN="+accountSasToken)
1✔
539
        }
1✔
540

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

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

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

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

561
        return rgName, accountName, accountKey, containerName, authEnv, err
4✔
562
}
563

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

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

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

625
                        if rgName == "" {
2✔
626
                                rgName = d.cloud.ResourceGroup
×
627
                        }
×
628

629
                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName, getLatestAccountKey)
2✔
630
                        if err != nil {
3✔
631
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
632
                        }
1✔
633
                }
634
        }
635

636
        if containerName == "" {
1✔
637
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
638
        }
×
639

640
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
641
}
642

643
func IsCorruptedDir(dir string) bool {
4✔
644
        _, pathErr := mount.PathExists(dir)
4✔
645
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
646
}
4✔
647

648
func isRetriableError(err error) bool {
5✔
649
        if err != nil {
9✔
650
                for _, v := range retriableErrors {
19✔
651
                        if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(v)) {
18✔
652
                                return true
3✔
653
                        }
3✔
654
                }
655
        }
656
        return false
2✔
657
}
658

659
func isSupportedProtocol(protocol string) bool {
15✔
660
        if protocol == "" {
16✔
661
                return true
1✔
662
        }
1✔
663
        for _, v := range supportedProtocolList {
36✔
664
                if protocol == v {
34✔
665
                        return true
12✔
666
                }
12✔
667
        }
668
        return false
2✔
669
}
670

671
func isSupportedAccessTier(accessTier string) bool {
18✔
672
        if accessTier == "" {
29✔
673
                return true
11✔
674
        }
11✔
675
        for _, tier := range storage.PossibleAccessTierValues() {
25✔
676
                if accessTier == string(tier) {
21✔
677
                        return true
3✔
678
                }
3✔
679
        }
680
        return false
4✔
681
}
682

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

703
// get storage account from secrets map
704
func getStorageAccount(secrets map[string]string) (string, string, error) {
18✔
705
        if secrets == nil {
19✔
706
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
707
        }
1✔
708

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

724
        if accountName == "" {
21✔
725
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field secrets(%v)", accountNameField, defaultSecretAccountName, secrets)
4✔
726
        }
4✔
727
        if accountKey == "" {
16✔
728
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets(%v)", accountKeyField, defaultSecretAccountKey, secrets)
3✔
729
        }
3✔
730

731
        accountName = strings.TrimSpace(accountName)
10✔
732
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
10✔
733
        return accountName, accountKey, nil
10✔
734
}
735

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

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

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

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

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

811
        secret, err := d.cloud.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
812
        if err != nil {
8✔
813
                return "", "", "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
814
        }
2✔
815

816
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
817
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
818
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
819
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
820
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
821
        spnClientID := strings.TrimSpace(string(secret.Data[storageSPNClientIDField][:]))
4✔
822
        spnTenantID := strings.TrimSpace(string(secret.Data[storageSPNTenantIDField][:]))
4✔
823

4✔
824
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
4✔
825
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
826
}
827

828
// getSubnetResourceID get default subnet resource ID from cloud provider config
829
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
830
        subsID := d.cloud.SubscriptionID
6✔
831
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
832
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
833
        }
4✔
834

835
        if len(vnetResourceGroup) == 0 {
11✔
836
                vnetResourceGroup = d.cloud.ResourceGroup
5✔
837
                if len(d.cloud.VnetResourceGroup) > 0 {
8✔
838
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
839
                }
3✔
840
        }
841

842
        if len(vnetName) == 0 {
11✔
843
                vnetName = d.cloud.VnetName
5✔
844
        }
5✔
845

846
        if len(subnetName) == 0 {
11✔
847
                subnetName = d.cloud.SubnetName
5✔
848
        }
5✔
849
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
850
}
851

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

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

4✔
883
        // stores the mount options already included in mountOptions
4✔
884
        included := make(map[string]bool)
4✔
885

4✔
886
        for _, mountOption := range mountOptions {
11✔
887
                for k := range defaultMountOptions {
49✔
888
                        if strings.HasPrefix(mountOption, k) {
46✔
889
                                included[k] = true
4✔
890
                        }
4✔
891
                }
892
        }
893

894
        allMountOptions := mountOptions
4✔
895

4✔
896
        for k, v := range defaultMountOptions {
28✔
897
                if _, isIncluded := included[k]; !isIncluded {
44✔
898
                        if v != "" {
40✔
899
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
20✔
900
                        } else {
20✔
901
                                allMountOptions = append(allMountOptions, k)
×
902
                        }
×
903
                }
904
        }
905

906
        return allMountOptions
4✔
907
}
908

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

927
func createStorageAccountSecret(account, key string) map[string]string {
1✔
928
        secret := make(map[string]string)
1✔
929
        secret[defaultSecretAccountName] = account
1✔
930
        secret[defaultSecretAccountKey] = key
1✔
931
        return secret
1✔
932
}
1✔
933

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

949
// replaceWithMap replace key with value for str
950
func replaceWithMap(str string, m map[string]string) string {
11✔
951
        for k, v := range m {
16✔
952
                if k != "" {
9✔
953
                        str = strings.ReplaceAll(str, k, v)
4✔
954
                }
4✔
955
        }
956
        return str
11✔
957
}
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