1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
|
package gcputil
import (
"fmt"
"net/url"
"regexp"
"strings"
)
const (
resourceIdRegex = "^[^\t\n\f\r]+$"
collectionIdRegex = "^[a-z][a-zA-Z]*$"
fullResourceNameRegex = "^//([a-z]+).googleapis.com/(.+)$"
selfLinkMarker = "projects/"
)
var singleCollectionIds = map[string]struct{}{
"global": {},
}
type RelativeResourceName struct {
Name string
TypeKey string
IdTuples map[string]string
OrderedCollectionIds []string
}
func ParseRelativeName(resource string) (*RelativeResourceName, error) {
resourceRe := regexp.MustCompile(resourceIdRegex)
collectionRe := regexp.MustCompile(collectionIdRegex)
tokens := strings.Split(resource, "/")
if len(tokens) < 2 {
return nil, fmt.Errorf("invalid relative resource name %s (too few tokens)", resource)
}
ids := map[string]string{}
typeKey := ""
currColId := ""
for idx, v := range tokens {
if len(currColId) == 0 {
if _, ok := singleCollectionIds[v]; ok {
// Ignore 'single' collectionIds like Global, but error if they are the last ID
if idx == len(tokens)-1 {
return nil, fmt.Errorf("invalid relative resource name %s (last collection '%s' has no ID)", resource, currColId)
}
continue
}
if len(collectionRe.FindAllString(v, 1)) == 0 {
return nil, fmt.Errorf("invalid relative resource name %s (invalid collection ID %s)", resource, v)
}
currColId = v
typeKey += currColId + "/"
} else {
if len(resourceRe.FindAllString(v, 1)) == 0 {
return nil, fmt.Errorf("invalid relative resource name %s (invalid resource sub-ID %s)", resource, v)
}
ids[currColId] = v
currColId = ""
}
}
typeKey = typeKey[:len(typeKey)-1]
collectionIds := strings.Split(typeKey, "/")
resourceName := tokens[len(tokens)-2]
return &RelativeResourceName{
Name: resourceName,
TypeKey: typeKey,
OrderedCollectionIds: collectionIds,
IdTuples: ids,
}, nil
}
type FullResourceName struct {
Service string
*RelativeResourceName
}
func ParseFullResourceName(name string) (*FullResourceName, error) {
fullRe := regexp.MustCompile(fullResourceNameRegex)
matches := fullRe.FindAllStringSubmatch(name, 1)
if len(matches) == 0 {
return nil, fmt.Errorf("invalid full name '%s'", name)
}
if len(matches[0]) != 3 {
return nil, fmt.Errorf("invalid full name '%s'", name)
}
serviceName := matches[0][1]
relName, err := ParseRelativeName(strings.Trim(matches[0][2], "/"))
if err != nil {
return nil, fmt.Errorf("error parsing relative resource path in full resource name '%s': %v", name, err)
}
return &FullResourceName{
Service: serviceName,
RelativeResourceName: relName,
}, nil
}
type SelfLink struct {
Prefix string
*RelativeResourceName
}
func ParseProjectResourceSelfLink(link string) (*SelfLink, error) {
u, err := url.Parse(link)
if err != nil || u.Scheme == "" || u.Host == "" {
return nil, fmt.Errorf("invalid self link '%s' must have scheme/host", link)
}
split := strings.SplitAfterN(link, selfLinkMarker, 2)
if len(split) != 2 {
return nil, fmt.Errorf("self link '%s' is not for project-level resource, must contain '%s')", link, selfLinkMarker)
}
relName, err := ParseRelativeName(selfLinkMarker + split[1])
if err != nil {
return nil, fmt.Errorf("error parsing relative resource path in self-link '%s': %v", link, err)
}
return &SelfLink{
Prefix: strings.TrimSuffix(split[0], selfLinkMarker),
RelativeResourceName: relName,
}, nil
}
|