-
Notifications
You must be signed in to change notification settings - Fork 9.4k
/
instance_key.go
220 lines (193 loc) · 6.34 KB
/
instance_key.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package addrs
import (
"fmt"
"strings"
"unicode"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
)
// InstanceKey represents the key of an instance within an object that
// contains multiple instances due to using "count" or "for_each" arguments
// in configuration.
//
// IntKey and StringKey are the two implementations of this type. No other
// implementations are allowed. The single instance of an object that _isn't_
// using "count" or "for_each" is represented by NoKey, which is a nil
// InstanceKey.
type InstanceKey interface {
instanceKeySigil()
String() string
// Value returns the cty.Value of the appropriate type for the InstanceKey
// value.
Value() cty.Value
}
// ParseInstanceKey returns the instance key corresponding to the given value,
// which must be known and non-null.
//
// If an unknown or null value is provided then this function will panic. This
// function is intended to deal with the values that would naturally be found
// in a hcl.TraverseIndex, which (when parsed from source, at least) can never
// contain unknown or null values.
func ParseInstanceKey(key cty.Value) (InstanceKey, error) {
switch key.Type() {
case cty.String:
return StringKey(key.AsString()), nil
case cty.Number:
var idx int
err := gocty.FromCtyValue(key, &idx)
return IntKey(idx), err
default:
return NoKey, fmt.Errorf("either a string or an integer is required")
}
}
// NoKey represents the absense of an InstanceKey, for the single instance
// of a configuration object that does not use "count" or "for_each" at all.
var NoKey InstanceKey
// WildcardKey represents the "unknown" value of an InstanceKey. This is used
// within the deferral logic to express absolute module and resource addresses
// that are not known at the time of planning.
var WildcardKey InstanceKey = &wildcardKey{}
// wildcardKey is a special kind of InstanceKey that represents the "unknown"
// value of an InstanceKey. This is used within the deferral logic to express
// absolute module and resource addresses that are not known at the time of
// planning.
type wildcardKey struct{}
func (w *wildcardKey) instanceKeySigil() {}
func (w *wildcardKey) String() string {
return "[*]"
}
func (w *wildcardKey) Value() cty.Value {
return cty.DynamicVal
}
// IntKey is the InstanceKey representation representing integer indices, as
// used when the "count" argument is specified or if for_each is used with
// a sequence type.
type IntKey int
func (k IntKey) instanceKeySigil() {
}
func (k IntKey) String() string {
return fmt.Sprintf("[%d]", int(k))
}
func (k IntKey) Value() cty.Value {
return cty.NumberIntVal(int64(k))
}
// StringKey is the InstanceKey representation representing string indices, as
// used when the "for_each" argument is specified with a map or object type.
type StringKey string
func (k StringKey) instanceKeySigil() {
}
func (k StringKey) String() string {
// We use HCL's quoting syntax here so that we can in principle parse
// an address constructed by this package as if it were an HCL
// traversal, even if the string contains HCL's own metacharacters.
return fmt.Sprintf("[%s]", toHCLQuotedString(string(k)))
}
func (k StringKey) Value() cty.Value {
return cty.StringVal(string(k))
}
// InstanceKeyLess returns true if the first given instance key i should sort
// before the second key j, and false otherwise.
func InstanceKeyLess(i, j InstanceKey) bool {
iTy := instanceKeyType(i)
jTy := instanceKeyType(j)
switch {
case i == j:
return false
case i == NoKey:
return true
case j == NoKey:
return false
case iTy != jTy:
// The ordering here is arbitrary except that we want NoKeyType
// to sort before the others, so we'll just use the enum values
// of InstanceKeyType here (where NoKey is zero, sorting before
// any other).
return uint32(iTy) < uint32(jTy)
case iTy == IntKeyType:
return int(i.(IntKey)) < int(j.(IntKey))
case iTy == StringKeyType:
return string(i.(StringKey)) < string(j.(StringKey))
default:
// Shouldn't be possible to get down here in practice, since the
// above is exhaustive.
return false
}
}
func instanceKeyType(k InstanceKey) InstanceKeyType {
if _, ok := k.(StringKey); ok {
return StringKeyType
}
if _, ok := k.(IntKey); ok {
return IntKeyType
}
return NoKeyType
}
// InstanceKeyType represents the different types of instance key that are
// supported. Usually it is sufficient to simply type-assert an InstanceKey
// value to either IntKey or StringKey, but this type and its values can be
// used to represent the types themselves, rather than specific values
// of those types.
type InstanceKeyType rune
const (
NoKeyType InstanceKeyType = 0
IntKeyType InstanceKeyType = 'I'
StringKeyType InstanceKeyType = 'S'
// UnknownKeyType is a placeholder key type for situations where Terraform
// doesn't yet know which key type to use. There are no [InstanceKey]
// values of this type.
UnknownKeyType InstanceKeyType = '?'
)
// toHCLQuotedString is a helper which formats the given string in a way that
// HCL's expression parser would treat as a quoted string template.
//
// This includes:
// - Adding quote marks at the start and the end.
// - Using backslash escapes as needed for characters that cannot be represented directly.
// - Escaping anything that would be treated as a template interpolation or control sequence.
func toHCLQuotedString(s string) string {
// This is an adaptation of a similar function inside the hclwrite package,
// inlined here because hclwrite's version generates HCL tokens but we
// only need normal strings.
if len(s) == 0 {
return `""`
}
var buf strings.Builder
buf.WriteByte('"')
for i, r := range s {
switch r {
case '\n':
buf.WriteString(`\n`)
case '\r':
buf.WriteString(`\r`)
case '\t':
buf.WriteString(`\t`)
case '"':
buf.WriteString(`\"`)
case '\\':
buf.WriteString(`\\`)
case '$', '%':
buf.WriteRune(r)
remain := s[i+1:]
if len(remain) > 0 && remain[0] == '{' {
// Double up our template introducer symbol to escape it.
buf.WriteRune(r)
}
default:
if !unicode.IsPrint(r) {
var fmted string
if r < 65536 {
fmted = fmt.Sprintf("\\u%04x", r)
} else {
fmted = fmt.Sprintf("\\U%08x", r)
}
buf.WriteString(fmted)
} else {
buf.WriteRune(r)
}
}
}
buf.WriteByte('"')
return buf.String()
}