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