-
Notifications
You must be signed in to change notification settings - Fork 0
/
awsarn.go
139 lines (123 loc) · 3.37 KB
/
awsarn.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
129
130
131
132
133
134
135
136
137
138
139
package awsarn
import (
"errors"
"reflect"
"regexp"
"strings"
)
var (
// ErrMalformed is returned when the ARN appears to be invalid.
ErrMalformed = errors.New("malformed ARN")
// ErrVariablesNotSupported is returned when the ARN contains policy
// variables.
ErrVariablesNotSupported = errors.New("policy variables are not supported")
)
// Components encapsulate the individual pieces of an AWS ARN.
type Components struct {
ARN string
Partition string
Service string
Region string
AccountID string
ResourceType string
Resource string
ResourceDelimiter string
}
// Validate checks if an input ARN string conforms to a format which can be
// parsed by this package.
func Validate(arn string) error {
pieces := strings.SplitN(arn, ":", 6)
return validate(arn, pieces)
}
func validate(arn string, pieces []string) error {
if strings.Contains(arn, "${") {
return ErrVariablesNotSupported
}
if len(pieces) < 6 {
return ErrMalformed
}
return nil
}
// Parse accepts and ARN string and attempts to break it into constiuent parts.
func Parse(arn string) (*Components, error) {
pieces := strings.SplitN(arn, ":", 6)
if err := validate(arn, pieces); err != nil {
return nil, err
}
components := &Components{
ARN: pieces[0],
Partition: pieces[1],
Service: pieces[2],
Region: pieces[3],
AccountID: pieces[4],
}
if n := strings.Count(pieces[5], ":"); n > 0 {
components.ResourceDelimiter = ":"
resourceParts := strings.SplitN(pieces[5], ":", 2)
components.ResourceType = resourceParts[0]
components.Resource = resourceParts[1]
} else {
if m := strings.Count(pieces[5], "/"); m == 0 {
components.Resource = pieces[5]
} else {
components.ResourceDelimiter = "/"
resourceParts := strings.SplitN(pieces[5], "/", 2)
components.ResourceType = resourceParts[0]
components.Resource = resourceParts[1]
}
}
return components, nil
}
// String rebuilds the original input ARN to res
func (c Components) String() string {
front := []string{
c.ARN,
c.Partition,
c.Service,
c.Region,
c.AccountID,
}
s := strings.Join(front, ":") + ":" + c.ResourceChunk()
return s
}
// ResourceChunk returns the tail-end of the ARN, containing resource
// specifications.
func (c Components) ResourceChunk() string {
return c.ResourceType + c.ResourceDelimiter + c.Resource
}
// SupersetOf returns true if c is a superset of the other passed components.
func (c Components) SupersetOf(other *Components) bool {
if reflect.DeepEqual(c, other) {
return true
}
type pair struct {
a string
b string
}
pairs := []pair{
{c.ARN, other.ARN},
{c.Partition, other.Partition},
{c.Service, other.Service},
{c.Region, other.Region},
{c.AccountID, other.AccountID},
{c.ResourceChunk(), other.ResourceChunk()},
}
for _, p := range pairs {
if !toRegExp(p.a).MatchString(p.b) {
return false
}
}
return true
}
// toRegexp takes an AWS ARN resource component and converts it to a
// go-compatible regular expression. The '*' and '?' characters have special
// wildcard meanings in this context.
func toRegExp(s string) *regexp.Regexp {
// Escape the input string.
s = regexp.QuoteMeta(s)
// Unescape special * and ? characters because they have special meanings.
s = strings.Replace(s, "\\*", ".*", -1)
s = strings.Replace(s, "\\?", ".", -1)
expr := regexp.MustCompile(s)
return expr
}