2017-01-19 05:49:42 +01:00
|
|
|
package format
|
|
|
|
|
|
|
|
import (
|
command/format: improve consistency of plan results
Previously the rendered plan output was constructed directly from the
core plan and then annotated with counts derived from the count hook.
At various places we applied little adjustments to deal with the fact that
the user-facing diff model is not identical to the internal diff model,
including the special handling of data source reads and destroys. Since
this logic was just muddled into the rendering code, it behaved
inconsistently with the tally of adds, updates and deletes.
This change reworks the plan formatter so that it happens in two stages:
- First, we produce a specialized Plan object that is tailored for use
in the UI. This applies all the relevant logic to transform the
physical model into the user model.
- Second, we do a straightforward visual rendering of the display-oriented
plan object.
For the moment this is slightly overkill since there's only one rendering
path, but it does give us the benefit of letting the counts be derived
from the same data as the full detailed diff, ensuring that they'll stay
consistent.
Later we may choose to have other UIs for plans, such as a
machine-readable output intended to drive a web UI. In that case, we'd
want the web UI to consume a serialization of the _display-oriented_ plan
so that it doesn't need to re-implement all of these UI special cases.
This introduces to core a new diff action type for "refresh". Currently
this is used _only_ in the UI layer, to represent data source reads.
Later it would be good to use this type for the core diff as well, to
improve consistency, but that is left for another day to keep this change
focused on the UI.
2017-08-24 01:23:02 +02:00
|
|
|
"reflect"
|
2017-01-19 05:49:42 +01:00
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
command/format: improve consistency of plan results
Previously the rendered plan output was constructed directly from the
core plan and then annotated with counts derived from the count hook.
At various places we applied little adjustments to deal with the fact that
the user-facing diff model is not identical to the internal diff model,
including the special handling of data source reads and destroys. Since
this logic was just muddled into the rendering code, it behaved
inconsistently with the tally of adds, updates and deletes.
This change reworks the plan formatter so that it happens in two stages:
- First, we produce a specialized Plan object that is tailored for use
in the UI. This applies all the relevant logic to transform the
physical model into the user model.
- Second, we do a straightforward visual rendering of the display-oriented
plan object.
For the moment this is slightly overkill since there's only one rendering
path, but it does give us the benefit of letting the counts be derived
from the same data as the full detailed diff, ensuring that they'll stay
consistent.
Later we may choose to have other UIs for plans, such as a
machine-readable output intended to drive a web UI. In that case, we'd
want the web UI to consume a serialization of the _display-oriented_ plan
so that it doesn't need to re-implement all of these UI special cases.
This introduces to core a new diff action type for "refresh". Currently
this is used _only_ in the UI layer, to represent data source reads.
Later it would be good to use this type for the core diff as well, to
improve consistency, but that is left for another day to keep this change
focused on the UI.
2017-08-24 01:23:02 +02:00
|
|
|
"github.com/davecgh/go-spew/spew"
|
2017-01-19 05:49:42 +01:00
|
|
|
"github.com/hashicorp/terraform/terraform"
|
|
|
|
"github.com/mitchellh/colorstring"
|
|
|
|
)
|
|
|
|
|
command/format: improve consistency of plan results
Previously the rendered plan output was constructed directly from the
core plan and then annotated with counts derived from the count hook.
At various places we applied little adjustments to deal with the fact that
the user-facing diff model is not identical to the internal diff model,
including the special handling of data source reads and destroys. Since
this logic was just muddled into the rendering code, it behaved
inconsistently with the tally of adds, updates and deletes.
This change reworks the plan formatter so that it happens in two stages:
- First, we produce a specialized Plan object that is tailored for use
in the UI. This applies all the relevant logic to transform the
physical model into the user model.
- Second, we do a straightforward visual rendering of the display-oriented
plan object.
For the moment this is slightly overkill since there's only one rendering
path, but it does give us the benefit of letting the counts be derived
from the same data as the full detailed diff, ensuring that they'll stay
consistent.
Later we may choose to have other UIs for plans, such as a
machine-readable output intended to drive a web UI. In that case, we'd
want the web UI to consume a serialization of the _display-oriented_ plan
so that it doesn't need to re-implement all of these UI special cases.
This introduces to core a new diff action type for "refresh". Currently
this is used _only_ in the UI layer, to represent data source reads.
Later it would be good to use this type for the core diff as well, to
improve consistency, but that is left for another day to keep this change
focused on the UI.
2017-08-24 01:23:02 +02:00
|
|
|
var disabledColorize = &colorstring.Colorize{
|
|
|
|
Colors: colorstring.DefaultColors,
|
|
|
|
Disable: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNewPlan(t *testing.T) {
|
|
|
|
tests := map[string]struct {
|
|
|
|
Input *terraform.Plan
|
|
|
|
Want *Plan
|
|
|
|
}{
|
|
|
|
"nil input": {
|
|
|
|
Input: nil,
|
|
|
|
Want: &Plan{
|
|
|
|
Resources: nil,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"nil diff": {
|
|
|
|
Input: &terraform.Plan{},
|
|
|
|
Want: &Plan{
|
|
|
|
Resources: nil,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"empty diff": {
|
|
|
|
Input: &terraform.Plan{
|
|
|
|
Diff: &terraform.Diff{
|
|
|
|
Modules: []*terraform.ModuleDiff{
|
|
|
|
{
|
|
|
|
Path: []string{"root"},
|
|
|
|
Resources: map[string]*terraform.InstanceDiff{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Want: &Plan{
|
|
|
|
Resources: nil,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"create managed resource": {
|
|
|
|
Input: &terraform.Plan{
|
|
|
|
Diff: &terraform.Diff{
|
|
|
|
Modules: []*terraform.ModuleDiff{
|
|
|
|
{
|
|
|
|
Path: []string{"root"},
|
|
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
|
|
"test_resource.foo": {
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"id": {
|
|
|
|
NewComputed: true,
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Want: &Plan{
|
|
|
|
Resources: []*InstanceDiff{
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo"),
|
|
|
|
Action: terraform.DiffCreate,
|
|
|
|
Attributes: []*AttributeDiff{
|
|
|
|
{
|
|
|
|
Path: "id",
|
|
|
|
Action: terraform.DiffCreate,
|
|
|
|
NewComputed: true,
|
|
|
|
ForcesNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"create managed resource in child module": {
|
|
|
|
Input: &terraform.Plan{
|
|
|
|
Diff: &terraform.Diff{
|
|
|
|
Modules: []*terraform.ModuleDiff{
|
|
|
|
{
|
|
|
|
Path: []string{"root"},
|
|
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
|
|
"test_resource.foo": {
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"id": {
|
|
|
|
NewComputed: true,
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Path: []string{"root", "foo"},
|
|
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
|
|
"test_resource.foo": {
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"id": {
|
|
|
|
NewComputed: true,
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Want: &Plan{
|
|
|
|
Resources: []*InstanceDiff{
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo"),
|
|
|
|
Action: terraform.DiffCreate,
|
|
|
|
Attributes: []*AttributeDiff{
|
|
|
|
{
|
|
|
|
Path: "id",
|
|
|
|
Action: terraform.DiffCreate,
|
|
|
|
NewComputed: true,
|
|
|
|
ForcesNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("module.foo.test_resource.foo"),
|
|
|
|
Action: terraform.DiffCreate,
|
|
|
|
Attributes: []*AttributeDiff{
|
|
|
|
{
|
|
|
|
Path: "id",
|
|
|
|
Action: terraform.DiffCreate,
|
|
|
|
NewComputed: true,
|
|
|
|
ForcesNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"create data resource": {
|
|
|
|
Input: &terraform.Plan{
|
|
|
|
Diff: &terraform.Diff{
|
|
|
|
Modules: []*terraform.ModuleDiff{
|
|
|
|
{
|
|
|
|
Path: []string{"root"},
|
|
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
|
|
"data.test_data_source.foo": {
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"id": {
|
|
|
|
NewComputed: true,
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Want: &Plan{
|
|
|
|
Resources: []*InstanceDiff{
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("data.test_data_source.foo"),
|
|
|
|
Action: terraform.DiffRefresh,
|
|
|
|
Attributes: []*AttributeDiff{
|
|
|
|
{
|
|
|
|
Path: "id",
|
|
|
|
Action: terraform.DiffUpdate,
|
|
|
|
NewComputed: true,
|
|
|
|
ForcesNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"destroy managed resource": {
|
|
|
|
Input: &terraform.Plan{
|
|
|
|
Diff: &terraform.Diff{
|
|
|
|
Modules: []*terraform.ModuleDiff{
|
|
|
|
{
|
|
|
|
Path: []string{"root"},
|
|
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
|
|
"test_resource.foo": {
|
|
|
|
Destroy: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Want: &Plan{
|
|
|
|
Resources: []*InstanceDiff{
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo"),
|
|
|
|
Action: terraform.DiffDestroy,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"destroy data resource": {
|
|
|
|
Input: &terraform.Plan{
|
|
|
|
Diff: &terraform.Diff{
|
|
|
|
Modules: []*terraform.ModuleDiff{
|
|
|
|
{
|
|
|
|
Path: []string{"root"},
|
|
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
|
|
"data.test_data_source.foo": {
|
|
|
|
Destroy: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Want: &Plan{
|
|
|
|
// Data source destroys are not shown
|
|
|
|
Resources: nil,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"destroy many instances of a resource": {
|
|
|
|
Input: &terraform.Plan{
|
|
|
|
Diff: &terraform.Diff{
|
|
|
|
Modules: []*terraform.ModuleDiff{
|
|
|
|
{
|
|
|
|
Path: []string{"root"},
|
|
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
|
|
"test_resource.foo.0": {
|
|
|
|
Destroy: true,
|
|
|
|
},
|
|
|
|
"test_resource.foo.1": {
|
|
|
|
Destroy: true,
|
|
|
|
},
|
|
|
|
"test_resource.foo.10": {
|
|
|
|
Destroy: true,
|
|
|
|
},
|
|
|
|
"test_resource.foo.2": {
|
|
|
|
Destroy: true,
|
|
|
|
},
|
|
|
|
"test_resource.foo.3": {
|
|
|
|
Destroy: true,
|
|
|
|
},
|
|
|
|
"test_resource.foo.4": {
|
|
|
|
Destroy: true,
|
|
|
|
},
|
|
|
|
"test_resource.foo.5": {
|
|
|
|
Destroy: true,
|
|
|
|
},
|
|
|
|
"test_resource.foo.6": {
|
|
|
|
Destroy: true,
|
|
|
|
},
|
|
|
|
"test_resource.foo.7": {
|
|
|
|
Destroy: true,
|
|
|
|
},
|
|
|
|
"test_resource.foo.8": {
|
|
|
|
Destroy: true,
|
|
|
|
},
|
|
|
|
"test_resource.foo.9": {
|
|
|
|
Destroy: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Want: &Plan{
|
|
|
|
Resources: []*InstanceDiff{
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo[0]"),
|
|
|
|
Action: terraform.DiffDestroy,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo[1]"),
|
|
|
|
Action: terraform.DiffDestroy,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo[2]"),
|
|
|
|
Action: terraform.DiffDestroy,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo[3]"),
|
|
|
|
Action: terraform.DiffDestroy,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo[4]"),
|
|
|
|
Action: terraform.DiffDestroy,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo[5]"),
|
|
|
|
Action: terraform.DiffDestroy,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo[6]"),
|
|
|
|
Action: terraform.DiffDestroy,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo[7]"),
|
|
|
|
Action: terraform.DiffDestroy,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo[8]"),
|
|
|
|
Action: terraform.DiffDestroy,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo[9]"),
|
|
|
|
Action: terraform.DiffDestroy,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo[10]"),
|
|
|
|
Action: terraform.DiffDestroy,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, test := range tests {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
got := NewPlan(test.Input)
|
|
|
|
if !reflect.DeepEqual(got, test.Want) {
|
|
|
|
t.Errorf(
|
|
|
|
"wrong result\ninput: %sgot: %swant:%s",
|
|
|
|
spew.Sdump(test.Input),
|
|
|
|
spew.Sdump(got),
|
|
|
|
spew.Sdump(test.Want),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPlanStats(t *testing.T) {
|
|
|
|
tests := map[string]struct {
|
|
|
|
Input *Plan
|
|
|
|
Want PlanStats
|
|
|
|
}{
|
|
|
|
"empty": {
|
|
|
|
&Plan{},
|
|
|
|
PlanStats{},
|
|
|
|
},
|
|
|
|
"destroy": {
|
|
|
|
&Plan{
|
|
|
|
Resources: []*InstanceDiff{
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo"),
|
|
|
|
Action: terraform.DiffDestroy,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.bar"),
|
|
|
|
Action: terraform.DiffDestroy,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
PlanStats{
|
|
|
|
ToDestroy: 2,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"create": {
|
|
|
|
&Plan{
|
|
|
|
Resources: []*InstanceDiff{
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo"),
|
|
|
|
Action: terraform.DiffCreate,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.bar"),
|
|
|
|
Action: terraform.DiffCreate,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
PlanStats{
|
|
|
|
ToAdd: 2,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"update": {
|
|
|
|
&Plan{
|
|
|
|
Resources: []*InstanceDiff{
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo"),
|
|
|
|
Action: terraform.DiffUpdate,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.bar"),
|
|
|
|
Action: terraform.DiffUpdate,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
PlanStats{
|
|
|
|
ToChange: 2,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"data source refresh": {
|
|
|
|
&Plan{
|
|
|
|
Resources: []*InstanceDiff{
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("data.test.foo"),
|
|
|
|
Action: terraform.DiffRefresh,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
PlanStats{
|
|
|
|
// data resource refreshes are not counted in our stats
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"replace": {
|
|
|
|
&Plan{
|
|
|
|
Resources: []*InstanceDiff{
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.foo"),
|
|
|
|
Action: terraform.DiffDestroyCreate,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: mustParseResourceAddress("test_resource.bar"),
|
|
|
|
Action: terraform.DiffDestroyCreate,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
PlanStats{
|
|
|
|
ToDestroy: 2,
|
|
|
|
ToAdd: 2,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, test := range tests {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
got := test.Input.Stats()
|
|
|
|
if !reflect.DeepEqual(got, test.Want) {
|
|
|
|
t.Errorf(
|
|
|
|
"wrong result\ninput: %sgot: %swant:%s",
|
|
|
|
spew.Sdump(test.Input),
|
|
|
|
spew.Sdump(got),
|
|
|
|
spew.Sdump(test.Want),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test that deposed instances are marked as such
|
2017-01-19 05:49:42 +01:00
|
|
|
func TestPlan_destroyDeposed(t *testing.T) {
|
|
|
|
plan := &terraform.Plan{
|
|
|
|
Diff: &terraform.Diff{
|
|
|
|
Modules: []*terraform.ModuleDiff{
|
|
|
|
&terraform.ModuleDiff{
|
|
|
|
Path: []string{"root"},
|
|
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
|
|
"aws_instance.foo": &terraform.InstanceDiff{
|
|
|
|
DestroyDeposed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
command/format: improve consistency of plan results
Previously the rendered plan output was constructed directly from the
core plan and then annotated with counts derived from the count hook.
At various places we applied little adjustments to deal with the fact that
the user-facing diff model is not identical to the internal diff model,
including the special handling of data source reads and destroys. Since
this logic was just muddled into the rendering code, it behaved
inconsistently with the tally of adds, updates and deletes.
This change reworks the plan formatter so that it happens in two stages:
- First, we produce a specialized Plan object that is tailored for use
in the UI. This applies all the relevant logic to transform the
physical model into the user model.
- Second, we do a straightforward visual rendering of the display-oriented
plan object.
For the moment this is slightly overkill since there's only one rendering
path, but it does give us the benefit of letting the counts be derived
from the same data as the full detailed diff, ensuring that they'll stay
consistent.
Later we may choose to have other UIs for plans, such as a
machine-readable output intended to drive a web UI. In that case, we'd
want the web UI to consume a serialization of the _display-oriented_ plan
so that it doesn't need to re-implement all of these UI special cases.
This introduces to core a new diff action type for "refresh". Currently
this is used _only_ in the UI layer, to represent data source reads.
Later it would be good to use this type for the core diff as well, to
improve consistency, but that is left for another day to keep this change
focused on the UI.
2017-08-24 01:23:02 +02:00
|
|
|
dispPlan := NewPlan(plan)
|
|
|
|
actual := dispPlan.Format(disabledColorize)
|
2017-01-19 05:49:42 +01:00
|
|
|
|
|
|
|
expected := strings.TrimSpace(`
|
|
|
|
- aws_instance.foo (deposed)
|
|
|
|
`)
|
|
|
|
if actual != expected {
|
|
|
|
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test that computed fields with an interpolation string get displayed
|
|
|
|
func TestPlan_displayInterpolations(t *testing.T) {
|
|
|
|
plan := &terraform.Plan{
|
|
|
|
Diff: &terraform.Diff{
|
|
|
|
Modules: []*terraform.ModuleDiff{
|
|
|
|
&terraform.ModuleDiff{
|
|
|
|
Path: []string{"root"},
|
|
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
|
|
"aws_instance.foo": &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"computed_field": &terraform.ResourceAttrDiff{
|
|
|
|
New: "${aws_instance.other.id}",
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
command/format: improve consistency of plan results
Previously the rendered plan output was constructed directly from the
core plan and then annotated with counts derived from the count hook.
At various places we applied little adjustments to deal with the fact that
the user-facing diff model is not identical to the internal diff model,
including the special handling of data source reads and destroys. Since
this logic was just muddled into the rendering code, it behaved
inconsistently with the tally of adds, updates and deletes.
This change reworks the plan formatter so that it happens in two stages:
- First, we produce a specialized Plan object that is tailored for use
in the UI. This applies all the relevant logic to transform the
physical model into the user model.
- Second, we do a straightforward visual rendering of the display-oriented
plan object.
For the moment this is slightly overkill since there's only one rendering
path, but it does give us the benefit of letting the counts be derived
from the same data as the full detailed diff, ensuring that they'll stay
consistent.
Later we may choose to have other UIs for plans, such as a
machine-readable output intended to drive a web UI. In that case, we'd
want the web UI to consume a serialization of the _display-oriented_ plan
so that it doesn't need to re-implement all of these UI special cases.
This introduces to core a new diff action type for "refresh". Currently
this is used _only_ in the UI layer, to represent data source reads.
Later it would be good to use this type for the core diff as well, to
improve consistency, but that is left for another day to keep this change
focused on the UI.
2017-08-24 01:23:02 +02:00
|
|
|
dispPlan := NewPlan(plan)
|
|
|
|
out := dispPlan.Format(disabledColorize)
|
2017-01-19 05:49:42 +01:00
|
|
|
lines := strings.Split(out, "\n")
|
|
|
|
if len(lines) != 2 {
|
|
|
|
t.Fatal("expected 2 lines of output, got:\n", out)
|
|
|
|
}
|
|
|
|
|
|
|
|
actual := strings.TrimSpace(lines[1])
|
|
|
|
expected := `computed_field: "" => "${aws_instance.other.id}"`
|
|
|
|
|
|
|
|
if actual != expected {
|
|
|
|
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test that a root level data source gets a special plan output on create
|
|
|
|
func TestPlan_rootDataSource(t *testing.T) {
|
|
|
|
plan := &terraform.Plan{
|
|
|
|
Diff: &terraform.Diff{
|
|
|
|
Modules: []*terraform.ModuleDiff{
|
|
|
|
&terraform.ModuleDiff{
|
|
|
|
Path: []string{"root"},
|
|
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
|
|
"data.type.name": &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"A": &terraform.ResourceAttrDiff{
|
|
|
|
New: "B",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
command/format: improve consistency of plan results
Previously the rendered plan output was constructed directly from the
core plan and then annotated with counts derived from the count hook.
At various places we applied little adjustments to deal with the fact that
the user-facing diff model is not identical to the internal diff model,
including the special handling of data source reads and destroys. Since
this logic was just muddled into the rendering code, it behaved
inconsistently with the tally of adds, updates and deletes.
This change reworks the plan formatter so that it happens in two stages:
- First, we produce a specialized Plan object that is tailored for use
in the UI. This applies all the relevant logic to transform the
physical model into the user model.
- Second, we do a straightforward visual rendering of the display-oriented
plan object.
For the moment this is slightly overkill since there's only one rendering
path, but it does give us the benefit of letting the counts be derived
from the same data as the full detailed diff, ensuring that they'll stay
consistent.
Later we may choose to have other UIs for plans, such as a
machine-readable output intended to drive a web UI. In that case, we'd
want the web UI to consume a serialization of the _display-oriented_ plan
so that it doesn't need to re-implement all of these UI special cases.
This introduces to core a new diff action type for "refresh". Currently
this is used _only_ in the UI layer, to represent data source reads.
Later it would be good to use this type for the core diff as well, to
improve consistency, but that is left for another day to keep this change
focused on the UI.
2017-08-24 01:23:02 +02:00
|
|
|
dispPlan := NewPlan(plan)
|
|
|
|
actual := dispPlan.Format(disabledColorize)
|
2017-01-19 05:49:42 +01:00
|
|
|
|
|
|
|
expected := strings.TrimSpace(`
|
|
|
|
<= data.type.name
|
2017-06-22 03:38:33 +02:00
|
|
|
A: "B"
|
2017-01-19 05:49:42 +01:00
|
|
|
`)
|
|
|
|
if actual != expected {
|
|
|
|
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test that data sources nested in modules get the same plan output
|
|
|
|
func TestPlan_nestedDataSource(t *testing.T) {
|
|
|
|
plan := &terraform.Plan{
|
|
|
|
Diff: &terraform.Diff{
|
|
|
|
Modules: []*terraform.ModuleDiff{
|
|
|
|
&terraform.ModuleDiff{
|
|
|
|
Path: []string{"root", "nested"},
|
|
|
|
Resources: map[string]*terraform.InstanceDiff{
|
|
|
|
"data.type.name": &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"A": &terraform.ResourceAttrDiff{
|
|
|
|
New: "B",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
command/format: improve consistency of plan results
Previously the rendered plan output was constructed directly from the
core plan and then annotated with counts derived from the count hook.
At various places we applied little adjustments to deal with the fact that
the user-facing diff model is not identical to the internal diff model,
including the special handling of data source reads and destroys. Since
this logic was just muddled into the rendering code, it behaved
inconsistently with the tally of adds, updates and deletes.
This change reworks the plan formatter so that it happens in two stages:
- First, we produce a specialized Plan object that is tailored for use
in the UI. This applies all the relevant logic to transform the
physical model into the user model.
- Second, we do a straightforward visual rendering of the display-oriented
plan object.
For the moment this is slightly overkill since there's only one rendering
path, but it does give us the benefit of letting the counts be derived
from the same data as the full detailed diff, ensuring that they'll stay
consistent.
Later we may choose to have other UIs for plans, such as a
machine-readable output intended to drive a web UI. In that case, we'd
want the web UI to consume a serialization of the _display-oriented_ plan
so that it doesn't need to re-implement all of these UI special cases.
This introduces to core a new diff action type for "refresh". Currently
this is used _only_ in the UI layer, to represent data source reads.
Later it would be good to use this type for the core diff as well, to
improve consistency, but that is left for another day to keep this change
focused on the UI.
2017-08-24 01:23:02 +02:00
|
|
|
dispPlan := NewPlan(plan)
|
|
|
|
actual := dispPlan.Format(disabledColorize)
|
2017-01-19 05:49:42 +01:00
|
|
|
|
|
|
|
expected := strings.TrimSpace(`
|
|
|
|
<= module.nested.data.type.name
|
2017-06-22 03:38:33 +02:00
|
|
|
A: "B"
|
2017-01-19 05:49:42 +01:00
|
|
|
`)
|
|
|
|
if actual != expected {
|
|
|
|
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
|
|
|
}
|
|
|
|
}
|
command/format: improve consistency of plan results
Previously the rendered plan output was constructed directly from the
core plan and then annotated with counts derived from the count hook.
At various places we applied little adjustments to deal with the fact that
the user-facing diff model is not identical to the internal diff model,
including the special handling of data source reads and destroys. Since
this logic was just muddled into the rendering code, it behaved
inconsistently with the tally of adds, updates and deletes.
This change reworks the plan formatter so that it happens in two stages:
- First, we produce a specialized Plan object that is tailored for use
in the UI. This applies all the relevant logic to transform the
physical model into the user model.
- Second, we do a straightforward visual rendering of the display-oriented
plan object.
For the moment this is slightly overkill since there's only one rendering
path, but it does give us the benefit of letting the counts be derived
from the same data as the full detailed diff, ensuring that they'll stay
consistent.
Later we may choose to have other UIs for plans, such as a
machine-readable output intended to drive a web UI. In that case, we'd
want the web UI to consume a serialization of the _display-oriented_ plan
so that it doesn't need to re-implement all of these UI special cases.
This introduces to core a new diff action type for "refresh". Currently
this is used _only_ in the UI layer, to represent data source reads.
Later it would be good to use this type for the core diff as well, to
improve consistency, but that is left for another day to keep this change
focused on the UI.
2017-08-24 01:23:02 +02:00
|
|
|
|
|
|
|
func mustParseResourceAddress(s string) *terraform.ResourceAddress {
|
|
|
|
addr, err := terraform.ParseResourceAddress(s)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return addr
|
|
|
|
}
|