github.com/jrasell/terraform@v0.6.17-0.20160523115548-2652f5232949/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. 124 for name, _ := range command.InternalProviders { 125 if path, found := c.Providers[name]; found { 126 ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provider.\n"+ 127 " If you did not expect to see this message you will need to remove the old plugin.\n"+ 128 " See https://www.terraform.io/docs/plugins/index.html", path, name)) 129 } else { 130 131 cmd, err := command.BuildPluginCommandString("provider", name) 132 if err != nil { 133 return err 134 } 135 c.Providers[name] = cmd 136 } 137 } 138 for name, _ := range command.InternalProvisioners { 139 if path, found := c.Provisioners[name]; found { 140 ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provisioner.\n"+ 141 " If you did not expect to see this message you will need to remove the old plugin.\n"+ 142 " See https://www.terraform.io/docs/plugins/index.html", path, name)) 143 } else { 144 cmd, err := command.BuildPluginCommandString("provisioner", name) 145 if err != nil { 146 return err 147 } 148 c.Provisioners[name] = cmd 149 } 150 } 151 152 return nil 153 } 154 155 // Merge merges two configurations and returns a third entirely 156 // new configuration with the two merged. 157 func (c1 *Config) Merge(c2 *Config) *Config { 158 var result Config 159 result.Providers = make(map[string]string) 160 result.Provisioners = make(map[string]string) 161 for k, v := range c1.Providers { 162 result.Providers[k] = v 163 } 164 for k, v := range c2.Providers { 165 result.Providers[k] = v 166 } 167 for k, v := range c1.Provisioners { 168 result.Provisioners[k] = v 169 } 170 for k, v := range c2.Provisioners { 171 result.Provisioners[k] = v 172 } 173 174 return &result 175 } 176 177 func (c *Config) discover(path string) error { 178 var err error 179 180 if !filepath.IsAbs(path) { 181 path, err = filepath.Abs(path) 182 if err != nil { 183 return err 184 } 185 } 186 187 err = c.discoverSingle( 188 filepath.Join(path, "terraform-provider-*"), &c.Providers) 189 if err != nil { 190 return err 191 } 192 193 err = c.discoverSingle( 194 filepath.Join(path, "terraform-provisioner-*"), &c.Provisioners) 195 if err != nil { 196 return err 197 } 198 199 return nil 200 } 201 202 func (c *Config) discoverSingle(glob string, m *map[string]string) error { 203 matches, err := filepath.Glob(glob) 204 if err != nil { 205 return err 206 } 207 208 if *m == nil { 209 *m = make(map[string]string) 210 } 211 212 for _, match := range matches { 213 file := filepath.Base(match) 214 215 // If the filename has a ".", trim up to there 216 if idx := strings.Index(file, "."); idx >= 0 { 217 file = file[:idx] 218 } 219 220 // Look for foo-bar-baz. The plugin name is "baz" 221 parts := strings.SplitN(file, "-", 3) 222 if len(parts) != 3 { 223 continue 224 } 225 226 log.Printf("[DEBUG] Discovered plugin: %s = %s", parts[2], match) 227 (*m)[parts[2]] = match 228 } 229 230 return nil 231 } 232 233 // ProviderFactories returns the mapping of prefixes to 234 // ResourceProviderFactory that can be used to instantiate a 235 // binary-based plugin. 236 func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactory { 237 result := make(map[string]terraform.ResourceProviderFactory) 238 for k, v := range c.Providers { 239 result[k] = c.providerFactory(v) 240 } 241 242 return result 243 } 244 245 func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory { 246 // Build the plugin client configuration and init the plugin 247 var config plugin.ClientConfig 248 config.Cmd = pluginCmd(path) 249 config.HandshakeConfig = tfplugin.Handshake 250 config.Managed = true 251 config.Plugins = tfplugin.PluginMap 252 client := plugin.NewClient(&config) 253 254 return func() (terraform.ResourceProvider, error) { 255 // Request the RPC client so we can get the provider 256 // so we can build the actual RPC-implemented provider. 257 rpcClient, err := client.Client() 258 if err != nil { 259 return nil, err 260 } 261 262 raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName) 263 if err != nil { 264 return nil, err 265 } 266 267 return raw.(terraform.ResourceProvider), nil 268 } 269 } 270 271 // ProvisionerFactories returns the mapping of prefixes to 272 // ResourceProvisionerFactory that can be used to instantiate a 273 // binary-based plugin. 274 func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisionerFactory { 275 result := make(map[string]terraform.ResourceProvisionerFactory) 276 for k, v := range c.Provisioners { 277 result[k] = c.provisionerFactory(v) 278 } 279 280 return result 281 } 282 283 func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory { 284 // Build the plugin client configuration and init the plugin 285 var config plugin.ClientConfig 286 config.HandshakeConfig = tfplugin.Handshake 287 config.Cmd = pluginCmd(path) 288 config.Managed = true 289 config.Plugins = tfplugin.PluginMap 290 client := plugin.NewClient(&config) 291 292 return func() (terraform.ResourceProvisioner, error) { 293 rpcClient, err := client.Client() 294 if err != nil { 295 return nil, err 296 } 297 298 raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName) 299 if err != nil { 300 return nil, err 301 } 302 303 return raw.(terraform.ResourceProvisioner), nil 304 } 305 } 306 307 func pluginCmd(path string) *exec.Cmd { 308 cmdPath := "" 309 310 // If the path doesn't contain a separator, look in the same 311 // directory as the Terraform executable first. 312 if !strings.ContainsRune(path, os.PathSeparator) { 313 exePath, err := osext.Executable() 314 if err == nil { 315 temp := filepath.Join( 316 filepath.Dir(exePath), 317 filepath.Base(path)) 318 319 if _, err := os.Stat(temp); err == nil { 320 cmdPath = temp 321 } 322 } 323 324 // If we still haven't found the executable, look for it 325 // in the PATH. 326 if v, err := exec.LookPath(path); err == nil { 327 cmdPath = v 328 } 329 } 330 331 // No plugin binary found, so try to use an internal plugin. 332 if strings.Contains(path, command.TFSPACE) { 333 parts := strings.Split(path, command.TFSPACE) 334 return exec.Command(parts[0], parts[1:]...) 335 } 336 337 // If we still don't have a path, then just set it to the original 338 // given path. 339 if cmdPath == "" { 340 cmdPath = path 341 } 342 343 // Build the command to execute the plugin 344 return exec.Command(cmdPath) 345 }