2023-05-02 10:33:06 -05:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2018-03-30 21:58:57 -05:00
|
|
|
package addrs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2022-06-15 19:37:00 -05:00
|
|
|
"strings"
|
|
|
|
"unicode"
|
2018-03-30 21:58:57 -05:00
|
|
|
|
|
|
|
"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()
|
2018-04-03 20:03:47 -05:00
|
|
|
String() string
|
2020-02-19 15:00:10 -06:00
|
|
|
|
|
|
|
// Value returns the cty.Value of the appropriate type for the InstanceKey
|
|
|
|
// value.
|
|
|
|
Value() cty.Value
|
2018-03-30 21:58:57 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// 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() {
|
|
|
|
}
|
|
|
|
|
2018-04-03 20:03:47 -05:00
|
|
|
func (k IntKey) String() string {
|
|
|
|
return fmt.Sprintf("[%d]", int(k))
|
|
|
|
}
|
|
|
|
|
2020-02-19 15:00:10 -06:00
|
|
|
func (k IntKey) Value() cty.Value {
|
|
|
|
return cty.NumberIntVal(int64(k))
|
|
|
|
}
|
|
|
|
|
2018-03-30 21:58:57 -05:00
|
|
|
// 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() {
|
|
|
|
}
|
2018-04-03 20:03:47 -05:00
|
|
|
|
|
|
|
func (k StringKey) String() string {
|
2022-06-15 19:37:00 -05:00
|
|
|
// 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)))
|
2018-04-03 20:03:47 -05:00
|
|
|
}
|
2018-06-07 19:17:47 -05:00
|
|
|
|
2020-02-19 15:00:10 -06:00
|
|
|
func (k StringKey) Value() cty.Value {
|
|
|
|
return cty.StringVal(string(k))
|
|
|
|
}
|
|
|
|
|
2018-06-07 19:17:47 -05:00
|
|
|
// 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'
|
|
|
|
)
|
2022-06-15 19:37:00 -05:00
|
|
|
|
|
|
|
// 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()
|
|
|
|
}
|