github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/schema/shims.go (about)

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