mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-22 22:52:57 -06:00
f2f35265bc
* command/jsonplan: - add variables to plan output - print known planned values for resources Previously, resource attribute values were only displayed if the values were wholly known. Now we will filter the unknown values out of the change and print the known values. * command/jsonstate: added depends_on and tainted fields * command/show: update tests to reflect added fields
224 lines
5.7 KiB
Go
224 lines
5.7 KiB
Go
package jsonplan
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
ctyjson "github.com/zclconf/go-cty/cty/json"
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/configs/configschema"
|
|
"github.com/hashicorp/terraform/plans"
|
|
"github.com/hashicorp/terraform/terraform"
|
|
)
|
|
|
|
// 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 {
|
|
if value == cty.NilVal {
|
|
return nil
|
|
}
|
|
ret := make(attributeValues)
|
|
|
|
it := value.ElementIterator()
|
|
for it.Next() {
|
|
k, v := it.Element()
|
|
vJSON, _ := ctyjson.Marshal(v, v.Type())
|
|
ret[k.AsString()] = json.RawMessage(vJSON)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// marshalPlannedOutputs takes a list of changes and returns a map of output
|
|
// values
|
|
func marshalPlannedOutputs(changes *plans.Changes) (map[string]output, error) {
|
|
if changes.Outputs == nil {
|
|
// No changes - we're done here!
|
|
return nil, nil
|
|
}
|
|
|
|
ret := 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, err
|
|
}
|
|
|
|
if changeV.After != cty.NilVal {
|
|
if changeV.After.IsWhollyKnown() {
|
|
after, err = ctyjson.Marshal(changeV.After, changeV.After.Type())
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
}
|
|
}
|
|
|
|
ret[oc.Addr.OutputValue.Name] = output{
|
|
Value: json.RawMessage(after),
|
|
Sensitive: oc.Sensitive,
|
|
}
|
|
}
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
func marshalPlannedValues(changes *plans.Changes, schemas *terraform.Schemas) (module, error) {
|
|
var ret module
|
|
|
|
// 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, err := marshalPlanResources(changes, moduleResourceMap[""], schemas)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
ret.Resources = resources
|
|
|
|
childModules, err := marshalPlanModules(changes, schemas, moduleMap[""], moduleMap, moduleResourceMap)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
ret.ChildModules = childModules
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// marshalPlanResources
|
|
func marshalPlanResources(changes *plans.Changes, ris []addrs.AbsResourceInstance, schemas *terraform.Schemas) ([]resource, error) {
|
|
var ret []resource
|
|
|
|
for _, ri := range ris {
|
|
r := changes.ResourceInstance(ri)
|
|
if r.Action == plans.Delete {
|
|
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, 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, fmt.Errorf("no schema found for %s", r.Addr.String())
|
|
}
|
|
resource.SchemaVersion = schemaVer
|
|
changeV, err := r.Decode(schema.ImpliedType())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if changeV.After != cty.NilVal {
|
|
if changeV.After.IsWhollyKnown() {
|
|
resource.AttributeValues = marshalAttributeValues(changeV.After, schema)
|
|
} else {
|
|
knowns := omitUnknowns(changeV.After)
|
|
resource.AttributeValues = marshalAttributeValues(knowns, schema)
|
|
}
|
|
}
|
|
|
|
ret = append(ret, resource)
|
|
}
|
|
|
|
sort.Slice(ret, func(i, j int) bool {
|
|
return ret[i].Address < ret[j].Address
|
|
})
|
|
|
|
return ret, 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
|
|
}
|