1package command
2
3import (
4	"errors"
5	"fmt"
6	"log"
7	"os"
8	"os/exec"
9	"path/filepath"
10	"strings"
11
12	"github.com/hashicorp/go-multierror"
13	plugin "github.com/hashicorp/go-plugin"
14
15	"github.com/hashicorp/terraform/internal/addrs"
16	terraformProvider "github.com/hashicorp/terraform/internal/builtin/providers/terraform"
17	"github.com/hashicorp/terraform/internal/getproviders"
18	"github.com/hashicorp/terraform/internal/logging"
19	"github.com/hashicorp/terraform/internal/moduletest"
20	tfplugin "github.com/hashicorp/terraform/internal/plugin"
21	tfplugin6 "github.com/hashicorp/terraform/internal/plugin6"
22	"github.com/hashicorp/terraform/internal/providercache"
23	"github.com/hashicorp/terraform/internal/providers"
24	"github.com/hashicorp/terraform/internal/tfdiags"
25)
26
27// The TF_DISABLE_PLUGIN_TLS environment variable is intended only for use by
28// the plugin SDK test framework, to reduce startup overhead when rapidly
29// launching and killing lots of instances of the same provider.
30//
31// This is not intended to be set by end-users.
32var enableProviderAutoMTLS = os.Getenv("TF_DISABLE_PLUGIN_TLS") == ""
33
34// providerInstaller returns an object that knows how to install providers and
35// how to recover the selections from a prior installation process.
36//
37// The resulting provider installer is constructed from the results of
38// the other methods providerLocalCacheDir, providerGlobalCacheDir, and
39// providerInstallSource.
40//
41// Only one object returned from this method should be live at any time,
42// because objects inside contain caches that must be maintained properly.
43// Because this method wraps a result from providerLocalCacheDir, that
44// limitation applies also to results from that method.
45func (m *Meta) providerInstaller() *providercache.Installer {
46	return m.providerInstallerCustomSource(m.providerInstallSource())
47}
48
49// providerInstallerCustomSource is a variant of providerInstaller that
50// allows the caller to specify a different installation source than the one
51// that would naturally be selected.
52//
53// The result of this method has the same dependencies and constraints as
54// providerInstaller.
55//
56// The result of providerInstallerCustomSource differs from
57// providerInstaller only in how it determines package installation locations
58// during EnsureProviderVersions. A caller that doesn't call
59// EnsureProviderVersions (anything other than "terraform init") can safely
60// just use the providerInstaller method unconditionally.
61func (m *Meta) providerInstallerCustomSource(source getproviders.Source) *providercache.Installer {
62	targetDir := m.providerLocalCacheDir()
63	globalCacheDir := m.providerGlobalCacheDir()
64	inst := providercache.NewInstaller(targetDir, source)
65	if globalCacheDir != nil {
66		inst.SetGlobalCacheDir(globalCacheDir)
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.
90func (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.
111func (m *Meta) providerLocalCacheDir() *providercache.Dir {
112	dir := filepath.Join(m.DataDir(), "providers")
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.
126func (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.
145func (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.
166func (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.
198func (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.
228func (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
326func (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.
340func 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		}
356
357		client := plugin.NewClient(config)
358		rpcClient, err := client.Client()
359		if err != nil {
360			return nil, err
361		}
362
363		raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName)
364		if err != nil {
365			return nil, err
366		}
367
368		// store the client so that the plugin can kill the child process
369		protoVer := client.NegotiatedVersion()
370		switch protoVer {
371		case 5:
372			p := raw.(*tfplugin.GRPCProvider)
373			p.PluginClient = client
374			return p, nil
375		case 6:
376			p := raw.(*tfplugin6.GRPCProvider)
377			p.PluginClient = client
378			return p, nil
379		default:
380			panic("unsupported protocol version")
381		}
382	}
383}
384
385func devOverrideProviderFactory(provider addrs.Provider, localDir getproviders.PackageLocalDir) providers.Factory {
386	// A dev override is essentially a synthetic cache entry for our purposes
387	// here, so that's how we'll construct it. The providerFactory function
388	// doesn't actually care about the version, so we can leave it
389	// unspecified: overridden providers are not explicitly versioned.
390	log.Printf("[DEBUG] Provider %s is overridden to load from %s", provider, localDir)
391	return providerFactory(&providercache.CachedProvider{
392		Provider:   provider,
393		Version:    getproviders.UnspecifiedVersion,
394		PackageDir: string(localDir),
395	})
396}
397
398// unmanagedProviderFactory produces a provider factory that uses the passed
399// reattach information to connect to go-plugin processes that are already
400// running, and implements providers.Interface against it.
401func unmanagedProviderFactory(provider addrs.Provider, reattach *plugin.ReattachConfig) providers.Factory {
402	return func() (providers.Interface, error) {
403		config := &plugin.ClientConfig{
404			HandshakeConfig:  tfplugin.Handshake,
405			Logger:           logging.NewProviderLogger("unmanaged."),
406			AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
407			Managed:          false,
408			Reattach:         reattach,
409		}
410
411		if reattach.ProtocolVersion == 0 {
412			// As of the 0.15 release, sdk.v2 doesn't include the protocol
413			// version in the ReattachConfig (only recently added to
414			// go-plugin), so client.NegotiatedVersion() always returns 0. We
415			// assume that an unmanaged provider reporting protocol version 0 is
416			// actually using proto v5 for backwards compatibility.
417			if defaultPlugins, ok := tfplugin.VersionedPlugins[5]; ok {
418				config.Plugins = defaultPlugins
419			} else {
420				return nil, errors.New("no supported plugins for protocol 0")
421			}
422		} else if plugins, ok := tfplugin.VersionedPlugins[reattach.ProtocolVersion]; !ok {
423			return nil, fmt.Errorf("no supported plugins for protocol %d", reattach.ProtocolVersion)
424		} else {
425			config.Plugins = plugins
426		}
427
428		client := plugin.NewClient(config)
429		rpcClient, err := client.Client()
430		if err != nil {
431			return nil, err
432		}
433
434		raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName)
435		if err != nil {
436			return nil, err
437		}
438
439		// store the client so that the plugin can kill the child process
440		protoVer := client.NegotiatedVersion()
441		switch protoVer {
442		case 0, 5:
443			// As of the 0.15 release, sdk.v2 doesn't include the protocol
444			// version in the ReattachConfig (only recently added to
445			// go-plugin), so client.NegotiatedVersion() always returns 0. We
446			// assume that an unmanaged provider reporting protocol version 0 is
447			// actually using proto v5 for backwards compatibility.
448			p := raw.(*tfplugin.GRPCProvider)
449			p.PluginClient = client
450			return p, nil
451		case 6:
452			p := raw.(*tfplugin6.GRPCProvider)
453			p.PluginClient = client
454			return p, nil
455		default:
456			return nil, fmt.Errorf("unsupported protocol version %d", protoVer)
457		}
458	}
459}
460
461// providerFactoryError is a stub providers.Factory that returns an error
462// when called. It's used to allow providerFactories to still produce a
463// factory for each available provider in an error case, for situations
464// where the caller can do something useful with that partial result.
465func providerFactoryError(err error) providers.Factory {
466	return func() (providers.Interface, error) {
467		return nil, err
468	}
469}
470