github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/shims.go (about)

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