command/format: concise diff is now the default (#27079)
* command/format: concise diff is no longer an experiment Since state formatting goes through the "diff" printer, I have repurposed the concise flag as a verbose flag, used only when printing state. It's silly but it works! * remove helper/experiment With this experiment concluded, we no longer need helper/experiment. The shadow experiment had not been touched in many years, so I removed all references, and removed the package entirely. Any new experiments are expected to be configuration experiments handled by our (other) experiments package. * check for the verbose flag consistently, in case we end up using it in plans in the future
This commit is contained in:
parent
b2d0e20759
commit
3fa063b8dc
|
@ -14,7 +14,6 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/helper/experiment"
|
|
||||||
"github.com/hashicorp/terraform/plans"
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/hashicorp/terraform/plans/objchange"
|
"github.com/hashicorp/terraform/plans/objchange"
|
||||||
"github.com/hashicorp/terraform/states"
|
"github.com/hashicorp/terraform/states"
|
||||||
|
@ -99,7 +98,6 @@ func ResourceChange(
|
||||||
color: color,
|
color: color,
|
||||||
action: change.Action,
|
action: change.Action,
|
||||||
requiredReplace: change.RequiredReplace,
|
requiredReplace: change.RequiredReplace,
|
||||||
concise: experiment.Enabled(experiment.X_concise_diff),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Most commonly-used resources have nested blocks that result in us
|
// Most commonly-used resources have nested blocks that result in us
|
||||||
|
@ -154,10 +152,9 @@ func OutputChanges(
|
||||||
) string {
|
) string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
p := blockBodyDiffPrinter{
|
p := blockBodyDiffPrinter{
|
||||||
buf: &buf,
|
buf: &buf,
|
||||||
color: color,
|
color: color,
|
||||||
action: plans.Update, // not actually used in this case, because we're not printing a containing block
|
action: plans.Update, // not actually used in this case, because we're not printing a containing block
|
||||||
concise: experiment.Enabled(experiment.X_concise_diff),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're going to reuse the codepath we used for printing resource block
|
// We're going to reuse the codepath we used for printing resource block
|
||||||
|
@ -200,7 +197,8 @@ type blockBodyDiffPrinter struct {
|
||||||
color *colorstring.Colorize
|
color *colorstring.Colorize
|
||||||
action plans.Action
|
action plans.Action
|
||||||
requiredReplace cty.PathSet
|
requiredReplace cty.PathSet
|
||||||
concise bool
|
// verbose is set to true when using the "diff" printer to format state
|
||||||
|
verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type blockBodyDiffResult struct {
|
type blockBodyDiffResult struct {
|
||||||
|
@ -326,7 +324,7 @@ func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.At
|
||||||
path = append(path, cty.GetAttrStep{Name: name})
|
path = append(path, cty.GetAttrStep{Name: name})
|
||||||
action, showJustNew := getPlanActionAndShow(old, new)
|
action, showJustNew := getPlanActionAndShow(old, new)
|
||||||
|
|
||||||
if action == plans.NoOp && p.concise && !identifyingAttribute(name, attrS) {
|
if action == plans.NoOp && !p.verbose && !identifyingAttribute(name, attrS) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -624,7 +622,7 @@ func (p *blockBodyDiffPrinter) writeSensitiveNestedBlockDiff(name string, old, n
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *blockBodyDiffPrinter) writeNestedBlockDiff(name string, label *string, blockS *configschema.Block, action plans.Action, old, new cty.Value, indent int, path cty.Path) bool {
|
func (p *blockBodyDiffPrinter) writeNestedBlockDiff(name string, label *string, blockS *configschema.Block, action plans.Action, old, new cty.Value, indent int, path cty.Path) bool {
|
||||||
if action == plans.NoOp && p.concise {
|
if action == plans.NoOp && !p.verbose {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -984,7 +982,7 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
||||||
action = plans.NoOp
|
action = plans.NoOp
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == plans.NoOp && p.concise {
|
if action == plans.NoOp && !p.verbose {
|
||||||
suppressedElements++
|
suppressedElements++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -1024,8 +1022,7 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
||||||
var changeShown bool
|
var changeShown bool
|
||||||
|
|
||||||
for i := 0; i < len(elemDiffs); i++ {
|
for i := 0; i < len(elemDiffs); i++ {
|
||||||
// In concise mode, push any no-op diff elements onto the stack
|
if !p.verbose {
|
||||||
if p.concise {
|
|
||||||
for i < len(elemDiffs) && elemDiffs[i].Action == plans.NoOp {
|
for i < len(elemDiffs) && elemDiffs[i].Action == plans.NoOp {
|
||||||
suppressedElements = append(suppressedElements, elemDiffs[i])
|
suppressedElements = append(suppressedElements, elemDiffs[i])
|
||||||
i++
|
i++
|
||||||
|
@ -1161,7 +1158,7 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == plans.NoOp && p.concise {
|
if action == plans.NoOp && !p.verbose {
|
||||||
suppressedElements++
|
suppressedElements++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -1262,7 +1259,7 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
||||||
action = plans.Update
|
action = plans.Update
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == plans.NoOp && p.concise {
|
if action == plans.NoOp && !p.verbose {
|
||||||
suppressedElements++
|
suppressedElements++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/helper/experiment"
|
|
||||||
"github.com/hashicorp/terraform/plans"
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/mitchellh/colorstring"
|
"github.com/mitchellh/colorstring"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
@ -362,13 +361,6 @@ new line
|
||||||
~ str = "before" -> "after"
|
~ str = "before" -> "after"
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
~ id = "blah" -> (known after apply)
|
|
||||||
password = (sensitive value)
|
|
||||||
~ str = "before" -> "after"
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -502,18 +494,6 @@ new line
|
||||||
}
|
}
|
||||||
# (2 unchanged attributes hidden)
|
# (2 unchanged attributes hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
||||||
bar = "bar"
|
|
||||||
foo = "foo"
|
|
||||||
id = "i-02ae66f368e8518a9"
|
|
||||||
name = "alice"
|
|
||||||
tags = {
|
|
||||||
"name" = "bob"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -594,19 +574,6 @@ func TestResourceChange_JSON(t *testing.T) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ json_field = jsonencode(
|
|
||||||
~ {
|
|
||||||
aaa = "value"
|
|
||||||
+ bbb = "new_value"
|
|
||||||
- ccc = 5 -> null
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
},
|
},
|
||||||
"in-place update (from empty tuple)": {
|
"in-place update (from empty tuple)": {
|
||||||
Action: plans.Update,
|
Action: plans.Update,
|
||||||
|
@ -739,17 +706,6 @@ func TestResourceChange_JSON(t *testing.T) {
|
||||||
} # forces replacement
|
} # forces replacement
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example must be replaced
|
|
||||||
-/+ resource "test_instance" "example" {
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ json_field = jsonencode(
|
|
||||||
~ {
|
|
||||||
aaa = "value"
|
|
||||||
+ bbb = "new_value"
|
|
||||||
} # forces replacement
|
|
||||||
)
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update (whitespace change)": {
|
"in-place update (whitespace change)": {
|
||||||
|
@ -871,18 +827,6 @@ func TestResourceChange_JSON(t *testing.T) {
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ json_field = jsonencode(
|
|
||||||
~ [
|
|
||||||
"first",
|
|
||||||
"second",
|
|
||||||
- "third",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"JSON list item addition": {
|
"JSON list item addition": {
|
||||||
|
@ -916,19 +860,6 @@ func TestResourceChange_JSON(t *testing.T) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ json_field = jsonencode(
|
|
||||||
~ [
|
|
||||||
"first",
|
|
||||||
"second",
|
|
||||||
+ "third",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
},
|
},
|
||||||
"JSON list object addition": {
|
"JSON list object addition": {
|
||||||
Action: plans.Update,
|
Action: plans.Update,
|
||||||
|
@ -1223,15 +1154,6 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
+ list_field = [
|
|
||||||
+ "new-element",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update - first addition": {
|
"in-place update - first addition": {
|
||||||
|
@ -1266,15 +1188,6 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ list_field = [
|
|
||||||
+ "new-element",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update - insertion": {
|
"in-place update - insertion": {
|
||||||
|
@ -1324,20 +1237,6 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ list_field = [
|
|
||||||
"aaaa",
|
|
||||||
"bbbb",
|
|
||||||
+ "cccc",
|
|
||||||
"dddd",
|
|
||||||
"eeee",
|
|
||||||
"ffff",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"force-new update - insertion": {
|
"force-new update - insertion": {
|
||||||
|
@ -1381,17 +1280,6 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example must be replaced
|
|
||||||
-/+ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ list_field = [ # forces replacement
|
|
||||||
"aaaa",
|
|
||||||
+ "bbbb",
|
|
||||||
"cccc",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update - deletion": {
|
"in-place update - deletion": {
|
||||||
|
@ -1438,19 +1326,6 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ list_field = [
|
|
||||||
- "aaaa",
|
|
||||||
"bbbb",
|
|
||||||
- "cccc",
|
|
||||||
"dddd",
|
|
||||||
"eeee",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"creation - empty list": {
|
"creation - empty list": {
|
||||||
|
@ -1515,17 +1390,6 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ list_field = [
|
|
||||||
- "aaaa",
|
|
||||||
- "bbbb",
|
|
||||||
- "cccc",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update - null to empty": {
|
"in-place update - null to empty": {
|
||||||
|
@ -1556,13 +1420,6 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
||||||
+ list_field = []
|
+ list_field = []
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
+ list_field = []
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"update to unknown element": {
|
"update to unknown element": {
|
||||||
|
@ -1606,18 +1463,6 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ list_field = [
|
|
||||||
"aaaa",
|
|
||||||
- "bbbb",
|
|
||||||
+ (known after apply),
|
|
||||||
"cccc",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"update - two new unknown elements": {
|
"update - two new unknown elements": {
|
||||||
|
@ -1668,21 +1513,6 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ list_field = [
|
|
||||||
"aaaa",
|
|
||||||
- "bbbb",
|
|
||||||
+ (known after apply),
|
|
||||||
+ (known after apply),
|
|
||||||
"cccc",
|
|
||||||
"dddd",
|
|
||||||
"eeee",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1734,19 +1564,6 @@ func TestResourceChange_primitiveTuple(t *testing.T) {
|
||||||
# (1 unchanged element hidden)
|
# (1 unchanged element hidden)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
id = "i-02ae66f368e8518a9"
|
|
||||||
~ tuple_field = [
|
|
||||||
"aaaa",
|
|
||||||
"bbbb",
|
|
||||||
- "dddd",
|
|
||||||
+ "cccc",
|
|
||||||
"eeee",
|
|
||||||
"ffff",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1787,15 +1604,6 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
+ set_field = [
|
|
||||||
+ "new-element",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update - first insertion": {
|
"in-place update - first insertion": {
|
||||||
|
@ -1830,15 +1638,6 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ set_field = [
|
|
||||||
+ "new-element",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update - insertion": {
|
"in-place update - insertion": {
|
||||||
|
@ -1879,17 +1678,6 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ set_field = [
|
|
||||||
"aaaa",
|
|
||||||
+ "bbbb",
|
|
||||||
"cccc",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"force-new update - insertion": {
|
"force-new update - insertion": {
|
||||||
|
@ -1932,17 +1720,6 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example must be replaced
|
|
||||||
-/+ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ set_field = [ # forces replacement
|
|
||||||
"aaaa",
|
|
||||||
+ "bbbb",
|
|
||||||
"cccc",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update - deletion": {
|
"in-place update - deletion": {
|
||||||
|
@ -1983,17 +1760,6 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ set_field = [
|
|
||||||
- "aaaa",
|
|
||||||
"bbbb",
|
|
||||||
- "cccc",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"creation - empty set": {
|
"creation - empty set": {
|
||||||
|
@ -2055,16 +1821,6 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ set_field = [
|
|
||||||
- "aaaa",
|
|
||||||
- "bbbb",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update - null to empty set": {
|
"in-place update - null to empty set": {
|
||||||
|
@ -2095,13 +1851,6 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
||||||
+ set_field = []
|
+ set_field = []
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
+ set_field = []
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update to unknown": {
|
"in-place update to unknown": {
|
||||||
|
@ -2138,16 +1887,6 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
||||||
] -> (known after apply)
|
] -> (known after apply)
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ set_field = [
|
|
||||||
- "aaaa",
|
|
||||||
- "bbbb",
|
|
||||||
] -> (known after apply)
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update to unknown element": {
|
"in-place update to unknown element": {
|
||||||
|
@ -2188,17 +1927,6 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
||||||
]
|
]
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ set_field = [
|
|
||||||
"aaaa",
|
|
||||||
- "bbbb",
|
|
||||||
~ (known after apply),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -2239,15 +1967,6 @@ func TestResourceChange_map(t *testing.T) {
|
||||||
}
|
}
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
+ map_field = {
|
|
||||||
+ "new-key" = "new-element"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update - first insertion": {
|
"in-place update - first insertion": {
|
||||||
|
@ -2282,15 +2001,6 @@ func TestResourceChange_map(t *testing.T) {
|
||||||
}
|
}
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ map_field = {
|
|
||||||
+ "new-key" = "new-element"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update - insertion": {
|
"in-place update - insertion": {
|
||||||
|
@ -2331,17 +2041,6 @@ func TestResourceChange_map(t *testing.T) {
|
||||||
}
|
}
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ map_field = {
|
|
||||||
"a" = "aaaa"
|
|
||||||
+ "b" = "bbbb"
|
|
||||||
"c" = "cccc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"force-new update - insertion": {
|
"force-new update - insertion": {
|
||||||
|
@ -2384,17 +2083,6 @@ func TestResourceChange_map(t *testing.T) {
|
||||||
}
|
}
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example must be replaced
|
|
||||||
-/+ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ map_field = { # forces replacement
|
|
||||||
"a" = "aaaa"
|
|
||||||
+ "b" = "bbbb"
|
|
||||||
"c" = "cccc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update - deletion": {
|
"in-place update - deletion": {
|
||||||
|
@ -2435,17 +2123,6 @@ func TestResourceChange_map(t *testing.T) {
|
||||||
}
|
}
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ map_field = {
|
|
||||||
- "a" = "aaaa" -> null
|
|
||||||
"b" = "bbbb"
|
|
||||||
- "c" = "cccc" -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"creation - empty": {
|
"creation - empty": {
|
||||||
|
@ -2513,17 +2190,6 @@ func TestResourceChange_map(t *testing.T) {
|
||||||
}
|
}
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
ami = "ami-STATIC"
|
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
|
||||||
~ map_field = {
|
|
||||||
"a" = "aaaa"
|
|
||||||
~ "b" = "bbbb" -> (known after apply)
|
|
||||||
"c" = "cccc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -2582,16 +2248,6 @@ func TestResourceChange_nestedList(t *testing.T) {
|
||||||
|
|
||||||
# (1 unchanged block hidden)
|
# (1 unchanged block hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
||||||
id = "i-02ae66f368e8518a9"
|
|
||||||
|
|
||||||
root_block_device {
|
|
||||||
volume_type = "gp2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update - creation": {
|
"in-place update - creation": {
|
||||||
|
@ -2756,17 +2412,6 @@ func TestResourceChange_nestedList(t *testing.T) {
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
||||||
id = "i-02ae66f368e8518a9"
|
|
||||||
|
|
||||||
~ root_block_device {
|
|
||||||
+ new_field = "new_value"
|
|
||||||
volume_type = "gp2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"force-new update (inside block)": {
|
"force-new update (inside block)": {
|
||||||
|
@ -3344,17 +2989,6 @@ func TestResourceChange_nestedMap(t *testing.T) {
|
||||||
# (1 unchanged attribute hidden)
|
# (1 unchanged attribute hidden)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
||||||
id = "i-02ae66f368e8518a9"
|
|
||||||
|
|
||||||
~ root_block_device "a" {
|
|
||||||
+ new_field = "new_value"
|
|
||||||
volume_type = "gp2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update - insertion": {
|
"in-place update - insertion": {
|
||||||
|
@ -3422,20 +3056,6 @@ func TestResourceChange_nestedMap(t *testing.T) {
|
||||||
}
|
}
|
||||||
# (1 unchanged block hidden)
|
# (1 unchanged block hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example will be updated in-place
|
|
||||||
~ resource "test_instance" "example" {
|
|
||||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
||||||
id = "i-02ae66f368e8518a9"
|
|
||||||
|
|
||||||
root_block_device "a" {
|
|
||||||
volume_type = "gp2"
|
|
||||||
}
|
|
||||||
+ root_block_device "b" {
|
|
||||||
+ new_field = "new_value"
|
|
||||||
+ volume_type = "gp2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"force-new update (whole block)": {
|
"force-new update (whole block)": {
|
||||||
|
@ -3500,19 +3120,6 @@ func TestResourceChange_nestedMap(t *testing.T) {
|
||||||
}
|
}
|
||||||
# (1 unchanged block hidden)
|
# (1 unchanged block hidden)
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
VerboseOutput: ` # test_instance.example must be replaced
|
|
||||||
-/+ resource "test_instance" "example" {
|
|
||||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
|
||||||
id = "i-02ae66f368e8518a9"
|
|
||||||
|
|
||||||
~ root_block_device "a" { # forces replacement
|
|
||||||
~ volume_type = "gp2" -> "different"
|
|
||||||
}
|
|
||||||
root_block_device "b" {
|
|
||||||
volume_type = "standard"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"in-place update - deletion": {
|
"in-place update - deletion": {
|
||||||
|
@ -4512,10 +4119,6 @@ type testCase struct {
|
||||||
RequiredReplace cty.PathSet
|
RequiredReplace cty.PathSet
|
||||||
Tainted bool
|
Tainted bool
|
||||||
ExpectedOutput string
|
ExpectedOutput string
|
||||||
|
|
||||||
// This field and all associated values can be removed if the concise diff
|
|
||||||
// experiment succeeds.
|
|
||||||
VerboseOutput string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTestCases(t *testing.T, testCases map[string]testCase) {
|
func runTestCases(t *testing.T, testCases map[string]testCase) {
|
||||||
|
@ -4569,25 +4172,11 @@ func runTestCases(t *testing.T, testCases map[string]testCase) {
|
||||||
RequiredReplace: tc.RequiredReplace,
|
RequiredReplace: tc.RequiredReplace,
|
||||||
}
|
}
|
||||||
|
|
||||||
experiment.SetEnabled(experiment.X_concise_diff, true)
|
|
||||||
output := ResourceChange(change, tc.Tainted, tc.Schema, color)
|
output := ResourceChange(change, tc.Tainted, tc.Schema, color)
|
||||||
if output != tc.ExpectedOutput {
|
if output != tc.ExpectedOutput {
|
||||||
t.Errorf("Unexpected diff.\ngot:\n%s\nwant:\n%s\n", output, tc.ExpectedOutput)
|
t.Errorf("Unexpected diff.\ngot:\n%s\nwant:\n%s\n", output, tc.ExpectedOutput)
|
||||||
t.Errorf("%s", cmp.Diff(output, tc.ExpectedOutput))
|
t.Errorf("%s", cmp.Diff(output, tc.ExpectedOutput))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporary coverage for verbose diff behaviour. All lines below
|
|
||||||
// in this function can be removed if the concise diff experiment
|
|
||||||
// succeeds.
|
|
||||||
if tc.VerboseOutput == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
experiment.SetEnabled(experiment.X_concise_diff, false)
|
|
||||||
output = ResourceChange(change, tc.Tainted, tc.Schema, color)
|
|
||||||
if output != tc.VerboseOutput {
|
|
||||||
t.Errorf("Unexpected diff.\ngot:\n%s\nwant:\n%s\n", output, tc.VerboseOutput)
|
|
||||||
t.Errorf("%s", cmp.Diff(output, tc.VerboseOutput))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4694,7 +4283,6 @@ func TestOutputChanges(t *testing.T) {
|
||||||
|
|
||||||
for name, tc := range testCases {
|
for name, tc := range testCases {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
experiment.SetEnabled(experiment.X_concise_diff, true)
|
|
||||||
output := OutputChanges(tc.changes, color)
|
output := OutputChanges(tc.changes, color)
|
||||||
if output != tc.output {
|
if output != tc.output {
|
||||||
t.Errorf("Unexpected diff.\ngot:\n%s\nwant:\n%s\n", output, tc.output)
|
t.Errorf("Unexpected diff.\ngot:\n%s\nwant:\n%s\n", output, tc.output)
|
||||||
|
|
|
@ -45,9 +45,10 @@ func State(opts *StateOpts) string {
|
||||||
|
|
||||||
buf := bytes.NewBufferString("[reset]")
|
buf := bytes.NewBufferString("[reset]")
|
||||||
p := blockBodyDiffPrinter{
|
p := blockBodyDiffPrinter{
|
||||||
buf: buf,
|
buf: buf,
|
||||||
color: opts.Color,
|
color: opts.Color,
|
||||||
action: plans.NoOp,
|
action: plans.NoOp,
|
||||||
|
verbose: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format all the modules
|
// Format all the modules
|
||||||
|
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"github.com/hashicorp/terraform/command/format"
|
"github.com/hashicorp/terraform/command/format"
|
||||||
"github.com/hashicorp/terraform/command/webbrowser"
|
"github.com/hashicorp/terraform/command/webbrowser"
|
||||||
"github.com/hashicorp/terraform/configs/configload"
|
"github.com/hashicorp/terraform/configs/configload"
|
||||||
"github.com/hashicorp/terraform/helper/experiment"
|
|
||||||
"github.com/hashicorp/terraform/helper/wrappedstreams"
|
"github.com/hashicorp/terraform/helper/wrappedstreams"
|
||||||
"github.com/hashicorp/terraform/internal/getproviders"
|
"github.com/hashicorp/terraform/internal/getproviders"
|
||||||
"github.com/hashicorp/terraform/providers"
|
"github.com/hashicorp/terraform/providers"
|
||||||
|
@ -500,9 +499,6 @@ func (m *Meta) extendedFlagSet(n string) *flag.FlagSet {
|
||||||
f.Var(varValues, "var", "variables")
|
f.Var(varValues, "var", "variables")
|
||||||
f.Var(varFiles, "var-file", "variable file")
|
f.Var(varFiles, "var-file", "variable file")
|
||||||
|
|
||||||
// Experimental features
|
|
||||||
experiment.Flag(f)
|
|
||||||
|
|
||||||
// commands that bypass locking will supply their own flag on this var,
|
// commands that bypass locking will supply their own flag on this var,
|
||||||
// but set the initial meta value to true as a failsafe.
|
// but set the initial meta value to true as a failsafe.
|
||||||
m.stateLock = true
|
m.stateLock = true
|
||||||
|
@ -639,49 +635,6 @@ func (m *Meta) showDiagnostics(vals ...interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// outputShadowError outputs the error from ctx.ShadowError. If the
|
|
||||||
// error is nil then nothing happens. If output is false then it isn't
|
|
||||||
// outputted to the user (you can define logic to guard against outputting).
|
|
||||||
func (m *Meta) outputShadowError(err error, output bool) bool {
|
|
||||||
// Do nothing if no error
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not outputting, do nothing
|
|
||||||
if !output {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the shadow error output to a file
|
|
||||||
path := fmt.Sprintf("terraform-error-%d.log", time.Now().UTC().Unix())
|
|
||||||
if err := ioutil.WriteFile(path, []byte(err.Error()), 0644); err != nil {
|
|
||||||
// If there is an error writing it, just let it go
|
|
||||||
log.Printf("[ERROR] Error writing shadow error: %s", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output!
|
|
||||||
m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
|
|
||||||
"[reset][bold][yellow]\nExperimental feature failure! Please report a bug.\n\n"+
|
|
||||||
"This is not an error. Your Terraform operation completed successfully.\n"+
|
|
||||||
"Your real infrastructure is unaffected by this message.\n\n"+
|
|
||||||
"[reset][yellow]While running, Terraform sometimes tests experimental features in the\n"+
|
|
||||||
"background. These features cannot affect real state and never touch\n"+
|
|
||||||
"real infrastructure. If the features work properly, you see nothing.\n"+
|
|
||||||
"If the features fail, this message appears.\n\n"+
|
|
||||||
"You can report an issue at: https://github.com/hashicorp/terraform/issues\n\n"+
|
|
||||||
"The failure was written to %q. Please\n"+
|
|
||||||
"double check this file contains no sensitive information and report\n"+
|
|
||||||
"it with your issue.\n\n"+
|
|
||||||
"This is not an error. Your terraform operation completed successfully\n"+
|
|
||||||
"and your real infrastructure is unaffected by this message.",
|
|
||||||
path,
|
|
||||||
)))
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// WorkspaceNameEnvVar is the name of the environment variable that can be used
|
// WorkspaceNameEnvVar is the name of the environment variable that can be used
|
||||||
// to set the name of the Terraform workspace, overriding the workspace chosen
|
// to set the name of the Terraform workspace, overriding the workspace chosen
|
||||||
// by `terraform workspace select`.
|
// by `terraform workspace select`.
|
||||||
|
|
|
@ -1,158 +0,0 @@
|
||||||
// experiment package contains helper functions for tracking experimental
|
|
||||||
// features throughout Terraform.
|
|
||||||
//
|
|
||||||
// This package should be used for creating, enabling, querying, and deleting
|
|
||||||
// experimental features. By unifying all of that onto a single interface,
|
|
||||||
// we can have the Go compiler help us by enforcing every place we touch
|
|
||||||
// an experimental feature.
|
|
||||||
//
|
|
||||||
// To create a new experiment:
|
|
||||||
//
|
|
||||||
// 1. Add the experiment to the global vars list below, prefixed with X_
|
|
||||||
//
|
|
||||||
// 2. Add the experiment variable to the All listin the init() function
|
|
||||||
//
|
|
||||||
// 3. Use it!
|
|
||||||
//
|
|
||||||
// To remove an experiment:
|
|
||||||
//
|
|
||||||
// 1. Delete the experiment global var.
|
|
||||||
//
|
|
||||||
// 2. Try to compile and fix all the places where the var was referenced.
|
|
||||||
//
|
|
||||||
// To use an experiment:
|
|
||||||
//
|
|
||||||
// 1. Use Flag() if you want the experiment to be available from the CLI.
|
|
||||||
//
|
|
||||||
// 2. Use Enabled() to check whether it is enabled.
|
|
||||||
//
|
|
||||||
// As a general user:
|
|
||||||
//
|
|
||||||
// 1. The `-Xexperiment-name` flag
|
|
||||||
// 2. The `TF_X_<experiment-name>` env var.
|
|
||||||
// 3. The `TF_X_FORCE` env var can be set to force an experimental feature
|
|
||||||
// without human verifications.
|
|
||||||
//
|
|
||||||
package experiment
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The experiments that are available are listed below. Any package in
|
|
||||||
// Terraform defining an experiment should define the experiments below.
|
|
||||||
// By keeping them all within the experiment package we force a single point
|
|
||||||
// of definition and use. This allows the compiler to enforce references
|
|
||||||
// so it becomes easy to remove the features.
|
|
||||||
var (
|
|
||||||
// Shadow graph. This is already on by default. Disabling it will be
|
|
||||||
// allowed for awhile in order for it to not block operations.
|
|
||||||
X_shadow = newBasicID("shadow", "SHADOW", false)
|
|
||||||
|
|
||||||
// Concise plan diff output
|
|
||||||
X_concise_diff = newBasicID("concise_diff", "CONCISE_DIFF", true)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Global variables this package uses because we are a package
|
|
||||||
// with global state.
|
|
||||||
var (
|
|
||||||
// all is the list of all experiements. Do not modify this.
|
|
||||||
All []ID
|
|
||||||
|
|
||||||
// enabled keeps track of what flags have been enabled
|
|
||||||
enabled map[string]bool
|
|
||||||
enabledLock sync.Mutex
|
|
||||||
|
|
||||||
// Hidden "experiment" that forces all others to be on without verification
|
|
||||||
x_force = newBasicID("force", "FORCE", false)
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// The list of all experiments, update this when an experiment is added.
|
|
||||||
All = []ID{
|
|
||||||
X_shadow,
|
|
||||||
X_concise_diff,
|
|
||||||
x_force,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load
|
|
||||||
reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
// reload is used by tests to reload the global state. This is called by
|
|
||||||
// init publicly.
|
|
||||||
func reload() {
|
|
||||||
// Initialize
|
|
||||||
enabledLock.Lock()
|
|
||||||
enabled = make(map[string]bool)
|
|
||||||
enabledLock.Unlock()
|
|
||||||
|
|
||||||
// Set defaults and check env vars
|
|
||||||
for _, id := range All {
|
|
||||||
// Get the default value
|
|
||||||
def := id.Default()
|
|
||||||
|
|
||||||
// If we set it in the env var, default it to true
|
|
||||||
key := fmt.Sprintf("TF_X_%s", strings.ToUpper(id.Env()))
|
|
||||||
if v := os.Getenv(key); v != "" {
|
|
||||||
def = v != "0"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the default
|
|
||||||
SetEnabled(id, def)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enabled returns whether an experiment has been enabled or not.
|
|
||||||
func Enabled(id ID) bool {
|
|
||||||
enabledLock.Lock()
|
|
||||||
defer enabledLock.Unlock()
|
|
||||||
return enabled[id.Flag()]
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEnabled sets an experiment to enabled/disabled. Please check with
|
|
||||||
// the experiment docs for when calling this actually affects the experiment.
|
|
||||||
func SetEnabled(id ID, v bool) {
|
|
||||||
enabledLock.Lock()
|
|
||||||
defer enabledLock.Unlock()
|
|
||||||
enabled[id.Flag()] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force returns true if the -Xforce of TF_X_FORCE flag is present, which
|
|
||||||
// advises users of this package to not verify with the user that they want
|
|
||||||
// experimental behavior and to just continue with it.
|
|
||||||
func Force() bool {
|
|
||||||
return Enabled(x_force)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flag configures the given FlagSet with the flags to configure
|
|
||||||
// all active experiments.
|
|
||||||
func Flag(fs *flag.FlagSet) {
|
|
||||||
for _, id := range All {
|
|
||||||
desc := id.Flag()
|
|
||||||
key := fmt.Sprintf("X%s", id.Flag())
|
|
||||||
fs.Var(&idValue{X: id}, key, desc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// idValue implements flag.Value for setting the enabled/disabled state
|
|
||||||
// of an experiment from the CLI.
|
|
||||||
type idValue struct {
|
|
||||||
X ID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *idValue) IsBoolFlag() bool { return true }
|
|
||||||
func (v *idValue) String() string { return strconv.FormatBool(Enabled(v.X)) }
|
|
||||||
func (v *idValue) Set(raw string) error {
|
|
||||||
b, err := strconv.ParseBool(raw)
|
|
||||||
if err == nil {
|
|
||||||
SetEnabled(v.X, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
package experiment
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test experiments
|
|
||||||
var (
|
|
||||||
X_test1 = newBasicID("test1", "TEST1", false)
|
|
||||||
X_test2 = newBasicID("test2", "TEST2", true)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reinitializes the package to a clean slate
|
|
||||||
func testReinit() {
|
|
||||||
All = []ID{X_test1, X_test2, x_force}
|
|
||||||
reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
testReinit()
|
|
||||||
|
|
||||||
// Clear all env vars so they don't affect tests
|
|
||||||
for _, id := range All {
|
|
||||||
os.Unsetenv(fmt.Sprintf("TF_X_%s", id.Env()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefault(t *testing.T) {
|
|
||||||
testReinit()
|
|
||||||
|
|
||||||
if Enabled(X_test1) {
|
|
||||||
t.Fatal("test1 should not be enabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !Enabled(X_test2) {
|
|
||||||
t.Fatal("test2 should be enabled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnv(t *testing.T) {
|
|
||||||
os.Setenv("TF_X_TEST2", "0")
|
|
||||||
defer os.Unsetenv("TF_X_TEST2")
|
|
||||||
|
|
||||||
testReinit()
|
|
||||||
|
|
||||||
if Enabled(X_test2) {
|
|
||||||
t.Fatal("test2 should be enabled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFlag(t *testing.T) {
|
|
||||||
testReinit()
|
|
||||||
|
|
||||||
// Verify default
|
|
||||||
if !Enabled(X_test2) {
|
|
||||||
t.Fatal("test2 should be enabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup a flag set
|
|
||||||
fs := flag.NewFlagSet("test", flag.ContinueOnError)
|
|
||||||
Flag(fs)
|
|
||||||
fs.Parse([]string{"-Xtest2=false"})
|
|
||||||
|
|
||||||
if Enabled(X_test2) {
|
|
||||||
t.Fatal("test2 should not be enabled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFlag_overEnv(t *testing.T) {
|
|
||||||
os.Setenv("TF_X_TEST2", "1")
|
|
||||||
defer os.Unsetenv("TF_X_TEST2")
|
|
||||||
|
|
||||||
testReinit()
|
|
||||||
|
|
||||||
// Verify default
|
|
||||||
if !Enabled(X_test2) {
|
|
||||||
t.Fatal("test2 should be enabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup a flag set
|
|
||||||
fs := flag.NewFlagSet("test", flag.ContinueOnError)
|
|
||||||
Flag(fs)
|
|
||||||
fs.Parse([]string{"-Xtest2=false"})
|
|
||||||
|
|
||||||
if Enabled(X_test2) {
|
|
||||||
t.Fatal("test2 should not be enabled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestForce(t *testing.T) {
|
|
||||||
os.Setenv("TF_X_FORCE", "1")
|
|
||||||
defer os.Unsetenv("TF_X_FORCE")
|
|
||||||
|
|
||||||
testReinit()
|
|
||||||
|
|
||||||
if !Force() {
|
|
||||||
t.Fatal("should force")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestForce_flag(t *testing.T) {
|
|
||||||
os.Unsetenv("TF_X_FORCE")
|
|
||||||
|
|
||||||
testReinit()
|
|
||||||
|
|
||||||
// Setup a flag set
|
|
||||||
fs := flag.NewFlagSet("test", flag.ContinueOnError)
|
|
||||||
Flag(fs)
|
|
||||||
fs.Parse([]string{"-Xforce"})
|
|
||||||
|
|
||||||
if !Force() {
|
|
||||||
t.Fatal("should force")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
package experiment
|
|
||||||
|
|
||||||
// ID represents an experimental feature.
|
|
||||||
//
|
|
||||||
// The global vars defined on this package should be used as ID values.
|
|
||||||
// This interface is purposely not implement-able outside of this package
|
|
||||||
// so that we can rely on the Go compiler to enforce all experiment references.
|
|
||||||
type ID interface {
|
|
||||||
Env() string
|
|
||||||
Flag() string
|
|
||||||
Default() bool
|
|
||||||
|
|
||||||
unexported() // So the ID can't be implemented externally.
|
|
||||||
}
|
|
||||||
|
|
||||||
// basicID implements ID.
|
|
||||||
type basicID struct {
|
|
||||||
EnvValue string
|
|
||||||
FlagValue string
|
|
||||||
DefaultValue bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBasicID(flag, env string, def bool) ID {
|
|
||||||
return &basicID{
|
|
||||||
EnvValue: env,
|
|
||||||
FlagValue: flag,
|
|
||||||
DefaultValue: def,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (id *basicID) Env() string { return id.EnvValue }
|
|
||||||
func (id *basicID) Flag() string { return id.FlagValue }
|
|
||||||
func (id *basicID) Default() bool { return id.DefaultValue }
|
|
||||||
func (id *basicID) unexported() {}
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/configs/configload"
|
"github.com/hashicorp/terraform/configs/configload"
|
||||||
"github.com/hashicorp/terraform/helper/experiment"
|
|
||||||
"github.com/hashicorp/terraform/internal/initwd"
|
"github.com/hashicorp/terraform/internal/initwd"
|
||||||
"github.com/hashicorp/terraform/plans"
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/hashicorp/terraform/providers"
|
"github.com/hashicorp/terraform/providers"
|
||||||
|
@ -32,11 +31,6 @@ import (
|
||||||
const fixtureDir = "./testdata"
|
const fixtureDir = "./testdata"
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
// We want to shadow on tests just to make sure the shadow graph works
|
|
||||||
// in case we need it and to find any race issues.
|
|
||||||
experiment.SetEnabled(experiment.X_shadow, true)
|
|
||||||
|
|
||||||
experiment.Flag(flag.CommandLine)
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// Make sure shadow operations fail our real tests
|
// Make sure shadow operations fail our real tests
|
||||||
|
|
Loading…
Reference in New Issue