github.com/chanzuckerberg/terraform@v0.11.12-beta1/helper/experiment/experiment.go (about)

     1  // experiment package contains helper functions for tracking experimental
     2  // features throughout Terraform.
     3  //
     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.
     8  //
     9  // To create a new experiment:
    10  //
    11  //   1. Add the experiment to the global vars list below, prefixed with X_
    12  //
    13  //   2. Add the experiment variable to the All listin the init() function
    14  //
    15  //   3. Use it!
    16  //
    17  // To remove an experiment:
    18  //
    19  //   1. Delete the experiment global var.
    20  //
    21  //   2. Try to compile and fix all the places where the var was referenced.
    22  //
    23  // To use an experiment:
    24  //
    25  //   1. Use Flag() if you want the experiment to be available from the CLI.
    26  //
    27  //   2. Use Enabled() to check whether it is enabled.
    28  //
    29  // As a general user:
    30  //
    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.
    35  //
    36  package experiment
    37  
    38  import (
    39  	"flag"
    40  	"fmt"
    41  	"os"
    42  	"strconv"
    43  	"strings"
    44  	"sync"
    45  )
    46  
    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.
    52  var (
    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)
    56  )
    57  
    58  // Global variables this package uses because we are a package
    59  // with global state.
    60  var (
    61  	// all is the list of all experiements. Do not modify this.
    62  	All []ID
    63  
    64  	// enabled keeps track of what flags have been enabled
    65  	enabled     map[string]bool
    66  	enabledLock sync.Mutex
    67  
    68  	// Hidden "experiment" that forces all others to be on without verification
    69  	x_force = newBasicID("force", "FORCE", false)
    70  )
    71  
    72  func init() {
    73  	// The list of all experiments, update this when an experiment is added.
    74  	All = []ID{
    75  		X_shadow,
    76  		x_force,
    77  	}
    78  
    79  	// Load
    80  	reload()
    81  }
    82  
    83  // reload is used by tests to reload the global state. This is called by
    84  // init publicly.
    85  func reload() {
    86  	// Initialize
    87  	enabledLock.Lock()
    88  	enabled = make(map[string]bool)
    89  	enabledLock.Unlock()
    90  
    91  	// Set defaults and check env vars
    92  	for _, id := range All {
    93  		// Get the default value
    94  		def := id.Default()
    95  
    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 != "" {
    99  			def = v != "0"
   100  		}
   101  
   102  		// Set the default
   103  		SetEnabled(id, def)
   104  	}
   105  }
   106  
   107  // Enabled returns whether an experiment has been enabled or not.
   108  func Enabled(id ID) bool {
   109  	enabledLock.Lock()
   110  	defer enabledLock.Unlock()
   111  	return enabled[id.Flag()]
   112  }
   113  
   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) {
   117  	enabledLock.Lock()
   118  	defer enabledLock.Unlock()
   119  	enabled[id.Flag()] = v
   120  }
   121  
   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.
   125  func Force() bool {
   126  	return Enabled(x_force)
   127  }
   128  
   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 {
   133  		desc := id.Flag()
   134  		key := fmt.Sprintf("X%s", id.Flag())
   135  		fs.Var(&idValue{X: id}, key, desc)
   136  	}
   137  }
   138  
   139  // idValue implements flag.Value for setting the enabled/disabled state
   140  // of an experiment from the CLI.
   141  type idValue struct {
   142  	X ID
   143  }
   144  
   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)
   149  	if err == nil {
   150  		SetEnabled(v.X, b)
   151  	}
   152  
   153  	return err
   154  }