diff --git a/pkg/cluster/deploybaseresources.go b/pkg/cluster/deploybaseresources.go index 3dd5ac16e8b..cf9068ceb28 100644 --- a/pkg/cluster/deploybaseresources.go +++ b/pkg/cluster/deploybaseresources.go @@ -140,10 +140,10 @@ func (m *manager) deployBaseResourceTemplate(ctx context.Context) error { } resources := []*arm.Resource{ - m.storageAccount(clusterStorageAccountName, azureRegion, ocpSubnets, true), + m.storageAccount(clusterStorageAccountName, azureRegion, ocpSubnets), m.storageAccountBlobContainer(clusterStorageAccountName, "ignition"), m.storageAccountBlobContainer(clusterStorageAccountName, "aro"), - m.storageAccount(m.doc.OpenShiftCluster.Properties.ImageRegistryStorageAccountName, azureRegion, ocpSubnets, true), + m.storageAccount(m.doc.OpenShiftCluster.Properties.ImageRegistryStorageAccountName, azureRegion, ocpSubnets), m.storageAccountBlobContainer(m.doc.OpenShiftCluster.Properties.ImageRegistryStorageAccountName, "image-registry"), m.clusterNSG(infraID, azureRegion), m.clusterServicePrincipalRBAC(), diff --git a/pkg/cluster/deploybaseresources_additional.go b/pkg/cluster/deploybaseresources_additional.go index d9e8b0bbe29..b2b7c8407f5 100644 --- a/pkg/cluster/deploybaseresources_additional.go +++ b/pkg/cluster/deploybaseresources_additional.go @@ -76,9 +76,57 @@ func (m *manager) clusterServicePrincipalRBAC() *arm.Resource { } // storageAccount will return storage account resource. -// Legacy storage accounts (public) are not encrypted and cannot be retrofitted. -// The flag controls this behavior in update/create. -func (m *manager) storageAccount(name, region string, ocpSubnets []string, encrypted bool) *arm.Resource { +func (m *manager) storageAccount(name, region string, ocpSubnets []string) *arm.Resource { + // shared properties across creation and update + properties := m.storageAccountProperties(name, ocpSubnets) + + sa := &mgmtstorage.Account{ + Kind: mgmtstorage.StorageV2, + Sku: &mgmtstorage.Sku{ + Name: "Standard_LRS", + }, + AccountProperties: &mgmtstorage.AccountProperties{ + AllowBlobPublicAccess: properties.AllowBlobPublicAccess, + EnableHTTPSTrafficOnly: properties.EnableHTTPSTrafficOnly, + MinimumTLSVersion: properties.MinimumTLSVersion, + NetworkRuleSet: properties.NetworkRuleSet, + Encryption: &mgmtstorage.Encryption{ + RequireInfrastructureEncryption: to.BoolPtr(true), + Services: &mgmtstorage.EncryptionServices{ + Blob: &mgmtstorage.EncryptionService{ + KeyType: mgmtstorage.KeyTypeAccount, + Enabled: to.BoolPtr(true), + }, + File: &mgmtstorage.EncryptionService{ + KeyType: mgmtstorage.KeyTypeAccount, + Enabled: to.BoolPtr(true), + }, + Table: &mgmtstorage.EncryptionService{ + KeyType: mgmtstorage.KeyTypeAccount, + Enabled: to.BoolPtr(true), + }, + Queue: &mgmtstorage.EncryptionService{ + KeyType: mgmtstorage.KeyTypeAccount, + Enabled: to.BoolPtr(true), + }, + }, + KeySource: mgmtstorage.KeySourceMicrosoftStorage, + }, + }, + Name: &name, + Location: ®ion, + Type: to.StringPtr("Microsoft.Storage/storageAccounts"), + } + + return &arm.Resource{ + Resource: sa, + APIVersion: azureclient.APIVersion("Microsoft.Storage"), + } +} + +// reconcileStorageAccount will provide all properties we want to enforce on the storage account +// It is used for both resource creation as well as reconciliation during adminUpdate. +func (m *manager) storageAccountProperties(storageAccountName string, ocpSubnets []string) *mgmtstorage.AccountPropertiesUpdateParameters { virtualNetworkRules := []mgmtstorage.VirtualNetworkRule{ { VirtualNetworkResourceID: to.StringPtr("/subscriptions/" + m.env.SubscriptionID() + "/resourceGroups/" + m.env.ResourceGroup() + "/providers/Microsoft.Network/virtualNetworks/rp-pe-vnet-001/subnets/rp-pe-subnet"), @@ -101,7 +149,7 @@ func (m *manager) storageAccount(name, region string, ocpSubnets []string, encry // when installing via Hive we need to allow Hive to persist the installConfig graph in the cluster's storage account // TODO: add AKS shard support hiveShard := 1 - if m.installViaHive && strings.Index(name, "cluster") == 0 { + if m.installViaHive && strings.HasPrefix(storageAccountName, "cluster") { virtualNetworkRules = append(virtualNetworkRules, mgmtstorage.VirtualNetworkRule{ VirtualNetworkResourceID: to.StringPtr(fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/aks-net/subnets/PodSubnet-%03d", m.env.SubscriptionID(), m.env.ResourceGroup(), hiveShard)), Action: mgmtstorage.Allow, @@ -118,65 +166,24 @@ func (m *manager) storageAccount(name, region string, ocpSubnets []string, encry }) } - sa := &mgmtstorage.Account{ - Kind: mgmtstorage.StorageV2, - Sku: &mgmtstorage.Sku{ - Name: "Standard_LRS", - }, - AccountProperties: &mgmtstorage.AccountProperties{ - AllowBlobPublicAccess: to.BoolPtr(false), - EnableHTTPSTrafficOnly: to.BoolPtr(true), - MinimumTLSVersion: mgmtstorage.TLS12, - NetworkRuleSet: &mgmtstorage.NetworkRuleSet{ - Bypass: mgmtstorage.AzureServices, - VirtualNetworkRules: &virtualNetworkRules, - DefaultAction: "Deny", - }, - }, - Name: &name, - Location: ®ion, - Type: to.StringPtr("Microsoft.Storage/storageAccounts"), + properties := &mgmtstorage.AccountPropertiesUpdateParameters{} + + properties.AllowBlobPublicAccess = to.BoolPtr(false) + properties.EnableHTTPSTrafficOnly = to.BoolPtr(true) + properties.MinimumTLSVersion = mgmtstorage.TLS12 + properties.NetworkRuleSet = &mgmtstorage.NetworkRuleSet{ + Bypass: mgmtstorage.AzureServices, + VirtualNetworkRules: &virtualNetworkRules, + DefaultAction: mgmtstorage.DefaultActionDeny, } // In development API calls originates from user laptop so we allow all. // TODO: Move to development on VPN so we can make this IPRule. Will be done as part of Simply secure v2 work if m.env.IsLocalDevelopmentMode() { - sa.NetworkRuleSet.DefaultAction = mgmtstorage.DefaultActionAllow - } - // When migrating storage accounts for old clusters we are not able to change - // encryption which is why we have this encryption flag. We will not add this - // retrospectively to old clusters - // If a storage account already has encryption enabled and the encrypted - // bool is set to false, it will still maintain the encryption on the storage account. - if encrypted { - sa.AccountProperties.Encryption = &mgmtstorage.Encryption{ - RequireInfrastructureEncryption: to.BoolPtr(true), - Services: &mgmtstorage.EncryptionServices{ - Blob: &mgmtstorage.EncryptionService{ - KeyType: mgmtstorage.KeyTypeAccount, - Enabled: to.BoolPtr(true), - }, - File: &mgmtstorage.EncryptionService{ - KeyType: mgmtstorage.KeyTypeAccount, - Enabled: to.BoolPtr(true), - }, - Table: &mgmtstorage.EncryptionService{ - KeyType: mgmtstorage.KeyTypeAccount, - Enabled: to.BoolPtr(true), - }, - Queue: &mgmtstorage.EncryptionService{ - KeyType: mgmtstorage.KeyTypeAccount, - Enabled: to.BoolPtr(true), - }, - }, - KeySource: mgmtstorage.KeySourceMicrosoftStorage, - } + properties.NetworkRuleSet.DefaultAction = mgmtstorage.DefaultActionAllow } - return &arm.Resource{ - Resource: sa, - APIVersion: azureclient.APIVersion("Microsoft.Storage"), - } + return properties } func (m *manager) storageAccountBlobContainer(storageAccountName, name string) *arm.Resource { diff --git a/pkg/cluster/deploybaseresources_additional_test.go b/pkg/cluster/deploybaseresources_additional_test.go index dd3a4370143..93be752e78f 100644 --- a/pkg/cluster/deploybaseresources_additional_test.go +++ b/pkg/cluster/deploybaseresources_additional_test.go @@ -261,7 +261,7 @@ func TestStorageAccount(t *testing.T) { env: env, } - got := m.storageAccount(tt.storageAccountName, location, tt.ocpSubnets, true) + got := m.storageAccount(tt.storageAccountName, location, tt.ocpSubnets) if !reflect.DeepEqual(got, tt.want) { t.Error(cmp.Diff(got, tt.want)) diff --git a/pkg/cluster/storageaccounts.go b/pkg/cluster/storageaccounts.go index b38d7981d06..e5698c5035e 100644 --- a/pkg/cluster/storageaccounts.go +++ b/pkg/cluster/storageaccounts.go @@ -7,10 +7,10 @@ import ( "context" "fmt" + mgmtstorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/Azure/ARO-RP/pkg/api" - "github.com/Azure/ARO-RP/pkg/util/arm" "github.com/Azure/ARO-RP/pkg/util/stringutils" ) @@ -26,16 +26,16 @@ func (m *manager) migrateStorageAccounts(ctx context.Context) error { clusterStorageAccountName := "cluster" + m.doc.OpenShiftCluster.Properties.StorageSuffix registryStorageAccountName := m.doc.OpenShiftCluster.Properties.ImageRegistryStorageAccountName - t := &arm.Template{ - Schema: "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - ContentVersion: "1.0.0.0", - Resources: []*arm.Resource{ - m.storageAccount(clusterStorageAccountName, m.doc.OpenShiftCluster.Location, ocpSubnets, false), - m.storageAccount(registryStorageAccountName, m.doc.OpenShiftCluster.Location, ocpSubnets, false), - }, + for _, storageAccountName := range []string{clusterStorageAccountName, registryStorageAccountName} { + parameters := mgmtstorage.AccountUpdateParameters{ + AccountPropertiesUpdateParameters: m.storageAccountProperties(storageAccountName, ocpSubnets), + } + if _, err := m.storage.UpdateAccount(ctx, resourceGroup, storageAccountName, parameters); err != nil { + return err + } } - return arm.DeployTemplate(ctx, m.log, m.deployments, resourceGroup, "storage", t, nil) + return nil } func (m *manager) populateRegistryStorageAccountName(ctx context.Context) error { diff --git a/pkg/cluster/storageaccounts_test.go b/pkg/cluster/storageaccounts_test.go new file mode 100644 index 00000000000..c4bd643a64f --- /dev/null +++ b/pkg/cluster/storageaccounts_test.go @@ -0,0 +1,248 @@ +package cluster + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "fmt" + "reflect" + "strings" + "testing" + + mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-08-01/network" + mgmtstorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage" + "github.com/Azure/go-autorest/autorest/to" + "github.com/golang/mock/gomock" + "github.com/sirupsen/logrus" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/util/cmp" + mock_env "github.com/Azure/ARO-RP/pkg/util/mocks/env" + mock_storage "github.com/Azure/ARO-RP/pkg/util/mocks/storage" + mock_subnet "github.com/Azure/ARO-RP/pkg/util/mocks/subnet" +) + +func TestMigrateStorageAccounts(t *testing.T) { + ctx := context.Background() + location := "eastus" + + rpSubscriptionId := "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" + rpResourceGroup := "aro-" + location + gwyResourceGroup := "aro-gwy-" + location + + wantVnetRuleRpPrivateEndpoint := mgmtstorage.VirtualNetworkRule{ + VirtualNetworkResourceID: to.StringPtr(fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/rp-pe-vnet-001/subnets/rp-pe-subnet", rpSubscriptionId, rpResourceGroup)), + Action: mgmtstorage.Allow, + } + wantVnetRuleRpSubnet := mgmtstorage.VirtualNetworkRule{ + VirtualNetworkResourceID: to.StringPtr(fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/rp-vnet/subnets/rp-subnet", rpSubscriptionId, rpResourceGroup)), + Action: mgmtstorage.Allow, + } + wantVnetRuleHive := mgmtstorage.VirtualNetworkRule{ + VirtualNetworkResourceID: to.StringPtr(fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/aks-net/subnets/PodSubnet-001", rpSubscriptionId, rpResourceGroup)), + Action: mgmtstorage.Allow, + } + wantVnetRuleGateway := mgmtstorage.VirtualNetworkRule{ + VirtualNetworkResourceID: to.StringPtr(fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/gateway-vnet/subnets/gateway-subnet", rpSubscriptionId, gwyResourceGroup)), + Action: mgmtstorage.Allow, + } + + clusterResourceGroup := "testgroup" + clusterResourceGroupId := fmt.Sprintf("/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/%s", clusterResourceGroup) + storageSuffix := "asdfg" + imageRegistryStorageAccountName := "imageregistryasdfg" + + masterSubnetId := fmt.Sprintf("/subscriptions/00000000-0000-0000-0000-00000000000/resourceGroups/%s/providers/Microsoft.network/virtualNetworks/aro-vnet/subnets/master-subnet", clusterResourceGroup) + workerSubnetId := fmt.Sprintf("/subscriptions/00000000-0000-0000-0000-00000000000/resourceGroups/%s/providers/Microsoft.network/virtualNetworks/aro-vnet/subnets/master-subnet", clusterResourceGroup) + + for _, tt := range []struct { + name string + enableServiceEndpoints bool + wantClusterSAUpdate mgmtstorage.AccountUpdateParameters + wantImageRegistrySAUpdate mgmtstorage.AccountUpdateParameters + }{ + { + name: "default cluster works as expected", + wantClusterSAUpdate: mgmtstorage.AccountUpdateParameters{ + AccountPropertiesUpdateParameters: &mgmtstorage.AccountPropertiesUpdateParameters{ + AllowBlobPublicAccess: to.BoolPtr(false), + EnableHTTPSTrafficOnly: to.BoolPtr(true), + MinimumTLSVersion: mgmtstorage.TLS12, + NetworkRuleSet: &mgmtstorage.NetworkRuleSet{ + Bypass: mgmtstorage.AzureServices, + VirtualNetworkRules: &[]mgmtstorage.VirtualNetworkRule{ + wantVnetRuleRpPrivateEndpoint, + wantVnetRuleRpSubnet, + wantVnetRuleHive, + wantVnetRuleGateway, + }, + DefaultAction: mgmtstorage.DefaultActionDeny, + }, + }, + }, + wantImageRegistrySAUpdate: mgmtstorage.AccountUpdateParameters{ + AccountPropertiesUpdateParameters: &mgmtstorage.AccountPropertiesUpdateParameters{ + AllowBlobPublicAccess: to.BoolPtr(false), + EnableHTTPSTrafficOnly: to.BoolPtr(true), + MinimumTLSVersion: mgmtstorage.TLS12, + NetworkRuleSet: &mgmtstorage.NetworkRuleSet{ + Bypass: mgmtstorage.AzureServices, + VirtualNetworkRules: &[]mgmtstorage.VirtualNetworkRule{ + wantVnetRuleRpPrivateEndpoint, + wantVnetRuleRpSubnet, + wantVnetRuleGateway, + }, + DefaultAction: mgmtstorage.DefaultActionDeny, + }, + }, + }, + }, + { + name: "cluster subnets with service endpoints are added to storage account vnet rules", + enableServiceEndpoints: true, + wantClusterSAUpdate: mgmtstorage.AccountUpdateParameters{ + AccountPropertiesUpdateParameters: &mgmtstorage.AccountPropertiesUpdateParameters{ + AllowBlobPublicAccess: to.BoolPtr(false), + EnableHTTPSTrafficOnly: to.BoolPtr(true), + MinimumTLSVersion: mgmtstorage.TLS12, + NetworkRuleSet: &mgmtstorage.NetworkRuleSet{ + Bypass: mgmtstorage.AzureServices, + VirtualNetworkRules: &[]mgmtstorage.VirtualNetworkRule{ + wantVnetRuleRpPrivateEndpoint, + wantVnetRuleRpSubnet, + { + VirtualNetworkResourceID: &masterSubnetId, + Action: mgmtstorage.Allow, + }, + { + VirtualNetworkResourceID: to.StringPtr(strings.ToLower(workerSubnetId)), + Action: mgmtstorage.Allow, + }, + wantVnetRuleHive, + wantVnetRuleGateway, + }, + DefaultAction: mgmtstorage.DefaultActionDeny, + }, + }, + }, + wantImageRegistrySAUpdate: mgmtstorage.AccountUpdateParameters{ + AccountPropertiesUpdateParameters: &mgmtstorage.AccountPropertiesUpdateParameters{ + AllowBlobPublicAccess: to.BoolPtr(false), + EnableHTTPSTrafficOnly: to.BoolPtr(true), + MinimumTLSVersion: mgmtstorage.TLS12, + NetworkRuleSet: &mgmtstorage.NetworkRuleSet{ + Bypass: mgmtstorage.AzureServices, + VirtualNetworkRules: &[]mgmtstorage.VirtualNetworkRule{ + wantVnetRuleRpPrivateEndpoint, + wantVnetRuleRpSubnet, + { + VirtualNetworkResourceID: &masterSubnetId, + Action: mgmtstorage.Allow, + }, + { + VirtualNetworkResourceID: to.StringPtr(strings.ToLower(workerSubnetId)), + Action: mgmtstorage.Allow, + }, + wantVnetRuleGateway, + }, + DefaultAction: mgmtstorage.DefaultActionDeny, + }, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + controller := gomock.NewController(t) + defer controller.Finish() + + env := mock_env.NewMockInterface(controller) + subnet := mock_subnet.NewMockManager(controller) + storage := mock_storage.NewMockManager(controller) + + env.EXPECT().Location().AnyTimes().Return(location) + env.EXPECT().SubscriptionID().AnyTimes().Return(rpSubscriptionId) + env.EXPECT().ResourceGroup().AnyTimes().Return(rpResourceGroup) + env.EXPECT().GatewayResourceGroup().AnyTimes().Return(gwyResourceGroup) + env.EXPECT().IsLocalDevelopmentMode().AnyTimes().Return(false) + + masterSubnet := &mgmtnetwork.Subnet{ + SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{ + ServiceEndpoints: &[]mgmtnetwork.ServiceEndpointPropertiesFormat{}, + }, + } + workerSubnet := &mgmtnetwork.Subnet{ + SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{ + ServiceEndpoints: &[]mgmtnetwork.ServiceEndpointPropertiesFormat{}, + }, + } + if tt.enableServiceEndpoints { + serviceEndpoint := mgmtnetwork.ServiceEndpointPropertiesFormat{ + Service: to.StringPtr(storageServiceEndpoint), + Locations: &[]string{location}, + } + masterSubnet.SubnetPropertiesFormat.ServiceEndpoints = &[]mgmtnetwork.ServiceEndpointPropertiesFormat{serviceEndpoint} + workerSubnet.SubnetPropertiesFormat.ServiceEndpoints = &[]mgmtnetwork.ServiceEndpointPropertiesFormat{serviceEndpoint} + } + + subnet.EXPECT().Get(gomock.Eq(ctx), gomock.Eq(masterSubnetId)).Return(masterSubnet, nil) + subnet.EXPECT().Get(gomock.Eq(ctx), gomock.Eq(strings.ToLower(workerSubnetId))).Return(workerSubnet, nil) + + var gotClusterSAUpdate, gotImageRegistrySAUpdate mgmtstorage.AccountUpdateParameters + + storage.EXPECT().UpdateAccount(gomock.Eq(ctx), gomock.Eq(clusterResourceGroup), gomock.Any(), gomock.Any()). + AnyTimes(). + DoAndReturn(func(ctx context.Context, resourceGroup, accountName string, parameters mgmtstorage.AccountUpdateParameters) (mgmtstorage.Account, error) { + switch accountName { + case "cluster" + storageSuffix: + gotClusterSAUpdate = parameters + case imageRegistryStorageAccountName: + gotImageRegistrySAUpdate = parameters + } + return mgmtstorage.Account{}, nil + }) + + m := &manager{ + log: logrus.NewEntry(logrus.StandardLogger()), + + env: env, + subnet: subnet, + storage: storage, + + doc: &api.OpenShiftClusterDocument{ + OpenShiftCluster: &api.OpenShiftCluster{ + Location: location, + Properties: api.OpenShiftClusterProperties{ + ClusterProfile: api.ClusterProfile{ + ResourceGroupID: clusterResourceGroupId, + }, + MasterProfile: api.MasterProfile{ + SubnetID: masterSubnetId, + }, + WorkerProfiles: []api.WorkerProfile{ + { + SubnetID: workerSubnetId, + }, + }, + StorageSuffix: storageSuffix, + ImageRegistryStorageAccountName: imageRegistryStorageAccountName, + }, + }, + }, + installViaHive: true, + } + + err := m.migrateStorageAccounts(ctx) + + if err != nil { + t.Errorf("expected no error but got %v", err) + } + if !reflect.DeepEqual(gotClusterSAUpdate, tt.wantClusterSAUpdate) { + t.Errorf(cmp.Diff(gotClusterSAUpdate, tt.wantClusterSAUpdate)) + } + if !reflect.DeepEqual(gotImageRegistrySAUpdate, tt.wantImageRegistrySAUpdate) { + t.Errorf(cmp.Diff(gotImageRegistrySAUpdate, tt.wantImageRegistrySAUpdate)) + } + }) + } +} diff --git a/pkg/util/mocks/storage/storage.go b/pkg/util/mocks/storage/storage.go index 3e27ea47344..97fc447bf5c 100644 --- a/pkg/util/mocks/storage/storage.go +++ b/pkg/util/mocks/storage/storage.go @@ -50,3 +50,18 @@ func (mr *MockManagerMockRecorder) BlobService(arg0, arg1, arg2, arg3, arg4 inte mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlobService", reflect.TypeOf((*MockManager)(nil).BlobService), arg0, arg1, arg2, arg3, arg4) } + +// UpdateAccount mocks base method. +func (m *MockManager) UpdateAccount(arg0 context.Context, arg1, arg2 string, arg3 storage.AccountUpdateParameters) (storage.Account, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateAccount", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(storage.Account) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateAccount indicates an expected call of UpdateAccount. +func (mr *MockManagerMockRecorder) UpdateAccount(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAccount", reflect.TypeOf((*MockManager)(nil).UpdateAccount), arg0, arg1, arg2, arg3) +} diff --git a/pkg/util/storage/manager.go b/pkg/util/storage/manager.go index 54bdcde82c5..d3eb8a53a67 100644 --- a/pkg/util/storage/manager.go +++ b/pkg/util/storage/manager.go @@ -19,6 +19,7 @@ import ( type Manager interface { BlobService(ctx context.Context, resourceGroup, account string, p mgmtstorage.Permissions, r mgmtstorage.SignedResourceTypes) (*azstorage.BlobStorageClient, error) + UpdateAccount(ctx context.Context, resourceGroup, accountName string, parameters mgmtstorage.AccountUpdateParameters) (mgmtstorage.Account, error) } type manager struct { @@ -56,3 +57,7 @@ func (m *manager) BlobService(ctx context.Context, resourceGroup, account string return &blobcli, nil } + +func (m *manager) UpdateAccount(ctx context.Context, resourceGroup, accountName string, parameters mgmtstorage.AccountUpdateParameters) (mgmtstorage.Account, error) { + return m.storageAccounts.Update(ctx, resourceGroup, accountName, parameters) +}