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  }