e2etest/newe2e_arm_storage_account.go (237 lines of code) (raw):
package e2etest
import (
"encoding/json"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service"
"github.com/Azure/azure-storage-azcopy/v10/common"
"net/http"
"net/url"
"strings"
)
// ARMStorageAccount implements an API to interface with a singular Azure Storage account via the Storage Resource Provider's REST APIs.
// https://learn.microsoft.com/en-us/rest/api/storagerp/storage-accounts
type ARMStorageAccount struct {
*ARMResourceGroup
AccountName string
}
func (sa *ARMStorageAccount) ManagementURI() url.URL {
baseURI := sa.ARMResourceGroup.ManagementURI()
newURI := baseURI.JoinPath("providers/Microsoft.Storage/storageAccounts", sa.AccountName)
return *newURI
}
// GetResourceManager should not be called repeatedly; it makes calls to REST APIs and does not cache.
func (sa *ARMStorageAccount) GetResourceManager() (*AzureAccountResourceManager, error) {
keyList, err := sa.GetKeys()
if err != nil {
return nil, fmt.Errorf("failed to get account keys: %w", err)
}
var acctKey string
for _, v := range keyList.Keys { // todo: fallback to RO key
if v.Permissions == ARMStorageAccountKeyPermissionFull || v.Permissions == "" {
acctKey = v.Value
break
}
}
if acctKey == "" {
return nil, fmt.Errorf("failed to find suitable account key; did you intentionally make it RO")
}
props, err := sa.GetProperties(nil)
if err != nil {
return nil, fmt.Errorf("failed to poll account properties: %w", err)
}
var acctType AccountType
switch {
case strings.EqualFold(props.Sku.Tier, "Premium"):
switch props.Kind {
case service.AccountKindBlockBlobStorage: // both use the same kind
acctType = common.Iff(props.Properties.IsHNSEnabled, EAccountType.PremiumHNSEnabled(), EAccountType.PremiumBlockBlobs())
case service.AccountKindFileStorage:
acctType = EAccountType.PremiumFileShares()
case service.AccountKindStorageV2:
acctType = EAccountType.PremiumPageBlobs()
}
case props.Properties.IsHNSEnabled:
acctType = EAccountType.HierarchicalNamespaceEnabled()
case strings.EqualFold(props.Sku.Tier, "Standard"):
acctType = EAccountType.Standard()
// // Classic comes from Microsoft.ClassicStorage/storageAccounts, so, not possible here.
// // Managed Disks also won't appear here.
default:
return nil, fmt.Errorf("failed to assign an appropriate account type")
}
return &AzureAccountResourceManager{
InternalAccountName: sa.AccountName,
InternalAccountKey: acctKey,
InternalAccountType: acctType,
ArmClient: sa,
}, nil
}
func (sa *ARMStorageAccount) PrepareRequest(reqSettings *ARMRequestSettings) {
if reqSettings.Query == nil {
reqSettings.Query = make(url.Values)
}
if !reqSettings.Query.Has("api-version") {
reqSettings.Query.Add("api-version", "2023-01-01") // Attach default query
}
}
// ARMStorageAccountCreateParams is the request body for https://learn.microsoft.com/en-us/rest/api/storagerp/storage-accounts/create?tabs=HTTP#storageaccount
type ARMStorageAccountCreateParams struct {
Kind service.AccountKind `json:"kind"`
Location string `json:"location"`
Sku ARMStorageAccountSKU `json:"sku"`
ExtendedLocation *ARMExtendedLocation `json:"extendedLocation,omitempty"`
Identity *ARMStorageAccountIdentity `json:"identity,omitempty"`
Properties *ARMStorageAccountCreateProperties `json:"properties,omitempty"`
}
// ARMStorageAccountCreateProperties implements a portion of ARMStorageAccountCreateParams.
// https://learn.microsoft.com/en-us/rest/api/storagerp/storage-accounts/create?tabs=HTTP#storageaccount
type ARMStorageAccountCreateProperties struct { // json.RawMessage(s) are used as filler, if needed for one-offs
AccessTier *blob.AccessTier `json:"accessTier,omitempty"`
AllowBlobPublicAccess *bool `json:"allowBlobPublicAccess,omitempty"`
AllowCrossTenantReplication *bool `json:"allowCrossTenantReplication,omitempty"`
AllowSharedKeyAccess *bool `json:"allowSharedKeyAccess,omitempty"`
AllowedCopyScope json.RawMessage `json:"allowedCopyScope,omitempty"`
AzureFilesIdentityBasedAuthentication json.RawMessage `json:"azureFilesIdentityBasedAuthentication,omitempty"`
CustomDomain json.RawMessage `json:"customDomain,omitempty"`
DefaultToOAuthAuthentication *bool `json:"defaultToOAuthAuthentication,omitempty"`
DNSEndpointType json.RawMessage `json:"dnsEndpointType,omitempty"`
Encryption json.RawMessage `json:"encryption,omitempty"` // todo cpk?
ImmutableStorageWithVersioning json.RawMessage `json:"immutableStorageWithVersioning,omitempty"` // todo version level WORM
IsHnsEnabled *bool `json:"isHnsEnabled,omitempty"`
IsLocalUserEnabled *bool `json:"isLocalUserEnabled,omitempty"`
IsNfsV3Enabled *bool `json:"isNfsV3Enabled,omitempty"`
IsSftpEnabled *bool `json:"isSftpEnabled,omitempty"`
KeyPolicy json.RawMessage `json:"keyPolicy,omitempty"`
LargeFileSharesState *string `json:"largeFileSharesState,omitempty"` // "Enabled" or "Disabled"
MinimumTLSVersion *string `json:"minimumTLSVersion,omitempty"`
NetworkACLs json.RawMessage `json:"networkAcls,omitempty"`
PublicNetworkAccess *string `json:"publicNetworkAccess,omitempty"` // "Enabled" or "Disabled"
RoutingPreference json.RawMessage `json:"routingPreference,omitempty"`
SASPolicy json.RawMessage `json:"sasPolicy,omitempty"`
SupportsHttpsOnly *bool `json:"supportsHttpsOnly,omitempty"`
Tags map[string]string `json:"tags"`
}
func (sa *ARMStorageAccount) Create(params ARMStorageAccountCreateParams) (*ARMStorageAccountProperties, error) {
var out ARMStorageAccountProperties
_, err := PerformRequest(sa, ARMRequestSettings{
Method: http.MethodPut,
Body: params,
}, &out)
return &out, err
}
func (sa *ARMStorageAccount) Delete() error {
_, err := PerformRequest[any](sa, ARMRequestSettings{
Method: http.MethodDelete,
}, nil)
return err
}
const (
ARMStorageAccountExpandGeoReplicationStats = "geoReplicationStats"
ARMStorageAccountExpandBlobRestoreStatus = "blobRestoreStatus"
)
// GetProperties pulls storage account properties; expand uses the above constants
func (sa *ARMStorageAccount) GetProperties(expand []string) (*ARMStorageAccountProperties, error) {
query := make(url.Values)
if expand != nil {
query["$expand"] = expand
}
var out ARMStorageAccountProperties
_, err := PerformRequest(sa, ARMRequestSettings{
Method: http.MethodGet,
}, &out)
return &out, err
}
func (sa *ARMStorageAccount) GetKeys() (*ARMStorageAccountListKeysResult, error) { // Kerberos keys can be listed, but AzCopy doesn't currently support this.
var resp ARMStorageAccountListKeysResult
_, err := PerformRequest(sa, ARMRequestSettings{
Method: http.MethodPost,
PathExtension: "listKeys",
}, &resp)
return &resp, err
}
// =========== Shared Types ===========
type ARMStorageAccountProperties struct {
ExtendedLocation ARMExtendedLocation `json:"extendedLocation"`
ID string `json:"id"`
Identity ARMStorageAccountIdentity `json:"identity"`
Kind service.AccountKind `json:"kind"`
Location string `json:"location"`
Name string `json:"name"`
Properties struct {
AccessTier blob.AccessTier `json:"accessTier"`
AccountMigrationInProcess bool `json:"accountMigrationInProcess"`
AllowBlobPublicAccess bool `json:"allowBlobPublicAccess"`
AllowCrossTenantReplication bool `json:"allowCrossTenantReplication"`
AllowSharedKeyAccess bool `json:"allowSharedKeyAccess"`
AllowedCopyScope json.RawMessage `json:"allowedCopyScope"`
AzureFilesIdentityBasedAuthentication json.RawMessage `json:"azureFilesIdentityBasedAuthentication"`
BlobRestoreStatus json.RawMessage `json:"blobRestoreStatus"`
CreationTime string `json:"creationTime"`
CustomDomain json.RawMessage `json:"customDomain"`
DefaultToOAuthAuthentication bool `json:"defaultToOAuthAuthentication"`
DNSEndpointType json.RawMessage `json:"dnsEndpointType"`
Encryption json.RawMessage `json:"encryption"` // todo: maybe needed for CPK?
FailoverInProgress bool `json:"failoverInProgress"`
GeoReplicationStats json.RawMessage `json:"geoReplicationStats"`
ImmutableStorageWithVersioning json.RawMessage `json:"immutableStorageWithVersioning"` // todo: needed for testing version-level WORM
IsHNSEnabled bool `json:"isHNSEnabled"`
IsLocalUserEnabled bool `json:"isLocalUserEnabled"`
IsNFSV3Enabled bool `json:"isNfsV3Enabled"`
IsSFTPEnabled bool `json:"isSftpEnabled"`
IsSKUConversionBlocked bool `json:"isSkuConversionBlocked"`
KeyCreationTime json.RawMessage `json:"keyCreationTime"`
KeyPolicy json.RawMessage `json:"keyPolicy"` // todo: CPK?
LargeFileSharesState string `json:"largeFileSharesState"` // "Enabled" or "Disabled"
LastGeoFailoverTime string `json:"lastGeoFailoverTime"`
MinimumTLSVersion json.RawMessage `json:"minimumTLSVersion"`
NetworkACLs json.RawMessage `json:"networkAcls"`
PrimaryEndpoints json.RawMessage `json:"primaryEndpoints"`
PrimaryLocation string `json:"primaryLocation"`
PrivateEndpointConnections []json.RawMessage `json:"privateEndpointConnections"`
ProvisioningState string `json:"provisioningState"`
PublicNetworkAccess string `json:"publicNetworkAccess"` // "Enabled" or "Disabled"
RoutingPreference json.RawMessage `json:"routingPreference"`
SASPolicy json.RawMessage `json:"sasPolicy"`
SecondaryEndpoints json.RawMessage `json:"secondaryEndpoints"` // todo: could test azcopy's ability to fail over?
SecondaryLocation string `json:"secondaryLocation"`
StatusOfPrimary string `json:"statusOfPrimary"`
StatusOfSecondary string `json:"statusOfSecondary"`
StorageAccountSkuConversionStatus json.RawMessage `json:"storageAccountSkuConversionStatus"`
SupportsHTTPSTrafficOnly bool `json:"supportsHttpsTrafficOnly"`
} `json:"properties"`
Sku ARMStorageAccountSKU `json:"sku"`
Tags map[string]string `json:"tags"`
Type string `json:"type"`
}
type ARMStorageAccountSKU struct {
Name string `json:"name"`
Tier string `json:"tier"`
}
var (
// SKU names https://learn.microsoft.com/en-us/rest/api/storagerp/storage-accounts/create?tabs=HTTP#skuname
ARMStorageAccountSKUPremiumLRS = ARMStorageAccountSKU{"Premium_LRS", "Premium"}
ARMStorageAccountSKUPremiumZRS = ARMStorageAccountSKU{"Premium_ZRS", "Premium"}
ARMStorageAccountSKUStandardGRS = ARMStorageAccountSKU{"Standard_GRS", "Standard"}
ARMStorageAccountSKUStandardGZRS = ARMStorageAccountSKU{"Standard_GZRS", "Standard"}
ARMStorageAccountSKUStandardLRS = ARMStorageAccountSKU{"Standard_LRS", "Standard"}
ARMStorageAccountSKUStandardRAGRS = ARMStorageAccountSKU{"Standard_RAGRS", "Standard"}
ARMStorageAccountSKUStandardRAGZRS = ARMStorageAccountSKU{"Standard_RAGZRS", "Standard"}
ARMStorageAccountSKUStandardZRS = ARMStorageAccountSKU{"Standard_ZRS", "Standard"}
)
type ARMExtendedLocation struct {
Name string `json:"name"`
Type string `json:"type"` // Can be "EdgeZone" or empty
}
type ARMStorageAccountIdentity struct {
PrincipalID string `json:"principalId"`
TenantID string `json:"tenantId"`
Type string `json:"type"` // https://learn.microsoft.com/en-us/rest/api/storagerp/storage-accounts/create?tabs=HTTP#identitytype
UserAssignedIdentities map[string]ARMStorageAccountUserAssignedIdentity `json:"userAssignedIdentities"`
}
type ARMStorageAccountUserAssignedIdentity struct {
ClientID string `json:"clientId"`
PrincipalID string `json:"principalId"`
}
type ARMStorageAccountListKeysResult struct {
Keys []ARMStorageAccountKey `json:"keys"`
}
type ARMStorageAccountKey struct {
CreationTime string `json:"creationTime"`
KeyName string `json:"keyName"`
Permissions string `json:"permissions"`
Value string `json:"value"`
}
const (
ARMStorageAccountKeyPermissionReadOnly = "Read"
ARMStorageAccountKeyPermissionFull = "Full"
)