github.com/IBM-Cloud/terraform@v0.6.4-0.20170726051544-8872b87621df/config.go (about) 1 //go:generate go run ./scripts/generate-plugins.go 2 package main 3 4 import ( 5 "fmt" 6 "io/ioutil" 7 "log" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "strings" 12 13 "github.com/hashicorp/go-plugin" 14 "github.com/hashicorp/hcl" 15 "github.com/hashicorp/terraform/command" 16 tfplugin "github.com/hashicorp/terraform/plugin" 17 "github.com/hashicorp/terraform/terraform" 18 "github.com/kardianos/osext" 19 "github.com/mitchellh/cli" 20 ) 21 22 // Config is the structure of the configuration for the Terraform CLI. 23 // 24 // This is not the configuration for Terraform itself. That is in the 25 // "config" package. 26 type Config struct { 27 Providers map[string]string 28 Provisioners map[string]string 29 30 DisableCheckpoint bool `hcl:"disable_checkpoint"` 31 DisableCheckpointSignature bool `hcl:"disable_checkpoint_signature"` 32 } 33 34 // BuiltinConfig is the built-in defaults for the configuration. These 35 // can be overridden by user configurations. 36 var BuiltinConfig Config 37 38 // ContextOpts are the global ContextOpts we use to initialize the CLI. 39 var ContextOpts terraform.ContextOpts 40 41 // ConfigFile returns the default path to the configuration file. 42 // 43 // On Unix-like systems this is the ".terraformrc" file in the home directory. 44 // On Windows, this is the "terraform.rc" file in the application data 45 // directory. 46 func ConfigFile() (string, error) { 47 return configFile() 48 } 49 50 // ConfigDir returns the configuration directory for Terraform. 51 func ConfigDir() (string, error) { 52 return configDir() 53 } 54 55 // LoadConfig loads the CLI configuration from ".terraformrc" files. 56 func LoadConfig(path string) (*Config, error) { 57 // Read the HCL file and prepare for parsing 58 d, err := ioutil.ReadFile(path) 59 if err != nil { 60 return nil, fmt.Errorf( 61 "Error reading %s: %s", path, err) 62 } 63 64 // Parse it 65 obj, err := hcl.Parse(string(d)) 66 if err != nil { 67 return nil, fmt.Errorf( 68 "Error parsing %s: %s", path, err) 69 } 70 71 // Build up the result 72 var result Config 73 if err := hcl.DecodeObject(&result, obj); err != nil { 74 return nil, err 75 } 76 77 // Replace all env vars 78 for k, v := range result.Providers { 79 result.Providers[k] = os.ExpandEnv(v) 80 } 81 for k, v := range result.Provisioners { 82 result.Provisioners[k] = os.ExpandEnv(v) 83 } 84 85 return &result, nil 86 } 87 88 // Discover plugins located on disk, and fall back on plugins baked into the 89 // Terraform binary. 90 // 91 // We look in the following places for plugins: 92 // 93 // 1. Terraform configuration path 94 // 2. Path where Terraform is installed 95 // 3. Path where Terraform is invoked 96 // 97 // Whichever file is discoverd LAST wins. 98 // 99 // Finally, we look at the list of plugins compiled into Terraform. If any of 100 // them has not been found on disk we use the internal version. This allows 101 // users to add / replace plugins without recompiling the main binary. 102 func (c *Config) Discover(ui cli.Ui) error { 103 // Look in ~/.terraform.d/plugins/ 104 dir, err := ConfigDir() 105 if err != nil { 106 log.Printf("[ERR] Error loading config directory: %s", err) 107 } else { 108 if err := c.discover(filepath.Join(dir, "plugins")); err != nil { 109 return err 110 } 111 } 112 113 // Next, look in the same directory as the Terraform executable, usually 114 // /usr/local/bin. If found, this replaces what we found in the config path. 115 exePath, err := osext.Executable() 116 if err != nil { 117 log.Printf("[ERR] Error loading exe directory: %s", err) 118 } else { 119 if err := c.discover(filepath.Dir(exePath)); err != nil { 120 return err 121 } 122 } 123 124 // Finally look in the cwd (where we are invoke Terraform). If found, this 125 // replaces anything we found in the config / install paths. 126 if err := c.discover("."); err != nil { 127 return err 128 } 129 130 // Finally, if we have a plugin compiled into Terraform and we didn't find 131 // a replacement on disk, we'll just use the internal version. Only do this 132 // from the main process, or the log output will break the plugin handshake. 133 if os.Getenv("TF_PLUGIN_MAGIC_COOKIE") == "" { 134 for name, _ := range command.InternalProviders { 135 if path, found := c.Providers[name]; found { 136 // Allow these warnings to be suppressed via TF_PLUGIN_DEV=1 or similar 137 if os.Getenv("TF_PLUGIN_DEV") == "" { 138 ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provider.\n"+ 139 " If you did not expect to see this message you will need to remove the old plugin.\n"+ 140 " See https://www.terraform.io/docs/internals/internal-plugins.html", path, name)) 141 } 142 } else { 143 cmd, err := command.BuildPluginCommandString("provider", name) 144 if err != nil { 145 return err 146 } 147 c.Providers[name] = cmd 148 } 149 } 150 for name, _ := range command.InternalProvisioners { 151 if path, found := c.Provisioners[name]; found { 152 if os.Getenv("TF_PLUGIN_DEV") == "" { 153 ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provisioner.\n"+ 154 " If you did not expect to see this message you will need to remove the old plugin.\n"+ 155 " See https://www.terraform.io/docs/internals/internal-plugins.html", path, name)) 156 } 157 } else { 158 cmd, err := command.BuildPluginCommandString("provisioner", name) 159 if err != nil { 160 return err 161 } 162 c.Provisioners[name] = cmd 163 } 164 } 165 } 166 167 return nil 168 } 169 170 // Merge merges two configurations and returns a third entirely 171 // new configuration with the two merged. 172 func (c1 *Config) Merge(c2 *Config) *Config { 173 var result Config 174 result.Providers = make(map[string]string) 175 result.Provisioners = make(map[string]string) 176 for k, v := range c1.Providers { 177 result.Providers[k] = v 178 } 179 for k, v := range c2.Providers { 180 if v1, ok := c1.Providers[k]; ok { 181 log.Printf("[INFO] Local %s provider configuration '%s' overrides '%s'", k, v, v1) 182 } 183 result.Providers[k] = v 184 } 185 for k, v := range c1.Provisioners { 186 result.Provisioners[k] = v 187 } 188 for k, v := range c2.Provisioners { 189 if v1, ok := c1.Provisioners[k]; ok { 190 log.Printf("[INFO] Local %s provisioner configuration '%s' overrides '%s'", k, v, v1) 191 } 192 result.Provisioners[k] = v 193 } 194 result.DisableCheckpoint = c1.DisableCheckpoint || c2.DisableCheckpoint 195 result.DisableCheckpointSignature = c1.DisableCheckpointSignature || c2.DisableCheckpointSignature 196 197 return &result 198 } 199 200 func (c *Config) discover(path string) error { 201 var err error 202 203 if !filepath.IsAbs(path) { 204 path, err = filepath.Abs(path) 205 if err != nil { 206 return err 207 } 208 } 209 210 err = c.discoverSingle( 211 filepath.Join(path, "terraform-provider-*"), &c.Providers) 212 if err != nil { 213 return err 214 } 215 216 err = c.discoverSingle( 217 filepath.Join(path, "terraform-provisioner-*"), &c.Provisioners) 218 if err != nil { 219 return err 220 } 221 222 return nil 223 } 224 225 func (c *Config) discoverSingle(glob string, m *map[string]string) error { 226 matches, err := filepath.Glob(glob) 227 if err != nil { 228 return err 229 } 230 231 if *m == nil { 232 *m = make(map[string]string) 233 } 234 235 for _, match := range matches { 236 file := filepath.Base(match) 237 238 // If the filename has a ".", trim up to there 239 if idx := strings.Index(file, "."); idx >= 0 { 240 file = file[:idx] 241 } 242 243 // Look for foo-bar-baz. The plugin name is "baz" 244 parts := strings.SplitN(file, "-", 3) 245 if len(parts) != 3 { 246 continue 247 } 248 249 log.Printf("[DEBUG] Discovered plugin: %s = %s", parts[2], match) 250 (*m)[parts[2]] = match 251 } 252 253 return nil 254 } 255 256 // ProviderFactories returns the mapping of prefixes to 257 // ResourceProviderFactory that can be used to instantiate a 258 // binary-based plugin. 259 func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactory { 260 result := make(map[string]terraform.ResourceProviderFactory) 261 for k, v := range c.Providers { 262 result[k] = c.providerFactory(v) 263 } 264 265 return result 266 } 267 268 func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory { 269 // Build the plugin client configuration and init the plugin 270 var config plugin.ClientConfig 271 config.Cmd = pluginCmd(path) 272 config.HandshakeConfig = tfplugin.Handshake 273 config.Managed = true 274 config.Plugins = tfplugin.PluginMap 275 client := plugin.NewClient(&config) 276 277 return func() (terraform.ResourceProvider, error) { 278 // Request the RPC client so we can get the provider 279 // so we can build the actual RPC-implemented provider. 280 rpcClient, err := client.Client() 281 if err != nil { 282 return nil, err 283 } 284 285 raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName) 286 if err != nil { 287 return nil, err 288 } 289 290 return raw.(terraform.ResourceProvider), nil 291 } 292 } 293 294 // ProvisionerFactories returns the mapping of prefixes to 295 // ResourceProvisionerFactory that can be used to instantiate a 296 // binary-based plugin. 297 func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisionerFactory { 298 result := make(map[string]terraform.ResourceProvisionerFactory) 299 for k, v := range c.Provisioners { 300 result[k] = c.provisionerFactory(v) 301 } 302 303 return result 304 } 305 306 func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory { 307 // Build the plugin client configuration and init the plugin 308 var config plugin.ClientConfig 309 config.HandshakeConfig = tfplugin.Handshake 310 config.Cmd = pluginCmd(path) 311 config.Managed = true 312 config.Plugins = tfplugin.PluginMap 313 client := plugin.NewClient(&config) 314 315 return func() (terraform.ResourceProvisioner, error) { 316 rpcClient, err := client.Client() 317 if err != nil { 318 return nil, err 319 } 320 321 raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName) 322 if err != nil { 323 return nil, err 324 } 325 326 return raw.(terraform.ResourceProvisioner), nil 327 } 328 } 329 330 func pluginCmd(path string) *exec.Cmd { 331 cmdPath := "" 332 333 // If the path doesn't contain a separator, look in the same 334 // directory as the Terraform executable first. 335 if !strings.ContainsRune(path, os.PathSeparator) { 336 exePath, err := osext.Executable() 337 if err == nil { 338 temp := filepath.Join( 339 filepath.Dir(exePath), 340 filepath.Base(path)) 341 342 if _, err := os.Stat(temp); err == nil { 343 cmdPath = temp 344 } 345 } 346 347 // If we still haven't found the executable, look for it 348 // in the PATH. 349 if v, err := exec.LookPath(path); err == nil { 350 cmdPath = v 351 } 352 } 353 354 // No plugin binary found, so try to use an internal plugin. 355 if strings.Contains(path, command.TFSPACE) { 356 parts := strings.Split(path, command.TFSPACE) 357 return exec.Command(parts[0], parts[1:]...) 358 } 359 360 // If we still don't have a path, then just set it to the original 361 // given path. 362 if cmdPath == "" { 363 cmdPath = path 364 } 365 366 // Build the command to execute the plugin 367 return exec.Command(cmdPath) 368 }