Skip to content

Commit 32963fd

Browse files
committed
pkg/storage/azure: Add UserTags while creating Azure Storage Account
1 parent 8da85e4 commit 32963fd

File tree

2 files changed

+163
-5
lines changed

2 files changed

+163
-5
lines changed

pkg/storage/azure/azure.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func (d *driver) accountExists(storageAccountsClient storage.AccountsClient, acc
154154
)
155155
}
156156

157-
func (d *driver) createStorageAccount(storageAccountsClient storage.AccountsClient, resourceGroupName, accountName, location, cloudName string) error {
157+
func (d *driver) createStorageAccount(storageAccountsClient storage.AccountsClient, resourceGroupName, accountName, location, cloudName string, tagset map[string]*string) error {
158158
klog.Infof("attempt to create azure storage account %s (resourceGroup=%q, location=%q)...", accountName, resourceGroupName, location)
159159

160160
kind := storage.StorageV2
@@ -181,6 +181,7 @@ func (d *driver) createStorageAccount(storageAccountsClient storage.AccountsClie
181181
Name: storage.StandardLRS,
182182
},
183183
AccountPropertiesCreateParameters: params,
184+
Tags: tagset,
184185
},
185186
)
186187
if err != nil {
@@ -485,9 +486,9 @@ func (d *driver) StorageChanged(cr *imageregistryv1.Config) bool {
485486
return !reflect.DeepEqual(cr.Status.Storage.Azure, cr.Spec.Storage.Azure)
486487
}
487488

488-
// assureStorageAccount makes sure there is a storage account in place. If no storage account name
489-
// is provided it attempts to generate one. Returns the account name (either the one provided or
490-
// the one generated), if the account was created or was already there and an error.
489+
// assureStorageAccount makes sure there is a storage account in place and apply any provided tags.
490+
// If no storage account name is provided it attempts to generate one. Returns the account name
491+
// (either the one provided or the one generated), if the account was created or was already there and an error.
491492
func (d *driver) assureStorageAccount(cfg *Azure, infra *configv1.Infrastructure) (string, bool, error) {
492493
environment, err := getEnvironmentByName(d.Config.CloudName)
493494
if err != nil {
@@ -516,13 +517,33 @@ func (d *driver) assureStorageAccount(cfg *Azure, infra *configv1.Infrastructure
516517
return "", false, fmt.Errorf("create storage account failed, name not available")
517518
}
518519

520+
// Tag the storage account with the openshiftClusterID
521+
// along with any user defined tags from the cluster configuration
522+
klog.V(2).Info("setting azure storage account tags")
523+
524+
tagset := map[string]*string{
525+
fmt.Sprintf("kubernetes.io_cluster.%s", infra.Status.InfrastructureName): to.StringPtr("owned"),
526+
}
527+
528+
// at this stage we are not keeping user tags in sync. as per enhancement proposal
529+
// we only set user provided tags when we created the bucket.
530+
hasAzureStatus := infra.Status.PlatformStatus != nil && infra.Status.PlatformStatus.Azure != nil && infra.Status.PlatformStatus.Azure.ResourceTags != nil
531+
if hasAzureStatus {
532+
klog.V(5).Infof("user has provided %d tags", len(infra.Status.PlatformStatus.Azure.ResourceTags))
533+
for _, tag := range infra.Status.PlatformStatus.Azure.ResourceTags {
534+
klog.V(5).Infof("user has provided storage account tag: %s: %s", tag.Key, tag.Value)
535+
tagset[tag.Key] = to.StringPtr(tag.Value)
536+
}
537+
}
538+
klog.V(5).Infof("tagging storage account with tags: %+v", tagset)
539+
519540
// regardless if the storage account name was provided by the user or we generated it,
520541
// if it is available, we do attempt to create it.
521542
var storageAccountCreated bool
522543
if *result.NameAvailable {
523544
storageAccountCreated = true
524545
if err := d.createStorageAccount(
525-
storageAccountsClient, cfg.ResourceGroup, accountName, cfg.Region, d.Config.CloudName,
546+
storageAccountsClient, cfg.ResourceGroup, accountName, cfg.Region, d.Config.CloudName, tagset,
526547
); err != nil {
527548
return "", false, err
528549
}

pkg/storage/azure/azure_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package azure
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/base64"
7+
"encoding/json"
8+
"fmt"
9+
"io"
610
"net/http"
711
"reflect"
812
"regexp"
@@ -12,6 +16,7 @@ import (
1216
"github.com/Azure/azure-pipeline-go/pipeline"
1317
"github.com/Azure/go-autorest/autorest"
1418
"github.com/Azure/go-autorest/autorest/mocks"
19+
"github.com/google/go-cmp/cmp"
1520

1621
corev1 "k8s.io/api/core/v1"
1722
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -329,6 +334,138 @@ func TestConfigEnvWithUserKey(t *testing.T) {
329334
}
330335
}
331336

337+
// custom sender for mocking
338+
type sender struct {
339+
response []*http.Response
340+
body string
341+
}
342+
343+
// Do implements the Sender interface for mocking
344+
// Do accepts the passed request and body, then appends the response and emits it.
345+
func (s *sender) Do(r *http.Request) (*http.Response, error) {
346+
resp := &http.Response{
347+
StatusCode: http.StatusOK,
348+
Request: r,
349+
Body: io.NopCloser(bytes.NewBufferString(s.body)),
350+
}
351+
s.response = append(s.response, resp)
352+
return resp, nil
353+
}
354+
355+
func TestUserProvidedTags(t *testing.T) {
356+
for _, tt := range []struct {
357+
name string
358+
userTags []configv1.AzureResourceTag
359+
expectedTags map[string]string
360+
infraName string
361+
responseBody string
362+
}{
363+
{
364+
name: "no-user-tags",
365+
infraName: "some-infra",
366+
// only default tags
367+
expectedTags: map[string]string{
368+
"kubernetes.io_cluster.some-infra": "owned",
369+
},
370+
responseBody: `{"nameAvailable":true}`,
371+
},
372+
{
373+
name: "with-user-tags",
374+
infraName: "test-infra",
375+
userTags: []configv1.AzureResourceTag{
376+
{
377+
Key: "tag1",
378+
Value: "value1",
379+
},
380+
{
381+
Key: "tag2",
382+
Value: "value2",
383+
},
384+
},
385+
// default tags and user tags
386+
expectedTags: map[string]string{
387+
"kubernetes.io_cluster.test-infra": "owned",
388+
"tag1": "value1",
389+
"tag2": "value2",
390+
},
391+
responseBody: `{"nameAvailable":true}`,
392+
},
393+
} {
394+
t.Run(tt.name, func(t *testing.T) {
395+
sender := &sender{
396+
body: tt.responseBody,
397+
}
398+
399+
storageConfig := &imageregistryv1.ImageRegistryConfigStorageAzure{}
400+
401+
drv := NewDriver(context.Background(), storageConfig, nil)
402+
drv.authorizer = autorest.NullAuthorizer{}
403+
drv.sender = sender
404+
405+
_, _, err := drv.assureStorageAccount(
406+
&Azure{
407+
SubscriptionID: "subscription-id",
408+
ResourceGroup: "resource-group",
409+
},
410+
&configv1.Infrastructure{
411+
Status: configv1.InfrastructureStatus{
412+
InfrastructureName: tt.infraName,
413+
Platform: configv1.AzurePlatformType,
414+
PlatformStatus: &configv1.PlatformStatus{
415+
Type: configv1.AzurePlatformType,
416+
Azure: &configv1.AzurePlatformStatus{
417+
ResourceTags: tt.userTags,
418+
},
419+
},
420+
},
421+
},
422+
)
423+
if err != nil {
424+
t.Errorf("unexpected error %q", err)
425+
}
426+
427+
// flag to confirm presence of tags
428+
foundTags := false
429+
430+
for _, resp := range sender.response {
431+
if resp != nil && resp.Request != nil && resp.Request.Body != nil {
432+
433+
reqBody := make(map[string]interface{})
434+
if err := json.NewDecoder(resp.Request.Body).Decode(&reqBody); err != nil {
435+
t.Fatalf("error decoding request: %q", err)
436+
}
437+
438+
// ignore request without tags
439+
if _, ok := reqBody["tags"]; ok {
440+
foundTags = true
441+
442+
tags, ok := reqBody["tags"].(map[string]interface{})
443+
if !ok {
444+
t.Fatal("unable to type assert tags field")
445+
}
446+
// convert into correct type
447+
receivedTags := make(map[string]string)
448+
for k, v := range tags {
449+
receivedTags[k] = fmt.Sprintf("%+v", v)
450+
}
451+
452+
// compare the tags
453+
if !reflect.DeepEqual(tt.expectedTags, receivedTags) {
454+
t.Fatalf(
455+
"unexpected tags: %s",
456+
cmp.Diff(tt.expectedTags, receivedTags),
457+
)
458+
}
459+
}
460+
}
461+
}
462+
if !foundTags {
463+
t.Fatal("no tags present in the request")
464+
}
465+
})
466+
}
467+
}
468+
332469
func Test_assureStorageAccount(t *testing.T) {
333470
for _, tt := range []struct {
334471
name string

0 commit comments

Comments
 (0)