github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/meta_providers.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "log" 8 "os" 9 "os/exec" 10 "strings" 11 12 plugin "github.com/hashicorp/go-plugin" 13 14 "github.com/hashicorp/terraform/internal/addrs" 15 terraformProvider "github.com/hashicorp/terraform/internal/builtin/providers/terraform" 16 "github.com/hashicorp/terraform/internal/getproviders" 17 "github.com/hashicorp/terraform/internal/logging" 18 "github.com/hashicorp/terraform/internal/moduletest" 19 tfplugin "github.com/hashicorp/terraform/internal/plugin" 20 tfplugin6 "github.com/hashicorp/terraform/internal/plugin6" 21 "github.com/hashicorp/terraform/internal/providercache" 22 "github.com/hashicorp/terraform/internal/providers" 23 "github.com/hashicorp/terraform/internal/tfdiags" 24 ) 25 26 // The TF_DISABLE_PLUGIN_TLS environment variable is intended only for use by 27 // the plugin SDK test framework, to reduce startup overhead when rapidly 28 // launching and killing lots of instances of the same provider. 29 // 30 // This is not intended to be set by end-users. 31 var enableProviderAutoMTLS = os.Getenv("TF_DISABLE_PLUGIN_TLS") == "" 32 33 // providerInstaller returns an object that knows how to install providers and 34 // how to recover the selections from a prior installation process. 35 // 36 // The resulting provider installer is constructed from the results of 37 // the other methods providerLocalCacheDir, providerGlobalCacheDir, and 38 // providerInstallSource. 39 // 40 // Only one object returned from this method should be live at any time, 41 // because objects inside contain caches that must be maintained properly. 42 // Because this method wraps a result from providerLocalCacheDir, that 43 // limitation applies also to results from that method. 44 func (m *Meta) providerInstaller() *providercache.Installer { 45 return m.providerInstallerCustomSource(m.providerInstallSource()) 46 } 47 48 // providerInstallerCustomSource is a variant of providerInstaller that 49 // allows the caller to specify a different installation source than the one 50 // that would naturally be selected. 51 // 52 // The result of this method has the same dependencies and constraints as 53 // providerInstaller. 54 // 55 // The result of providerInstallerCustomSource differs from 56 // providerInstaller only in how it determines package installation locations 57 // during EnsureProviderVersions. A caller that doesn't call 58 // EnsureProviderVersions (anything other than "terraform init") can safely 59 // just use the providerInstaller method unconditionally. 60 func (m *Meta) providerInstallerCustomSource(source getproviders.Source) *providercache.Installer { 61 targetDir := m.providerLocalCacheDir() 62 globalCacheDir := m.providerGlobalCacheDir() 63 inst := providercache.NewInstaller(targetDir, source) 64 if globalCacheDir != nil { 65 inst.SetGlobalCacheDir(globalCacheDir) 66 inst.SetGlobalCacheDirMayBreakDependencyLockFile(m.PluginCacheMayBreakDependencyLockFile) 67 } 68 var builtinProviderTypes []string 69 for ty := range m.internalProviders() { 70 builtinProviderTypes = append(builtinProviderTypes, ty) 71 } 72 inst.SetBuiltInProviderTypes(builtinProviderTypes) 73 unmanagedProviderTypes := make(map[addrs.Provider]struct{}, len(m.UnmanagedProviders)) 74 for ty := range m.UnmanagedProviders { 75 unmanagedProviderTypes[ty] = struct{}{} 76 } 77 inst.SetUnmanagedProviderTypes(unmanagedProviderTypes) 78 return inst 79 } 80 81 // providerCustomLocalDirectorySource produces a provider source that consults 82 // only the given local filesystem directories for plugins to install. 83 // 84 // This is used to implement the -plugin-dir option for "terraform init", where 85 // the result of this method is used instead of what would've been returned 86 // from m.providerInstallSource. 87 // 88 // If the given list of directories is empty then the resulting source will 89 // have no providers available for installation at all. 90 func (m *Meta) providerCustomLocalDirectorySource(dirs []string) getproviders.Source { 91 var ret getproviders.MultiSource 92 for _, dir := range dirs { 93 ret = append(ret, getproviders.MultiSourceSelector{ 94 Source: getproviders.NewFilesystemMirrorSource(dir), 95 }) 96 } 97 return ret 98 } 99 100 // providerLocalCacheDir returns an object representing the 101 // configuration-specific local cache directory. This is the 102 // only location consulted for provider plugin packages for Terraform 103 // operations other than provider installation. 104 // 105 // Only the provider installer (in "terraform init") is permitted to make 106 // modifications to this cache directory. All other commands must treat it 107 // as read-only. 108 // 109 // Only one object returned from this method should be live at any time, 110 // because objects inside contain caches that must be maintained properly. 111 func (m *Meta) providerLocalCacheDir() *providercache.Dir { 112 m.fixupMissingWorkingDir() 113 dir := m.WorkingDir.ProviderLocalCacheDir() 114 return providercache.NewDir(dir) 115 } 116 117 // providerGlobalCacheDir returns an object representing the shared global 118 // provider cache directory, used as a read-through cache when installing 119 // new provider plugin packages. 120 // 121 // This function may return nil, in which case there is no global cache 122 // configured and new packages should be downloaded directly into individual 123 // configuration-specific cache directories. 124 // 125 // Only one object returned from this method should be live at any time, 126 // because objects inside contain caches that must be maintained properly. 127 func (m *Meta) providerGlobalCacheDir() *providercache.Dir { 128 dir := m.PluginCacheDir 129 if dir == "" { 130 return nil // cache disabled 131 } 132 return providercache.NewDir(dir) 133 } 134 135 // providerInstallSource returns an object that knows how to consult one or 136 // more external sources to determine the availability of and package 137 // locations for versions of Terraform providers that are available for 138 // automatic installation. 139 // 140 // This returns the standard provider install source that consults a number 141 // of directories selected either automatically or via the CLI configuration. 142 // Users may choose to override this during a "terraform init" command by 143 // specifying one or more -plugin-dir options, in which case the installation 144 // process will construct its own source consulting only those directories 145 // and use that instead. 146 func (m *Meta) providerInstallSource() getproviders.Source { 147 // A provider source should always be provided in normal use, but our 148 // unit tests might not always populate Meta fully and so we'll be robust 149 // by returning a non-nil source that just always answers that no plugins 150 // are available. 151 if m.ProviderSource == nil { 152 // A multi-source with no underlying sources is effectively an 153 // always-empty source. 154 return getproviders.MultiSource(nil) 155 } 156 return m.ProviderSource 157 } 158 159 // providerDevOverrideInitWarnings returns a diagnostics that contains at 160 // least one warning if and only if there is at least one provider development 161 // override in effect. If not, the result is always empty. The result never 162 // contains error diagnostics. 163 // 164 // The init command can use this to include a warning that the results 165 // may differ from what's expected due to the development overrides. For 166 // other commands, providerDevOverrideRuntimeWarnings should be used. 167 func (m *Meta) providerDevOverrideInitWarnings() tfdiags.Diagnostics { 168 if len(m.ProviderDevOverrides) == 0 { 169 return nil 170 } 171 var detailMsg strings.Builder 172 detailMsg.WriteString("The following provider development overrides are set in the CLI configuration:\n") 173 for addr, path := range m.ProviderDevOverrides { 174 detailMsg.WriteString(fmt.Sprintf(" - %s in %s\n", addr.ForDisplay(), path)) 175 } 176 detailMsg.WriteString("\nSkip terraform init when using provider development overrides. It is not necessary and may error unexpectedly.") 177 return tfdiags.Diagnostics{ 178 tfdiags.Sourceless( 179 tfdiags.Warning, 180 "Provider development overrides are in effect", 181 detailMsg.String(), 182 ), 183 } 184 } 185 186 // providerDevOverrideRuntimeWarnings returns a diagnostics that contains at 187 // least one warning if and only if there is at least one provider development 188 // override in effect. If not, the result is always empty. The result never 189 // contains error diagnostics. 190 // 191 // Certain commands can use this to include a warning that their results 192 // may differ from what's expected due to the development overrides. It's 193 // not necessary to bother the user with this warning on every command, but 194 // it's helpful to return it on commands that have externally-visible side 195 // effects and on commands that are used to verify conformance to schemas. 196 // 197 // See providerDevOverrideInitWarnings for warnings specific to the init 198 // command. 199 func (m *Meta) providerDevOverrideRuntimeWarnings() tfdiags.Diagnostics { 200 if len(m.ProviderDevOverrides) == 0 { 201 return nil 202 } 203 var detailMsg strings.Builder 204 detailMsg.WriteString("The following provider development overrides are set in the CLI configuration:\n") 205 for addr, path := range m.ProviderDevOverrides { 206 detailMsg.WriteString(fmt.Sprintf(" - %s in %s\n", addr.ForDisplay(), path)) 207 } 208 detailMsg.WriteString("\nThe behavior may therefore not match any released version of the provider and applying changes may cause the state to become incompatible with published releases.") 209 return tfdiags.Diagnostics{ 210 tfdiags.Sourceless( 211 tfdiags.Warning, 212 "Provider development overrides are in effect", 213 detailMsg.String(), 214 ), 215 } 216 } 217 218 // providerFactories uses the selections made previously by an installer in 219 // the local cache directory (m.providerLocalCacheDir) to produce a map 220 // from provider addresses to factory functions to create instances of 221 // those providers. 222 // 223 // providerFactories will return an error if the installer's selections cannot 224 // be honored with what is currently in the cache, such as if a selected 225 // package has been removed from the cache or if the contents of a selected 226 // package have been modified outside of the installer. If it returns an error, 227 // the returned map may be incomplete or invalid, but will be as complete 228 // as possible given the cause of the error. 229 func (m *Meta) providerFactories() (map[addrs.Provider]providers.Factory, error) { 230 locks, diags := m.lockedDependencies() 231 if diags.HasErrors() { 232 return nil, fmt.Errorf("failed to read dependency lock file: %s", diags.Err()) 233 } 234 235 // We'll always run through all of our providers, even if one of them 236 // encounters an error, so that we can potentially report multiple errors 237 // where appropriate and so that callers can potentially make use of the 238 // partial result we return if e.g. they want to enumerate which providers 239 // are available, or call into one of the providers that didn't fail. 240 errs := make(map[addrs.Provider]error) 241 242 // For the providers from the lock file, we expect them to be already 243 // available in the provider cache because "terraform init" should already 244 // have put them there. 245 providerLocks := locks.AllProviders() 246 cacheDir := m.providerLocalCacheDir() 247 248 // The internal providers are _always_ available, even if the configuration 249 // doesn't request them, because they don't need any special installation 250 // and they'll just be ignored if not used. 251 internalFactories := m.internalProviders() 252 253 // We have two different special cases aimed at provider development 254 // use-cases, which are not for "production" use: 255 // - The CLI config can specify that a particular provider should always 256 // use a plugin from a particular local directory, ignoring anything the 257 // lock file or cache directory might have to say about it. This is useful 258 // for manual testing of local development builds. 259 // - The Terraform SDK test harness (and possibly other callers in future) 260 // can ask that we use its own already-started provider servers, which we 261 // call "unmanaged" because Terraform isn't responsible for starting 262 // and stopping them. This is intended for automated testing where a 263 // calling harness is responsible both for starting the provider server 264 // and orchestrating one or more non-interactive Terraform runs that then 265 // exercise it. 266 // Unmanaged providers take precedence over overridden providers because 267 // overrides are typically a "session-level" setting while unmanaged 268 // providers are typically scoped to a single unattended command. 269 devOverrideProviders := m.ProviderDevOverrides 270 unmanagedProviders := m.UnmanagedProviders 271 272 factories := make(map[addrs.Provider]providers.Factory, len(providerLocks)+len(internalFactories)+len(unmanagedProviders)) 273 for name, factory := range internalFactories { 274 factories[addrs.NewBuiltInProvider(name)] = factory 275 } 276 for provider, lock := range providerLocks { 277 reportError := func(thisErr error) { 278 errs[provider] = thisErr 279 // We'll populate a provider factory that just echoes our error 280 // again if called, which allows us to still report a helpful 281 // error even if it gets detected downstream somewhere from the 282 // caller using our partial result. 283 factories[provider] = providerFactoryError(thisErr) 284 } 285 286 if locks.ProviderIsOverridden(provider) { 287 // Overridden providers we'll handle with the other separate 288 // loops below, for dev overrides etc. 289 continue 290 } 291 292 version := lock.Version() 293 cached := cacheDir.ProviderVersion(provider, version) 294 if cached == nil { 295 reportError(fmt.Errorf( 296 "there is no package for %s %s cached in %s", 297 provider, version, cacheDir.BasePath(), 298 )) 299 continue 300 } 301 // The cached package must match one of the checksums recorded in 302 // the lock file, if any. 303 if allowedHashes := lock.PreferredHashes(); len(allowedHashes) != 0 { 304 matched, err := cached.MatchesAnyHash(allowedHashes) 305 if err != nil { 306 reportError(fmt.Errorf( 307 "failed to verify checksum of %s %s package cached in in %s: %s", 308 provider, version, cacheDir.BasePath(), err, 309 )) 310 continue 311 } 312 if !matched { 313 reportError(fmt.Errorf( 314 "the cached package for %s %s (in %s) does not match any of the checksums recorded in the dependency lock file", 315 provider, version, cacheDir.BasePath(), 316 )) 317 continue 318 } 319 } 320 factories[provider] = providerFactory(cached) 321 } 322 for provider, localDir := range devOverrideProviders { 323 factories[provider] = devOverrideProviderFactory(provider, localDir) 324 } 325 for provider, reattach := range unmanagedProviders { 326 factories[provider] = unmanagedProviderFactory(provider, reattach) 327 } 328 329 var err error 330 if len(errs) > 0 { 331 err = providerPluginErrors(errs) 332 } 333 return factories, err 334 } 335 336 func (m *Meta) internalProviders() map[string]providers.Factory { 337 return map[string]providers.Factory{ 338 "terraform": func() (providers.Interface, error) { 339 return terraformProvider.NewProvider(), nil 340 }, 341 "test": func() (providers.Interface, error) { 342 return moduletest.NewProvider(), nil 343 }, 344 } 345 } 346 347 // providerFactory produces a provider factory that runs up the executable 348 // file in the given cache package and uses go-plugin to implement 349 // providers.Interface against it. 350 func providerFactory(meta *providercache.CachedProvider) providers.Factory { 351 return func() (providers.Interface, error) { 352 execFile, err := meta.ExecutableFile() 353 if err != nil { 354 return nil, err 355 } 356 357 config := &plugin.ClientConfig{ 358 HandshakeConfig: tfplugin.Handshake, 359 Logger: logging.NewProviderLogger(""), 360 AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, 361 Managed: true, 362 Cmd: exec.Command(execFile), 363 AutoMTLS: enableProviderAutoMTLS, 364 VersionedPlugins: tfplugin.VersionedPlugins, 365 SyncStdout: logging.PluginOutputMonitor(fmt.Sprintf("%s:stdout", meta.Provider)), 366 SyncStderr: logging.PluginOutputMonitor(fmt.Sprintf("%s:stderr", meta.Provider)), 367 } 368 369 client := plugin.NewClient(config) 370 rpcClient, err := client.Client() 371 if err != nil { 372 return nil, err 373 } 374 375 raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName) 376 if err != nil { 377 return nil, err 378 } 379 380 // store the client so that the plugin can kill the child process 381 protoVer := client.NegotiatedVersion() 382 switch protoVer { 383 case 5: 384 p := raw.(*tfplugin.GRPCProvider) 385 p.PluginClient = client 386 return p, nil 387 case 6: 388 p := raw.(*tfplugin6.GRPCProvider) 389 p.PluginClient = client 390 return p, nil 391 default: 392 panic("unsupported protocol version") 393 } 394 } 395 } 396 397 func devOverrideProviderFactory(provider addrs.Provider, localDir getproviders.PackageLocalDir) providers.Factory { 398 // A dev override is essentially a synthetic cache entry for our purposes 399 // here, so that's how we'll construct it. The providerFactory function 400 // doesn't actually care about the version, so we can leave it 401 // unspecified: overridden providers are not explicitly versioned. 402 log.Printf("[DEBUG] Provider %s is overridden to load from %s", provider, localDir) 403 return providerFactory(&providercache.CachedProvider{ 404 Provider: provider, 405 Version: getproviders.UnspecifiedVersion, 406 PackageDir: string(localDir), 407 }) 408 } 409 410 // unmanagedProviderFactory produces a provider factory that uses the passed 411 // reattach information to connect to go-plugin processes that are already 412 // running, and implements providers.Interface against it. 413 func unmanagedProviderFactory(provider addrs.Provider, reattach *plugin.ReattachConfig) providers.Factory { 414 return func() (providers.Interface, error) { 415 config := &plugin.ClientConfig{ 416 HandshakeConfig: tfplugin.Handshake, 417 Logger: logging.NewProviderLogger("unmanaged."), 418 AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, 419 Managed: false, 420 Reattach: reattach, 421 SyncStdout: logging.PluginOutputMonitor(fmt.Sprintf("%s:stdout", provider)), 422 SyncStderr: logging.PluginOutputMonitor(fmt.Sprintf("%s:stderr", provider)), 423 } 424 425 if reattach.ProtocolVersion == 0 { 426 // As of the 0.15 release, sdk.v2 doesn't include the protocol 427 // version in the ReattachConfig (only recently added to 428 // go-plugin), so client.NegotiatedVersion() always returns 0. We 429 // assume that an unmanaged provider reporting protocol version 0 is 430 // actually using proto v5 for backwards compatibility. 431 if defaultPlugins, ok := tfplugin.VersionedPlugins[5]; ok { 432 config.Plugins = defaultPlugins 433 } else { 434 return nil, errors.New("no supported plugins for protocol 0") 435 } 436 } else if plugins, ok := tfplugin.VersionedPlugins[reattach.ProtocolVersion]; !ok { 437 return nil, fmt.Errorf("no supported plugins for protocol %d", reattach.ProtocolVersion) 438 } else { 439 config.Plugins = plugins 440 } 441 442 client := plugin.NewClient(config) 443 rpcClient, err := client.Client() 444 if err != nil { 445 return nil, err 446 } 447 448 raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName) 449 if err != nil { 450 return nil, err 451 } 452 453 // store the client so that the plugin can kill the child process 454 protoVer := client.NegotiatedVersion() 455 switch protoVer { 456 case 0, 5: 457 // As of the 0.15 release, sdk.v2 doesn't include the protocol 458 // version in the ReattachConfig (only recently added to 459 // go-plugin), so client.NegotiatedVersion() always returns 0. We 460 // assume that an unmanaged provider reporting protocol version 0 is 461 // actually using proto v5 for backwards compatibility. 462 p := raw.(*tfplugin.GRPCProvider) 463 p.PluginClient = client 464 return p, nil 465 case 6: 466 p := raw.(*tfplugin6.GRPCProvider) 467 p.PluginClient = client 468 return p, nil 469 default: 470 return nil, fmt.Errorf("unsupported protocol version %d", protoVer) 471 } 472 } 473 } 474 475 // providerFactoryError is a stub providers.Factory that returns an error 476 // when called. It's used to allow providerFactories to still produce a 477 // factory for each available provider in an error case, for situations 478 // where the caller can do something useful with that partial result. 479 func providerFactoryError(err error) providers.Factory { 480 return func() (providers.Interface, error) { 481 return nil, err 482 } 483 } 484 485 // providerPluginErrors is an error implementation we can return from 486 // Meta.providerFactories to capture potentially multiple errors about the 487 // locally-cached plugins (or lack thereof) for particular external providers. 488 // 489 // Some functions closer to the UI layer can sniff for this error type in order 490 // to return a more helpful error message. 491 type providerPluginErrors map[addrs.Provider]error 492 493 func (errs providerPluginErrors) Error() string { 494 if len(errs) == 1 { 495 for addr, err := range errs { 496 return fmt.Sprintf("%s: %s", addr, err) 497 } 498 } 499 var buf bytes.Buffer 500 fmt.Fprintf(&buf, "missing or corrupted provider plugins:") 501 for addr, err := range errs { 502 fmt.Fprintf(&buf, "\n - %s: %s", addr, err) 503 } 504 return buf.String() 505 }