2016-10-26 21:46:22 +02:00
|
|
|
// 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.
|
2017-02-07 03:02:32 +01:00
|
|
|
X_shadow = newBasicID("shadow", "SHADOW", false)
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
|
|
|
|
// Concise plan diff output
|
|
|
|
X_concise_diff = newBasicID("concise_diff", "CONCISE_DIFF", true)
|
2016-10-26 21:46:22 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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,
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
X_concise_diff,
|
2016-10-26 21:46:22 +02:00
|
|
|
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
|
|
|
|
}
|