github.com/pulumi/terraform@v1.4.0/pkg/addrs/instance_key.go (about) 1 package addrs 2 3 import ( 4 "fmt" 5 "strings" 6 "unicode" 7 8 "github.com/zclconf/go-cty/cty" 9 "github.com/zclconf/go-cty/cty/gocty" 10 ) 11 12 // InstanceKey represents the key of an instance within an object that 13 // contains multiple instances due to using "count" or "for_each" arguments 14 // in configuration. 15 // 16 // IntKey and StringKey are the two implementations of this type. No other 17 // implementations are allowed. The single instance of an object that _isn't_ 18 // using "count" or "for_each" is represented by NoKey, which is a nil 19 // InstanceKey. 20 type InstanceKey interface { 21 instanceKeySigil() 22 String() string 23 24 // Value returns the cty.Value of the appropriate type for the InstanceKey 25 // value. 26 Value() cty.Value 27 } 28 29 // ParseInstanceKey returns the instance key corresponding to the given value, 30 // which must be known and non-null. 31 // 32 // If an unknown or null value is provided then this function will panic. This 33 // function is intended to deal with the values that would naturally be found 34 // in a hcl.TraverseIndex, which (when parsed from source, at least) can never 35 // contain unknown or null values. 36 func ParseInstanceKey(key cty.Value) (InstanceKey, error) { 37 switch key.Type() { 38 case cty.String: 39 return StringKey(key.AsString()), nil 40 case cty.Number: 41 var idx int 42 err := gocty.FromCtyValue(key, &idx) 43 return IntKey(idx), err 44 default: 45 return NoKey, fmt.Errorf("either a string or an integer is required") 46 } 47 } 48 49 // NoKey represents the absense of an InstanceKey, for the single instance 50 // of a configuration object that does not use "count" or "for_each" at all. 51 var NoKey InstanceKey 52 53 // IntKey is the InstanceKey representation representing integer indices, as 54 // used when the "count" argument is specified or if for_each is used with 55 // a sequence type. 56 type IntKey int 57 58 func (k IntKey) instanceKeySigil() { 59 } 60 61 func (k IntKey) String() string { 62 return fmt.Sprintf("[%d]", int(k)) 63 } 64 65 func (k IntKey) Value() cty.Value { 66 return cty.NumberIntVal(int64(k)) 67 } 68 69 // StringKey is the InstanceKey representation representing string indices, as 70 // used when the "for_each" argument is specified with a map or object type. 71 type StringKey string 72 73 func (k StringKey) instanceKeySigil() { 74 } 75 76 func (k StringKey) String() string { 77 // We use HCL's quoting syntax here so that we can in principle parse 78 // an address constructed by this package as if it were an HCL 79 // traversal, even if the string contains HCL's own metacharacters. 80 return fmt.Sprintf("[%s]", toHCLQuotedString(string(k))) 81 } 82 83 func (k StringKey) Value() cty.Value { 84 return cty.StringVal(string(k)) 85 } 86 87 // InstanceKeyLess returns true if the first given instance key i should sort 88 // before the second key j, and false otherwise. 89 func InstanceKeyLess(i, j InstanceKey) bool { 90 iTy := instanceKeyType(i) 91 jTy := instanceKeyType(j) 92 93 switch { 94 case i == j: 95 return false 96 case i == NoKey: 97 return true 98 case j == NoKey: 99 return false 100 case iTy != jTy: 101 // The ordering here is arbitrary except that we want NoKeyType 102 // to sort before the others, so we'll just use the enum values 103 // of InstanceKeyType here (where NoKey is zero, sorting before 104 // any other). 105 return uint32(iTy) < uint32(jTy) 106 case iTy == IntKeyType: 107 return int(i.(IntKey)) < int(j.(IntKey)) 108 case iTy == StringKeyType: 109 return string(i.(StringKey)) < string(j.(StringKey)) 110 default: 111 // Shouldn't be possible to get down here in practice, since the 112 // above is exhaustive. 113 return false 114 } 115 } 116 117 func instanceKeyType(k InstanceKey) InstanceKeyType { 118 if _, ok := k.(StringKey); ok { 119 return StringKeyType 120 } 121 if _, ok := k.(IntKey); ok { 122 return IntKeyType 123 } 124 return NoKeyType 125 } 126 127 // InstanceKeyType represents the different types of instance key that are 128 // supported. Usually it is sufficient to simply type-assert an InstanceKey 129 // value to either IntKey or StringKey, but this type and its values can be 130 // used to represent the types themselves, rather than specific values 131 // of those types. 132 type InstanceKeyType rune 133 134 const ( 135 NoKeyType InstanceKeyType = 0 136 IntKeyType InstanceKeyType = 'I' 137 StringKeyType InstanceKeyType = 'S' 138 ) 139 140 // toHCLQuotedString is a helper which formats the given string in a way that 141 // HCL's expression parser would treat as a quoted string template. 142 // 143 // This includes: 144 // - Adding quote marks at the start and the end. 145 // - Using backslash escapes as needed for characters that cannot be represented directly. 146 // - Escaping anything that would be treated as a template interpolation or control sequence. 147 func toHCLQuotedString(s string) string { 148 // This is an adaptation of a similar function inside the hclwrite package, 149 // inlined here because hclwrite's version generates HCL tokens but we 150 // only need normal strings. 151 if len(s) == 0 { 152 return `""` 153 } 154 var buf strings.Builder 155 buf.WriteByte('"') 156 for i, r := range s { 157 switch r { 158 case '\n': 159 buf.WriteString(`\n`) 160 case '\r': 161 buf.WriteString(`\r`) 162 case '\t': 163 buf.WriteString(`\t`) 164 case '"': 165 buf.WriteString(`\"`) 166 case '\\': 167 buf.WriteString(`\\`) 168 case '$', '%': 169 buf.WriteRune(r) 170 remain := s[i+1:] 171 if len(remain) > 0 && remain[0] == '{' { 172 // Double up our template introducer symbol to escape it. 173 buf.WriteRune(r) 174 } 175 default: 176 if !unicode.IsPrint(r) { 177 var fmted string 178 if r < 65536 { 179 fmted = fmt.Sprintf("\\u%04x", r) 180 } else { 181 fmted = fmt.Sprintf("\\U%08x", r) 182 } 183 buf.WriteString(fmted) 184 } else { 185 buf.WriteRune(r) 186 } 187 } 188 } 189 buf.WriteByte('"') 190 return buf.String() 191 }