// 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_` 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) ) // 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_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 }