github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/command/plugins.go (about) 1 package command 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "log" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "runtime" 13 "strings" 14 15 plugin "github.com/hashicorp/go-plugin" 16 terraformProvider "github.com/hashicorp/terraform/builtin/providers/terraform" 17 tfplugin "github.com/hashicorp/terraform/plugin" 18 "github.com/hashicorp/terraform/plugin/discovery" 19 "github.com/hashicorp/terraform/terraform" 20 "github.com/kardianos/osext" 21 ) 22 23 // multiVersionProviderResolver is an implementation of 24 // terraform.ResourceProviderResolver that matches the given version constraints 25 // against a set of versioned provider plugins to find the newest version of 26 // each that satisfies the given constraints. 27 type multiVersionProviderResolver struct { 28 Available discovery.PluginMetaSet 29 30 // Internal is a map that overrides the usual plugin selection process 31 // for internal plugins. These plugins do not support version constraints 32 // (will produce an error if one is set). This should be used only in 33 // exceptional circumstances since it forces the provider's release 34 // schedule to be tied to that of Terraform Core. 35 Internal map[string]terraform.ResourceProviderFactory 36 } 37 38 func choosePlugins(avail discovery.PluginMetaSet, internal map[string]terraform.ResourceProviderFactory, reqd discovery.PluginRequirements) map[string]discovery.PluginMeta { 39 candidates := avail.ConstrainVersions(reqd) 40 ret := map[string]discovery.PluginMeta{} 41 for name, metas := range candidates { 42 // If the provider is in our internal map then we ignore any 43 // discovered plugins for it since these are dealt with separately. 44 if _, isInternal := internal[name]; isInternal { 45 continue 46 } 47 48 if len(metas) == 0 { 49 continue 50 } 51 ret[name] = metas.Newest() 52 } 53 return ret 54 } 55 56 func (r *multiVersionProviderResolver) ResolveProviders( 57 reqd discovery.PluginRequirements, 58 ) (map[string]terraform.ResourceProviderFactory, []error) { 59 factories := make(map[string]terraform.ResourceProviderFactory, len(reqd)) 60 var errs []error 61 62 chosen := choosePlugins(r.Available, r.Internal, reqd) 63 for name, req := range reqd { 64 if factory, isInternal := r.Internal[name]; isInternal { 65 if !req.Versions.Unconstrained() { 66 errs = append(errs, fmt.Errorf("provider.%s: this provider is built in to Terraform and so it does not support version constraints", name)) 67 continue 68 } 69 factories[name] = factory 70 continue 71 } 72 73 if newest, available := chosen[name]; available { 74 digest, err := newest.SHA256() 75 if err != nil { 76 errs = append(errs, fmt.Errorf("provider.%s: failed to load plugin to verify its signature: %s", name, err)) 77 continue 78 } 79 if !reqd[name].AcceptsSHA256(digest) { 80 errs = append(errs, fmt.Errorf("provider.%s: new or changed plugin executable", name)) 81 continue 82 } 83 84 client := tfplugin.Client(newest) 85 factories[name] = providerFactory(client) 86 } else { 87 msg := fmt.Sprintf("provider.%s: no suitable version installed", name) 88 89 required := req.Versions.String() 90 // no version is unconstrained 91 if required == "" { 92 required = "(any version)" 93 } 94 95 foundVersions := []string{} 96 for meta := range r.Available.WithName(name) { 97 foundVersions = append(foundVersions, fmt.Sprintf("%q", meta.Version)) 98 } 99 100 found := "none" 101 if len(foundVersions) > 0 { 102 found = strings.Join(foundVersions, ", ") 103 } 104 105 msg += fmt.Sprintf("\n version requirements: %q\n versions installed: %s", required, found) 106 107 errs = append(errs, errors.New(msg)) 108 } 109 } 110 111 return factories, errs 112 } 113 114 // store the user-supplied path for plugin discovery 115 func (m *Meta) storePluginPath(pluginPath []string) error { 116 if len(pluginPath) == 0 { 117 return nil 118 } 119 120 path := filepath.Join(m.DataDir(), PluginPathFile) 121 122 // remove the plugin dir record if the path was set to an empty string 123 if len(pluginPath) == 1 && (pluginPath[0] == "") { 124 err := os.Remove(path) 125 if !os.IsNotExist(err) { 126 return err 127 } 128 return nil 129 } 130 131 js, err := json.MarshalIndent(pluginPath, "", " ") 132 if err != nil { 133 return err 134 } 135 136 // if this fails, so will WriteFile 137 os.MkdirAll(m.DataDir(), 0755) 138 139 return ioutil.WriteFile(path, js, 0644) 140 } 141 142 // Load the user-defined plugin search path into Meta.pluginPath if the file 143 // exists. 144 func (m *Meta) loadPluginPath() ([]string, error) { 145 js, err := ioutil.ReadFile(filepath.Join(m.DataDir(), PluginPathFile)) 146 if os.IsNotExist(err) { 147 return nil, nil 148 } 149 150 if err != nil { 151 return nil, err 152 } 153 154 var pluginPath []string 155 if err := json.Unmarshal(js, &pluginPath); err != nil { 156 return nil, err 157 } 158 159 return pluginPath, nil 160 } 161 162 // the default location for automatically installed plugins 163 func (m *Meta) pluginDir() string { 164 return filepath.Join(m.DataDir(), "plugins", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)) 165 } 166 167 // pluginDirs return a list of directories to search for plugins. 168 // 169 // Earlier entries in this slice get priority over later when multiple copies 170 // of the same plugin version are found, but newer versions always override 171 // older versions where both satisfy the provider version constraints. 172 func (m *Meta) pluginDirs(includeAutoInstalled bool) []string { 173 // user defined paths take precedence 174 if len(m.pluginPath) > 0 { 175 return m.pluginPath 176 } 177 178 // When searching the following directories, earlier entries get precedence 179 // if the same plugin version is found twice, but newer versions will 180 // always get preference below regardless of where they are coming from. 181 // TODO: Add auto-install dir, default vendor dir and optional override 182 // vendor dir(s). 183 dirs := []string{"."} 184 185 // Look in the same directory as the Terraform executable. 186 // If found, this replaces what we found in the config path. 187 exePath, err := osext.Executable() 188 if err != nil { 189 log.Printf("[ERROR] Error discovering exe directory: %s", err) 190 } else { 191 dirs = append(dirs, filepath.Dir(exePath)) 192 } 193 194 // add the user vendor directory 195 dirs = append(dirs, DefaultPluginVendorDir) 196 197 if includeAutoInstalled { 198 dirs = append(dirs, m.pluginDir()) 199 } 200 dirs = append(dirs, m.GlobalPluginDirs...) 201 202 return dirs 203 } 204 205 func (m *Meta) pluginCache() discovery.PluginCache { 206 dir := m.PluginCacheDir 207 if dir == "" { 208 return nil // cache disabled 209 } 210 211 dir = filepath.Join(dir, pluginMachineName) 212 213 return discovery.NewLocalPluginCache(dir) 214 } 215 216 // providerPluginSet returns the set of valid providers that were discovered in 217 // the defined search paths. 218 func (m *Meta) providerPluginSet() discovery.PluginMetaSet { 219 plugins := discovery.FindPlugins("provider", m.pluginDirs(true)) 220 221 // Add providers defined in the legacy .terraformrc, 222 if m.PluginOverrides != nil { 223 for k, v := range m.PluginOverrides.Providers { 224 log.Printf("[DEBUG] found plugin override in .terraformrc: %q, %q", k, v) 225 } 226 plugins = plugins.OverridePaths(m.PluginOverrides.Providers) 227 } 228 229 plugins, _ = plugins.ValidateVersions() 230 231 for p := range plugins { 232 log.Printf("[DEBUG] found valid plugin: %q, %q, %q", p.Name, p.Version, p.Path) 233 } 234 235 return plugins 236 } 237 238 // providerPluginAutoInstalledSet returns the set of providers that exist 239 // within the auto-install directory. 240 func (m *Meta) providerPluginAutoInstalledSet() discovery.PluginMetaSet { 241 plugins := discovery.FindPlugins("provider", []string{m.pluginDir()}) 242 plugins, _ = plugins.ValidateVersions() 243 244 for p := range plugins { 245 log.Printf("[DEBUG] found valid plugin: %q", p.Name) 246 } 247 248 return plugins 249 } 250 251 // providerPluginManuallyInstalledSet returns the set of providers that exist 252 // in all locations *except* the auto-install directory. 253 func (m *Meta) providerPluginManuallyInstalledSet() discovery.PluginMetaSet { 254 plugins := discovery.FindPlugins("provider", m.pluginDirs(false)) 255 256 // Add providers defined in the legacy .terraformrc, 257 if m.PluginOverrides != nil { 258 for k, v := range m.PluginOverrides.Providers { 259 log.Printf("[DEBUG] found plugin override in .terraformrc: %q, %q", k, v) 260 } 261 262 plugins = plugins.OverridePaths(m.PluginOverrides.Providers) 263 } 264 265 plugins, _ = plugins.ValidateVersions() 266 267 for p := range plugins { 268 log.Printf("[DEBUG] found valid plugin: %q, %q, %q", p.Name, p.Version, p.Path) 269 } 270 271 return plugins 272 } 273 274 func (m *Meta) providerResolver() terraform.ResourceProviderResolver { 275 return &multiVersionProviderResolver{ 276 Available: m.providerPluginSet(), 277 Internal: m.internalProviders(), 278 } 279 } 280 281 func (m *Meta) internalProviders() map[string]terraform.ResourceProviderFactory { 282 return map[string]terraform.ResourceProviderFactory{ 283 "terraform": func() (terraform.ResourceProvider, error) { 284 return terraformProvider.Provider(), nil 285 }, 286 } 287 } 288 289 // filter the requirements returning only the providers that we can't resolve 290 func (m *Meta) missingPlugins(avail discovery.PluginMetaSet, reqd discovery.PluginRequirements) discovery.PluginRequirements { 291 missing := make(discovery.PluginRequirements) 292 293 candidates := avail.ConstrainVersions(reqd) 294 internal := m.internalProviders() 295 296 for name, versionSet := range reqd { 297 // internal providers can't be missing 298 if _, ok := internal[name]; ok { 299 continue 300 } 301 302 log.Printf("[DEBUG] plugin requirements: %q=%q", name, versionSet.Versions) 303 if metas := candidates[name]; metas.Count() == 0 { 304 missing[name] = versionSet 305 } 306 } 307 308 return missing 309 } 310 311 func (m *Meta) provisionerFactories() map[string]terraform.ResourceProvisionerFactory { 312 dirs := m.pluginDirs(true) 313 plugins := discovery.FindPlugins("provisioner", dirs) 314 plugins, _ = plugins.ValidateVersions() 315 316 // For now our goal is to just find the latest version of each plugin 317 // we have on the system. All provisioners should be at version 0.0.0 318 // currently, so there should actually only be one instance of each plugin 319 // name here, even though the discovery interface forces us to pretend 320 // that might not be true. 321 322 factories := make(map[string]terraform.ResourceProvisionerFactory) 323 324 // Wire up the internal provisioners first. These might be overridden 325 // by discovered provisioners below. 326 for name := range InternalProvisioners { 327 client, err := internalPluginClient("provisioner", name) 328 if err != nil { 329 log.Printf("[WARN] failed to build command line for internal plugin %q: %s", name, err) 330 continue 331 } 332 factories[name] = provisionerFactory(client) 333 } 334 335 byName := plugins.ByName() 336 for name, metas := range byName { 337 // Since we validated versions above and we partitioned the sets 338 // by name, we're guaranteed that the metas in our set all have 339 // valid versions and that there's at least one meta. 340 newest := metas.Newest() 341 client := tfplugin.Client(newest) 342 factories[name] = provisionerFactory(client) 343 } 344 345 return factories 346 } 347 348 func internalPluginClient(kind, name string) (*plugin.Client, error) { 349 cmdLine, err := BuildPluginCommandString(kind, name) 350 if err != nil { 351 return nil, err 352 } 353 354 // See the docstring for BuildPluginCommandString for why we need to do 355 // this split here. 356 cmdArgv := strings.Split(cmdLine, TFSPACE) 357 358 cfg := &plugin.ClientConfig{ 359 Cmd: exec.Command(cmdArgv[0], cmdArgv[1:]...), 360 HandshakeConfig: tfplugin.Handshake, 361 Managed: true, 362 Plugins: tfplugin.PluginMap, 363 } 364 365 return plugin.NewClient(cfg), nil 366 } 367 368 func providerFactory(client *plugin.Client) terraform.ResourceProviderFactory { 369 return func() (terraform.ResourceProvider, error) { 370 // Request the RPC client so we can get the provider 371 // so we can build the actual RPC-implemented provider. 372 rpcClient, err := client.Client() 373 if err != nil { 374 return nil, err 375 } 376 377 raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName) 378 if err != nil { 379 return nil, err 380 } 381 382 return raw.(terraform.ResourceProvider), nil 383 } 384 } 385 386 func provisionerFactory(client *plugin.Client) terraform.ResourceProvisionerFactory { 387 return func() (terraform.ResourceProvisioner, error) { 388 // Request the RPC client so we can get the provisioner 389 // so we can build the actual RPC-implemented provisioner. 390 rpcClient, err := client.Client() 391 if err != nil { 392 return nil, err 393 } 394 395 raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName) 396 if err != nil { 397 return nil, err 398 } 399 400 return raw.(terraform.ResourceProvisioner), nil 401 } 402 }