github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/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 }