github.com/rhenning/terraform@v0.8.0-beta2/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 return &result, nil 78 } 79 80 // Discover plugins located on disk, and fall back on plugins baked into the 81 // Terraform binary. 82 // 83 // We look in the following places for plugins: 84 // 85 // 1. Terraform configuration path 86 // 2. Path where Terraform is installed 87 // 3. Path where Terraform is invoked 88 // 89 // Whichever file is discoverd LAST wins. 90 // 91 // Finally, we look at the list of plugins compiled into Terraform. If any of 92 // them has not been found on disk we use the internal version. This allows 93 // users to add / replace plugins without recompiling the main binary. 94 func (c *Config) Discover(ui cli.Ui) error { 95 // Look in ~/.terraform.d/plugins/ 96 dir, err := ConfigDir() 97 if err != nil { 98 log.Printf("[ERR] Error loading config directory: %s", err) 99 } else { 100 if err := c.discover(filepath.Join(dir, "plugins")); err != nil { 101 return err 102 } 103 } 104 105 // Next, look in the same directory as the Terraform executable, usually 106 // /usr/local/bin. If found, this replaces what we found in the config path. 107 exePath, err := osext.Executable() 108 if err != nil { 109 log.Printf("[ERR] Error loading exe directory: %s", err) 110 } else { 111 if err := c.discover(filepath.Dir(exePath)); err != nil { 112 return err 113 } 114 } 115 116 // Finally look in the cwd (where we are invoke Terraform). If found, this 117 // replaces anything we found in the config / install paths. 118 if err := c.discover("."); err != nil { 119 return err 120 } 121 122 // Finally, if we have a plugin compiled into Terraform and we didn't find 123 // a replacement on disk, we'll just use the internal version. Only do this 124 // from the main process, or the log output will break the plugin handshake. 125 if os.Getenv("TF_PLUGIN_MAGIC_COOKIE") == "" { 126 for name, _ := range command.InternalProviders { 127 if path, found := c.Providers[name]; found { 128 // Allow these warnings to be suppressed via TF_PLUGIN_DEV=1 or similar 129 if os.Getenv("TF_PLUGIN_DEV") == "" { 130 ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provider.\n"+ 131 " If you did not expect to see this message you will need to remove the old plugin.\n"+ 132 " See https://www.terraform.io/docs/internals/internal-plugins.html", path, name)) 133 } 134 } else { 135 cmd, err := command.BuildPluginCommandString("provider", name) 136 if err != nil { 137 return err 138 } 139 c.Providers[name] = cmd 140 } 141 } 142 for name, _ := range command.InternalProvisioners { 143 if path, found := c.Provisioners[name]; found { 144 if os.Getenv("TF_PLUGIN_DEV") == "" { 145 ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provisioner.\n"+ 146 " If you did not expect to see this message you will need to remove the old plugin.\n"+ 147 " See https://www.terraform.io/docs/internals/internal-plugins.html", path, name)) 148 } 149 } else { 150 cmd, err := command.BuildPluginCommandString("provisioner", name) 151 if err != nil { 152 return err 153 } 154 c.Provisioners[name] = cmd 155 } 156 } 157 } 158 159 return nil 160 } 161 162 // Merge merges two configurations and returns a third entirely 163 // new configuration with the two merged. 164 func (c1 *Config) Merge(c2 *Config) *Config { 165 var result Config 166 result.Providers = make(map[string]string) 167 result.Provisioners = make(map[string]string) 168 for k, v := range c1.Providers { 169 result.Providers[k] = v 170 } 171 for k, v := range c2.Providers { 172 if v1, ok := c1.Providers[k]; ok { 173 log.Printf("[INFO] Local %s provider configuration '%s' overrides '%s'", k, v, v1) 174 } 175 result.Providers[k] = v 176 } 177 for k, v := range c1.Provisioners { 178 result.Provisioners[k] = v 179 } 180 for k, v := range c2.Provisioners { 181 if v1, ok := c1.Provisioners[k]; ok { 182 log.Printf("[INFO] Local %s provisioner configuration '%s' overrides '%s'", k, v, v1) 183 } 184 result.Provisioners[k] = v 185 } 186 187 return &result 188 } 189 190 func (c *Config) discover(path string) error { 191 var err error 192 193 if !filepath.IsAbs(path) { 194 path, err = filepath.Abs(path) 195 if err != nil { 196 return err 197 } 198 } 199 200 err = c.discoverSingle( 201 filepath.Join(path, "terraform-provider-*"), &c.Providers) 202 if err != nil { 203 return err 204 } 205 206 err = c.discoverSingle( 207 filepath.Join(path, "terraform-provisioner-*"), &c.Provisioners) 208 if err != nil { 209 return err 210 } 211 212 return nil 213 } 214 215 func (c *Config) discoverSingle(glob string, m *map[string]string) error { 216 matches, err := filepath.Glob(glob) 217 if err != nil { 218 return err 219 } 220 221 if *m == nil { 222 *m = make(map[string]string) 223 } 224 225 for _, match := range matches { 226 file := filepath.Base(match) 227 228 // If the filename has a ".", trim up to there 229 if idx := strings.Index(file, "."); idx >= 0 { 230 file = file[:idx] 231 } 232 233 // Look for foo-bar-baz. The plugin name is "baz" 234 parts := strings.SplitN(file, "-", 3) 235 if len(parts) != 3 { 236 continue 237 } 238 239 log.Printf("[DEBUG] Discovered plugin: %s = %s", parts[2], match) 240 (*m)[parts[2]] = match 241 } 242 243 return nil 244 } 245 246 // ProviderFactories returns the mapping of prefixes to 247 // ResourceProviderFactory that can be used to instantiate a 248 // binary-based plugin. 249 func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactory { 250 result := make(map[string]terraform.ResourceProviderFactory) 251 for k, v := range c.Providers { 252 result[k] = c.providerFactory(v) 253 } 254 255 return result 256 } 257 258 func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory { 259 // Build the plugin client configuration and init the plugin 260 var config plugin.ClientConfig 261 config.Cmd = pluginCmd(path) 262 config.HandshakeConfig = tfplugin.Handshake 263 config.Managed = true 264 config.Plugins = tfplugin.PluginMap 265 client := plugin.NewClient(&config) 266 267 return func() (terraform.ResourceProvider, error) { 268 // Request the RPC client so we can get the provider 269 // so we can build the actual RPC-implemented provider. 270 rpcClient, err := client.Client() 271 if err != nil { 272 return nil, err 273 } 274 275 raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName) 276 if err != nil { 277 return nil, err 278 } 279 280 return raw.(terraform.ResourceProvider), nil 281 } 282 } 283 284 // ProvisionerFactories returns the mapping of prefixes to 285 // ResourceProvisionerFactory that can be used to instantiate a 286 // binary-based plugin. 287 func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisionerFactory { 288 result := make(map[string]terraform.ResourceProvisionerFactory) 289 for k, v := range c.Provisioners { 290 result[k] = c.provisionerFactory(v) 291 } 292 293 return result 294 } 295 296 func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory { 297 // Build the plugin client configuration and init the plugin 298 var config plugin.ClientConfig 299 config.HandshakeConfig = tfplugin.Handshake 300 config.Cmd = pluginCmd(path) 301 config.Managed = true 302 config.Plugins = tfplugin.PluginMap 303 client := plugin.NewClient(&config) 304 305 return func() (terraform.ResourceProvisioner, error) { 306 rpcClient, err := client.Client() 307 if err != nil { 308 return nil, err 309 } 310 311 raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName) 312 if err != nil { 313 return nil, err 314 } 315 316 return raw.(terraform.ResourceProvisioner), nil 317 } 318 } 319 320 func pluginCmd(path string) *exec.Cmd { 321 cmdPath := "" 322 323 // If the path doesn't contain a separator, look in the same 324 // directory as the Terraform executable first. 325 if !strings.ContainsRune(path, os.PathSeparator) { 326 exePath, err := osext.Executable() 327 if err == nil { 328 temp := filepath.Join( 329 filepath.Dir(exePath), 330 filepath.Base(path)) 331 332 if _, err := os.Stat(temp); err == nil { 333 cmdPath = temp 334 } 335 } 336 337 // If we still haven't found the executable, look for it 338 // in the PATH. 339 if v, err := exec.LookPath(path); err == nil { 340 cmdPath = v 341 } 342 } 343 344 // No plugin binary found, so try to use an internal plugin. 345 if strings.Contains(path, command.TFSPACE) { 346 parts := strings.Split(path, command.TFSPACE) 347 return exec.Command(parts[0], parts[1:]...) 348 } 349 350 // If we still don't have a path, then just set it to the original 351 // given path. 352 if cmdPath == "" { 353 cmdPath = path 354 } 355 356 // Build the command to execute the plugin 357 return exec.Command(cmdPath) 358 }