github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/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 }