-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #98 from tombuildsstuff/f/reintroduce-resource-id-…
…parsers reintroducing the Resource ID Parsers
- Loading branch information
Showing
47 changed files
with
5,446 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
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" | ||
DataLakeStoreSubDomainType SubDomainType = "dfs" | ||
FileSubDomainType SubDomainType = "file" | ||
QueueSubDomainType SubDomainType = "queue" | ||
TableSubDomainType SubDomainType = "table" | ||
) | ||
|
||
func PossibleValuesForSubDomainType() []SubDomainType { | ||
return []SubDomainType{ | ||
BlobSubDomainType, | ||
DataLakeStoreSubDomainType, | ||
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) != 2 { | ||
return nil, fmt.Errorf("expected the path to contain 2 segments but got %d", len(segments)) | ||
} | ||
|
||
containerName := segments[0] | ||
blobName := strings.TrimPrefix(path, containerName) | ||
blobName = strings.TrimPrefix(blobName, "/") | ||
return &BlobId{ | ||
AccountId: *account, | ||
ContainerName: containerName, | ||
BlobName: blobName, | ||
}, nil | ||
} |
Oops, something went wrong.