github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/shims.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package schema
     5  
     6  import (
     7  	"encoding/json"
     8  
     9  	"github.com/zclconf/go-cty/cty"
    10  	ctyjson "github.com/zclconf/go-cty/cty/json"
    11  
    12  	"github.com/terramate-io/tf/configs/configschema"
    13  	"github.com/terramate-io/tf/configs/hcl2shim"
    14  	"github.com/terramate-io/tf/legacy/terraform"
    15  )
    16  
    17  // DiffFromValues takes the current state and desired state as cty.Values and
    18  // derives a terraform.InstanceDiff to give to the legacy providers. This is
    19  // used to take the states provided by the new ApplyResourceChange method and
    20  // convert them to a state+diff required for the legacy Apply method.
    21  func DiffFromValues(prior, planned cty.Value, res *Resource) (*terraform.InstanceDiff, error) {
    22  	return diffFromValues(prior, planned, res, nil)
    23  }
    24  
    25  // diffFromValues takes an additional CustomizeDiffFunc, so we can generate our
    26  // test fixtures from the legacy tests. In the new provider protocol the diff
    27  // only needs to be created for the apply operation, and any customizations
    28  // have already been done.
    29  func diffFromValues(prior, planned cty.Value, res *Resource, cust CustomizeDiffFunc) (*terraform.InstanceDiff, error) {
    30  	instanceState, err := res.ShimInstanceStateFromValue(prior)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	configSchema := res.CoreConfigSchema()
    36  
    37  	cfg := terraform.NewResourceConfigShimmed(planned, configSchema)
    38  	removeConfigUnknowns(cfg.Config)
    39  	removeConfigUnknowns(cfg.Raw)
    40  
    41  	diff, err := schemaMap(res.Schema).Diff(instanceState, cfg, cust, nil, false)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	return diff, err
    47  }
    48  
    49  // During apply the only unknown values are those which are to be computed by
    50  // the resource itself. These may have been marked as unknown config values, and
    51  // need to be removed to prevent the UnknownVariableValue from appearing the diff.
    52  func removeConfigUnknowns(cfg map[string]interface{}) {
    53  	for k, v := range cfg {
    54  		switch v := v.(type) {
    55  		case string:
    56  			if v == hcl2shim.UnknownVariableValue {
    57  				delete(cfg, k)
    58  			}
    59  		case []interface{}:
    60  			for _, i := range v {
    61  				if m, ok := i.(map[string]interface{}); ok {
    62  					removeConfigUnknowns(m)
    63  				}
    64  			}
    65  		case map[string]interface{}:
    66  			removeConfigUnknowns(v)
    67  		}
    68  	}
    69  }
    70  
    71  // ApplyDiff takes a cty.Value state and applies a terraform.InstanceDiff to
    72  // get a new cty.Value state. This is used to convert the diff returned from
    73  // the legacy provider Diff method to the state required for the new
    74  // PlanResourceChange method.
    75  func ApplyDiff(base cty.Value, d *terraform.InstanceDiff, schema *configschema.Block) (cty.Value, error) {
    76  	return d.ApplyToValue(base, schema)
    77  }
    78  
    79  // StateValueToJSONMap converts a cty.Value to generic JSON map via the cty JSON
    80  // encoding.
    81  func StateValueToJSONMap(val cty.Value, ty cty.Type) (map[string]interface{}, error) {
    82  	js, err := ctyjson.Marshal(val, ty)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	var m map[string]interface{}
    88  	if err := json.Unmarshal(js, &m); err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	return m, nil
    93  }
    94  
    95  // JSONMapToStateValue takes a generic json map[string]interface{} and converts it
    96  // to the specific type, ensuring that the values conform to the schema.
    97  func JSONMapToStateValue(m map[string]interface{}, block *configschema.Block) (cty.Value, error) {
    98  	var val cty.Value
    99  
   100  	js, err := json.Marshal(m)
   101  	if err != nil {
   102  		return val, err
   103  	}
   104  
   105  	val, err = ctyjson.Unmarshal(js, block.ImpliedType())
   106  	if err != nil {
   107  		return val, err
   108  	}
   109  
   110  	return block.CoerceValue(val)
   111  }
   112  
   113  // StateValueFromInstanceState converts a terraform.InstanceState to a
   114  // cty.Value as described by the provided cty.Type, and maintains the resource
   115  // ID as the "id" attribute.
   116  func StateValueFromInstanceState(is *terraform.InstanceState, ty cty.Type) (cty.Value, error) {
   117  	return is.AttrsAsObjectValue(ty)
   118  }