-
Notifications
You must be signed in to change notification settings - Fork 861
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Some minor refactoring of azcore (#20291)
Moved ARM resource ID types and helpers to an internal package so they can be leveraged elsewhere. The public versions are type-aliased to the matching internal implementations. policyFunc has moved to the shared package so it can be used elsewhere. Sanitizing of URLs has been split into its own function. Moved a func-local constant to the shared package.
- Loading branch information
1 parent
dae8ab4
commit 2877174
Showing
12 changed files
with
398 additions
and
322 deletions.
There are no files selected for viewing
224 changes: 224 additions & 0 deletions
224
sdk/azcore/arm/internal/resource/resource_identifier.go
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,224 @@ | ||
//go:build go1.18 | ||
// +build go1.18 | ||
|
||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
package resource | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
const ( | ||
providersKey = "providers" | ||
subscriptionsKey = "subscriptions" | ||
resourceGroupsLowerKey = "resourcegroups" | ||
locationsKey = "locations" | ||
builtInResourceNamespace = "Microsoft.Resources" | ||
) | ||
|
||
// RootResourceID defines the tenant as the root parent of all other ResourceID. | ||
var RootResourceID = &ResourceID{ | ||
Parent: nil, | ||
ResourceType: TenantResourceType, | ||
Name: "", | ||
} | ||
|
||
// ResourceID represents a resource ID such as `/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg`. | ||
// Don't create this type directly, use ParseResourceID instead. | ||
type ResourceID struct { | ||
// Parent is the parent ResourceID of this instance. | ||
// Can be nil if there is no parent. | ||
Parent *ResourceID | ||
|
||
// SubscriptionID is the subscription ID in this resource ID. | ||
// The value can be empty if the resource ID does not contain a subscription ID. | ||
SubscriptionID string | ||
|
||
// ResourceGroupName is the resource group name in this resource ID. | ||
// The value can be empty if the resource ID does not contain a resource group name. | ||
ResourceGroupName string | ||
|
||
// Provider represents the provider name in this resource ID. | ||
// This is only valid when the resource ID represents a resource provider. | ||
// Example: `/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Insights` | ||
Provider string | ||
|
||
// Location is the location in this resource ID. | ||
// The value can be empty if the resource ID does not contain a location name. | ||
Location string | ||
|
||
// ResourceType represents the type of this resource ID. | ||
ResourceType ResourceType | ||
|
||
// Name is the resource name of this resource ID. | ||
Name string | ||
|
||
isChild bool | ||
stringValue string | ||
} | ||
|
||
// ParseResourceID parses a string to an instance of ResourceID | ||
func ParseResourceID(id string) (*ResourceID, error) { | ||
if len(id) == 0 { | ||
return nil, fmt.Errorf("invalid resource ID: id cannot be empty") | ||
} | ||
|
||
if !strings.HasPrefix(id, "/") { | ||
return nil, fmt.Errorf("invalid resource ID: resource id '%s' must start with '/'", id) | ||
} | ||
|
||
parts := splitStringAndOmitEmpty(id, "/") | ||
|
||
if len(parts) < 2 { | ||
return nil, fmt.Errorf("invalid resource ID: %s", id) | ||
} | ||
|
||
if !strings.EqualFold(parts[0], subscriptionsKey) && !strings.EqualFold(parts[0], providersKey) { | ||
return nil, fmt.Errorf("invalid resource ID: %s", id) | ||
} | ||
|
||
return appendNext(RootResourceID, parts, id) | ||
} | ||
|
||
// String returns the string of the ResourceID | ||
func (id *ResourceID) String() string { | ||
if len(id.stringValue) > 0 { | ||
return id.stringValue | ||
} | ||
|
||
if id.Parent == nil { | ||
return "" | ||
} | ||
|
||
builder := strings.Builder{} | ||
builder.WriteString(id.Parent.String()) | ||
|
||
if id.isChild { | ||
builder.WriteString(fmt.Sprintf("/%s", id.ResourceType.lastType())) | ||
if len(id.Name) > 0 { | ||
builder.WriteString(fmt.Sprintf("/%s", id.Name)) | ||
} | ||
} else { | ||
builder.WriteString(fmt.Sprintf("/providers/%s/%s/%s", id.ResourceType.Namespace, id.ResourceType.Type, id.Name)) | ||
} | ||
|
||
id.stringValue = builder.String() | ||
|
||
return id.stringValue | ||
} | ||
|
||
func newResourceID(parent *ResourceID, resourceTypeName string, resourceName string) *ResourceID { | ||
id := &ResourceID{} | ||
id.init(parent, chooseResourceType(resourceTypeName, parent), resourceName, true) | ||
return id | ||
} | ||
|
||
func newResourceIDWithResourceType(parent *ResourceID, resourceType ResourceType, resourceName string) *ResourceID { | ||
id := &ResourceID{} | ||
id.init(parent, resourceType, resourceName, true) | ||
return id | ||
} | ||
|
||
func newResourceIDWithProvider(parent *ResourceID, providerNamespace, resourceTypeName, resourceName string) *ResourceID { | ||
id := &ResourceID{} | ||
id.init(parent, NewResourceType(providerNamespace, resourceTypeName), resourceName, false) | ||
return id | ||
} | ||
|
||
func chooseResourceType(resourceTypeName string, parent *ResourceID) ResourceType { | ||
if strings.EqualFold(resourceTypeName, resourceGroupsLowerKey) { | ||
return ResourceGroupResourceType | ||
} else if strings.EqualFold(resourceTypeName, subscriptionsKey) && parent != nil && parent.ResourceType.String() == TenantResourceType.String() { | ||
return SubscriptionResourceType | ||
} | ||
|
||
return parent.ResourceType.AppendChild(resourceTypeName) | ||
} | ||
|
||
func (id *ResourceID) init(parent *ResourceID, resourceType ResourceType, name string, isChild bool) { | ||
if parent != nil { | ||
id.Provider = parent.Provider | ||
id.SubscriptionID = parent.SubscriptionID | ||
id.ResourceGroupName = parent.ResourceGroupName | ||
id.Location = parent.Location | ||
} | ||
|
||
if resourceType.String() == SubscriptionResourceType.String() { | ||
id.SubscriptionID = name | ||
} | ||
|
||
if resourceType.lastType() == locationsKey { | ||
id.Location = name | ||
} | ||
|
||
if resourceType.String() == ResourceGroupResourceType.String() { | ||
id.ResourceGroupName = name | ||
} | ||
|
||
if resourceType.String() == ProviderResourceType.String() { | ||
id.Provider = name | ||
} | ||
|
||
if parent == nil { | ||
id.Parent = RootResourceID | ||
} else { | ||
id.Parent = parent | ||
} | ||
id.isChild = isChild | ||
id.ResourceType = resourceType | ||
id.Name = name | ||
} | ||
|
||
func appendNext(parent *ResourceID, parts []string, id string) (*ResourceID, error) { | ||
if len(parts) == 0 { | ||
return parent, nil | ||
} | ||
|
||
if len(parts) == 1 { | ||
// subscriptions and resourceGroups are not valid ids without their names | ||
if strings.EqualFold(parts[0], subscriptionsKey) || strings.EqualFold(parts[0], resourceGroupsLowerKey) { | ||
return nil, fmt.Errorf("invalid resource ID: %s", id) | ||
} | ||
|
||
// resourceGroup must contain either child or provider resource type | ||
if parent.ResourceType.String() == ResourceGroupResourceType.String() { | ||
return nil, fmt.Errorf("invalid resource ID: %s", id) | ||
} | ||
|
||
return newResourceID(parent, parts[0], ""), nil | ||
} | ||
|
||
if strings.EqualFold(parts[0], providersKey) && (len(parts) == 2 || strings.EqualFold(parts[2], providersKey)) { | ||
//provider resource can only be on a tenant or a subscription parent | ||
if parent.ResourceType.String() != SubscriptionResourceType.String() && parent.ResourceType.String() != TenantResourceType.String() { | ||
return nil, fmt.Errorf("invalid resource ID: %s", id) | ||
} | ||
|
||
return appendNext(newResourceIDWithResourceType(parent, ProviderResourceType, parts[1]), parts[2:], id) | ||
} | ||
|
||
if len(parts) > 3 && strings.EqualFold(parts[0], providersKey) { | ||
return appendNext(newResourceIDWithProvider(parent, parts[1], parts[2], parts[3]), parts[4:], id) | ||
} | ||
|
||
if len(parts) > 1 && !strings.EqualFold(parts[0], providersKey) { | ||
return appendNext(newResourceID(parent, parts[0], parts[1]), parts[2:], id) | ||
} | ||
|
||
return nil, fmt.Errorf("invalid resource ID: %s", id) | ||
} | ||
|
||
func splitStringAndOmitEmpty(v, sep string) []string { | ||
r := make([]string, 0) | ||
for _, s := range strings.Split(v, sep) { | ||
if len(s) == 0 { | ||
continue | ||
} | ||
r = append(r, s) | ||
} | ||
|
||
return r | ||
} |
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
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,114 @@ | ||
//go:build go1.18 | ||
// +build go1.18 | ||
|
||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
package resource | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
// SubscriptionResourceType is the ResourceType of a subscription | ||
var SubscriptionResourceType = NewResourceType(builtInResourceNamespace, "subscriptions") | ||
|
||
// ResourceGroupResourceType is the ResourceType of a resource group | ||
var ResourceGroupResourceType = NewResourceType(builtInResourceNamespace, "resourceGroups") | ||
|
||
// TenantResourceType is the ResourceType of a tenant | ||
var TenantResourceType = NewResourceType(builtInResourceNamespace, "tenants") | ||
|
||
// ProviderResourceType is the ResourceType of a provider | ||
var ProviderResourceType = NewResourceType(builtInResourceNamespace, "providers") | ||
|
||
// ResourceType represents an Azure resource type, e.g. "Microsoft.Network/virtualNetworks/subnets". | ||
// Don't create this type directly, use ParseResourceType or NewResourceType instead. | ||
type ResourceType struct { | ||
// Namespace is the namespace of the resource type. | ||
// e.g. "Microsoft.Network" in resource type "Microsoft.Network/virtualNetworks/subnets" | ||
Namespace string | ||
|
||
// Type is the full type name of the resource type. | ||
// e.g. "virtualNetworks/subnets" in resource type "Microsoft.Network/virtualNetworks/subnets" | ||
Type string | ||
|
||
// Types is the slice of all the sub-types of this resource type. | ||
// e.g. ["virtualNetworks", "subnets"] in resource type "Microsoft.Network/virtualNetworks/subnets" | ||
Types []string | ||
|
||
stringValue string | ||
} | ||
|
||
// String returns the string of the ResourceType | ||
func (t ResourceType) String() string { | ||
return t.stringValue | ||
} | ||
|
||
// IsParentOf returns true when the receiver is the parent resource type of the child. | ||
func (t ResourceType) IsParentOf(child ResourceType) bool { | ||
if !strings.EqualFold(t.Namespace, child.Namespace) { | ||
return false | ||
} | ||
if len(t.Types) >= len(child.Types) { | ||
return false | ||
} | ||
for i := range t.Types { | ||
if !strings.EqualFold(t.Types[i], child.Types[i]) { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} | ||
|
||
// AppendChild creates an instance of ResourceType using the receiver as the parent with childType appended to it. | ||
func (t ResourceType) AppendChild(childType string) ResourceType { | ||
return NewResourceType(t.Namespace, fmt.Sprintf("%s/%s", t.Type, childType)) | ||
} | ||
|
||
// NewResourceType creates an instance of ResourceType using a provider namespace | ||
// such as "Microsoft.Network" and type such as "virtualNetworks/subnets". | ||
func NewResourceType(providerNamespace, typeName string) ResourceType { | ||
return ResourceType{ | ||
Namespace: providerNamespace, | ||
Type: typeName, | ||
Types: splitStringAndOmitEmpty(typeName, "/"), | ||
stringValue: fmt.Sprintf("%s/%s", providerNamespace, typeName), | ||
} | ||
} | ||
|
||
// ParseResourceType parses the ResourceType from a resource type string (e.g. Microsoft.Network/virtualNetworks/subsets) | ||
// or a resource identifier string. | ||
// e.g. /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Network/virtualNetworks/vnet/subnets/mySubnet) | ||
func ParseResourceType(resourceIDOrType string) (ResourceType, error) { | ||
// split the path into segments | ||
parts := splitStringAndOmitEmpty(resourceIDOrType, "/") | ||
|
||
// There must be at least a namespace and type name | ||
if len(parts) < 1 { | ||
return ResourceType{}, fmt.Errorf("invalid resource ID or type: %s", resourceIDOrType) | ||
} | ||
|
||
// if the type is just subscriptions, it is a built-in type in the Microsoft.Resources namespace | ||
if len(parts) == 1 { | ||
// Simple resource type | ||
return NewResourceType(builtInResourceNamespace, parts[0]), nil | ||
} else if strings.Contains(parts[0], ".") { | ||
// Handle resource types (Microsoft.Compute/virtualMachines, Microsoft.Network/virtualNetworks/subnets) | ||
// it is a full type name | ||
return NewResourceType(parts[0], strings.Join(parts[1:], "/")), nil | ||
} else { | ||
// Check if ResourceID | ||
id, err := ParseResourceID(resourceIDOrType) | ||
if err != nil { | ||
return ResourceType{}, err | ||
} | ||
return NewResourceType(id.ResourceType.Namespace, id.ResourceType.Type), nil | ||
} | ||
} | ||
|
||
func (t ResourceType) lastType() string { | ||
return t.Types[len(t.Types)-1] | ||
} |
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
Oops, something went wrong.