github.com/rjeczalik/terraform@v0.6.7-0.20160812060014-e251d5c7bd39/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 result.Providers[k] = v 173 } 174 for k, v := range c1.Provisioners { 175 result.Provisioners[k] = v 176 } 177 for k, v := range c2.Provisioners { 178 result.Provisioners[k] = v 179 } 180 181 return &result 182 } 183 184 func (c *Config) discover(path string) error { 185 var err error 186 187 if !filepath.IsAbs(path) { 188 path, err = filepath.Abs(path) 189 if err != nil { 190 return err 191 } 192 } 193 194 err = c.discoverSingle( 195 filepath.Join(path, "terraform-provider-*"), &c.Providers) 196 if err != nil { 197 return err 198 } 199 200 err = c.discoverSingle( 201 filepath.Join(path, "terraform-provisioner-*"), &c.Provisioners) 202 if err != nil { 203 return err 204 } 205 206 return nil 207 } 208 209 func (c *Config) discoverSingle(glob string, m *map[string]string) error { 210 matches, err := filepath.Glob(glob) 211 if err != nil { 212 return err 213 } 214 215 if *m == nil { 216 *m = make(map[string]string) 217 } 218 219 for _, match := range matches { 220 file := filepath.Base(match) 221 222 // If the filename has a ".", trim up to there 223 if idx := strings.Index(file, "."); idx >= 0 { 224 file = file[:idx] 225 } 226 227 // Look for foo-bar-baz. The plugin name is "baz" 228 parts := strings.SplitN(file, "-", 3) 229 if len(parts) != 3 { 230 continue 231 } 232 233 log.Printf("[DEBUG] Discovered plugin: %s = %s", parts[2], match) 234 (*m)[parts[2]] = match 235 } 236 237 return nil 238 } 239 240 // ProviderFactories returns the mapping of prefixes to 241 // ResourceProviderFactory that can be used to instantiate a 242 // binary-based plugin. 243 func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactory { 244 result := make(map[string]terraform.ResourceProviderFactory) 245 for k, v := range c.Providers { 246 result[k] = c.providerFactory(v) 247 } 248 249 return result 250 } 251 252 func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory { 253 // Build the plugin client configuration and init the plugin 254 var config plugin.ClientConfig 255 config.Cmd = pluginCmd(path) 256 config.HandshakeConfig = tfplugin.Handshake 257 config.Managed = true 258 config.Plugins = tfplugin.PluginMap 259 client := plugin.NewClient(&config) 260 261 return func() (terraform.ResourceProvider, error) { 262 // Request the RPC client so we can get the provider 263 // so we can build the actual RPC-implemented provider. 264 rpcClient, err := client.Client() 265 if err != nil { 266 return nil, err 267 } 268 269 raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName) 270 if err != nil { 271 return nil, err 272 } 273 274 return raw.(terraform.ResourceProvider), nil 275 } 276 } 277 278 // ProvisionerFactories returns the mapping of prefixes to 279 // ResourceProvisionerFactory that can be used to instantiate a 280 // binary-based plugin. 281 func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisionerFactory { 282 result := make(map[string]terraform.ResourceProvisionerFactory) 283 for k, v := range c.Provisioners { 284 result[k] = c.provisionerFactory(v) 285 } 286 287 return result 288 } 289 290 func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory { 291 // Build the plugin client configuration and init the plugin 292 var config plugin.ClientConfig 293 config.HandshakeConfig = tfplugin.Handshake 294 config.Cmd = pluginCmd(path) 295 config.Managed = true 296 config.Plugins = tfplugin.PluginMap 297 client := plugin.NewClient(&config) 298 299 return func() (terraform.ResourceProvisioner, error) { 300 rpcClient, err := client.Client() 301 if err != nil { 302 return nil, err 303 } 304 305 raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName) 306 if err != nil { 307 return nil, err 308 } 309 310 return raw.(terraform.ResourceProvisioner), nil 311 } 312 } 313 314 func pluginCmd(path string) *exec.Cmd { 315 cmdPath := "" 316 317 // If the path doesn't contain a separator, look in the same 318 // directory as the Terraform executable first. 319 if !strings.ContainsRune(path, os.PathSeparator) { 320 exePath, err := osext.Executable() 321 if err == nil { 322 temp := filepath.Join( 323 filepath.Dir(exePath), 324 filepath.Base(path)) 325 326 if _, err := os.Stat(temp); err == nil { 327 cmdPath = temp 328 } 329 } 330 331 // If we still haven't found the executable, look for it 332 // in the PATH. 333 if v, err := exec.LookPath(path); err == nil { 334 cmdPath = v 335 } 336 } 337 338 // No plugin binary found, so try to use an internal plugin. 339 if strings.Contains(path, command.TFSPACE) { 340 parts := strings.Split(path, command.TFSPACE) 341 return exec.Command(parts[0], parts[1:]...) 342 } 343 344 // If we still don't have a path, then just set it to the original 345 // given path. 346 if cmdPath == "" { 347 cmdPath = path 348 } 349 350 // Build the command to execute the plugin 351 return exec.Command(cmdPath) 352 }