1 // experiment package contains helper functions for tracking experimental
2 // features throughout Terraform.
4 // This package should be used for creating, enabling, querying, and deleting
5 // experimental features. By unifying all of that onto a single interface,
6 // we can have the Go compiler help us by enforcing every place we touch
7 // an experimental feature.
9 // To create a new experiment:
11 // 1. Add the experiment to the global vars list below, prefixed with X_
13 // 2. Add the experiment variable to the All listin the init() function
17 // To remove an experiment:
19 // 1. Delete the experiment global var.
21 // 2. Try to compile and fix all the places where the var was referenced.
23 // To use an experiment:
25 // 1. Use Flag() if you want the experiment to be available from the CLI.
27 // 2. Use Enabled() to check whether it is enabled.
31 // 1. The `-Xexperiment-name` flag
32 // 2. The `TF_X_<experiment-name>` env var.
33 // 3. The `TF_X_FORCE` env var can be set to force an experimental feature
34 // without human verifications.
47 // The experiments that are available are listed below. Any package in
48 // Terraform defining an experiment should define the experiments below.
49 // By keeping them all within the experiment package we force a single point
50 // of definition and use. This allows the compiler to enforce references
51 // so it becomes easy to remove the features.
53 // Shadow graph. This is already on by default. Disabling it will be
54 // allowed for awhile in order for it to not block operations.
55 X_shadow = newBasicID("shadow", "SHADOW", false)
58 // Global variables this package uses because we are a package
61 // all is the list of all experiements. Do not modify this.
64 // enabled keeps track of what flags have been enabled
65 enabled map[string]bool
66 enabledLock sync.Mutex
68 // Hidden "experiment" that forces all others to be on without verification
69 x_force = newBasicID("force", "FORCE", false)
73 // The list of all experiments, update this when an experiment is added.
83 // reload is used by tests to reload the global state. This is called by
88 enabled = make(map[string]bool)
91 // Set defaults and check env vars
92 for _, id := range All {
93 // Get the default value
96 // If we set it in the env var, default it to true
97 key := fmt.Sprintf("TF_X_%s", strings.ToUpper(id.Env()))
98 if v := os.Getenv(key); v != "" {
107 // Enabled returns whether an experiment has been enabled or not.
108 func Enabled(id ID) bool {
110 defer enabledLock.Unlock()
111 return enabled[id.Flag()]
114 // SetEnabled sets an experiment to enabled/disabled. Please check with
115 // the experiment docs for when calling this actually affects the experiment.
116 func SetEnabled(id ID, v bool) {
118 defer enabledLock.Unlock()
119 enabled[id.Flag()] = v
122 // Force returns true if the -Xforce of TF_X_FORCE flag is present, which
123 // advises users of this package to not verify with the user that they want
124 // experimental behavior and to just continue with it.
126 return Enabled(x_force)
129 // Flag configures the given FlagSet with the flags to configure
130 // all active experiments.
131 func Flag(fs *flag.FlagSet) {
132 for _, id := range All {
134 key := fmt.Sprintf("X%s", id.Flag())
135 fs.Var(&idValue{X: id}, key, desc)
139 // idValue implements flag.Value for setting the enabled/disabled state
140 // of an experiment from the CLI.
141 type idValue struct {
145 func (v *idValue) IsBoolFlag() bool { return true }
146 func (v *idValue) String() string { return strconv.FormatBool(Enabled(v.X)) }
147 func (v *idValue) Set(raw string) error {
148 b, err := strconv.ParseBool(raw)