github.com/aspring/terraform@v0.8.2-0.20161216122603-6a8619a5db2e/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  	// Reuse the old graphs from TF 0.7.x. These will be removed at some point.
    54  	X_legacyGraph = newBasicID("legacy-graph", "LEGACY_GRAPH", false)
    55  
    56  	// Shadow graph. This is already on by default. Disabling it will be
    57  	// allowed for awhile in order for it to not block operations.
    58  	X_shadow = newBasicID("shadow", "SHADOW", true)
    59  )
    60  
    61  // Global variables this package uses because we are a package
    62  // with global state.
    63  var (
    64  	// all is the list of all experiements. Do not modify this.
    65  	All []ID
    66  
    67  	// enabled keeps track of what flags have been enabled
    68  	enabled     map[string]bool
    69  	enabledLock sync.Mutex
    70  
    71  	// Hidden "experiment" that forces all others to be on without verification
    72  	x_force = newBasicID("force", "FORCE", false)
    73  )
    74  
    75  func init() {
    76  	// The list of all experiments, update this when an experiment is added.
    77  	All = []ID{
    78  		X_legacyGraph,
    79  		X_shadow,
    80  		x_force,
    81  	}
    82  
    83  	// Load
    84  	reload()
    85  }
    86  
    87  // reload is used by tests to reload the global state. This is called by
    88  // init publicly.
    89  func reload() {
    90  	// Initialize
    91  	enabledLock.Lock()
    92  	enabled = make(map[string]bool)
    93  	enabledLock.Unlock()
    94  
    95  	// Set defaults and check env vars
    96  	for _, id := range All {
    97  		// Get the default value
    98  		def := id.Default()
    99  
   100  		// If we set it in the env var, default it to true
   101  		key := fmt.Sprintf("TF_X_%s", strings.ToUpper(id.Env()))
   102  		if v := os.Getenv(key); v != "" {
   103  			def = v != "0"
   104  		}
   105  
   106  		// Set the default
   107  		SetEnabled(id, def)
   108  	}
   109  }
   110  
   111  // Enabled returns whether an experiment has been enabled or not.
   112  func Enabled(id ID) bool {
   113  	enabledLock.Lock()
   114  	defer enabledLock.Unlock()
   115  	return enabled[id.Flag()]
   116  }
   117  
   118  // SetEnabled sets an experiment to enabled/disabled. Please check with
   119  // the experiment docs for when calling this actually affects the experiment.
   120  func SetEnabled(id ID, v bool) {
   121  	enabledLock.Lock()
   122  	defer enabledLock.Unlock()
   123  	enabled[id.Flag()] = v
   124  }
   125  
   126  // Force returns true if the -Xforce of TF_X_FORCE flag is present, which
   127  // advises users of this package to not verify with the user that they want
   128  // experimental behavior and to just continue with it.
   129  func Force() bool {
   130  	return Enabled(x_force)
   131  }
   132  
   133  // Flag configures the given FlagSet with the flags to configure
   134  // all active experiments.
   135  func Flag(fs *flag.FlagSet) {
   136  	for _, id := range All {
   137  		desc := id.Flag()
   138  		key := fmt.Sprintf("X%s", id.Flag())
   139  		fs.Var(&idValue{X: id}, key, desc)
   140  	}
   141  }
   142  
   143  // idValue implements flag.Value for setting the enabled/disabled state
   144  // of an experiment from the CLI.
   145  type idValue struct {
   146  	X ID
   147  }
   148  
   149  func (v *idValue) IsBoolFlag() bool { return true }
   150  func (v *idValue) String() string   { return strconv.FormatBool(Enabled(v.X)) }
   151  func (v *idValue) Set(raw string) error {
   152  	b, err := strconv.ParseBool(raw)
   153  	if err == nil {
   154  		SetEnabled(v.X, b)
   155  	}
   156  
   157  	return err
   158  }