github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/jsonconfig/expression.go (about)

     1  package jsonconfig
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  
     8  	"github.com/hashicorp/hcl/v2"
     9  	"github.com/hashicorp/hcl/v2/hcldec"
    10  	"github.com/hashicorp/terraform/internal/addrs"
    11  	"github.com/hashicorp/terraform/internal/configs/configschema"
    12  	"github.com/hashicorp/terraform/internal/lang"
    13  	"github.com/hashicorp/terraform/internal/lang/blocktoattr"
    14  	"github.com/zclconf/go-cty/cty"
    15  	ctyjson "github.com/zclconf/go-cty/cty/json"
    16  )
    17  
    18  // expression represents any unparsed expression
    19  type expression struct {
    20  	// "constant_value" is set only if the expression contains no references to
    21  	// other objects, in which case it gives the resulting constant value. This
    22  	// is mapped as for the individual values in the common value
    23  	// representation.
    24  	ConstantValue json.RawMessage `json:"constant_value,omitempty"`
    25  
    26  	// Alternatively, "references" will be set to a list of references in the
    27  	// expression. Multi-step references will be unwrapped and duplicated for
    28  	// each significant traversal step, allowing callers to more easily
    29  	// recognize the objects they care about without attempting to parse the
    30  	// expressions. Callers should only use string equality checks here, since
    31  	// the syntax may be extended in future releases.
    32  	References []string `json:"references,omitempty"`
    33  }
    34  
    35  func marshalExpression(ex hcl.Expression) expression {
    36  	var ret expression
    37  	if ex == nil {
    38  		return ret
    39  	}
    40  
    41  	val, _ := ex.Value(nil)
    42  	if val != cty.NilVal {
    43  		valJSON, _ := ctyjson.Marshal(val, val.Type())
    44  		ret.ConstantValue = valJSON
    45  	}
    46  
    47  	refs, _ := lang.ReferencesInExpr(ex)
    48  	if len(refs) > 0 {
    49  		var varString []string
    50  		for _, ref := range refs {
    51  			// We work backwards here, starting with the full reference +
    52  			// reamining traversal, and then unwrapping the remaining traversals
    53  			// into parts until we end up at the smallest referencable address.
    54  			remains := ref.Remaining
    55  			for len(remains) > 0 {
    56  				varString = append(varString, fmt.Sprintf("%s%s", ref.Subject, traversalStr(remains)))
    57  				remains = remains[:(len(remains) - 1)]
    58  			}
    59  			varString = append(varString, ref.Subject.String())
    60  
    61  			switch ref.Subject.(type) {
    62  			case addrs.ModuleCallInstance:
    63  				if ref.Subject.(addrs.ModuleCallInstance).Key != addrs.NoKey {
    64  					// Include the module call, without the key
    65  					varString = append(varString, ref.Subject.(addrs.ModuleCallInstance).Call.String())
    66  				}
    67  			case addrs.ResourceInstance:
    68  				if ref.Subject.(addrs.ResourceInstance).Key != addrs.NoKey {
    69  					// Include the resource, without the key
    70  					varString = append(varString, ref.Subject.(addrs.ResourceInstance).Resource.String())
    71  				}
    72  			case addrs.ModuleCallInstanceOutput:
    73  				// Include the module name, without the output name
    74  				varString = append(varString, ref.Subject.(addrs.ModuleCallInstanceOutput).Call.String())
    75  			}
    76  		}
    77  		ret.References = varString
    78  	}
    79  
    80  	return ret
    81  }
    82  
    83  func (e *expression) Empty() bool {
    84  	return e.ConstantValue == nil && e.References == nil
    85  }
    86  
    87  // expressions is used to represent the entire content of a block. Attribute
    88  // arguments are mapped directly with the attribute name as key and an
    89  // expression as value.
    90  type expressions map[string]interface{}
    91  
    92  func marshalExpressions(body hcl.Body, schema *configschema.Block) expressions {
    93  	// Since we want the raw, un-evaluated expressions we need to use the
    94  	// low-level HCL API here, rather than the hcldec decoder API. That means we
    95  	// need the low-level schema.
    96  	lowSchema := hcldec.ImpliedSchema(schema.DecoderSpec())
    97  	// (lowSchema is an hcl.BodySchema:
    98  	// https://godoc.org/github.com/hashicorp/hcl/v2/hcl#BodySchema )
    99  
   100  	// fix any ConfigModeAttr blocks present from legacy providers
   101  	body = blocktoattr.FixUpBlockAttrs(body, schema)
   102  
   103  	// Use the low-level schema with the body to decode one level We'll just
   104  	// ignore any additional content that's not covered by the schema, which
   105  	// will effectively ignore "dynamic" blocks, and may also ignore other
   106  	// unknown stuff but anything else would get flagged by Terraform as an
   107  	// error anyway, and so we wouldn't end up in here.
   108  	content, _, _ := body.PartialContent(lowSchema)
   109  	if content == nil {
   110  		// Should never happen for a valid body, but we'll just generate empty
   111  		// if there were any problems.
   112  		return nil
   113  	}
   114  
   115  	ret := make(expressions)
   116  
   117  	// Any attributes we encode directly as expression objects.
   118  	for name, attr := range content.Attributes {
   119  		ret[name] = marshalExpression(attr.Expr) // note: singular expression for this one
   120  	}
   121  
   122  	// Any nested blocks require a recursive call to produce nested expressions
   123  	// objects.
   124  	for _, block := range content.Blocks {
   125  		typeName := block.Type
   126  		blockS, exists := schema.BlockTypes[typeName]
   127  		if !exists {
   128  			// Should never happen since only block types in the schema would be
   129  			// put in blocks list
   130  			continue
   131  		}
   132  
   133  		switch blockS.Nesting {
   134  		case configschema.NestingSingle, configschema.NestingGroup:
   135  			ret[typeName] = marshalExpressions(block.Body, &blockS.Block)
   136  		case configschema.NestingList, configschema.NestingSet:
   137  			if _, exists := ret[typeName]; !exists {
   138  				ret[typeName] = make([]map[string]interface{}, 0, 1)
   139  			}
   140  			ret[typeName] = append(ret[typeName].([]map[string]interface{}), marshalExpressions(block.Body, &blockS.Block))
   141  		case configschema.NestingMap:
   142  			if _, exists := ret[typeName]; !exists {
   143  				ret[typeName] = make(map[string]map[string]interface{})
   144  			}
   145  			// NestingMap blocks always have the key in the first (and only) label
   146  			key := block.Labels[0]
   147  			retMap := ret[typeName].(map[string]map[string]interface{})
   148  			retMap[key] = marshalExpressions(block.Body, &blockS.Block)
   149  		}
   150  	}
   151  
   152  	return ret
   153  }
   154  
   155  // traversalStr produces a representation of an HCL traversal that is compact,
   156  // resembles HCL native syntax, and is suitable for display in the UI.
   157  //
   158  // This was copied (and simplified) from internal/command/views/json/diagnostic.go.
   159  func traversalStr(traversal hcl.Traversal) string {
   160  	var buf bytes.Buffer
   161  	for _, step := range traversal {
   162  		switch tStep := step.(type) {
   163  		case hcl.TraverseRoot:
   164  			buf.WriteString(tStep.Name)
   165  		case hcl.TraverseAttr:
   166  			buf.WriteByte('.')
   167  			buf.WriteString(tStep.Name)
   168  		case hcl.TraverseIndex:
   169  			buf.WriteByte('[')
   170  			switch tStep.Key.Type() {
   171  			case cty.String:
   172  				buf.WriteString(fmt.Sprintf("%q", tStep.Key.AsString()))
   173  			case cty.Number:
   174  				bf := tStep.Key.AsBigFloat()
   175  				buf.WriteString(bf.Text('g', 10))
   176  			}
   177  			buf.WriteByte(']')
   178  		}
   179  	}
   180  	return buf.String()
   181  }