mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-21 10:47:34 -05:00
Due to some past confusion about the purpose of this package, it has grown to include a confusing mix of currently-viable code and legacy support code from the move to HCL 2. This has in turn caused confusion about which parts of this package _should_ be used for new code. To help clarify that distinction we'll move the legacy support code into a package under the "legacy" directory, which is also where most of its callers live. There are unfortunately still some callers to these outside of the legacy tree, but the vast majority are either old tests written before HCL 2 adoption or helper code used only by those tests. The one dubious exception is the use in ResourceInstanceObjectSrc.Decode, which makes a best effort to shim flatmap as a concession to the fact that not all state-loading codepaths are able to run the provider state upgrade function that would normally be responsible for the flatmap-to-JSON conversion, which is explained in a new comment inline. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
121 lines
3.8 KiB
Go
121 lines
3.8 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package schema
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
ctyjson "github.com/zclconf/go-cty/cty/json"
|
|
|
|
"github.com/opentofu/opentofu/internal/configs/configschema"
|
|
"github.com/opentofu/opentofu/internal/legacy/hcl2shim"
|
|
"github.com/opentofu/opentofu/internal/legacy/tofu"
|
|
)
|
|
|
|
// DiffFromValues takes the current state and desired state as cty.Values and
|
|
// derives a tofu.InstanceDiff to give to the legacy providers. This is
|
|
// used to take the states provided by the new ApplyResourceChange method and
|
|
// convert them to a state+diff required for the legacy Apply method.
|
|
func DiffFromValues(prior, planned cty.Value, res *Resource) (*tofu.InstanceDiff, error) {
|
|
return diffFromValues(prior, planned, res, nil)
|
|
}
|
|
|
|
// diffFromValues takes an additional CustomizeDiffFunc, so we can generate our
|
|
// test fixtures from the legacy tests. In the new provider protocol the diff
|
|
// only needs to be created for the apply operation, and any customizations
|
|
// have already been done.
|
|
func diffFromValues(prior, planned cty.Value, res *Resource, cust CustomizeDiffFunc) (*tofu.InstanceDiff, error) {
|
|
instanceState, err := res.ShimInstanceStateFromValue(prior)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
configSchema := res.CoreConfigSchema()
|
|
|
|
cfg := tofu.NewResourceConfigShimmed(planned, configSchema)
|
|
removeConfigUnknowns(cfg.Config)
|
|
removeConfigUnknowns(cfg.Raw)
|
|
|
|
diff, err := schemaMap(res.Schema).Diff(instanceState, cfg, cust, nil, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return diff, err
|
|
}
|
|
|
|
// During apply the only unknown values are those which are to be computed by
|
|
// the resource itself. These may have been marked as unknown config values, and
|
|
// need to be removed to prevent the UnknownVariableValue from appearing the diff.
|
|
func removeConfigUnknowns(cfg map[string]interface{}) {
|
|
for k, v := range cfg {
|
|
switch v := v.(type) {
|
|
case string:
|
|
if v == hcl2shim.UnknownVariableValue {
|
|
delete(cfg, k)
|
|
}
|
|
case []interface{}:
|
|
for _, i := range v {
|
|
if m, ok := i.(map[string]interface{}); ok {
|
|
removeConfigUnknowns(m)
|
|
}
|
|
}
|
|
case map[string]interface{}:
|
|
removeConfigUnknowns(v)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ApplyDiff takes a cty.Value state and applies a tofu.InstanceDiff to
|
|
// get a new cty.Value state. This is used to convert the diff returned from
|
|
// the legacy provider Diff method to the state required for the new
|
|
// PlanResourceChange method.
|
|
func ApplyDiff(base cty.Value, d *tofu.InstanceDiff, schema *configschema.Block) (cty.Value, error) {
|
|
return d.ApplyToValue(base, schema)
|
|
}
|
|
|
|
// StateValueToJSONMap converts a cty.Value to generic JSON map via the cty JSON
|
|
// encoding.
|
|
func StateValueToJSONMap(val cty.Value, ty cty.Type) (map[string]interface{}, error) {
|
|
js, err := ctyjson.Marshal(val, ty)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var m map[string]interface{}
|
|
if err := json.Unmarshal(js, &m); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
// JSONMapToStateValue takes a generic json map[string]interface{} and converts it
|
|
// to the specific type, ensuring that the values conform to the schema.
|
|
func JSONMapToStateValue(m map[string]interface{}, block *configschema.Block) (cty.Value, error) {
|
|
var val cty.Value
|
|
|
|
js, err := json.Marshal(m)
|
|
if err != nil {
|
|
return val, err
|
|
}
|
|
|
|
val, err = ctyjson.Unmarshal(js, block.ImpliedType())
|
|
if err != nil {
|
|
return val, err
|
|
}
|
|
|
|
return block.CoerceValue(val)
|
|
}
|
|
|
|
// StateValueFromInstanceState converts a tofu.InstanceState to a
|
|
// cty.Value as described by the provided cty.Type, and maintains the resource
|
|
// ID as the "id" attribute.
|
|
func StateValueFromInstanceState(is *tofu.InstanceState, ty cty.Type) (cty.Value, error) {
|
|
return is.AttrsAsObjectValue(ty)
|
|
}
|