github.com/posener/terraform@v0.11.0-beta1.0.20171103235147-645df36af025/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 }