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