mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-22 22:52:57 -06:00
126e5f337f
* command/show: adding functions to aid refactoring The planfile -> statefile -> state logic path was getting hard to follow with blurry human eyes. The getPlan... and getState... functions were added to help streamline the logic flow. Continued refactoring may follow. * command/show: use ctx.Config() instead of a config snapshot As originally written, the jsonconfig marshaller was getting an error when loading configs that included one or more modules. It's not clear if that was an error in the function call or in the configloader itself, but as a simpler solution existed I did not dig too far. * command/jsonplan: implement jsonplan.Marshal Split the `config` portion into a discrete package to aid in naming sanity (so we could have for example jsonconfig.Resource instead of jsonplan.ConfigResource) and to enable marshaling the config on it's own.
254 lines
7.1 KiB
Go
254 lines
7.1 KiB
Go
package jsonplan
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/configs/configschema"
|
|
"github.com/hashicorp/terraform/plans"
|
|
"github.com/hashicorp/terraform/terraform"
|
|
ctyjson "github.com/zclconf/go-cty/cty/json"
|
|
)
|
|
|
|
// stateValues is the common representation of resolved values for both the
|
|
// prior state (which is always complete) and the planned new state.
|
|
type stateValues struct {
|
|
Outputs map[string]output `json:"outputs,omitempty"`
|
|
RootModule module `json:"root_module,omitempty"`
|
|
}
|
|
|
|
// attributeValues is the JSON representation of the attribute values of the
|
|
// resource, whose structure depends on the resource type schema.
|
|
type attributeValues map[string]interface{}
|
|
|
|
func marshalAttributeValues(value cty.Value, schema *configschema.Block) attributeValues {
|
|
ret := make(attributeValues)
|
|
|
|
it := value.ElementIterator()
|
|
for it.Next() {
|
|
k, v := it.Element()
|
|
ret[k.AsString()] = v
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// marshalAttributeValuesBool returns an attributeValues structure with "true" and
|
|
// "false" in place of the values indicating whether the value is known or not.
|
|
func marshalAttributeValuesBool(value cty.Value, schema *configschema.Block) attributeValues {
|
|
ret := make(attributeValues)
|
|
|
|
it := value.ElementIterator()
|
|
for it.Next() {
|
|
k, v := it.Element()
|
|
if v.IsWhollyKnown() {
|
|
ret[k.AsString()] = "true"
|
|
}
|
|
ret[k.AsString()] = "false"
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// marshalPlannedOutputs takes a list of changes and returns two output maps,
|
|
// the former with output values and the latter with true/false in place of
|
|
// values indicating whether the values are known at plan time.
|
|
func marshalPlannedOutputs(changes *plans.Changes) (map[string]output, map[string]output, error) {
|
|
if changes.Outputs == nil {
|
|
// No changes - we're done here!
|
|
return nil, nil, nil
|
|
}
|
|
|
|
ret := make(map[string]output)
|
|
uRet := make(map[string]output)
|
|
|
|
for _, oc := range changes.Outputs {
|
|
if oc.ChangeSrc.Action == plans.Delete {
|
|
continue
|
|
}
|
|
|
|
var after []byte
|
|
changeV, err := oc.Decode()
|
|
if err != nil {
|
|
return ret, uRet, err
|
|
}
|
|
|
|
if changeV.After != cty.NilVal {
|
|
if changeV.After.IsWhollyKnown() {
|
|
after, err = ctyjson.Marshal(changeV.After, changeV.After.Type())
|
|
if err != nil {
|
|
return ret, uRet, err
|
|
}
|
|
uRet[oc.Addr.OutputValue.Name] = output{
|
|
Value: json.RawMessage("true"),
|
|
Sensitive: oc.Sensitive,
|
|
}
|
|
} else {
|
|
uRet[oc.Addr.OutputValue.Name] = output{
|
|
Value: json.RawMessage("false"),
|
|
Sensitive: oc.Sensitive,
|
|
}
|
|
}
|
|
}
|
|
|
|
ret[oc.Addr.OutputValue.Name] = output{
|
|
Value: json.RawMessage(after),
|
|
Sensitive: oc.Sensitive,
|
|
}
|
|
}
|
|
|
|
return ret, uRet, nil
|
|
|
|
}
|
|
|
|
// marshalPlannedValues returns two modules:
|
|
// The former has attribute values populated and the latter has true/false in
|
|
// place of values indicating whether the values are known at plan time.
|
|
func marshalPlannedValues(changes *plans.Changes, schemas *terraform.Schemas) (module, module, error) {
|
|
var ret, uRet module
|
|
if changes.Empty() {
|
|
return ret, uRet, nil
|
|
}
|
|
|
|
// build two maps:
|
|
// module name -> [resource addresses]
|
|
// module -> [children modules]
|
|
moduleResourceMap := make(map[string][]addrs.AbsResourceInstance)
|
|
moduleMap := make(map[string][]addrs.ModuleInstance)
|
|
|
|
for _, resource := range changes.Resources {
|
|
// if the resource is being deleted, skip over it.
|
|
if resource.Action != plans.Delete {
|
|
containingModule := resource.Addr.Module.String()
|
|
moduleResourceMap[containingModule] = append(moduleResourceMap[containingModule], resource.Addr)
|
|
|
|
// root has no parents.
|
|
if containingModule != "" {
|
|
parent := resource.Addr.Module.Parent().String()
|
|
moduleMap[parent] = append(moduleMap[parent], resource.Addr.Module)
|
|
}
|
|
}
|
|
}
|
|
|
|
// start with the root module
|
|
resources, uResources, err := marshalPlanResources(changes, moduleResourceMap[""], schemas)
|
|
if err != nil {
|
|
return ret, uRet, err
|
|
}
|
|
ret.Resources = resources
|
|
uRet.Resources = uResources
|
|
|
|
childModules, err := marshalPlanModules(changes, schemas, moduleMap[""], moduleMap, moduleResourceMap)
|
|
if err != nil {
|
|
return ret, uRet, err
|
|
}
|
|
ret.ChildModules = childModules
|
|
|
|
return ret, uRet, nil
|
|
}
|
|
|
|
// marshalPlannedValues returns two resource slices:
|
|
// The former has attribute values populated and the latter has true/false in
|
|
// place of values indicating whether the values are known at plan time.
|
|
func marshalPlanResources(changes *plans.Changes, ris []addrs.AbsResourceInstance, schemas *terraform.Schemas) ([]resource, []resource, error) {
|
|
var ret, uRet []resource
|
|
|
|
for _, ri := range ris {
|
|
r := changes.ResourceInstance(ri)
|
|
if r.Action == plans.Delete || r.Action == plans.NoOp {
|
|
continue
|
|
}
|
|
|
|
resource := resource{
|
|
Address: r.Addr.String(),
|
|
Type: r.Addr.Resource.Resource.Type,
|
|
Name: r.Addr.Resource.Resource.Name,
|
|
ProviderName: r.ProviderAddr.ProviderConfig.StringCompact(),
|
|
Index: r.Addr.Resource.Key,
|
|
}
|
|
|
|
switch r.Addr.Resource.Resource.Mode {
|
|
case addrs.ManagedResourceMode:
|
|
resource.Mode = "managed"
|
|
case addrs.DataResourceMode:
|
|
resource.Mode = "data"
|
|
default:
|
|
return nil, nil, fmt.Errorf("resource %s has an unsupported mode %s",
|
|
r.Addr.String(),
|
|
r.Addr.Resource.Resource.Mode.String(),
|
|
)
|
|
}
|
|
|
|
schema, schemaVer := schemas.ResourceTypeConfig(
|
|
resource.ProviderName,
|
|
r.Addr.Resource.Resource.Mode,
|
|
resource.Type,
|
|
)
|
|
if schema == nil {
|
|
return nil, nil, fmt.Errorf("no schema found for %s", r.Addr.String())
|
|
}
|
|
resource.SchemaVersion = schemaVer
|
|
changeV, err := r.Decode(schema.ImpliedType())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var unknownAttributeValues attributeValues
|
|
if changeV.After != cty.NilVal {
|
|
if changeV.After.IsWhollyKnown() {
|
|
resource.AttributeValues = marshalAttributeValues(changeV.After, schema)
|
|
}
|
|
unknownAttributeValues = marshalAttributeValuesBool(changeV.After, schema)
|
|
}
|
|
|
|
uResource := resource
|
|
uResource.AttributeValues = unknownAttributeValues
|
|
|
|
ret = append(ret, resource)
|
|
uRet = append(uRet, uResource)
|
|
}
|
|
|
|
return ret, uRet, nil
|
|
}
|
|
|
|
// marshalPlanModules iterates over a list of modules to recursively describe
|
|
// the full module tree.
|
|
func marshalPlanModules(
|
|
changes *plans.Changes,
|
|
schemas *terraform.Schemas,
|
|
childModules []addrs.ModuleInstance,
|
|
moduleMap map[string][]addrs.ModuleInstance,
|
|
moduleResourceMap map[string][]addrs.AbsResourceInstance,
|
|
) ([]module, error) {
|
|
|
|
var ret []module
|
|
|
|
for _, child := range childModules {
|
|
moduleResources := moduleResourceMap[child.String()]
|
|
// cm for child module, naming things is hard.
|
|
var cm module
|
|
// don't populate the address for the root module
|
|
if child.String() != "" {
|
|
cm.Address = child.String()
|
|
}
|
|
rs, _, err := marshalPlanResources(changes, moduleResources, schemas)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cm.Resources = rs
|
|
|
|
if len(moduleMap[child.String()]) > 0 {
|
|
moreChildModules, err := marshalPlanModules(changes, schemas, moduleMap[child.String()], moduleMap, moduleResourceMap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cm.ChildModules = moreChildModules
|
|
}
|
|
|
|
ret = append(ret, cm)
|
|
}
|
|
|
|
return ret, nil
|
|
}
|