| 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 |
| |
| // 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' |
| ) |
| |
| // 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() |
| } |