-
Notifications
You must be signed in to change notification settings - Fork 6
/
resource_name.go
128 lines (109 loc) · 3.44 KB
/
resource_name.go
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
}