From 8e16f1247a57d1cf3fe939e15927b61b2620a94d Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Wed, 13 Dec 2023 14:33:06 +0100 Subject: [PATCH 01/14] internal: removing the unused `endpoints` type --- .../2020-08-04/file/files/copy_wait_test.go | 6 +-- .../2023-11-03/file/files/copy_wait_test.go | 6 +-- storage/internal/endpoints/endpoints.go | 39 ------------------- 3 files changed, 6 insertions(+), 45 deletions(-) delete mode 100644 storage/internal/endpoints/endpoints.go diff --git a/storage/2020-08-04/file/files/copy_wait_test.go b/storage/2020-08-04/file/files/copy_wait_test.go index cc02397..e49144b 100644 --- a/storage/2020-08-04/file/files/copy_wait_test.go +++ b/storage/2020-08-04/file/files/copy_wait_test.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/go-azure-sdk/resource-manager/storage/2023-01-01/storageaccounts" "github.com/hashicorp/go-azure-sdk/sdk/auth" "github.com/tombuildsstuff/giovanni/storage/2020-08-04/file/shares" - "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" "github.com/tombuildsstuff/giovanni/storage/internal/testhelpers" ) @@ -115,7 +114,8 @@ func TestFilesCopyAndWaitFromBlob(t *testing.T) { } defer sharesClient.Delete(ctx, shareName, shares.DeleteInput{DeleteSnapshots: false}) - filesClient, err := NewWithBaseUri(fmt.Sprintf("https://%s.file.%s", accountName, *domainSuffix)) + baseUri := fmt.Sprintf("https://%s.file.%s", accountName, *domainSuffix) + filesClient, err := NewWithBaseUri(baseUri) if err := client.PrepareWithSharedKeyAuth(filesClient.Client, testData, auth.SharedKey); err != nil { t.Fatalf("adding authorizer to client: %+v", err) } @@ -132,7 +132,7 @@ func TestFilesCopyAndWaitFromBlob(t *testing.T) { t.Logf("[DEBUG] Now copying that blob..") duplicateInput := CopyInput{ - CopySource: fmt.Sprintf("%s/%s/%s", endpoints.GetFileEndpoint(*domainSuffix, accountName), shareName, originalFileName), + CopySource: fmt.Sprintf("%s/%s/%s", baseUri, shareName, originalFileName), } if _, err := filesClient.CopyAndWait(ctx, shareName, "", copiedFileName, duplicateInput); err != nil { t.Fatalf("Error copying duplicate: %s", err) diff --git a/storage/2023-11-03/file/files/copy_wait_test.go b/storage/2023-11-03/file/files/copy_wait_test.go index cc02397..e49144b 100644 --- a/storage/2023-11-03/file/files/copy_wait_test.go +++ b/storage/2023-11-03/file/files/copy_wait_test.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/go-azure-sdk/resource-manager/storage/2023-01-01/storageaccounts" "github.com/hashicorp/go-azure-sdk/sdk/auth" "github.com/tombuildsstuff/giovanni/storage/2020-08-04/file/shares" - "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" "github.com/tombuildsstuff/giovanni/storage/internal/testhelpers" ) @@ -115,7 +114,8 @@ func TestFilesCopyAndWaitFromBlob(t *testing.T) { } defer sharesClient.Delete(ctx, shareName, shares.DeleteInput{DeleteSnapshots: false}) - filesClient, err := NewWithBaseUri(fmt.Sprintf("https://%s.file.%s", accountName, *domainSuffix)) + baseUri := fmt.Sprintf("https://%s.file.%s", accountName, *domainSuffix) + filesClient, err := NewWithBaseUri(baseUri) if err := client.PrepareWithSharedKeyAuth(filesClient.Client, testData, auth.SharedKey); err != nil { t.Fatalf("adding authorizer to client: %+v", err) } @@ -132,7 +132,7 @@ func TestFilesCopyAndWaitFromBlob(t *testing.T) { t.Logf("[DEBUG] Now copying that blob..") duplicateInput := CopyInput{ - CopySource: fmt.Sprintf("%s/%s/%s", endpoints.GetFileEndpoint(*domainSuffix, accountName), shareName, originalFileName), + CopySource: fmt.Sprintf("%s/%s/%s", baseUri, shareName, originalFileName), } if _, err := filesClient.CopyAndWait(ctx, shareName, "", copiedFileName, duplicateInput); err != nil { t.Fatalf("Error copying duplicate: %s", err) diff --git a/storage/internal/endpoints/endpoints.go b/storage/internal/endpoints/endpoints.go deleted file mode 100644 index 2c58e8f..0000000 --- a/storage/internal/endpoints/endpoints.go +++ /dev/null @@ -1,39 +0,0 @@ -package endpoints - -import ( - "fmt" - "strings" -) - -func GetAccountNameFromEndpoint(endpoint string) (*string, error) { - segments := strings.Split(endpoint, ".") - if len(segments) == 0 { - return nil, fmt.Errorf("The Endpoint contained no segments") - } - return &segments[0], nil -} - -// GetBlobEndpoint returns the endpoint for Blob API Operations on this storage account -func GetBlobEndpoint(baseUri string, accountName string) string { - return fmt.Sprintf("https://%s.blob.%s", accountName, baseUri) -} - -// GetDataLakeStoreEndpoint returns the endpoint for Data Lake Store API Operations on this storage account -func GetDataLakeStoreEndpoint(baseUri string, accountName string) string { - return fmt.Sprintf("https://%s.dfs.%s", accountName, baseUri) -} - -// GetFileEndpoint returns the endpoint for File Share API Operations on this storage account -func GetFileEndpoint(baseUri string, accountName string) string { - return fmt.Sprintf("https://%s.file.%s", accountName, baseUri) -} - -// GetQueueEndpoint returns the endpoint for Queue API Operations on this storage account -func GetQueueEndpoint(baseUri string, accountName string) string { - return fmt.Sprintf("https://%s.queue.%s", accountName, baseUri) -} - -// GetTableEndpoint returns the endpoint for Table API Operations on this storage account -func GetTableEndpoint(baseUri string, accountName string) string { - return fmt.Sprintf("https://%s.table.%s", accountName, baseUri) -} From 8411b39b8d34eec8edab12039b7334d081278951 Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-azure <> Date: Thu, 14 Dec 2023 10:48:37 +0100 Subject: [PATCH 02/14] blob/accounts: introducing a base account id parser --- .../2020-08-04/blob/accounts/resource_id.go | 153 ++++++++++++++++++ .../blob/accounts/resource_id_test.go | 133 +++++++++++++++ .../2023-11-03/blob/accounts/resource_id.go | 153 ++++++++++++++++++ .../blob/accounts/resource_id_test.go | 133 +++++++++++++++ 4 files changed, 572 insertions(+) create mode 100644 storage/2020-08-04/blob/accounts/resource_id.go create mode 100644 storage/2020-08-04/blob/accounts/resource_id_test.go create mode 100644 storage/2023-11-03/blob/accounts/resource_id.go create mode 100644 storage/2023-11-03/blob/accounts/resource_id_test.go diff --git a/storage/2020-08-04/blob/accounts/resource_id.go b/storage/2020-08-04/blob/accounts/resource_id.go new file mode 100644 index 0000000..007a72f --- /dev/null +++ b/storage/2020-08-04/blob/accounts/resource_id.go @@ -0,0 +1,153 @@ +package accounts + +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +type SubDomainType string + +const ( + BlobSubDomainType SubDomainType = "blob" + FileSubDomainType SubDomainType = "file" + QueueSubDomainType SubDomainType = "queue" + TableSubDomainType SubDomainType = "table" +) + +func PossibleValuesForSubDomainType() []SubDomainType { + return []SubDomainType{ + BlobSubDomainType, + FileSubDomainType, + QueueSubDomainType, + TableSubDomainType, + } +} + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = AccountId{} + +type AccountId struct { + AccountName string + ZoneName *string + SubDomainType SubDomainType + DomainSuffix string + IsEdgeZone bool +} + +func (a AccountId) ID() string { + components := []string{ + a.AccountName, + } + if a.IsEdgeZone { + // Storage Accounts hosted in an Edge Zone + // `{accountname}.{component}.{edgezone}.edgestorage.azure.net` + components = append(components, string(a.SubDomainType)) + if a.ZoneName != nil { + components = append(components, *a.ZoneName) + } + } else { + // Storage Accounts using a DNS Zone + // `{accountname}.{dnszone}.{component}.storage.azure.net` + // or a Regular Storage Account + // `{accountname}.{component}.core.windows.net` + if a.ZoneName != nil { + components = append(components, *a.ZoneName) + } + components = append(components, string(a.SubDomainType)) + } + components = append(components, a.DomainSuffix) + return fmt.Sprintf("https://%s", strings.Join(components, ".")) +} + +func (a AccountId) String() string { + components := []string{ + fmt.Sprintf("IsEdgeZone %t", a.IsEdgeZone), + fmt.Sprintf("ZoneName %q", pointer.From(a.ZoneName)), + fmt.Sprintf("Subdomain Type %q", string(a.SubDomainType)), + fmt.Sprintf("DomainSuffix %q", a.DomainSuffix), + } + return fmt.Sprintf("Account %q (%s)", a.AccountName, strings.Join(components, " / ")) +} + +func ParseAccountID(input, domainSuffix string) (*AccountId, error) { + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a URL: %s", input, err) + } + + if !strings.HasSuffix(uri.Host, domainSuffix) { + return nil, fmt.Errorf("expected the account %q to use a domain suffix of %q", uri.Host, domainSuffix) + } + + // There's 3 different types of Storage Account ID: + // 1. Regular ol' Storage Accounts + // `{name}.{component}.core.windows.net` (e.g. `example1.blob.core.windows.net`) + // 2. Storage Accounts using a DNS Zone + // `{accountname}.{dnszone}.{component}.storage.azure.net` + // 3. Storage Accounts hosted in an Edge Zone + // `{accountname}.{component}.{edgezone}.edgestorage.azure.net` + // since both `dnszone` and `edgezone` are the only two identifiers, we need to check if `domainSuffix` includes `edge` + // to know how to treat these + + hostName := strings.TrimSuffix(uri.Host, fmt.Sprintf(".%s", domainSuffix)) + components := strings.Split(hostName, ".") + accountId := AccountId{ + DomainSuffix: domainSuffix, + IsEdgeZone: strings.Contains(strings.ToLower(domainSuffix), "edge"), + } + + if len(components) == 2 { + // this will be a regular Storage Account (e.g. `example1.blob.core.windows.net`) + accountId.AccountName = components[0] + subDomainType, err := parseSubDomainType(components[1]) + if err != nil { + return nil, err + } + accountId.SubDomainType = *subDomainType + return &accountId, nil + } + + if len(components) == 3 { + // This can either be a Zone'd Storage Account or a Storage Account within an Edge Zone + accountName := "" + subDomainTypeRaw := "" + zone := "" + if accountId.IsEdgeZone { + // `{accountname}.{component}.{edgezone}.edgestorage.azure.net` + accountName = components[0] + subDomainTypeRaw = components[1] + zone = components[2] + } else { + // `{accountname}.{dnszone}.{component}.storage.azure.net` + accountName = components[0] + zone = components[1] + subDomainTypeRaw = components[2] + } + + accountId.AccountName = accountName + subDomainType, err := parseSubDomainType(subDomainTypeRaw) + if err != nil { + return nil, err + } + accountId.SubDomainType = *subDomainType + accountId.ZoneName = pointer.To(zone) + return &accountId, nil + } + + return nil, fmt.Errorf("unknown storage account domain type %q", input) +} + +func parseSubDomainType(input string) (*SubDomainType, error) { + for _, k := range PossibleValuesForSubDomainType() { + if strings.EqualFold(input, string(k)) { + return pointer.To(k), nil + } + } + + return nil, fmt.Errorf("expected the subdomain type to be one of [%+v] but got %q", PossibleValuesForSubDomainType(), input) +} diff --git a/storage/2020-08-04/blob/accounts/resource_id_test.go b/storage/2020-08-04/blob/accounts/resource_id_test.go new file mode 100644 index 0000000..2f8b1ed --- /dev/null +++ b/storage/2020-08-04/blob/accounts/resource_id_test.go @@ -0,0 +1,133 @@ +package accounts + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" +) + +func TestParseAccountIDStandard(t *testing.T) { + input := "https://example.blob.core.windows.net" + expected := AccountId{ + AccountName: "example", + SubDomainType: BlobSubDomainType, + DomainSuffix: "core.windows.net", + } + actual, err := ParseAccountID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountName != expected.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountName, actual.AccountName) + } + if actual.SubDomainType != expected.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.SubDomainType, actual.SubDomainType) + } + if actual.DomainSuffix != expected.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.DomainSuffix, actual.DomainSuffix) + } +} + +func TestParseAccountIDInDNSZone(t *testing.T) { + input := "https://example.zone.blob.storage.azure.net" + expected := AccountId{ + AccountName: "example", + ZoneName: pointer.To("zone"), + SubDomainType: BlobSubDomainType, + DomainSuffix: "storage.azure.net", + } + actual, err := ParseAccountID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountName != expected.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountName, actual.AccountName) + } + if actual.ZoneName == nil { + t.Fatalf("expected ZoneName to have a value but got nil") + } + if *actual.ZoneName != *expected.ZoneName { + t.Fatalf("expected ZoneName to be %q but got %q", *expected.ZoneName, *actual.ZoneName) + } + if actual.SubDomainType != expected.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.SubDomainType, actual.SubDomainType) + } + if actual.DomainSuffix != expected.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.DomainSuffix, actual.DomainSuffix) + } + if actual.IsEdgeZone { + t.Fatalf("expected IsEdgeZone to be false but got %t", actual.IsEdgeZone) + } +} + +func TestParseAccountIDInEdgeZone(t *testing.T) { + input := "https://example.blob.danger.edgestorage.azure.net" + expected := AccountId{ + AccountName: "example", + ZoneName: pointer.To("danger"), + IsEdgeZone: true, + SubDomainType: BlobSubDomainType, + DomainSuffix: "edgestorage.azure.net", + } + actual, err := ParseAccountID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountName != expected.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountName, actual.AccountName) + } + if actual.ZoneName == nil { + t.Fatalf("expected ZoneName to have a value but got nil") + } + if *actual.ZoneName != *expected.ZoneName { + t.Fatalf("expected ZoneName to be %q but got %q", *expected.ZoneName, *actual.ZoneName) + } + if actual.SubDomainType != expected.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.SubDomainType, actual.SubDomainType) + } + if !actual.IsEdgeZone { + t.Fatalf("expected IsEdgeZone to be true but got %t", actual.IsEdgeZone) + } +} + +func TestFormatAccountIDStandard(t *testing.T) { + actual := AccountId{ + AccountName: "example1", + ZoneName: nil, + SubDomainType: FileSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }.ID() + expected := "https://example1.file.core.windows.net" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatAccountIDInDNSZone(t *testing.T) { + actual := AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone1"), + SubDomainType: FileSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }.ID() + expected := "https://example1.zone1.file.storage.azure.net" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatAccountIDInEdgeZone(t *testing.T) { + actual := AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone1"), + SubDomainType: FileSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }.ID() + expected := "https://example1.file.zone1.edgestorage.azure.net" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} diff --git a/storage/2023-11-03/blob/accounts/resource_id.go b/storage/2023-11-03/blob/accounts/resource_id.go new file mode 100644 index 0000000..007a72f --- /dev/null +++ b/storage/2023-11-03/blob/accounts/resource_id.go @@ -0,0 +1,153 @@ +package accounts + +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +type SubDomainType string + +const ( + BlobSubDomainType SubDomainType = "blob" + FileSubDomainType SubDomainType = "file" + QueueSubDomainType SubDomainType = "queue" + TableSubDomainType SubDomainType = "table" +) + +func PossibleValuesForSubDomainType() []SubDomainType { + return []SubDomainType{ + BlobSubDomainType, + FileSubDomainType, + QueueSubDomainType, + TableSubDomainType, + } +} + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = AccountId{} + +type AccountId struct { + AccountName string + ZoneName *string + SubDomainType SubDomainType + DomainSuffix string + IsEdgeZone bool +} + +func (a AccountId) ID() string { + components := []string{ + a.AccountName, + } + if a.IsEdgeZone { + // Storage Accounts hosted in an Edge Zone + // `{accountname}.{component}.{edgezone}.edgestorage.azure.net` + components = append(components, string(a.SubDomainType)) + if a.ZoneName != nil { + components = append(components, *a.ZoneName) + } + } else { + // Storage Accounts using a DNS Zone + // `{accountname}.{dnszone}.{component}.storage.azure.net` + // or a Regular Storage Account + // `{accountname}.{component}.core.windows.net` + if a.ZoneName != nil { + components = append(components, *a.ZoneName) + } + components = append(components, string(a.SubDomainType)) + } + components = append(components, a.DomainSuffix) + return fmt.Sprintf("https://%s", strings.Join(components, ".")) +} + +func (a AccountId) String() string { + components := []string{ + fmt.Sprintf("IsEdgeZone %t", a.IsEdgeZone), + fmt.Sprintf("ZoneName %q", pointer.From(a.ZoneName)), + fmt.Sprintf("Subdomain Type %q", string(a.SubDomainType)), + fmt.Sprintf("DomainSuffix %q", a.DomainSuffix), + } + return fmt.Sprintf("Account %q (%s)", a.AccountName, strings.Join(components, " / ")) +} + +func ParseAccountID(input, domainSuffix string) (*AccountId, error) { + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a URL: %s", input, err) + } + + if !strings.HasSuffix(uri.Host, domainSuffix) { + return nil, fmt.Errorf("expected the account %q to use a domain suffix of %q", uri.Host, domainSuffix) + } + + // There's 3 different types of Storage Account ID: + // 1. Regular ol' Storage Accounts + // `{name}.{component}.core.windows.net` (e.g. `example1.blob.core.windows.net`) + // 2. Storage Accounts using a DNS Zone + // `{accountname}.{dnszone}.{component}.storage.azure.net` + // 3. Storage Accounts hosted in an Edge Zone + // `{accountname}.{component}.{edgezone}.edgestorage.azure.net` + // since both `dnszone` and `edgezone` are the only two identifiers, we need to check if `domainSuffix` includes `edge` + // to know how to treat these + + hostName := strings.TrimSuffix(uri.Host, fmt.Sprintf(".%s", domainSuffix)) + components := strings.Split(hostName, ".") + accountId := AccountId{ + DomainSuffix: domainSuffix, + IsEdgeZone: strings.Contains(strings.ToLower(domainSuffix), "edge"), + } + + if len(components) == 2 { + // this will be a regular Storage Account (e.g. `example1.blob.core.windows.net`) + accountId.AccountName = components[0] + subDomainType, err := parseSubDomainType(components[1]) + if err != nil { + return nil, err + } + accountId.SubDomainType = *subDomainType + return &accountId, nil + } + + if len(components) == 3 { + // This can either be a Zone'd Storage Account or a Storage Account within an Edge Zone + accountName := "" + subDomainTypeRaw := "" + zone := "" + if accountId.IsEdgeZone { + // `{accountname}.{component}.{edgezone}.edgestorage.azure.net` + accountName = components[0] + subDomainTypeRaw = components[1] + zone = components[2] + } else { + // `{accountname}.{dnszone}.{component}.storage.azure.net` + accountName = components[0] + zone = components[1] + subDomainTypeRaw = components[2] + } + + accountId.AccountName = accountName + subDomainType, err := parseSubDomainType(subDomainTypeRaw) + if err != nil { + return nil, err + } + accountId.SubDomainType = *subDomainType + accountId.ZoneName = pointer.To(zone) + return &accountId, nil + } + + return nil, fmt.Errorf("unknown storage account domain type %q", input) +} + +func parseSubDomainType(input string) (*SubDomainType, error) { + for _, k := range PossibleValuesForSubDomainType() { + if strings.EqualFold(input, string(k)) { + return pointer.To(k), nil + } + } + + return nil, fmt.Errorf("expected the subdomain type to be one of [%+v] but got %q", PossibleValuesForSubDomainType(), input) +} diff --git a/storage/2023-11-03/blob/accounts/resource_id_test.go b/storage/2023-11-03/blob/accounts/resource_id_test.go new file mode 100644 index 0000000..2f8b1ed --- /dev/null +++ b/storage/2023-11-03/blob/accounts/resource_id_test.go @@ -0,0 +1,133 @@ +package accounts + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" +) + +func TestParseAccountIDStandard(t *testing.T) { + input := "https://example.blob.core.windows.net" + expected := AccountId{ + AccountName: "example", + SubDomainType: BlobSubDomainType, + DomainSuffix: "core.windows.net", + } + actual, err := ParseAccountID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountName != expected.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountName, actual.AccountName) + } + if actual.SubDomainType != expected.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.SubDomainType, actual.SubDomainType) + } + if actual.DomainSuffix != expected.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.DomainSuffix, actual.DomainSuffix) + } +} + +func TestParseAccountIDInDNSZone(t *testing.T) { + input := "https://example.zone.blob.storage.azure.net" + expected := AccountId{ + AccountName: "example", + ZoneName: pointer.To("zone"), + SubDomainType: BlobSubDomainType, + DomainSuffix: "storage.azure.net", + } + actual, err := ParseAccountID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountName != expected.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountName, actual.AccountName) + } + if actual.ZoneName == nil { + t.Fatalf("expected ZoneName to have a value but got nil") + } + if *actual.ZoneName != *expected.ZoneName { + t.Fatalf("expected ZoneName to be %q but got %q", *expected.ZoneName, *actual.ZoneName) + } + if actual.SubDomainType != expected.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.SubDomainType, actual.SubDomainType) + } + if actual.DomainSuffix != expected.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.DomainSuffix, actual.DomainSuffix) + } + if actual.IsEdgeZone { + t.Fatalf("expected IsEdgeZone to be false but got %t", actual.IsEdgeZone) + } +} + +func TestParseAccountIDInEdgeZone(t *testing.T) { + input := "https://example.blob.danger.edgestorage.azure.net" + expected := AccountId{ + AccountName: "example", + ZoneName: pointer.To("danger"), + IsEdgeZone: true, + SubDomainType: BlobSubDomainType, + DomainSuffix: "edgestorage.azure.net", + } + actual, err := ParseAccountID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountName != expected.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountName, actual.AccountName) + } + if actual.ZoneName == nil { + t.Fatalf("expected ZoneName to have a value but got nil") + } + if *actual.ZoneName != *expected.ZoneName { + t.Fatalf("expected ZoneName to be %q but got %q", *expected.ZoneName, *actual.ZoneName) + } + if actual.SubDomainType != expected.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.SubDomainType, actual.SubDomainType) + } + if !actual.IsEdgeZone { + t.Fatalf("expected IsEdgeZone to be true but got %t", actual.IsEdgeZone) + } +} + +func TestFormatAccountIDStandard(t *testing.T) { + actual := AccountId{ + AccountName: "example1", + ZoneName: nil, + SubDomainType: FileSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }.ID() + expected := "https://example1.file.core.windows.net" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatAccountIDInDNSZone(t *testing.T) { + actual := AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone1"), + SubDomainType: FileSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }.ID() + expected := "https://example1.zone1.file.storage.azure.net" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatAccountIDInEdgeZone(t *testing.T) { + actual := AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone1"), + SubDomainType: FileSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }.ID() + expected := "https://example1.file.zone1.edgestorage.azure.net" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} From 21676302ed1ca08b17d5fef36b56f143a79fdf65 Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-azure <> Date: Thu, 14 Dec 2023 10:48:57 +0100 Subject: [PATCH 03/14] blob/containers: reintroducing the ID parser --- .../2020-08-04/blob/containers/resource_id.go | 70 ++++++++ .../blob/containers/resource_id_test.go | 149 ++++++++++++++++++ .../2023-11-03/blob/containers/resource_id.go | 70 ++++++++ .../blob/containers/resource_id_test.go | 149 ++++++++++++++++++ 4 files changed, 438 insertions(+) diff --git a/storage/2020-08-04/blob/containers/resource_id.go b/storage/2020-08-04/blob/containers/resource_id.go index 599ef93..3c86ab4 100644 --- a/storage/2020-08-04/blob/containers/resource_id.go +++ b/storage/2020-08-04/blob/containers/resource_id.go @@ -2,6 +2,11 @@ package containers import ( "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" ) // GetResourceManagerResourceID returns the Resource Manager specific @@ -10,3 +15,68 @@ func (c Client) GetResourceManagerResourceID(subscriptionID, resourceGroup, acco fmtStr := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/blobServices/default/containers/%s" return fmt.Sprintf(fmtStr, subscriptionID, resourceGroup, accountName, containerName) } + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = ContainerId{} + +type ContainerId struct { + // AccountId specifies the ID of the Storage Account where this Blob exists. + AccountId accounts.AccountId + + // ContainerName specifies the name of the Container within this Storage Account where this + // Blob exists. + ContainerName string +} + +func NewContainerID(accountId accounts.AccountId, containerName string) ContainerId { + return ContainerId{ + AccountId: accountId, + ContainerName: containerName, + } +} + +func (b ContainerId) ID() string { + return fmt.Sprintf("%s/%s", b.AccountId.ID(), b.ContainerName) +} + +func (b ContainerId) String() string { + components := []string{ + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("Containerr %q (%s)", b.ContainerName, strings.Join(components, " / ")) +} + +// ParseContainerID parses `input` into a Blob ID using a known `domainSuffix` +func ParseContainerID(input, domainSuffix string) (*ContainerId, error) { + // example: https://foo.blob.core.windows.net/Bar + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.BlobSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.BlobSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) == 0 { + return nil, fmt.Errorf("Expected the path to contain segments but got none") + } + + containerName := strings.TrimPrefix(uri.Path, "/") + return &ContainerId{ + AccountId: *account, + ContainerName: containerName, + }, nil +} diff --git a/storage/2020-08-04/blob/containers/resource_id_test.go b/storage/2020-08-04/blob/containers/resource_id_test.go index e46e5e8..76efd8f 100644 --- a/storage/2020-08-04/blob/containers/resource_id_test.go +++ b/storage/2020-08-04/blob/containers/resource_id_test.go @@ -2,6 +2,9 @@ package containers import ( "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" ) func TestGetResourceManagerResourceID(t *testing.T) { @@ -11,3 +14,149 @@ func TestGetResourceManagerResourceID(t *testing.T) { t.Fatalf("Expected the Resource Manager Resource ID to be %q but got %q", expected, actual) } } + +func TestParseContainerIDStandard(t *testing.T) { + input := "https://example1.blob.core.windows.net/container1" + expected := ContainerId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "core.windows.net", + }, + ContainerName: "container1", + } + actual, err := ParseContainerID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.ContainerName != expected.ContainerName { + t.Fatalf("expected ContainerName to be %q but got %q", expected.ContainerName, actual.ContainerName) + } +} + +func TestParseContainerIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.blob.storage.azure.net/container1" + expected := ContainerId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + ContainerName: "container1", + } + actual, err := ParseContainerID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.ContainerName != expected.ContainerName { + t.Fatalf("expected ContainerName to be %q but got %q", expected.ContainerName, actual.ContainerName) + } +} + +func TestParseContainerIDInAnEdgeZone(t *testing.T) { + input := "https://example1.blob.zone1.edgestorage.azure.net/container1" + expected := ContainerId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + ContainerName: "container1", + } + actual, err := ParseContainerID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.ContainerName != expected.ContainerName { + t.Fatalf("expected ContainerName to be %q but got %q", expected.ContainerName, actual.ContainerName) + } +} + +func TestFormatContainerIDStandard(t *testing.T) { + actual := ContainerId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + ContainerName: "container1", + }.ID() + expected := "https://example1.blob.core.windows.net/container1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInDNSZone(t *testing.T) { + actual := ContainerId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + ContainerName: "container1", + }.ID() + expected := "https://example1.zone2.blob.storage.azure.net/container1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInEdgeZone(t *testing.T) { + actual := ContainerId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + ContainerName: "container1", + }.ID() + expected := "https://example1.blob.zone2.edgestorage.azure.net/container1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} diff --git a/storage/2023-11-03/blob/containers/resource_id.go b/storage/2023-11-03/blob/containers/resource_id.go index 599ef93..114255e 100644 --- a/storage/2023-11-03/blob/containers/resource_id.go +++ b/storage/2023-11-03/blob/containers/resource_id.go @@ -2,6 +2,11 @@ package containers import ( "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" ) // GetResourceManagerResourceID returns the Resource Manager specific @@ -10,3 +15,68 @@ func (c Client) GetResourceManagerResourceID(subscriptionID, resourceGroup, acco fmtStr := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/blobServices/default/containers/%s" return fmt.Sprintf(fmtStr, subscriptionID, resourceGroup, accountName, containerName) } + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = ContainerId{} + +type ContainerId struct { + // AccountId specifies the ID of the Storage Account where this Blob exists. + AccountId accounts.AccountId + + // ContainerName specifies the name of the Container within this Storage Account where this + // Blob exists. + ContainerName string +} + +func NewContainerID(accountId accounts.AccountId, containerName string) ContainerId { + return ContainerId{ + AccountId: accountId, + ContainerName: containerName, + } +} + +func (b ContainerId) ID() string { + return fmt.Sprintf("%s/%s", b.AccountId.ID(), b.ContainerName) +} + +func (b ContainerId) String() string { + components := []string{ + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("Containerr %q (%s)", b.ContainerName, strings.Join(components, " / ")) +} + +// ParseContainerID parses `input` into a Blob ID using a known `domainSuffix` +func ParseContainerID(input, domainSuffix string) (*ContainerId, error) { + // example: https://foo.blob.core.windows.net/Bar + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.BlobSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.BlobSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) == 0 { + return nil, fmt.Errorf("Expected the path to contain segments but got none") + } + + containerName := strings.TrimPrefix(uri.Path, "/") + return &ContainerId{ + AccountId: *account, + ContainerName: containerName, + }, nil +} diff --git a/storage/2023-11-03/blob/containers/resource_id_test.go b/storage/2023-11-03/blob/containers/resource_id_test.go index e46e5e8..28fe3e1 100644 --- a/storage/2023-11-03/blob/containers/resource_id_test.go +++ b/storage/2023-11-03/blob/containers/resource_id_test.go @@ -2,6 +2,9 @@ package containers import ( "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" ) func TestGetResourceManagerResourceID(t *testing.T) { @@ -11,3 +14,149 @@ func TestGetResourceManagerResourceID(t *testing.T) { t.Fatalf("Expected the Resource Manager Resource ID to be %q but got %q", expected, actual) } } + +func TestParseContainerIDStandard(t *testing.T) { + input := "https://example1.blob.core.windows.net/container1" + expected := ContainerId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "core.windows.net", + }, + ContainerName: "container1", + } + actual, err := ParseContainerID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.ContainerName != expected.ContainerName { + t.Fatalf("expected ContainerName to be %q but got %q", expected.ContainerName, actual.ContainerName) + } +} + +func TestParseContainerIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.blob.storage.azure.net/container1" + expected := ContainerId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + ContainerName: "container1", + } + actual, err := ParseContainerID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.ContainerName != expected.ContainerName { + t.Fatalf("expected ContainerName to be %q but got %q", expected.ContainerName, actual.ContainerName) + } +} + +func TestParseContainerIDInAnEdgeZone(t *testing.T) { + input := "https://example1.blob.zone1.edgestorage.azure.net/container1" + expected := ContainerId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + ContainerName: "container1", + } + actual, err := ParseContainerID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.ContainerName != expected.ContainerName { + t.Fatalf("expected ContainerName to be %q but got %q", expected.ContainerName, actual.ContainerName) + } +} + +func TestFormatContainerIDStandard(t *testing.T) { + actual := ContainerId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + ContainerName: "container1", + }.ID() + expected := "https://example1.blob.core.windows.net/container1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInDNSZone(t *testing.T) { + actual := ContainerId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + ContainerName: "container1", + }.ID() + expected := "https://example1.zone2.blob.storage.azure.net/container1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInEdgeZone(t *testing.T) { + actual := ContainerId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + ContainerName: "container1", + }.ID() + expected := "https://example1.blob.zone2.edgestorage.azure.net/container1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} From af5c265705f6b9f5135449b4338cd01299b03288 Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-azure <> Date: Thu, 14 Dec 2023 10:49:22 +0100 Subject: [PATCH 04/14] blob/blobs: reintroducing the Resource ID parser --- storage/2020-08-04/blob/blobs/resource_id.go | 83 +++++++++ .../2020-08-04/blob/blobs/resource_id_test.go | 169 ++++++++++++++++++ storage/2023-11-03/blob/blobs/resource_id.go | 83 +++++++++ .../2023-11-03/blob/blobs/resource_id_test.go | 169 ++++++++++++++++++ 4 files changed, 504 insertions(+) create mode 100644 storage/2020-08-04/blob/blobs/resource_id.go create mode 100644 storage/2020-08-04/blob/blobs/resource_id_test.go create mode 100644 storage/2023-11-03/blob/blobs/resource_id.go create mode 100644 storage/2023-11-03/blob/blobs/resource_id_test.go diff --git a/storage/2020-08-04/blob/blobs/resource_id.go b/storage/2020-08-04/blob/blobs/resource_id.go new file mode 100644 index 0000000..2b83a5b --- /dev/null +++ b/storage/2020-08-04/blob/blobs/resource_id.go @@ -0,0 +1,83 @@ +package blobs + +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" +) + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = BlobId{} + +type BlobId struct { + // AccountId specifies the ID of the Storage Account where this Blob exists. + AccountId accounts.AccountId + + // ContainerName specifies the name of the Container within this Storage Account where this + // Blob exists. + ContainerName string + + // BlobName specifies the name of this Blob. + BlobName string +} + +func NewBlobID(accountId accounts.AccountId, containerName, blobName string) BlobId { + return BlobId{ + AccountId: accountId, + ContainerName: containerName, + BlobName: blobName, + } +} + +func (b BlobId) ID() string { + return fmt.Sprintf("%s/%s/%s", b.AccountId.ID(), b.ContainerName, b.BlobName) +} + +func (b BlobId) String() string { + components := []string{ + fmt.Sprintf("Account %q", b.AccountId.String()), + fmt.Sprintf("Container Name %q", b.ContainerName), + } + return fmt.Sprintf("Blob %q (%s)", b.BlobName, strings.Join(components, " / ")) +} + +// ParseBlobID parses `input` into a Blob ID using a known `domainSuffix` +func ParseBlobID(input, domainSuffix string) (*BlobId, error) { + // example: https://foo.blob.core.windows.net/Bar/example.vhd + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.BlobSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.BlobSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) == 0 { + return nil, fmt.Errorf("Expected the path to contain segments but got none") + } + + containerName := segments[0] + blobName := strings.TrimPrefix(path, containerName) + blobName = strings.TrimPrefix(blobName, "/") + return &BlobId{ + AccountId: *account, + ContainerName: containerName, + BlobName: blobName, + }, nil +} diff --git a/storage/2020-08-04/blob/blobs/resource_id_test.go b/storage/2020-08-04/blob/blobs/resource_id_test.go new file mode 100644 index 0000000..ae3ab02 --- /dev/null +++ b/storage/2020-08-04/blob/blobs/resource_id_test.go @@ -0,0 +1,169 @@ +package blobs + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" +) + +func TestParseBlobIDStandard(t *testing.T) { + input := "https://example1.blob.core.windows.net/container1/blob1.vhd" + expected := BlobId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "core.windows.net", + }, + ContainerName: "container1", + BlobName: "blob1.vhd", + } + actual, err := ParseBlobID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.ContainerName != expected.ContainerName { + t.Fatalf("expected ContainerName to be %q but got %q", expected.ContainerName, actual.ContainerName) + } + if actual.BlobName != expected.BlobName { + t.Fatalf("expected BlobName to be %q but got %q", expected.BlobName, actual.BlobName) + } +} + +func TestParseBlobIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.blob.storage.azure.net/container1/blob1.vhd" + expected := BlobId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + ContainerName: "container1", + BlobName: "blob1.vhd", + } + actual, err := ParseBlobID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.ContainerName != expected.ContainerName { + t.Fatalf("expected ContainerName to be %q but got %q", expected.ContainerName, actual.ContainerName) + } + if actual.BlobName != expected.BlobName { + t.Fatalf("expected BlobName to be %q but got %q", expected.BlobName, actual.BlobName) + } +} + +func TestParseBlobIDInAnEdgeZone(t *testing.T) { + input := "https://example1.blob.zone1.edgestorage.azure.net/container1/blob1.vhd" + expected := BlobId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + ContainerName: "container1", + BlobName: "blob1.vhd", + } + actual, err := ParseBlobID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.ContainerName != expected.ContainerName { + t.Fatalf("expected ContainerName to be %q but got %q", expected.ContainerName, actual.ContainerName) + } + if actual.BlobName != expected.BlobName { + t.Fatalf("expected BlobName to be %q but got %q", expected.BlobName, actual.BlobName) + } +} + +func TestFormatBlobIDStandard(t *testing.T) { + actual := BlobId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + ContainerName: "container1", + BlobName: "somefile.vhd", + }.ID() + expected := "https://example1.blob.core.windows.net/container1/somefile.vhd" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatBlobIDInDNSZone(t *testing.T) { + actual := BlobId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + ContainerName: "container1", + BlobName: "somefile.vhd", + }.ID() + expected := "https://example1.zone2.blob.storage.azure.net/container1/somefile.vhd" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatBlobIDInEdgeZone(t *testing.T) { + actual := BlobId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + ContainerName: "container1", + BlobName: "somefile.vhd", + }.ID() + expected := "https://example1.blob.zone2.edgestorage.azure.net/container1/somefile.vhd" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} diff --git a/storage/2023-11-03/blob/blobs/resource_id.go b/storage/2023-11-03/blob/blobs/resource_id.go new file mode 100644 index 0000000..057e161 --- /dev/null +++ b/storage/2023-11-03/blob/blobs/resource_id.go @@ -0,0 +1,83 @@ +package blobs + +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" +) + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = BlobId{} + +type BlobId struct { + // AccountId specifies the ID of the Storage Account where this Blob exists. + AccountId accounts.AccountId + + // ContainerName specifies the name of the Container within this Storage Account where this + // Blob exists. + ContainerName string + + // BlobName specifies the name of this Blob. + BlobName string +} + +func NewBlobID(accountId accounts.AccountId, containerName, blobName string) BlobId { + return BlobId{ + AccountId: accountId, + ContainerName: containerName, + BlobName: blobName, + } +} + +func (b BlobId) ID() string { + return fmt.Sprintf("%s/%s/%s", b.AccountId.ID(), b.ContainerName, b.BlobName) +} + +func (b BlobId) String() string { + components := []string{ + fmt.Sprintf("Account %q", b.AccountId.String()), + fmt.Sprintf("Container Name %q", b.ContainerName), + } + return fmt.Sprintf("Blob %q (%s)", b.BlobName, strings.Join(components, " / ")) +} + +// ParseBlobID parses `input` into a Blob ID using a known `domainSuffix` +func ParseBlobID(input, domainSuffix string) (*BlobId, error) { + // example: https://foo.blob.core.windows.net/Bar/example.vhd + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.BlobSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.BlobSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) == 0 { + return nil, fmt.Errorf("Expected the path to contain segments but got none") + } + + containerName := segments[0] + blobName := strings.TrimPrefix(path, containerName) + blobName = strings.TrimPrefix(blobName, "/") + return &BlobId{ + AccountId: *account, + ContainerName: containerName, + BlobName: blobName, + }, nil +} diff --git a/storage/2023-11-03/blob/blobs/resource_id_test.go b/storage/2023-11-03/blob/blobs/resource_id_test.go new file mode 100644 index 0000000..6e83de3 --- /dev/null +++ b/storage/2023-11-03/blob/blobs/resource_id_test.go @@ -0,0 +1,169 @@ +package blobs + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" +) + +func TestParseBlobIDStandard(t *testing.T) { + input := "https://example1.blob.core.windows.net/container1/blob1.vhd" + expected := BlobId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "core.windows.net", + }, + ContainerName: "container1", + BlobName: "blob1.vhd", + } + actual, err := ParseBlobID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.ContainerName != expected.ContainerName { + t.Fatalf("expected ContainerName to be %q but got %q", expected.ContainerName, actual.ContainerName) + } + if actual.BlobName != expected.BlobName { + t.Fatalf("expected BlobName to be %q but got %q", expected.BlobName, actual.BlobName) + } +} + +func TestParseBlobIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.blob.storage.azure.net/container1/blob1.vhd" + expected := BlobId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + ContainerName: "container1", + BlobName: "blob1.vhd", + } + actual, err := ParseBlobID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.ContainerName != expected.ContainerName { + t.Fatalf("expected ContainerName to be %q but got %q", expected.ContainerName, actual.ContainerName) + } + if actual.BlobName != expected.BlobName { + t.Fatalf("expected BlobName to be %q but got %q", expected.BlobName, actual.BlobName) + } +} + +func TestParseBlobIDInAnEdgeZone(t *testing.T) { + input := "https://example1.blob.zone1.edgestorage.azure.net/container1/blob1.vhd" + expected := BlobId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + ContainerName: "container1", + BlobName: "blob1.vhd", + } + actual, err := ParseBlobID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.ContainerName != expected.ContainerName { + t.Fatalf("expected ContainerName to be %q but got %q", expected.ContainerName, actual.ContainerName) + } + if actual.BlobName != expected.BlobName { + t.Fatalf("expected BlobName to be %q but got %q", expected.BlobName, actual.BlobName) + } +} + +func TestFormatBlobIDStandard(t *testing.T) { + actual := BlobId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + ContainerName: "container1", + BlobName: "somefile.vhd", + }.ID() + expected := "https://example1.blob.core.windows.net/container1/somefile.vhd" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatBlobIDInDNSZone(t *testing.T) { + actual := BlobId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + ContainerName: "container1", + BlobName: "somefile.vhd", + }.ID() + expected := "https://example1.zone2.blob.storage.azure.net/container1/somefile.vhd" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatBlobIDInEdgeZone(t *testing.T) { + actual := BlobId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.BlobSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + ContainerName: "container1", + BlobName: "somefile.vhd", + }.ID() + expected := "https://example1.blob.zone2.edgestorage.azure.net/container1/somefile.vhd" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} From 1fdc018be2fb95d550ffceb49a672152f144451a Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-azure <> Date: Thu, 14 Dec 2023 10:56:21 +0100 Subject: [PATCH 05/14] file/shares: reintroducing the Resource ID Parser --- storage/2020-08-04/file/shares/resource_id.go | 69 ++++++++ .../file/shares/resource_id_test.go | 153 +++++++++++++++++- storage/2023-11-03/file/shares/resource_id.go | 69 ++++++++ .../file/shares/resource_id_test.go | 153 +++++++++++++++++- 4 files changed, 442 insertions(+), 2 deletions(-) diff --git a/storage/2020-08-04/file/shares/resource_id.go b/storage/2020-08-04/file/shares/resource_id.go index 13f8086..73b93bd 100644 --- a/storage/2020-08-04/file/shares/resource_id.go +++ b/storage/2020-08-04/file/shares/resource_id.go @@ -2,6 +2,11 @@ package shares import ( "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" ) // GetResourceManagerResourceID returns the Resource Manager specific @@ -10,3 +15,67 @@ func (c Client) GetResourceManagerResourceID(subscriptionID, resourceGroup, acco fmtStr := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/fileServices/default/shares/%s" return fmt.Sprintf(fmtStr, subscriptionID, resourceGroup, accountName, shareName) } + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = ShareId{} + +type ShareId struct { + // AccountId specifies the ID of the Storage Account where this File Share exists. + AccountId accounts.AccountId + + // ShareName specifies the name of this File Share. + ShareName string +} + +func NewShareID(accountId accounts.AccountId, shareName string) ShareId { + return ShareId{ + AccountId: accountId, + ShareName: shareName, + } +} + +func (b ShareId) ID() string { + return fmt.Sprintf("%s/%s", b.AccountId.ID(), b.ShareName) +} + +func (b ShareId) String() string { + components := []string{ + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("File Share %q (%s)", b.ShareName, strings.Join(components, " / ")) +} + +// ParseShareID parses `input` into a Share ID using a known `domainSuffix` +func ParseShareID(input, domainSuffix string) (*ShareId, error) { + // example: https://foo.file.core.windows.net/Bar + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.FileSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.FileSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) == 0 { + return nil, fmt.Errorf("Expected the path to contain segments but got none") + } + + shareName := strings.TrimPrefix(uri.Path, "/") + return &ShareId{ + AccountId: *account, + ShareName: shareName, + }, nil +} diff --git a/storage/2020-08-04/file/shares/resource_id_test.go b/storage/2020-08-04/file/shares/resource_id_test.go index f6ff899..8339439 100644 --- a/storage/2020-08-04/file/shares/resource_id_test.go +++ b/storage/2020-08-04/file/shares/resource_id_test.go @@ -1,6 +1,11 @@ package shares -import "testing" +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" +) func TestGetResourceManagerResourceID(t *testing.T) { actual := Client{}.GetResourceManagerResourceID("11112222-3333-4444-5555-666677778888", "group1", "account1", "share1") @@ -9,3 +14,149 @@ func TestGetResourceManagerResourceID(t *testing.T) { t.Fatalf("Expected the Resource Manager Resource ID to be %q but got %q", expected, actual) } } + +func TestParseShareIDStandard(t *testing.T) { + input := "https://example1.file.core.windows.net/share1" + expected := ShareId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "core.windows.net", + }, + ShareName: "share1", + } + actual, err := ParseShareID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } +} + +func TestParseShareIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.file.storage.azure.net/share1" + expected := ShareId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + ShareName: "share1", + } + actual, err := ParseShareID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } +} + +func TestParseShareIDInAnEdgeZone(t *testing.T) { + input := "https://example1.file.zone1.edgestorage.azure.net/share1" + expected := ShareId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + ShareName: "share1", + } + actual, err := ParseShareID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } +} + +func TestFormatContainerIDStandard(t *testing.T) { + actual := ShareId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + ShareName: "share1", + }.ID() + expected := "https://example1.file.core.windows.net/share1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInDNSZone(t *testing.T) { + actual := ShareId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + ShareName: "share1", + }.ID() + expected := "https://example1.zone2.file.storage.azure.net/share1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInEdgeZone(t *testing.T) { + actual := ShareId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + ShareName: "share1", + }.ID() + expected := "https://example1.file.zone2.edgestorage.azure.net/share1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} diff --git a/storage/2023-11-03/file/shares/resource_id.go b/storage/2023-11-03/file/shares/resource_id.go index 13f8086..c46092b 100644 --- a/storage/2023-11-03/file/shares/resource_id.go +++ b/storage/2023-11-03/file/shares/resource_id.go @@ -2,6 +2,11 @@ package shares import ( "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" ) // GetResourceManagerResourceID returns the Resource Manager specific @@ -10,3 +15,67 @@ func (c Client) GetResourceManagerResourceID(subscriptionID, resourceGroup, acco fmtStr := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/fileServices/default/shares/%s" return fmt.Sprintf(fmtStr, subscriptionID, resourceGroup, accountName, shareName) } + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = ShareId{} + +type ShareId struct { + // AccountId specifies the ID of the Storage Account where this File Share exists. + AccountId accounts.AccountId + + // ShareName specifies the name of this File Share. + ShareName string +} + +func NewShareID(accountId accounts.AccountId, shareName string) ShareId { + return ShareId{ + AccountId: accountId, + ShareName: shareName, + } +} + +func (b ShareId) ID() string { + return fmt.Sprintf("%s/%s", b.AccountId.ID(), b.ShareName) +} + +func (b ShareId) String() string { + components := []string{ + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("File Share %q (%s)", b.ShareName, strings.Join(components, " / ")) +} + +// ParseShareID parses `input` into a Share ID using a known `domainSuffix` +func ParseShareID(input, domainSuffix string) (*ShareId, error) { + // example: https://foo.file.core.windows.net/Bar + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.FileSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.FileSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) == 0 { + return nil, fmt.Errorf("Expected the path to contain segments but got none") + } + + shareName := strings.TrimPrefix(uri.Path, "/") + return &ShareId{ + AccountId: *account, + ShareName: shareName, + }, nil +} diff --git a/storage/2023-11-03/file/shares/resource_id_test.go b/storage/2023-11-03/file/shares/resource_id_test.go index f6ff899..7ce247e 100644 --- a/storage/2023-11-03/file/shares/resource_id_test.go +++ b/storage/2023-11-03/file/shares/resource_id_test.go @@ -1,6 +1,11 @@ package shares -import "testing" +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" +) func TestGetResourceManagerResourceID(t *testing.T) { actual := Client{}.GetResourceManagerResourceID("11112222-3333-4444-5555-666677778888", "group1", "account1", "share1") @@ -9,3 +14,149 @@ func TestGetResourceManagerResourceID(t *testing.T) { t.Fatalf("Expected the Resource Manager Resource ID to be %q but got %q", expected, actual) } } + +func TestParseShareIDStandard(t *testing.T) { + input := "https://example1.file.core.windows.net/share1" + expected := ShareId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "core.windows.net", + }, + ShareName: "share1", + } + actual, err := ParseShareID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } +} + +func TestParseShareIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.file.storage.azure.net/share1" + expected := ShareId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + ShareName: "share1", + } + actual, err := ParseShareID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } +} + +func TestParseShareIDInAnEdgeZone(t *testing.T) { + input := "https://example1.file.zone1.edgestorage.azure.net/share1" + expected := ShareId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + ShareName: "share1", + } + actual, err := ParseShareID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } +} + +func TestFormatContainerIDStandard(t *testing.T) { + actual := ShareId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + ShareName: "share1", + }.ID() + expected := "https://example1.file.core.windows.net/share1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInDNSZone(t *testing.T) { + actual := ShareId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + ShareName: "share1", + }.ID() + expected := "https://example1.zone2.file.storage.azure.net/share1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInEdgeZone(t *testing.T) { + actual := ShareId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + ShareName: "share1", + }.ID() + expected := "https://example1.file.zone2.edgestorage.azure.net/share1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} From 887120f8a542c60042fb7ac68eac7056b84da6b2 Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-azure <> Date: Thu, 14 Dec 2023 11:03:23 +0100 Subject: [PATCH 06/14] file/directories: reintroducing the Resource ID Parser --- .../file/directories/resource_id.go | 80 +++++++++ .../file/directories/resource_id_test.go | 169 ++++++++++++++++++ .../file/directories/resource_id.go | 80 +++++++++ .../file/directories/resource_id_test.go | 169 ++++++++++++++++++ 4 files changed, 498 insertions(+) create mode 100644 storage/2020-08-04/file/directories/resource_id.go create mode 100644 storage/2020-08-04/file/directories/resource_id_test.go create mode 100644 storage/2023-11-03/file/directories/resource_id.go create mode 100644 storage/2023-11-03/file/directories/resource_id_test.go diff --git a/storage/2020-08-04/file/directories/resource_id.go b/storage/2020-08-04/file/directories/resource_id.go new file mode 100644 index 0000000..26b0d4c --- /dev/null +++ b/storage/2020-08-04/file/directories/resource_id.go @@ -0,0 +1,80 @@ +package directories + +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" +) + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = DirectoryId{} + +type DirectoryId struct { + // AccountId specifies the ID of the Storage Account where this Directory exists. + AccountId accounts.AccountId + + // ShareName specifies the name of the File Share containing this Directory. + ShareName string + + // DirectoryPath specifies the path representing this Directory. + DirectoryPath string +} + +func NewDirectoryID(accountId accounts.AccountId, shareName, directoryPath string) DirectoryId { + return DirectoryId{ + AccountId: accountId, + ShareName: shareName, + DirectoryPath: directoryPath, + } +} + +func (b DirectoryId) ID() string { + return fmt.Sprintf("%s/%s/%s", b.AccountId.ID(), b.ShareName, b.DirectoryPath) +} + +func (b DirectoryId) String() string { + components := []string{ + fmt.Sprintf("Share Name %q", b.ShareName), + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("Directory Path %q (%s)", b.DirectoryPath, strings.Join(components, " / ")) +} + +// ParseDirectoryID parses `input` into a Directory ID using a known `domainSuffix` +func ParseDirectoryID(input, domainSuffix string) (*DirectoryId, error) { + // example: https://foo.file.core.windows.net/Bar/some/directory + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.FileSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.FileSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) < 2 { + return nil, fmt.Errorf("expected the path to contain at least 2 segments but got %d", len(segments)) + } + shareName := segments[0] + directoryPath := strings.Join(segments[1:], "/") + return &DirectoryId{ + AccountId: *account, + ShareName: shareName, + DirectoryPath: directoryPath, + }, nil +} diff --git a/storage/2020-08-04/file/directories/resource_id_test.go b/storage/2020-08-04/file/directories/resource_id_test.go new file mode 100644 index 0000000..84abcdb --- /dev/null +++ b/storage/2020-08-04/file/directories/resource_id_test.go @@ -0,0 +1,169 @@ +package directories + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" +) + +func TestParseDirectoryIDStandard(t *testing.T) { + input := "https://example1.file.core.windows.net/share1/some/path" + expected := DirectoryId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "core.windows.net", + }, + ShareName: "share1", + DirectoryPath: "some/path", + } + actual, err := ParseDirectoryID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } + if actual.DirectoryPath != expected.DirectoryPath { + t.Fatalf("expected DirectoryPath to be %q but got %q", expected.DirectoryPath, actual.DirectoryPath) + } +} + +func TestParseDirectoryIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.file.storage.azure.net/share1/path" + expected := DirectoryId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + ShareName: "share1", + DirectoryPath: "path", + } + actual, err := ParseDirectoryID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } + if actual.DirectoryPath != expected.DirectoryPath { + t.Fatalf("expected DirectoryPath to be %q but got %q", expected.DirectoryPath, actual.DirectoryPath) + } +} + +func TestParseDirectoryIDInAnEdgeZone(t *testing.T) { + input := "https://example1.file.zone1.edgestorage.azure.net/share1/some/path" + expected := DirectoryId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + ShareName: "share1", + DirectoryPath: "some/path", + } + actual, err := ParseDirectoryID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } + if actual.DirectoryPath != expected.DirectoryPath { + t.Fatalf("expected DirectoryPath to be %q but got %q", expected.DirectoryPath, actual.DirectoryPath) + } +} + +func TestFormatContainerIDStandard(t *testing.T) { + actual := DirectoryId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + ShareName: "share1", + DirectoryPath: "some/path", + }.ID() + expected := "https://example1.file.core.windows.net/share1/some/path" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInDNSZone(t *testing.T) { + actual := DirectoryId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + ShareName: "share1", + DirectoryPath: "some/path", + }.ID() + expected := "https://example1.zone2.file.storage.azure.net/share1/some/path" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInEdgeZone(t *testing.T) { + actual := DirectoryId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + ShareName: "share1", + DirectoryPath: "path", + }.ID() + expected := "https://example1.file.zone2.edgestorage.azure.net/share1/path" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} diff --git a/storage/2023-11-03/file/directories/resource_id.go b/storage/2023-11-03/file/directories/resource_id.go new file mode 100644 index 0000000..b49c41a --- /dev/null +++ b/storage/2023-11-03/file/directories/resource_id.go @@ -0,0 +1,80 @@ +package directories + +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" +) + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = DirectoryId{} + +type DirectoryId struct { + // AccountId specifies the ID of the Storage Account where this Directory exists. + AccountId accounts.AccountId + + // ShareName specifies the name of the File Share containing this Directory. + ShareName string + + // DirectoryPath specifies the path representing this Directory. + DirectoryPath string +} + +func NewDirectoryID(accountId accounts.AccountId, shareName, directoryPath string) DirectoryId { + return DirectoryId{ + AccountId: accountId, + ShareName: shareName, + DirectoryPath: directoryPath, + } +} + +func (b DirectoryId) ID() string { + return fmt.Sprintf("%s/%s/%s", b.AccountId.ID(), b.ShareName, b.DirectoryPath) +} + +func (b DirectoryId) String() string { + components := []string{ + fmt.Sprintf("Share Name %q", b.ShareName), + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("Directory Path %q (%s)", b.DirectoryPath, strings.Join(components, " / ")) +} + +// ParseDirectoryID parses `input` into a Directory ID using a known `domainSuffix` +func ParseDirectoryID(input, domainSuffix string) (*DirectoryId, error) { + // example: https://foo.file.core.windows.net/Bar/some/directory + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.FileSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.FileSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) < 2 { + return nil, fmt.Errorf("expected the path to contain at least 2 segments but got %d", len(segments)) + } + shareName := segments[0] + directoryPath := strings.Join(segments[1:], "/") + return &DirectoryId{ + AccountId: *account, + ShareName: shareName, + DirectoryPath: directoryPath, + }, nil +} diff --git a/storage/2023-11-03/file/directories/resource_id_test.go b/storage/2023-11-03/file/directories/resource_id_test.go new file mode 100644 index 0000000..aa83b41 --- /dev/null +++ b/storage/2023-11-03/file/directories/resource_id_test.go @@ -0,0 +1,169 @@ +package directories + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" +) + +func TestParseDirectoryIDStandard(t *testing.T) { + input := "https://example1.file.core.windows.net/share1/some/path" + expected := DirectoryId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "core.windows.net", + }, + ShareName: "share1", + DirectoryPath: "some/path", + } + actual, err := ParseDirectoryID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } + if actual.DirectoryPath != expected.DirectoryPath { + t.Fatalf("expected DirectoryPath to be %q but got %q", expected.DirectoryPath, actual.DirectoryPath) + } +} + +func TestParseDirectoryIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.file.storage.azure.net/share1/path" + expected := DirectoryId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + ShareName: "share1", + DirectoryPath: "path", + } + actual, err := ParseDirectoryID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } + if actual.DirectoryPath != expected.DirectoryPath { + t.Fatalf("expected DirectoryPath to be %q but got %q", expected.DirectoryPath, actual.DirectoryPath) + } +} + +func TestParseDirectoryIDInAnEdgeZone(t *testing.T) { + input := "https://example1.file.zone1.edgestorage.azure.net/share1/some/path" + expected := DirectoryId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + ShareName: "share1", + DirectoryPath: "some/path", + } + actual, err := ParseDirectoryID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } + if actual.DirectoryPath != expected.DirectoryPath { + t.Fatalf("expected DirectoryPath to be %q but got %q", expected.DirectoryPath, actual.DirectoryPath) + } +} + +func TestFormatContainerIDStandard(t *testing.T) { + actual := DirectoryId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + ShareName: "share1", + DirectoryPath: "some/path", + }.ID() + expected := "https://example1.file.core.windows.net/share1/some/path" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInDNSZone(t *testing.T) { + actual := DirectoryId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + ShareName: "share1", + DirectoryPath: "some/path", + }.ID() + expected := "https://example1.zone2.file.storage.azure.net/share1/some/path" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInEdgeZone(t *testing.T) { + actual := DirectoryId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + ShareName: "share1", + DirectoryPath: "path", + }.ID() + expected := "https://example1.file.zone2.edgestorage.azure.net/share1/path" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} From 4e4edba6a5f3655ce733be7007361408cd234179 Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-azure <> Date: Thu, 14 Dec 2023 11:16:52 +0100 Subject: [PATCH 07/14] file/file: reintroducing the Resource ID parser --- storage/2020-08-04/file/files/resource_id.go | 87 +++++++++ .../2020-08-04/file/files/resource_id_test.go | 184 ++++++++++++++++++ storage/2023-11-03/file/files/resource_id.go | 87 +++++++++ .../2023-11-03/file/files/resource_id_test.go | 184 ++++++++++++++++++ 4 files changed, 542 insertions(+) create mode 100644 storage/2020-08-04/file/files/resource_id.go create mode 100644 storage/2020-08-04/file/files/resource_id_test.go create mode 100644 storage/2023-11-03/file/files/resource_id.go create mode 100644 storage/2023-11-03/file/files/resource_id_test.go diff --git a/storage/2020-08-04/file/files/resource_id.go b/storage/2020-08-04/file/files/resource_id.go new file mode 100644 index 0000000..b29471c --- /dev/null +++ b/storage/2020-08-04/file/files/resource_id.go @@ -0,0 +1,87 @@ +package files + +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" +) + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = FileId{} + +type FileId struct { + // AccountId specifies the ID of the Storage Account where this File exists. + AccountId accounts.AccountId + + // ShareName specifies the name of the File Share containing this File. + ShareName string + + // DirectoryPath specifies the path representing the Directory where this File exists. + DirectoryPath string + + // FileName specifies the name of the File. + FileName string +} + +func NewFileID(accountId accounts.AccountId, shareName, directoryPath, fileName string) FileId { + return FileId{ + AccountId: accountId, + ShareName: shareName, + DirectoryPath: directoryPath, + FileName: fileName, + } +} + +func (b FileId) ID() string { + return fmt.Sprintf("%s/%s/%s/%s", b.AccountId.ID(), b.ShareName, b.DirectoryPath, b.FileName) +} + +func (b FileId) String() string { + components := []string{ + fmt.Sprintf("Directory Path %q", b.DirectoryPath), + fmt.Sprintf("Share Name %q", b.ShareName), + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("File %q (%s)", b.FileName, strings.Join(components, " / ")) +} + +// ParseFileID parses `input` into a File ID using a known `domainSuffix` +func ParseFileID(input, domainSuffix string) (*FileId, error) { + // example: https://foo.file.core.windows.net/Bar/some/directory/some-file.txt + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.FileSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.FileSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) < 3 { + return nil, fmt.Errorf("expected the path to contain at least 3 segments but got %d", len(segments)) + } + shareName := segments[0] + directoryPath := strings.Join(segments[1:len(segments)-1], "/") + fileName := segments[len(segments)-1] + return &FileId{ + AccountId: *account, + ShareName: shareName, + DirectoryPath: directoryPath, + FileName: fileName, + }, nil +} diff --git a/storage/2020-08-04/file/files/resource_id_test.go b/storage/2020-08-04/file/files/resource_id_test.go new file mode 100644 index 0000000..83cd92b --- /dev/null +++ b/storage/2020-08-04/file/files/resource_id_test.go @@ -0,0 +1,184 @@ +package files + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" +) + +func TestParseFileIDStandard(t *testing.T) { + input := "https://example1.file.core.windows.net/share1/some/path/file.txt" + expected := FileId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "core.windows.net", + }, + ShareName: "share1", + DirectoryPath: "some/path", + FileName: "file.txt", + } + actual, err := ParseFileID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } + if actual.DirectoryPath != expected.DirectoryPath { + t.Fatalf("expected DirectoryPath to be %q but got %q", expected.DirectoryPath, actual.DirectoryPath) + } + if actual.FileName != expected.FileName { + t.Fatalf("expected FileName to be %q but got %q", expected.FileName, actual.FileName) + } +} + +func TestParseFileIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.file.storage.azure.net/share1/path/file.txt" + expected := FileId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + ShareName: "share1", + DirectoryPath: "path", + FileName: "file.txt", + } + actual, err := ParseFileID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } + if actual.DirectoryPath != expected.DirectoryPath { + t.Fatalf("expected DirectoryPath to be %q but got %q", expected.DirectoryPath, actual.DirectoryPath) + } + if actual.FileName != expected.FileName { + t.Fatalf("expected FileName to be %q but got %q", expected.FileName, actual.FileName) + } +} + +func TestParseFileIDInAnEdgeZone(t *testing.T) { + input := "https://example1.file.zone1.edgestorage.azure.net/share1/some/path/file.txt" + expected := FileId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + ShareName: "share1", + DirectoryPath: "some/path", + FileName: "file.txt", + } + actual, err := ParseFileID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } + if actual.DirectoryPath != expected.DirectoryPath { + t.Fatalf("expected DirectoryPath to be %q but got %q", expected.DirectoryPath, actual.DirectoryPath) + } + if actual.FileName != expected.FileName { + t.Fatalf("expected FileName to be %q but got %q", expected.FileName, actual.FileName) + } +} + +func TestFormatContainerIDStandard(t *testing.T) { + actual := FileId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + ShareName: "share1", + DirectoryPath: "some/path", + FileName: "file.txt", + }.ID() + expected := "https://example1.file.core.windows.net/share1/some/path/file.txt" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInDNSZone(t *testing.T) { + actual := FileId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + ShareName: "share1", + DirectoryPath: "some/path", + FileName: "file.txt", + }.ID() + expected := "https://example1.zone2.file.storage.azure.net/share1/some/path/file.txt" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInEdgeZone(t *testing.T) { + actual := FileId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + ShareName: "share1", + DirectoryPath: "path", + FileName: "file.txt", + }.ID() + expected := "https://example1.file.zone2.edgestorage.azure.net/share1/path/file.txt" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} diff --git a/storage/2023-11-03/file/files/resource_id.go b/storage/2023-11-03/file/files/resource_id.go new file mode 100644 index 0000000..4928897 --- /dev/null +++ b/storage/2023-11-03/file/files/resource_id.go @@ -0,0 +1,87 @@ +package files + +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" +) + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = FileId{} + +type FileId struct { + // AccountId specifies the ID of the Storage Account where this File exists. + AccountId accounts.AccountId + + // ShareName specifies the name of the File Share containing this File. + ShareName string + + // DirectoryPath specifies the path representing the Directory where this File exists. + DirectoryPath string + + // FileName specifies the name of the File. + FileName string +} + +func NewFileID(accountId accounts.AccountId, shareName, directoryPath, fileName string) FileId { + return FileId{ + AccountId: accountId, + ShareName: shareName, + DirectoryPath: directoryPath, + FileName: fileName, + } +} + +func (b FileId) ID() string { + return fmt.Sprintf("%s/%s/%s/%s", b.AccountId.ID(), b.ShareName, b.DirectoryPath, b.FileName) +} + +func (b FileId) String() string { + components := []string{ + fmt.Sprintf("Directory Path %q", b.DirectoryPath), + fmt.Sprintf("Share Name %q", b.ShareName), + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("File %q (%s)", b.FileName, strings.Join(components, " / ")) +} + +// ParseFileID parses `input` into a File ID using a known `domainSuffix` +func ParseFileID(input, domainSuffix string) (*FileId, error) { + // example: https://foo.file.core.windows.net/Bar/some/directory/some-file.txt + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.FileSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.FileSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) < 3 { + return nil, fmt.Errorf("expected the path to contain at least 3 segments but got %d", len(segments)) + } + shareName := segments[0] + directoryPath := strings.Join(segments[1:len(segments)-1], "/") + fileName := segments[len(segments)-1] + return &FileId{ + AccountId: *account, + ShareName: shareName, + DirectoryPath: directoryPath, + FileName: fileName, + }, nil +} diff --git a/storage/2023-11-03/file/files/resource_id_test.go b/storage/2023-11-03/file/files/resource_id_test.go new file mode 100644 index 0000000..157d5d3 --- /dev/null +++ b/storage/2023-11-03/file/files/resource_id_test.go @@ -0,0 +1,184 @@ +package files + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" +) + +func TestParseFileIDStandard(t *testing.T) { + input := "https://example1.file.core.windows.net/share1/some/path/file.txt" + expected := FileId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "core.windows.net", + }, + ShareName: "share1", + DirectoryPath: "some/path", + FileName: "file.txt", + } + actual, err := ParseFileID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } + if actual.DirectoryPath != expected.DirectoryPath { + t.Fatalf("expected DirectoryPath to be %q but got %q", expected.DirectoryPath, actual.DirectoryPath) + } + if actual.FileName != expected.FileName { + t.Fatalf("expected FileName to be %q but got %q", expected.FileName, actual.FileName) + } +} + +func TestParseFileIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.file.storage.azure.net/share1/path/file.txt" + expected := FileId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + ShareName: "share1", + DirectoryPath: "path", + FileName: "file.txt", + } + actual, err := ParseFileID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } + if actual.DirectoryPath != expected.DirectoryPath { + t.Fatalf("expected DirectoryPath to be %q but got %q", expected.DirectoryPath, actual.DirectoryPath) + } + if actual.FileName != expected.FileName { + t.Fatalf("expected FileName to be %q but got %q", expected.FileName, actual.FileName) + } +} + +func TestParseFileIDInAnEdgeZone(t *testing.T) { + input := "https://example1.file.zone1.edgestorage.azure.net/share1/some/path/file.txt" + expected := FileId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + ShareName: "share1", + DirectoryPath: "some/path", + FileName: "file.txt", + } + actual, err := ParseFileID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.ShareName != expected.ShareName { + t.Fatalf("expected ShareName to be %q but got %q", expected.ShareName, actual.ShareName) + } + if actual.DirectoryPath != expected.DirectoryPath { + t.Fatalf("expected DirectoryPath to be %q but got %q", expected.DirectoryPath, actual.DirectoryPath) + } + if actual.FileName != expected.FileName { + t.Fatalf("expected FileName to be %q but got %q", expected.FileName, actual.FileName) + } +} + +func TestFormatContainerIDStandard(t *testing.T) { + actual := FileId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + ShareName: "share1", + DirectoryPath: "some/path", + FileName: "file.txt", + }.ID() + expected := "https://example1.file.core.windows.net/share1/some/path/file.txt" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInDNSZone(t *testing.T) { + actual := FileId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + ShareName: "share1", + DirectoryPath: "some/path", + FileName: "file.txt", + }.ID() + expected := "https://example1.zone2.file.storage.azure.net/share1/some/path/file.txt" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatContainerIDInEdgeZone(t *testing.T) { + actual := FileId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.FileSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + ShareName: "share1", + DirectoryPath: "path", + FileName: "file.txt", + }.ID() + expected := "https://example1.file.zone2.edgestorage.azure.net/share1/path/file.txt" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} From 63289995fd0f44113ddce775478f49f6966f57c3 Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-azure <> Date: Thu, 14 Dec 2023 11:32:25 +0100 Subject: [PATCH 08/14] blob/container: fixing a couple of minor nits --- storage/2020-08-04/blob/containers/resource_id.go | 13 ++++++------- storage/2023-11-03/blob/containers/resource_id.go | 13 ++++++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/storage/2020-08-04/blob/containers/resource_id.go b/storage/2020-08-04/blob/containers/resource_id.go index 3c86ab4..321ea60 100644 --- a/storage/2020-08-04/blob/containers/resource_id.go +++ b/storage/2020-08-04/blob/containers/resource_id.go @@ -21,11 +21,10 @@ func (c Client) GetResourceManagerResourceID(subscriptionID, resourceGroup, acco var _ resourceids.Id = ContainerId{} type ContainerId struct { - // AccountId specifies the ID of the Storage Account where this Blob exists. + // AccountId specifies the ID of the Storage Account where this Container exists. AccountId accounts.AccountId - // ContainerName specifies the name of the Container within this Storage Account where this - // Blob exists. + // ContainerName specifies the name of this Container. ContainerName string } @@ -44,10 +43,10 @@ func (b ContainerId) String() string { components := []string{ fmt.Sprintf("Account %q", b.AccountId.String()), } - return fmt.Sprintf("Containerr %q (%s)", b.ContainerName, strings.Join(components, " / ")) + return fmt.Sprintf("Container %q (%s)", b.ContainerName, strings.Join(components, " / ")) } -// ParseContainerID parses `input` into a Blob ID using a known `domainSuffix` +// ParseContainerID parses `input` into a Container ID using a known `domainSuffix` func ParseContainerID(input, domainSuffix string) (*ContainerId, error) { // example: https://foo.blob.core.windows.net/Bar if input == "" { @@ -70,8 +69,8 @@ func ParseContainerID(input, domainSuffix string) (*ContainerId, error) { path := strings.TrimPrefix(uri.Path, "/") segments := strings.Split(path, "/") - if len(segments) == 0 { - return nil, fmt.Errorf("Expected the path to contain segments but got none") + if len(segments) != 1 { + return nil, fmt.Errorf("expected the path to contain 1 segment but got %d", len(segments)) } containerName := strings.TrimPrefix(uri.Path, "/") diff --git a/storage/2023-11-03/blob/containers/resource_id.go b/storage/2023-11-03/blob/containers/resource_id.go index 114255e..6913602 100644 --- a/storage/2023-11-03/blob/containers/resource_id.go +++ b/storage/2023-11-03/blob/containers/resource_id.go @@ -21,11 +21,10 @@ func (c Client) GetResourceManagerResourceID(subscriptionID, resourceGroup, acco var _ resourceids.Id = ContainerId{} type ContainerId struct { - // AccountId specifies the ID of the Storage Account where this Blob exists. + // AccountId specifies the ID of the Storage Account where this Container exists. AccountId accounts.AccountId - // ContainerName specifies the name of the Container within this Storage Account where this - // Blob exists. + // ContainerName specifies the name of this Container. ContainerName string } @@ -44,10 +43,10 @@ func (b ContainerId) String() string { components := []string{ fmt.Sprintf("Account %q", b.AccountId.String()), } - return fmt.Sprintf("Containerr %q (%s)", b.ContainerName, strings.Join(components, " / ")) + return fmt.Sprintf("Container %q (%s)", b.ContainerName, strings.Join(components, " / ")) } -// ParseContainerID parses `input` into a Blob ID using a known `domainSuffix` +// ParseContainerID parses `input` into a Container ID using a known `domainSuffix` func ParseContainerID(input, domainSuffix string) (*ContainerId, error) { // example: https://foo.blob.core.windows.net/Bar if input == "" { @@ -70,8 +69,8 @@ func ParseContainerID(input, domainSuffix string) (*ContainerId, error) { path := strings.TrimPrefix(uri.Path, "/") segments := strings.Split(path, "/") - if len(segments) == 0 { - return nil, fmt.Errorf("Expected the path to contain segments but got none") + if len(segments) != 1 { + return nil, fmt.Errorf("expected the path to contain 1 segment but got %d", len(segments)) } containerName := strings.TrimPrefix(uri.Path, "/") From 99881ae86f7dd456fca23999502000db4c8e0aa9 Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-azure <> Date: Thu, 14 Dec 2023 11:34:28 +0100 Subject: [PATCH 09/14] blob/blob: updating the segment validation --- storage/2020-08-04/blob/blobs/resource_id.go | 4 ++-- storage/2023-11-03/blob/blobs/resource_id.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/storage/2020-08-04/blob/blobs/resource_id.go b/storage/2020-08-04/blob/blobs/resource_id.go index 2b83a5b..a31673f 100644 --- a/storage/2020-08-04/blob/blobs/resource_id.go +++ b/storage/2020-08-04/blob/blobs/resource_id.go @@ -68,8 +68,8 @@ func ParseBlobID(input, domainSuffix string) (*BlobId, error) { path := strings.TrimPrefix(uri.Path, "/") segments := strings.Split(path, "/") - if len(segments) == 0 { - return nil, fmt.Errorf("Expected the path to contain segments but got none") + if len(segments) != 2 { + return nil, fmt.Errorf("expected the path to contain 2 segments but got %d", len(segments)) } containerName := segments[0] diff --git a/storage/2023-11-03/blob/blobs/resource_id.go b/storage/2023-11-03/blob/blobs/resource_id.go index 057e161..95b78f5 100644 --- a/storage/2023-11-03/blob/blobs/resource_id.go +++ b/storage/2023-11-03/blob/blobs/resource_id.go @@ -68,8 +68,8 @@ func ParseBlobID(input, domainSuffix string) (*BlobId, error) { path := strings.TrimPrefix(uri.Path, "/") segments := strings.Split(path, "/") - if len(segments) == 0 { - return nil, fmt.Errorf("Expected the path to contain segments but got none") + if len(segments) != 2 { + return nil, fmt.Errorf("expected the path to contain 2 segments but got %d", len(segments)) } containerName := segments[0] From 1c55ce45ea337dd9992508a649e39bbce175888c Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-azure <> Date: Thu, 14 Dec 2023 11:35:15 +0100 Subject: [PATCH 10/14] queue/queues: reintroducing the Resource ID Parser --- .../2020-08-04/queue/queues/resource_id.go | 69 ++++++++ .../queue/queues/resource_id_test.go | 153 +++++++++++++++++- .../2023-11-03/queue/queues/resource_id.go | 69 ++++++++ .../queue/queues/resource_id_test.go | 153 +++++++++++++++++- 4 files changed, 442 insertions(+), 2 deletions(-) diff --git a/storage/2020-08-04/queue/queues/resource_id.go b/storage/2020-08-04/queue/queues/resource_id.go index 7fe5124..455319a 100644 --- a/storage/2020-08-04/queue/queues/resource_id.go +++ b/storage/2020-08-04/queue/queues/resource_id.go @@ -2,6 +2,11 @@ package queues import ( "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" ) // GetResourceManagerResourceID returns the Resource Manager ID for the given Queue @@ -10,3 +15,67 @@ func (c Client) GetResourceManagerResourceID(subscriptionID, resourceGroup, acco fmtStr := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/queueServices/default/queues/%s" return fmt.Sprintf(fmtStr, subscriptionID, resourceGroup, accountName, queueName) } + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = QueueId{} + +type QueueId struct { + // AccountId specifies the ID of the Storage Account where this Queue exists. + AccountId accounts.AccountId + + // QueueName specifies the name of this Queue. + QueueName string +} + +func NewQueueID(accountId accounts.AccountId, queueName string) QueueId { + return QueueId{ + AccountId: accountId, + QueueName: queueName, + } +} + +func (b QueueId) ID() string { + return fmt.Sprintf("%s/%s", b.AccountId.ID(), b.QueueName) +} + +func (b QueueId) String() string { + components := []string{ + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("Queue %q (%s)", b.QueueName, strings.Join(components, " / ")) +} + +// ParseQueueID parses `input` into a Queue ID using a known `domainSuffix` +func ParseQueueID(input, domainSuffix string) (*QueueId, error) { + // example: https://foo.queue.core.windows.net/Bar + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.QueueSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.QueueSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) != 1 { + return nil, fmt.Errorf("expected the path to contain 1 segment but got %d", len(segments)) + } + + queueName := strings.TrimPrefix(uri.Path, "/") + return &QueueId{ + AccountId: *account, + QueueName: queueName, + }, nil +} diff --git a/storage/2020-08-04/queue/queues/resource_id_test.go b/storage/2020-08-04/queue/queues/resource_id_test.go index 5bfdb67..81f013c 100644 --- a/storage/2020-08-04/queue/queues/resource_id_test.go +++ b/storage/2020-08-04/queue/queues/resource_id_test.go @@ -1,6 +1,11 @@ package queues -import "testing" +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" +) func TestGetResourceManagerResourceID(t *testing.T) { actual := Client{}.GetResourceManagerResourceID("11112222-3333-4444-5555-666677778888", "group1", "account1", "queue1") @@ -9,3 +14,149 @@ func TestGetResourceManagerResourceID(t *testing.T) { t.Fatalf("Expected the Resource Manager Resource ID to be %q but got %q", expected, actual) } } + +func TestParseQueueIDStandard(t *testing.T) { + input := "https://example1.queue.core.windows.net/queue1" + expected := QueueId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.QueueSubDomainType, + DomainSuffix: "core.windows.net", + }, + QueueName: "queue1", + } + actual, err := ParseQueueID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.QueueName != expected.QueueName { + t.Fatalf("expected QueueName to be %q but got %q", expected.QueueName, actual.QueueName) + } +} + +func TestParseQueueIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.queue.storage.azure.net/queue1" + expected := QueueId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.QueueSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + QueueName: "queue1", + } + actual, err := ParseQueueID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.QueueName != expected.QueueName { + t.Fatalf("expected QueueName to be %q but got %q", expected.QueueName, actual.QueueName) + } +} + +func TestParseQueueIDInAnEdgeZone(t *testing.T) { + input := "https://example1.queue.zone1.edgestorage.azure.net/queue1" + expected := QueueId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.QueueSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + QueueName: "queue1", + } + actual, err := ParseQueueID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.QueueName != expected.QueueName { + t.Fatalf("expected QueueName to be %q but got %q", expected.QueueName, actual.QueueName) + } +} + +func TestFormatQueueIDStandard(t *testing.T) { + actual := QueueId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.QueueSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + QueueName: "queue1", + }.ID() + expected := "https://example1.queue.core.windows.net/queue1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatQueueIDInDNSZone(t *testing.T) { + actual := QueueId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.QueueSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + QueueName: "queue1", + }.ID() + expected := "https://example1.zone2.queue.storage.azure.net/queue1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatQueueIDInEdgeZone(t *testing.T) { + actual := QueueId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.QueueSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + QueueName: "queue1", + }.ID() + expected := "https://example1.queue.zone2.edgestorage.azure.net/queue1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} diff --git a/storage/2023-11-03/queue/queues/resource_id.go b/storage/2023-11-03/queue/queues/resource_id.go index 7fe5124..8acd0bb 100644 --- a/storage/2023-11-03/queue/queues/resource_id.go +++ b/storage/2023-11-03/queue/queues/resource_id.go @@ -2,6 +2,11 @@ package queues import ( "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" ) // GetResourceManagerResourceID returns the Resource Manager ID for the given Queue @@ -10,3 +15,67 @@ func (c Client) GetResourceManagerResourceID(subscriptionID, resourceGroup, acco fmtStr := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/queueServices/default/queues/%s" return fmt.Sprintf(fmtStr, subscriptionID, resourceGroup, accountName, queueName) } + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = QueueId{} + +type QueueId struct { + // AccountId specifies the ID of the Storage Account where this Queue exists. + AccountId accounts.AccountId + + // QueueName specifies the name of this Queue. + QueueName string +} + +func NewQueueID(accountId accounts.AccountId, queueName string) QueueId { + return QueueId{ + AccountId: accountId, + QueueName: queueName, + } +} + +func (b QueueId) ID() string { + return fmt.Sprintf("%s/%s", b.AccountId.ID(), b.QueueName) +} + +func (b QueueId) String() string { + components := []string{ + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("Queue %q (%s)", b.QueueName, strings.Join(components, " / ")) +} + +// ParseQueueID parses `input` into a Queue ID using a known `domainSuffix` +func ParseQueueID(input, domainSuffix string) (*QueueId, error) { + // example: https://foo.queue.core.windows.net/Bar + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.QueueSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.QueueSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) != 1 { + return nil, fmt.Errorf("expected the path to contain 1 segment but got %d", len(segments)) + } + + queueName := strings.TrimPrefix(uri.Path, "/") + return &QueueId{ + AccountId: *account, + QueueName: queueName, + }, nil +} diff --git a/storage/2023-11-03/queue/queues/resource_id_test.go b/storage/2023-11-03/queue/queues/resource_id_test.go index 5bfdb67..966d94a 100644 --- a/storage/2023-11-03/queue/queues/resource_id_test.go +++ b/storage/2023-11-03/queue/queues/resource_id_test.go @@ -1,6 +1,11 @@ package queues -import "testing" +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" +) func TestGetResourceManagerResourceID(t *testing.T) { actual := Client{}.GetResourceManagerResourceID("11112222-3333-4444-5555-666677778888", "group1", "account1", "queue1") @@ -9,3 +14,149 @@ func TestGetResourceManagerResourceID(t *testing.T) { t.Fatalf("Expected the Resource Manager Resource ID to be %q but got %q", expected, actual) } } + +func TestParseQueueIDStandard(t *testing.T) { + input := "https://example1.queue.core.windows.net/queue1" + expected := QueueId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.QueueSubDomainType, + DomainSuffix: "core.windows.net", + }, + QueueName: "queue1", + } + actual, err := ParseQueueID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.QueueName != expected.QueueName { + t.Fatalf("expected QueueName to be %q but got %q", expected.QueueName, actual.QueueName) + } +} + +func TestParseQueueIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.queue.storage.azure.net/queue1" + expected := QueueId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.QueueSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + QueueName: "queue1", + } + actual, err := ParseQueueID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.QueueName != expected.QueueName { + t.Fatalf("expected QueueName to be %q but got %q", expected.QueueName, actual.QueueName) + } +} + +func TestParseQueueIDInAnEdgeZone(t *testing.T) { + input := "https://example1.queue.zone1.edgestorage.azure.net/queue1" + expected := QueueId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.QueueSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + QueueName: "queue1", + } + actual, err := ParseQueueID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.QueueName != expected.QueueName { + t.Fatalf("expected QueueName to be %q but got %q", expected.QueueName, actual.QueueName) + } +} + +func TestFormatQueueIDStandard(t *testing.T) { + actual := QueueId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.QueueSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + QueueName: "queue1", + }.ID() + expected := "https://example1.queue.core.windows.net/queue1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatQueueIDInDNSZone(t *testing.T) { + actual := QueueId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.QueueSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + QueueName: "queue1", + }.ID() + expected := "https://example1.zone2.queue.storage.azure.net/queue1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatQueueIDInEdgeZone(t *testing.T) { + actual := QueueId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.QueueSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + QueueName: "queue1", + }.ID() + expected := "https://example1.queue.zone2.edgestorage.azure.net/queue1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} From f1c8100ba36712e14ee992a6aa4b63099200018d Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-azure <> Date: Thu, 14 Dec 2023 11:42:17 +0100 Subject: [PATCH 11/14] table/tables: reintroducing the Resource ID parser --- .../2020-08-04/table/tables/resource_id.go | 78 ++++++++- .../table/tables/resource_id_test.go | 149 ++++++++++++++++++ .../2023-11-03/table/tables/resource_id.go | 78 ++++++++- .../table/tables/resource_id_test.go | 149 ++++++++++++++++++ 4 files changed, 452 insertions(+), 2 deletions(-) diff --git a/storage/2020-08-04/table/tables/resource_id.go b/storage/2020-08-04/table/tables/resource_id.go index 7b872b9..2dbc4c9 100644 --- a/storage/2020-08-04/table/tables/resource_id.go +++ b/storage/2020-08-04/table/tables/resource_id.go @@ -1,6 +1,13 @@ package tables -import "fmt" +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" +) // GetResourceManagerResourceID returns the Resource ID for the given Table // This can be useful when, for example, you're using this as a unique identifier @@ -8,3 +15,72 @@ func (c Client) GetResourceManagerResourceID(subscriptionID, resourceGroup, acco fmtStr := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/tableServices/default/tables/%s" return fmt.Sprintf(fmtStr, subscriptionID, resourceGroup, accountName, tableName) } + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = TableId{} + +type TableId struct { + // AccountId specifies the ID of the Storage Account where this Table exists. + AccountId accounts.AccountId + + // TableName specifies the name of this Table. + TableName string +} + +func NewTableID(accountId accounts.AccountId, tableName string) TableId { + return TableId{ + AccountId: accountId, + TableName: tableName, + } +} + +func (b TableId) ID() string { + return fmt.Sprintf("%s/%s", b.AccountId.ID(), b.TableName) +} + +func (b TableId) String() string { + components := []string{ + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("Table %q (%s)", b.TableName, strings.Join(components, " / ")) +} + +// ParseTableID parses `input` into a Table ID using a known `domainSuffix` +func ParseTableID(input, domainSuffix string) (*TableId, error) { + // example: https://foo.table.core.windows.net/Bar + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.TableSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.TableSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) != 1 { + return nil, fmt.Errorf("expected the path to contain 1 segment but got %d", len(segments)) + } + + // Tables and Table Entities are similar with table being `table1` and entities + // being `table1(PartitionKey='samplepartition',RowKey='samplerow')` so we need to validate this is a table + tableName := strings.TrimPrefix(uri.Path, "/") + if strings.Contains(tableName, "(") || strings.Contains(tableName, ")") { + return nil, fmt.Errorf("expected the path to a table name and not an entity name but got %q", tableName) + } + return &TableId{ + AccountId: *account, + TableName: tableName, + }, nil +} diff --git a/storage/2020-08-04/table/tables/resource_id_test.go b/storage/2020-08-04/table/tables/resource_id_test.go index 7cb3d87..ff43048 100644 --- a/storage/2020-08-04/table/tables/resource_id_test.go +++ b/storage/2020-08-04/table/tables/resource_id_test.go @@ -2,6 +2,9 @@ package tables import ( "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" ) func TestGetResourceManagerResourceID(t *testing.T) { @@ -11,3 +14,149 @@ func TestGetResourceManagerResourceID(t *testing.T) { t.Fatalf("Expected the Resource Manager Resource ID to be %q but got %q", expected, actual) } } + +func TestParseTableIDStandard(t *testing.T) { + input := "https://example1.table.core.windows.net/table1" + expected := TableId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "core.windows.net", + }, + TableName: "table1", + } + actual, err := ParseTableID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.TableName != expected.TableName { + t.Fatalf("expected TableName to be %q but got %q", expected.TableName, actual.TableName) + } +} + +func TestParseTableIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.table.storage.azure.net/table1" + expected := TableId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + TableName: "table1", + } + actual, err := ParseTableID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.TableName != expected.TableName { + t.Fatalf("expected TableName to be %q but got %q", expected.TableName, actual.TableName) + } +} + +func TestParseTableIDInAnEdgeZone(t *testing.T) { + input := "https://example1.table.zone1.edgestorage.azure.net/table1" + expected := TableId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + TableName: "table1", + } + actual, err := ParseTableID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.TableName != expected.TableName { + t.Fatalf("expected TableName to be %q but got %q", expected.TableName, actual.TableName) + } +} + +func TestFormatTableIDStandard(t *testing.T) { + actual := TableId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + TableName: "table1", + }.ID() + expected := "https://example1.table.core.windows.net/table1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatTableIDInDNSZone(t *testing.T) { + actual := TableId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + TableName: "table1", + }.ID() + expected := "https://example1.zone2.table.storage.azure.net/table1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatTableIDInEdgeZone(t *testing.T) { + actual := TableId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + TableName: "table1", + }.ID() + expected := "https://example1.table.zone2.edgestorage.azure.net/table1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} diff --git a/storage/2023-11-03/table/tables/resource_id.go b/storage/2023-11-03/table/tables/resource_id.go index 7b872b9..8c5072f 100644 --- a/storage/2023-11-03/table/tables/resource_id.go +++ b/storage/2023-11-03/table/tables/resource_id.go @@ -1,6 +1,13 @@ package tables -import "fmt" +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" +) // GetResourceManagerResourceID returns the Resource ID for the given Table // This can be useful when, for example, you're using this as a unique identifier @@ -8,3 +15,72 @@ func (c Client) GetResourceManagerResourceID(subscriptionID, resourceGroup, acco fmtStr := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/tableServices/default/tables/%s" return fmt.Sprintf(fmtStr, subscriptionID, resourceGroup, accountName, tableName) } + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = TableId{} + +type TableId struct { + // AccountId specifies the ID of the Storage Account where this Table exists. + AccountId accounts.AccountId + + // TableName specifies the name of this Table. + TableName string +} + +func NewTableID(accountId accounts.AccountId, tableName string) TableId { + return TableId{ + AccountId: accountId, + TableName: tableName, + } +} + +func (b TableId) ID() string { + return fmt.Sprintf("%s/%s", b.AccountId.ID(), b.TableName) +} + +func (b TableId) String() string { + components := []string{ + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("Table %q (%s)", b.TableName, strings.Join(components, " / ")) +} + +// ParseTableID parses `input` into a Table ID using a known `domainSuffix` +func ParseTableID(input, domainSuffix string) (*TableId, error) { + // example: https://foo.table.core.windows.net/Bar + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.TableSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.TableSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) != 1 { + return nil, fmt.Errorf("expected the path to contain 1 segment but got %d", len(segments)) + } + + // Tables and Table Entities are similar with table being `table1` and entities + // being `table1(PartitionKey='samplepartition',RowKey='samplerow')` so we need to validate this is a table + tableName := strings.TrimPrefix(uri.Path, "/") + if strings.Contains(tableName, "(") || strings.Contains(tableName, ")") { + return nil, fmt.Errorf("expected the path to a table name and not an entity name but got %q", tableName) + } + return &TableId{ + AccountId: *account, + TableName: tableName, + }, nil +} diff --git a/storage/2023-11-03/table/tables/resource_id_test.go b/storage/2023-11-03/table/tables/resource_id_test.go index 7cb3d87..3bcb104 100644 --- a/storage/2023-11-03/table/tables/resource_id_test.go +++ b/storage/2023-11-03/table/tables/resource_id_test.go @@ -2,6 +2,9 @@ package tables import ( "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" ) func TestGetResourceManagerResourceID(t *testing.T) { @@ -11,3 +14,149 @@ func TestGetResourceManagerResourceID(t *testing.T) { t.Fatalf("Expected the Resource Manager Resource ID to be %q but got %q", expected, actual) } } + +func TestParseTableIDStandard(t *testing.T) { + input := "https://example1.table.core.windows.net/table1" + expected := TableId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "core.windows.net", + }, + TableName: "table1", + } + actual, err := ParseTableID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.TableName != expected.TableName { + t.Fatalf("expected TableName to be %q but got %q", expected.TableName, actual.TableName) + } +} + +func TestParseTableIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.table.storage.azure.net/table1" + expected := TableId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + TableName: "table1", + } + actual, err := ParseTableID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.TableName != expected.TableName { + t.Fatalf("expected TableName to be %q but got %q", expected.TableName, actual.TableName) + } +} + +func TestParseTableIDInAnEdgeZone(t *testing.T) { + input := "https://example1.table.zone1.edgestorage.azure.net/table1" + expected := TableId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + TableName: "table1", + } + actual, err := ParseTableID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.TableName != expected.TableName { + t.Fatalf("expected TableName to be %q but got %q", expected.TableName, actual.TableName) + } +} + +func TestFormatTableIDStandard(t *testing.T) { + actual := TableId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + TableName: "table1", + }.ID() + expected := "https://example1.table.core.windows.net/table1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatTableIDInDNSZone(t *testing.T) { + actual := TableId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + TableName: "table1", + }.ID() + expected := "https://example1.zone2.table.storage.azure.net/table1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatTableIDInEdgeZone(t *testing.T) { + actual := TableId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + TableName: "table1", + }.ID() + expected := "https://example1.table.zone2.edgestorage.azure.net/table1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} From c7d1b1feb5b72721b9d957175fea747d987cef7a Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-azure <> Date: Thu, 14 Dec 2023 11:57:57 +0100 Subject: [PATCH 12/14] table/entities: reintroducing the Resource ID Parser --- .../2020-08-04/table/entities/resource_id.go | 122 ++++++++++++ .../table/entities/resource_id_test.go | 184 ++++++++++++++++++ .../2023-11-03/table/entities/resource_id.go | 122 ++++++++++++ .../table/entities/resource_id_test.go | 184 ++++++++++++++++++ 4 files changed, 612 insertions(+) create mode 100644 storage/2020-08-04/table/entities/resource_id.go create mode 100644 storage/2020-08-04/table/entities/resource_id_test.go create mode 100644 storage/2023-11-03/table/entities/resource_id.go create mode 100644 storage/2023-11-03/table/entities/resource_id_test.go diff --git a/storage/2020-08-04/table/entities/resource_id.go b/storage/2020-08-04/table/entities/resource_id.go new file mode 100644 index 0000000..4e72402 --- /dev/null +++ b/storage/2020-08-04/table/entities/resource_id.go @@ -0,0 +1,122 @@ +package entities + +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" +) + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = EntityId{} + +type EntityId struct { + // AccountId specifies the ID of the Storage Account where this Entity exists. + AccountId accounts.AccountId + + // TableName specifies the name of the Table where this Entity exists. + TableName string + + // PartitionKey specifies the Partition Key for this Entity. + PartitionKey string + + // RowKey specifies the Row Key for this Entity. + RowKey string +} + +func NewEntityID(accountId accounts.AccountId, tableName, partitionKey, rowKey string) EntityId { + return EntityId{ + AccountId: accountId, + TableName: tableName, + PartitionKey: partitionKey, + RowKey: rowKey, + } +} + +func (b EntityId) ID() string { + return fmt.Sprintf("%s/%s(PartitionKey='%s',RowKey='%s')", b.AccountId.ID(), b.TableName, b.PartitionKey, b.RowKey) +} + +func (b EntityId) String() string { + components := []string{ + fmt.Sprintf("Partition Key %q", b.PartitionKey), + fmt.Sprintf("Row Key %q", b.RowKey), + fmt.Sprintf("Table Name %q", b.TableName), + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("Entity (%s)", strings.Join(components, " / ")) +} + +// ParseEntityID parses `input` into a Entity ID using a known `domainSuffix` +func ParseEntityID(input, domainSuffix string) (*EntityId, error) { + // example: https://foo.table.core.windows.net/Bar1(PartitionKey='partition1',RowKey='row1') + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.TableSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.TableSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) != 1 { + return nil, fmt.Errorf("expected the path to contain 1 segment but got %d", len(segments)) + } + + // Tables and Table Entities are similar with table being `table1` and entities + // being `table1(PartitionKey='samplepartition',RowKey='samplerow')` so we need to validate this is a table + key := strings.TrimPrefix(uri.Path, "/") + if !strings.Contains(key, "(") || !strings.HasSuffix(key, ")") { + return nil, fmt.Errorf("expected the path to be an entity name but got a table name %q", key) + } + + indexOfFirstBracket := strings.Index(key, "(") + tableName := key[0:indexOfFirstBracket] + componentString := key[indexOfFirstBracket:] + componentString = strings.TrimPrefix(componentString, "(") + componentString = strings.TrimSuffix(componentString, ")") + components := strings.Split(componentString, ",") + if len(components) != 2 { + return nil, fmt.Errorf("expected the path to be an entity name but got %q", key) + } + + partitionKey := parseValueFromKey(components[0], "PartitionKey") + rowKey := parseValueFromKey(components[1], "RowKey") + return &EntityId{ + AccountId: *account, + TableName: tableName, + PartitionKey: *partitionKey, + RowKey: *rowKey, + }, nil +} + +func parseValueFromKey(input, expectedKey string) *string { + components := strings.Split(input, "=") + if len(components) != 2 { + return nil + } + key := components[0] + value := components[1] + if key != expectedKey { + return nil + } + + // the value is surrounded in single quotes, remove those + value = strings.TrimPrefix(value, "'") + value = strings.TrimSuffix(value, "'") + return &value +} diff --git a/storage/2020-08-04/table/entities/resource_id_test.go b/storage/2020-08-04/table/entities/resource_id_test.go new file mode 100644 index 0000000..c8dce77 --- /dev/null +++ b/storage/2020-08-04/table/entities/resource_id_test.go @@ -0,0 +1,184 @@ +package entities + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" +) + +func TestParseEntityIDStandard(t *testing.T) { + input := "https://example1.table.core.windows.net/table1(PartitionKey='partition1',RowKey='row1')" + expected := EntityId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "core.windows.net", + }, + TableName: "table1", + PartitionKey: "partition1", + RowKey: "row1", + } + actual, err := ParseEntityID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.TableName != expected.TableName { + t.Fatalf("expected TableName to be %q but got %q", expected.TableName, actual.TableName) + } + if actual.PartitionKey != expected.PartitionKey { + t.Fatalf("expected PartitionKey to be %q but got %q", expected.PartitionKey, actual.PartitionKey) + } + if actual.RowKey != expected.RowKey { + t.Fatalf("expected RowKey to be %q but got %q", expected.RowKey, actual.RowKey) + } +} + +func TestParseEntityIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.table.storage.azure.net/table1(PartitionKey='partition1',RowKey='row1')" + expected := EntityId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + TableName: "table1", + PartitionKey: "partition1", + RowKey: "row1", + } + actual, err := ParseEntityID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.TableName != expected.TableName { + t.Fatalf("expected TableName to be %q but got %q", expected.TableName, actual.TableName) + } + if actual.PartitionKey != expected.PartitionKey { + t.Fatalf("expected PartitionKey to be %q but got %q", expected.PartitionKey, actual.PartitionKey) + } + if actual.RowKey != expected.RowKey { + t.Fatalf("expected RowKey to be %q but got %q", expected.RowKey, actual.RowKey) + } +} + +func TestParseEntityIDInAnEdgeZone(t *testing.T) { + input := "https://example1.table.zone1.edgestorage.azure.net/table1(PartitionKey='partition1',RowKey='row1')" + expected := EntityId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + TableName: "table1", + PartitionKey: "partition1", + RowKey: "row1", + } + actual, err := ParseEntityID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.TableName != expected.TableName { + t.Fatalf("expected TableName to be %q but got %q", expected.TableName, actual.TableName) + } + if actual.PartitionKey != expected.PartitionKey { + t.Fatalf("expected PartitionKey to be %q but got %q", expected.PartitionKey, actual.PartitionKey) + } + if actual.RowKey != expected.RowKey { + t.Fatalf("expected RowKey to be %q but got %q", expected.RowKey, actual.RowKey) + } +} + +func TestFormatEntityIDStandard(t *testing.T) { + actual := EntityId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + TableName: "table1", + PartitionKey: "partition1", + RowKey: "row1", + }.ID() + expected := "https://example1.table.core.windows.net/table1(PartitionKey='partition1',RowKey='row1')" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatEntityIDInDNSZone(t *testing.T) { + actual := EntityId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + TableName: "table1", + PartitionKey: "partition1", + RowKey: "row1", + }.ID() + expected := "https://example1.zone2.table.storage.azure.net/table1(PartitionKey='partition1',RowKey='row1')" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatEntityIDInEdgeZone(t *testing.T) { + actual := EntityId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + TableName: "table1", + PartitionKey: "partition1", + RowKey: "row1", + }.ID() + expected := "https://example1.table.zone2.edgestorage.azure.net/table1(PartitionKey='partition1',RowKey='row1')" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} diff --git a/storage/2023-11-03/table/entities/resource_id.go b/storage/2023-11-03/table/entities/resource_id.go new file mode 100644 index 0000000..0403b5e --- /dev/null +++ b/storage/2023-11-03/table/entities/resource_id.go @@ -0,0 +1,122 @@ +package entities + +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" +) + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = EntityId{} + +type EntityId struct { + // AccountId specifies the ID of the Storage Account where this Entity exists. + AccountId accounts.AccountId + + // TableName specifies the name of the Table where this Entity exists. + TableName string + + // PartitionKey specifies the Partition Key for this Entity. + PartitionKey string + + // RowKey specifies the Row Key for this Entity. + RowKey string +} + +func NewEntityID(accountId accounts.AccountId, tableName, partitionKey, rowKey string) EntityId { + return EntityId{ + AccountId: accountId, + TableName: tableName, + PartitionKey: partitionKey, + RowKey: rowKey, + } +} + +func (b EntityId) ID() string { + return fmt.Sprintf("%s/%s(PartitionKey='%s',RowKey='%s')", b.AccountId.ID(), b.TableName, b.PartitionKey, b.RowKey) +} + +func (b EntityId) String() string { + components := []string{ + fmt.Sprintf("Partition Key %q", b.PartitionKey), + fmt.Sprintf("Row Key %q", b.RowKey), + fmt.Sprintf("Table Name %q", b.TableName), + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("Entity (%s)", strings.Join(components, " / ")) +} + +// ParseEntityID parses `input` into a Entity ID using a known `domainSuffix` +func ParseEntityID(input, domainSuffix string) (*EntityId, error) { + // example: https://foo.table.core.windows.net/Bar1(PartitionKey='partition1',RowKey='row1') + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.TableSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.TableSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) != 1 { + return nil, fmt.Errorf("expected the path to contain 1 segment but got %d", len(segments)) + } + + // Tables and Table Entities are similar with table being `table1` and entities + // being `table1(PartitionKey='samplepartition',RowKey='samplerow')` so we need to validate this is a table + key := strings.TrimPrefix(uri.Path, "/") + if !strings.Contains(key, "(") || !strings.HasSuffix(key, ")") { + return nil, fmt.Errorf("expected the path to be an entity name but got a table name %q", key) + } + + indexOfFirstBracket := strings.Index(key, "(") + tableName := key[0:indexOfFirstBracket] + componentString := key[indexOfFirstBracket:] + componentString = strings.TrimPrefix(componentString, "(") + componentString = strings.TrimSuffix(componentString, ")") + components := strings.Split(componentString, ",") + if len(components) != 2 { + return nil, fmt.Errorf("expected the path to be an entity name but got %q", key) + } + + partitionKey := parseValueFromKey(components[0], "PartitionKey") + rowKey := parseValueFromKey(components[1], "RowKey") + return &EntityId{ + AccountId: *account, + TableName: tableName, + PartitionKey: *partitionKey, + RowKey: *rowKey, + }, nil +} + +func parseValueFromKey(input, expectedKey string) *string { + components := strings.Split(input, "=") + if len(components) != 2 { + return nil + } + key := components[0] + value := components[1] + if key != expectedKey { + return nil + } + + // the value is surrounded in single quotes, remove those + value = strings.TrimPrefix(value, "'") + value = strings.TrimSuffix(value, "'") + return &value +} diff --git a/storage/2023-11-03/table/entities/resource_id_test.go b/storage/2023-11-03/table/entities/resource_id_test.go new file mode 100644 index 0000000..1e6a65e --- /dev/null +++ b/storage/2023-11-03/table/entities/resource_id_test.go @@ -0,0 +1,184 @@ +package entities + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" +) + +func TestParseEntityIDStandard(t *testing.T) { + input := "https://example1.table.core.windows.net/table1(PartitionKey='partition1',RowKey='row1')" + expected := EntityId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "core.windows.net", + }, + TableName: "table1", + PartitionKey: "partition1", + RowKey: "row1", + } + actual, err := ParseEntityID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.TableName != expected.TableName { + t.Fatalf("expected TableName to be %q but got %q", expected.TableName, actual.TableName) + } + if actual.PartitionKey != expected.PartitionKey { + t.Fatalf("expected PartitionKey to be %q but got %q", expected.PartitionKey, actual.PartitionKey) + } + if actual.RowKey != expected.RowKey { + t.Fatalf("expected RowKey to be %q but got %q", expected.RowKey, actual.RowKey) + } +} + +func TestParseEntityIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.table.storage.azure.net/table1(PartitionKey='partition1',RowKey='row1')" + expected := EntityId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + TableName: "table1", + PartitionKey: "partition1", + RowKey: "row1", + } + actual, err := ParseEntityID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.TableName != expected.TableName { + t.Fatalf("expected TableName to be %q but got %q", expected.TableName, actual.TableName) + } + if actual.PartitionKey != expected.PartitionKey { + t.Fatalf("expected PartitionKey to be %q but got %q", expected.PartitionKey, actual.PartitionKey) + } + if actual.RowKey != expected.RowKey { + t.Fatalf("expected RowKey to be %q but got %q", expected.RowKey, actual.RowKey) + } +} + +func TestParseEntityIDInAnEdgeZone(t *testing.T) { + input := "https://example1.table.zone1.edgestorage.azure.net/table1(PartitionKey='partition1',RowKey='row1')" + expected := EntityId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + TableName: "table1", + PartitionKey: "partition1", + RowKey: "row1", + } + actual, err := ParseEntityID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.TableName != expected.TableName { + t.Fatalf("expected TableName to be %q but got %q", expected.TableName, actual.TableName) + } + if actual.PartitionKey != expected.PartitionKey { + t.Fatalf("expected PartitionKey to be %q but got %q", expected.PartitionKey, actual.PartitionKey) + } + if actual.RowKey != expected.RowKey { + t.Fatalf("expected RowKey to be %q but got %q", expected.RowKey, actual.RowKey) + } +} + +func TestFormatEntityIDStandard(t *testing.T) { + actual := EntityId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + TableName: "table1", + PartitionKey: "partition1", + RowKey: "row1", + }.ID() + expected := "https://example1.table.core.windows.net/table1(PartitionKey='partition1',RowKey='row1')" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatEntityIDInDNSZone(t *testing.T) { + actual := EntityId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + TableName: "table1", + PartitionKey: "partition1", + RowKey: "row1", + }.ID() + expected := "https://example1.zone2.table.storage.azure.net/table1(PartitionKey='partition1',RowKey='row1')" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatEntityIDInEdgeZone(t *testing.T) { + actual := EntityId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.TableSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + TableName: "table1", + PartitionKey: "partition1", + RowKey: "row1", + }.ID() + expected := "https://example1.table.zone2.edgestorage.azure.net/table1(PartitionKey='partition1',RowKey='row1')" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} From 91b1b4a30b10704ac9228cb4aa4f2ef427834691 Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-azure <> Date: Thu, 14 Dec 2023 11:59:10 +0100 Subject: [PATCH 13/14] blob/account: support for DFS resources --- .../2020-08-04/blob/accounts/resource_id.go | 10 +- .../datalakestore/filesystems/resource_id.go | 71 ++++++++- .../filesystems/resource_id_test.go | 149 ++++++++++++++++++ .../2023-11-03/blob/accounts/resource_id.go | 10 +- .../datalakestore/filesystems/resource_id.go | 71 ++++++++- .../filesystems/resource_id_test.go | 149 ++++++++++++++++++ 6 files changed, 450 insertions(+), 10 deletions(-) diff --git a/storage/2020-08-04/blob/accounts/resource_id.go b/storage/2020-08-04/blob/accounts/resource_id.go index 007a72f..e6c6303 100644 --- a/storage/2020-08-04/blob/accounts/resource_id.go +++ b/storage/2020-08-04/blob/accounts/resource_id.go @@ -12,15 +12,17 @@ import ( type SubDomainType string const ( - BlobSubDomainType SubDomainType = "blob" - FileSubDomainType SubDomainType = "file" - QueueSubDomainType SubDomainType = "queue" - TableSubDomainType SubDomainType = "table" + BlobSubDomainType SubDomainType = "blob" + DataLakeStoreSubDomainType SubDomainType = "dfs" + FileSubDomainType SubDomainType = "file" + QueueSubDomainType SubDomainType = "queue" + TableSubDomainType SubDomainType = "table" ) func PossibleValuesForSubDomainType() []SubDomainType { return []SubDomainType{ BlobSubDomainType, + DataLakeStoreSubDomainType, FileSubDomainType, QueueSubDomainType, TableSubDomainType, diff --git a/storage/2020-08-04/datalakestore/filesystems/resource_id.go b/storage/2020-08-04/datalakestore/filesystems/resource_id.go index 1090704..a2664ac 100644 --- a/storage/2020-08-04/datalakestore/filesystems/resource_id.go +++ b/storage/2020-08-04/datalakestore/filesystems/resource_id.go @@ -2,11 +2,80 @@ package filesystems import ( "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" ) // GetResourceManagerResourceID returns the Resource Manager specific -// ResourceID for a specific dfs filesystem +// ResourceID for a specific Data Lake FileSystem func (c Client) GetResourceManagerResourceID(subscriptionID, resourceGroup, accountName, fileSystemName string) string { fmtStr := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/blobServices/default/containers/%s" return fmt.Sprintf(fmtStr, subscriptionID, resourceGroup, accountName, fileSystemName) } + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = FileSystemId{} + +type FileSystemId struct { + // AccountId specifies the ID of the Storage Account where this Data Lake FileSystem exists. + AccountId accounts.AccountId + + // FileSystemName specifies the name of this Data Lake FileSystem. + FileSystemName string +} + +func NewFileSystemID(accountId accounts.AccountId, fileSystemName string) FileSystemId { + return FileSystemId{ + AccountId: accountId, + FileSystemName: fileSystemName, + } +} + +func (b FileSystemId) ID() string { + return fmt.Sprintf("%s/%s", b.AccountId.ID(), b.FileSystemName) +} + +func (b FileSystemId) String() string { + components := []string{ + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("File System %q (%s)", b.FileSystemName, strings.Join(components, " / ")) +} + +// ParseFileSystemID parses `input` into a File System ID using a known `domainSuffix` +func ParseFileSystemID(input, domainSuffix string) (*FileSystemId, error) { + // example: https://foo.dfs.core.windows.net/Bar + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.DataLakeStoreSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.DataLakeStoreSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) != 1 { + return nil, fmt.Errorf("expected the path to contain 1 segment but got %d", len(segments)) + } + + fileSystemName := segments[0] + return &FileSystemId{ + AccountId: *account, + FileSystemName: fileSystemName, + }, nil +} diff --git a/storage/2020-08-04/datalakestore/filesystems/resource_id_test.go b/storage/2020-08-04/datalakestore/filesystems/resource_id_test.go index 441438f..9c32230 100644 --- a/storage/2020-08-04/datalakestore/filesystems/resource_id_test.go +++ b/storage/2020-08-04/datalakestore/filesystems/resource_id_test.go @@ -2,6 +2,9 @@ package filesystems import ( "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" ) func TestGetResourceManagerResourceID(t *testing.T) { @@ -11,3 +14,149 @@ func TestGetResourceManagerResourceID(t *testing.T) { t.Fatalf("Expected the Resource Manager Resource ID to be %q but got %q", expected, actual) } } + +func TestParseFileSystemIDStandard(t *testing.T) { + input := "https://example1.dfs.core.windows.net/fileSystem1" + expected := FileSystemId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "core.windows.net", + }, + FileSystemName: "fileSystem1", + } + actual, err := ParseFileSystemID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.FileSystemName != expected.FileSystemName { + t.Fatalf("expected FileSystemName to be %q but got %q", expected.FileSystemName, actual.FileSystemName) + } +} + +func TestParseFileSystemIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.dfs.storage.azure.net/fileSystem1" + expected := FileSystemId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + FileSystemName: "fileSystem1", + } + actual, err := ParseFileSystemID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.FileSystemName != expected.FileSystemName { + t.Fatalf("expected FileSystemName to be %q but got %q", expected.FileSystemName, actual.FileSystemName) + } +} + +func TestParseFileSystemIDInAnEdgeZone(t *testing.T) { + input := "https://example1.dfs.zone1.edgestorage.azure.net/fileSystem1" + expected := FileSystemId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + FileSystemName: "fileSystem1", + } + actual, err := ParseFileSystemID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.FileSystemName != expected.FileSystemName { + t.Fatalf("expected FileSystemName to be %q but got %q", expected.FileSystemName, actual.FileSystemName) + } +} + +func TestFormatFileSystemIDStandard(t *testing.T) { + actual := FileSystemId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + FileSystemName: "fileSystem1", + }.ID() + expected := "https://example1.dfs.core.windows.net/fileSystem1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatFileSystemIDInDNSZone(t *testing.T) { + actual := FileSystemId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + FileSystemName: "fileSystem1", + }.ID() + expected := "https://example1.zone2.dfs.storage.azure.net/fileSystem1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatFileSystemIDInEdgeZone(t *testing.T) { + actual := FileSystemId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + FileSystemName: "fileSystem1", + }.ID() + expected := "https://example1.dfs.zone2.edgestorage.azure.net/fileSystem1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} diff --git a/storage/2023-11-03/blob/accounts/resource_id.go b/storage/2023-11-03/blob/accounts/resource_id.go index 007a72f..e6c6303 100644 --- a/storage/2023-11-03/blob/accounts/resource_id.go +++ b/storage/2023-11-03/blob/accounts/resource_id.go @@ -12,15 +12,17 @@ import ( type SubDomainType string const ( - BlobSubDomainType SubDomainType = "blob" - FileSubDomainType SubDomainType = "file" - QueueSubDomainType SubDomainType = "queue" - TableSubDomainType SubDomainType = "table" + BlobSubDomainType SubDomainType = "blob" + DataLakeStoreSubDomainType SubDomainType = "dfs" + FileSubDomainType SubDomainType = "file" + QueueSubDomainType SubDomainType = "queue" + TableSubDomainType SubDomainType = "table" ) func PossibleValuesForSubDomainType() []SubDomainType { return []SubDomainType{ BlobSubDomainType, + DataLakeStoreSubDomainType, FileSubDomainType, QueueSubDomainType, TableSubDomainType, diff --git a/storage/2023-11-03/datalakestore/filesystems/resource_id.go b/storage/2023-11-03/datalakestore/filesystems/resource_id.go index 1090704..60670df 100644 --- a/storage/2023-11-03/datalakestore/filesystems/resource_id.go +++ b/storage/2023-11-03/datalakestore/filesystems/resource_id.go @@ -2,11 +2,80 @@ package filesystems import ( "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" ) // GetResourceManagerResourceID returns the Resource Manager specific -// ResourceID for a specific dfs filesystem +// ResourceID for a specific Data Lake FileSystem func (c Client) GetResourceManagerResourceID(subscriptionID, resourceGroup, accountName, fileSystemName string) string { fmtStr := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/blobServices/default/containers/%s" return fmt.Sprintf(fmtStr, subscriptionID, resourceGroup, accountName, fileSystemName) } + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = FileSystemId{} + +type FileSystemId struct { + // AccountId specifies the ID of the Storage Account where this Data Lake FileSystem exists. + AccountId accounts.AccountId + + // FileSystemName specifies the name of this Data Lake FileSystem. + FileSystemName string +} + +func NewFileSystemID(accountId accounts.AccountId, fileSystemName string) FileSystemId { + return FileSystemId{ + AccountId: accountId, + FileSystemName: fileSystemName, + } +} + +func (b FileSystemId) ID() string { + return fmt.Sprintf("%s/%s", b.AccountId.ID(), b.FileSystemName) +} + +func (b FileSystemId) String() string { + components := []string{ + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("File System %q (%s)", b.FileSystemName, strings.Join(components, " / ")) +} + +// ParseFileSystemID parses `input` into a File System ID using a known `domainSuffix` +func ParseFileSystemID(input, domainSuffix string) (*FileSystemId, error) { + // example: https://foo.dfs.core.windows.net/Bar + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.DataLakeStoreSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.DataLakeStoreSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) != 1 { + return nil, fmt.Errorf("expected the path to contain 1 segment but got %d", len(segments)) + } + + fileSystemName := segments[0] + return &FileSystemId{ + AccountId: *account, + FileSystemName: fileSystemName, + }, nil +} diff --git a/storage/2023-11-03/datalakestore/filesystems/resource_id_test.go b/storage/2023-11-03/datalakestore/filesystems/resource_id_test.go index 441438f..6dd821e 100644 --- a/storage/2023-11-03/datalakestore/filesystems/resource_id_test.go +++ b/storage/2023-11-03/datalakestore/filesystems/resource_id_test.go @@ -2,6 +2,9 @@ package filesystems import ( "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" ) func TestGetResourceManagerResourceID(t *testing.T) { @@ -11,3 +14,149 @@ func TestGetResourceManagerResourceID(t *testing.T) { t.Fatalf("Expected the Resource Manager Resource ID to be %q but got %q", expected, actual) } } + +func TestParseFileSystemIDStandard(t *testing.T) { + input := "https://example1.dfs.core.windows.net/fileSystem1" + expected := FileSystemId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "core.windows.net", + }, + FileSystemName: "fileSystem1", + } + actual, err := ParseFileSystemID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.FileSystemName != expected.FileSystemName { + t.Fatalf("expected FileSystemName to be %q but got %q", expected.FileSystemName, actual.FileSystemName) + } +} + +func TestParseFileSystemIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.dfs.storage.azure.net/fileSystem1" + expected := FileSystemId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + FileSystemName: "fileSystem1", + } + actual, err := ParseFileSystemID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.FileSystemName != expected.FileSystemName { + t.Fatalf("expected FileSystemName to be %q but got %q", expected.FileSystemName, actual.FileSystemName) + } +} + +func TestParseFileSystemIDInAnEdgeZone(t *testing.T) { + input := "https://example1.dfs.zone1.edgestorage.azure.net/fileSystem1" + expected := FileSystemId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + FileSystemName: "fileSystem1", + } + actual, err := ParseFileSystemID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.FileSystemName != expected.FileSystemName { + t.Fatalf("expected FileSystemName to be %q but got %q", expected.FileSystemName, actual.FileSystemName) + } +} + +func TestFormatFileSystemIDStandard(t *testing.T) { + actual := FileSystemId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + FileSystemName: "fileSystem1", + }.ID() + expected := "https://example1.dfs.core.windows.net/fileSystem1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatFileSystemIDInDNSZone(t *testing.T) { + actual := FileSystemId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + FileSystemName: "fileSystem1", + }.ID() + expected := "https://example1.zone2.dfs.storage.azure.net/fileSystem1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatFileSystemIDInEdgeZone(t *testing.T) { + actual := FileSystemId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + FileSystemName: "fileSystem1", + }.ID() + expected := "https://example1.dfs.zone2.edgestorage.azure.net/fileSystem1" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} From fd7fe5d583193e498af301f53c9c11f8a3476d1b Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-azure <> Date: Thu, 14 Dec 2023 12:19:02 +0100 Subject: [PATCH 14/14] datalakestore/paths: reintroducing the Resource ID Parser --- .../datalakestore/paths/resource_id.go | 80 +++++++++ .../datalakestore/paths/resource_id_test.go | 169 ++++++++++++++++++ .../datalakestore/paths/resource_id.go | 80 +++++++++ .../datalakestore/paths/resource_id_test.go | 169 ++++++++++++++++++ 4 files changed, 498 insertions(+) create mode 100644 storage/2020-08-04/datalakestore/paths/resource_id.go create mode 100644 storage/2020-08-04/datalakestore/paths/resource_id_test.go create mode 100644 storage/2023-11-03/datalakestore/paths/resource_id.go create mode 100644 storage/2023-11-03/datalakestore/paths/resource_id_test.go diff --git a/storage/2020-08-04/datalakestore/paths/resource_id.go b/storage/2020-08-04/datalakestore/paths/resource_id.go new file mode 100644 index 0000000..6102209 --- /dev/null +++ b/storage/2020-08-04/datalakestore/paths/resource_id.go @@ -0,0 +1,80 @@ +package paths + +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" +) + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = PathId{} + +type PathId struct { + // AccountId specifies the ID of the Storage Account where this path exists. + AccountId accounts.AccountId + + // FileSystemName specifies the name of the Data Lake FileSystem where this Path exists. + FileSystemName string + + // Path specifies the path in question. + Path string +} + +func NewPathID(accountId accounts.AccountId, fileSystemName, path string) PathId { + return PathId{ + AccountId: accountId, + FileSystemName: fileSystemName, + Path: path, + } +} + +func (b PathId) ID() string { + return fmt.Sprintf("%s/%s/%s", b.AccountId.ID(), b.FileSystemName, b.Path) +} + +func (b PathId) String() string { + components := []string{ + fmt.Sprintf("File System %q", b.FileSystemName), + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("Path %q (%s)", b.Path, strings.Join(components, " / ")) +} + +// ParsePathID parses `input` into a Path ID using a known `domainSuffix` +func ParsePathID(input, domainSuffix string) (*PathId, error) { + // example: https://foo.dfs.core.windows.net/Bar/some/path + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.DataLakeStoreSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.DataLakeStoreSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + uriPath := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(uriPath, "/") + if len(segments) < 2 { + return nil, fmt.Errorf("expected the path to contain at least 2 segments but got %d", len(segments)) + } + fileSystemName := segments[0] + path := strings.Join(segments[1:], "/") + return &PathId{ + AccountId: *account, + FileSystemName: fileSystemName, + Path: path, + }, nil +} diff --git a/storage/2020-08-04/datalakestore/paths/resource_id_test.go b/storage/2020-08-04/datalakestore/paths/resource_id_test.go new file mode 100644 index 0000000..d6d69d3 --- /dev/null +++ b/storage/2020-08-04/datalakestore/paths/resource_id_test.go @@ -0,0 +1,169 @@ +package paths + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2020-08-04/blob/accounts" +) + +func TestParsePathIDStandard(t *testing.T) { + input := "https://example1.dfs.core.windows.net/fileSystem1/some/path" + expected := PathId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "core.windows.net", + }, + FileSystemName: "fileSystem1", + Path: "some/path", + } + actual, err := ParsePathID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.FileSystemName != expected.FileSystemName { + t.Fatalf("expected FileSystemName to be %q but got %q", expected.FileSystemName, actual.FileSystemName) + } + if actual.Path != expected.Path { + t.Fatalf("expected Path to be %q but got %q", expected.Path, actual.Path) + } +} + +func TestParsePathIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.dfs.storage.azure.net/fileSystem1/some/path" + expected := PathId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + FileSystemName: "fileSystem1", + Path: "some/path", + } + actual, err := ParsePathID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.FileSystemName != expected.FileSystemName { + t.Fatalf("expected FileSystemName to be %q but got %q", expected.FileSystemName, actual.FileSystemName) + } + if actual.Path != expected.Path { + t.Fatalf("expected Path to be %q but got %q", expected.Path, actual.Path) + } +} + +func TestParsePathIDInAnEdgeZone(t *testing.T) { + input := "https://example1.dfs.zone1.edgestorage.azure.net/fileSystem1/some/path" + expected := PathId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + FileSystemName: "fileSystem1", + Path: "some/path", + } + actual, err := ParsePathID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.FileSystemName != expected.FileSystemName { + t.Fatalf("expected FileSystemName to be %q but got %q", expected.FileSystemName, actual.FileSystemName) + } + if actual.Path != expected.Path { + t.Fatalf("expected Path to be %q but got %q", expected.Path, actual.Path) + } +} + +func TestFormatPathIDStandard(t *testing.T) { + actual := PathId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + FileSystemName: "fileSystem1", + Path: "some/path", + }.ID() + expected := "https://example1.dfs.core.windows.net/fileSystem1/some/path" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatPathIDInDNSZone(t *testing.T) { + actual := PathId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + FileSystemName: "fileSystem1", + Path: "some/path", + }.ID() + expected := "https://example1.zone2.dfs.storage.azure.net/fileSystem1/some/path" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatPathIDInEdgeZone(t *testing.T) { + actual := PathId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + FileSystemName: "fileSystem1", + Path: "some", + }.ID() + expected := "https://example1.dfs.zone2.edgestorage.azure.net/fileSystem1/some" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} diff --git a/storage/2023-11-03/datalakestore/paths/resource_id.go b/storage/2023-11-03/datalakestore/paths/resource_id.go new file mode 100644 index 0000000..58570e6 --- /dev/null +++ b/storage/2023-11-03/datalakestore/paths/resource_id.go @@ -0,0 +1,80 @@ +package paths + +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" +) + +// TODO: update this to implement `resourceids.ResourceId` once +// https://github.com/hashicorp/go-azure-helpers/issues/187 is fixed +var _ resourceids.Id = PathId{} + +type PathId struct { + // AccountId specifies the ID of the Storage Account where this path exists. + AccountId accounts.AccountId + + // FileSystemName specifies the name of the Data Lake FileSystem where this Path exists. + FileSystemName string + + // Path specifies the path in question. + Path string +} + +func NewPathID(accountId accounts.AccountId, fileSystemName, path string) PathId { + return PathId{ + AccountId: accountId, + FileSystemName: fileSystemName, + Path: path, + } +} + +func (b PathId) ID() string { + return fmt.Sprintf("%s/%s/%s", b.AccountId.ID(), b.FileSystemName, b.Path) +} + +func (b PathId) String() string { + components := []string{ + fmt.Sprintf("File System %q", b.FileSystemName), + fmt.Sprintf("Account %q", b.AccountId.String()), + } + return fmt.Sprintf("Path %q (%s)", b.Path, strings.Join(components, " / ")) +} + +// ParsePathID parses `input` into a Path ID using a known `domainSuffix` +func ParsePathID(input, domainSuffix string) (*PathId, error) { + // example: https://foo.dfs.core.windows.net/Bar/some/path + if input == "" { + return nil, fmt.Errorf("`input` was empty") + } + + account, err := accounts.ParseAccountID(input, domainSuffix) + if err != nil { + return nil, fmt.Errorf("parsing account %q: %+v", input, err) + } + + if account.SubDomainType != accounts.DataLakeStoreSubDomainType { + return nil, fmt.Errorf("expected the subdomain type to be %q but got %q", string(accounts.DataLakeStoreSubDomainType), string(account.SubDomainType)) + } + + uri, err := url.Parse(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as a uri: %+v", input, err) + } + + uriPath := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(uriPath, "/") + if len(segments) < 2 { + return nil, fmt.Errorf("expected the path to contain at least 2 segments but got %d", len(segments)) + } + fileSystemName := segments[0] + path := strings.Join(segments[1:], "/") + return &PathId{ + AccountId: *account, + FileSystemName: fileSystemName, + Path: path, + }, nil +} diff --git a/storage/2023-11-03/datalakestore/paths/resource_id_test.go b/storage/2023-11-03/datalakestore/paths/resource_id_test.go new file mode 100644 index 0000000..8f463eb --- /dev/null +++ b/storage/2023-11-03/datalakestore/paths/resource_id_test.go @@ -0,0 +1,169 @@ +package paths + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts" +) + +func TestParsePathIDStandard(t *testing.T) { + input := "https://example1.dfs.core.windows.net/fileSystem1/some/path" + expected := PathId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "core.windows.net", + }, + FileSystemName: "fileSystem1", + Path: "some/path", + } + actual, err := ParsePathID(input, "core.windows.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if actual.FileSystemName != expected.FileSystemName { + t.Fatalf("expected FileSystemName to be %q but got %q", expected.FileSystemName, actual.FileSystemName) + } + if actual.Path != expected.Path { + t.Fatalf("expected Path to be %q but got %q", expected.Path, actual.Path) + } +} + +func TestParsePathIDInADNSZone(t *testing.T) { + input := "https://example1.zone1.dfs.storage.azure.net/fileSystem1/some/path" + expected := PathId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "storage.azure.net", + ZoneName: pointer.To("zone1"), + }, + FileSystemName: "fileSystem1", + Path: "some/path", + } + actual, err := ParsePathID(input, "storage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if actual.FileSystemName != expected.FileSystemName { + t.Fatalf("expected FileSystemName to be %q but got %q", expected.FileSystemName, actual.FileSystemName) + } + if actual.Path != expected.Path { + t.Fatalf("expected Path to be %q but got %q", expected.Path, actual.Path) + } +} + +func TestParsePathIDInAnEdgeZone(t *testing.T) { + input := "https://example1.dfs.zone1.edgestorage.azure.net/fileSystem1/some/path" + expected := PathId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "edgestorage.azure.net", + ZoneName: pointer.To("zone1"), + IsEdgeZone: true, + }, + FileSystemName: "fileSystem1", + Path: "some/path", + } + actual, err := ParsePathID(input, "edgestorage.azure.net") + if err != nil { + t.Fatalf(err.Error()) + } + if actual.AccountId.AccountName != expected.AccountId.AccountName { + t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName) + } + if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType { + t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType) + } + if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix { + t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix) + } + if pointer.From(actual.AccountId.ZoneName) != pointer.From(expected.AccountId.ZoneName) { + t.Fatalf("expected ZoneName to be %q but got %q", pointer.From(expected.AccountId.ZoneName), pointer.From(actual.AccountId.ZoneName)) + } + if !actual.AccountId.IsEdgeZone { + t.Fatalf("expected the Account to be in an Edge Zone but it wasn't") + } + if actual.FileSystemName != expected.FileSystemName { + t.Fatalf("expected FileSystemName to be %q but got %q", expected.FileSystemName, actual.FileSystemName) + } + if actual.Path != expected.Path { + t.Fatalf("expected Path to be %q but got %q", expected.Path, actual.Path) + } +} + +func TestFormatPathIDStandard(t *testing.T) { + actual := PathId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "core.windows.net", + IsEdgeZone: false, + }, + FileSystemName: "fileSystem1", + Path: "some/path", + }.ID() + expected := "https://example1.dfs.core.windows.net/fileSystem1/some/path" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatPathIDInDNSZone(t *testing.T) { + actual := PathId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "storage.azure.net", + IsEdgeZone: false, + }, + FileSystemName: "fileSystem1", + Path: "some/path", + }.ID() + expected := "https://example1.zone2.dfs.storage.azure.net/fileSystem1/some/path" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +} + +func TestFormatPathIDInEdgeZone(t *testing.T) { + actual := PathId{ + AccountId: accounts.AccountId{ + AccountName: "example1", + ZoneName: pointer.To("zone2"), + SubDomainType: accounts.DataLakeStoreSubDomainType, + DomainSuffix: "edgestorage.azure.net", + IsEdgeZone: true, + }, + FileSystemName: "fileSystem1", + Path: "some", + }.ID() + expected := "https://example1.dfs.zone2.edgestorage.azure.net/fileSystem1/some" + if actual != expected { + t.Fatalf("expected %q but got %q", expected, actual) + } +}