mirror of https://github.com/hashicorp/terraform
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 futurepull/27088/head
parent
b2d0e20759
commit
3fa063b8dc
@ -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() {}
|
||||
Loading…
Reference in new issue