Skip to content

Commit 8e2509c

Browse files
internal/providercache: Handle built-in providers
Built-in providers are special providers that are distributed as part of Terraform CLI itself, rather than being installed separately. They always live in the terraform.io/builtin/... namespace so it's easier to see that they are special, and currently there is only one built-in provider named "terraform". Previous commits established the addressing scheme for built-in providers. This commit makes the installer aware of them to the extent that it knows not to try to install them the usual way and it's able to report an error if the user requests a built-in provider that doesn't exist or tries to impose a particular version constraint for a built-in provider. For the moment the tests for this are the ones in the "command" package because that's where the existing testing infrastructure for this functionality lives. A later commit should add some more focused unit tests here in the internal/providercache package, too.
1 parent 6b45d41 commit 8e2509c

File tree

6 files changed

+144
-3
lines changed

6 files changed

+144
-3
lines changed

command/init.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,16 @@ func (c *InitCommand) getProviders(earlyConfig *earlyconfig.Config, state *state
467467
ProviderAlreadyInstalled: func(provider addrs.Provider, selectedVersion getproviders.Version) {
468468
c.Ui.Info(fmt.Sprintf("- Using previously-installed %s v%s", provider, selectedVersion))
469469
},
470+
BuiltInProviderAvailable: func(provider addrs.Provider) {
471+
c.Ui.Info(fmt.Sprintf("- %s is built in to Terraform", provider))
472+
},
473+
BuiltInProviderFailure: func(provider addrs.Provider, err error) {
474+
diags = diags.Append(tfdiags.Sourceless(
475+
tfdiags.Error,
476+
"Invalid dependency on built-in provider",
477+
fmt.Sprintf("Cannot use %s: %s.", provider, err),
478+
))
479+
},
470480
QueryPackagesBegin: func(provider addrs.Provider, versionConstraints getproviders.VersionConstraints) {
471481
if len(versionConstraints) > 0 {
472482
c.Ui.Info(fmt.Sprintf("- Finding %s versions matching %q...", provider, getproviders.VersionConstraintsString(versionConstraints)))

command/init_test.go

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,7 +1396,7 @@ func TestInit_pluginDirProvidersDoesNotGet(t *testing.T) {
13961396
}
13971397

13981398
// Verify that plugin-dir doesn't prevent discovery of internal providers
1399-
func TestInit_pluginWithInternal(t *testing.T) {
1399+
func TestInit_pluginDirWithBuiltIn(t *testing.T) {
14001400
td := tempDir(t)
14011401
copy.CopyDir(testFixturePath("init-internal"), td)
14021402
defer os.RemoveAll(td)
@@ -1406,7 +1406,7 @@ func TestInit_pluginWithInternal(t *testing.T) {
14061406
providerSource, close := newMockProviderSource(t, nil)
14071407
defer close()
14081408

1409-
ui := new(cli.MockUi)
1409+
ui := cli.NewMockUi()
14101410
m := Meta{
14111411
testingOverrides: metaOverridesForProvider(testProvider()),
14121412
Ui: ui,
@@ -1418,10 +1418,53 @@ func TestInit_pluginWithInternal(t *testing.T) {
14181418
}
14191419

14201420
args := []string{"-plugin-dir", "./"}
1421-
//args := []string{}
14221421
if code := c.Run(args); code != 0 {
14231422
t.Fatalf("error: %s", ui.ErrorWriter)
14241423
}
1424+
1425+
outputStr := ui.OutputWriter.String()
1426+
if subStr := "terraform.io/builtin/terraform is built in to Terraform"; !strings.Contains(outputStr, subStr) {
1427+
t.Errorf("output should mention the terraform provider\nwant substr: %s\ngot:\n%s", subStr, outputStr)
1428+
}
1429+
}
1430+
1431+
func TestInit_invalidBuiltInProviders(t *testing.T) {
1432+
// This test fixture includes two invalid provider dependencies:
1433+
// - an implied dependency on terraform.io/builtin/terraform with an
1434+
// explicit version number, which is not allowed because it's builtin.
1435+
// - an explicit dependency on terraform.io/builtin/nonexist, which does
1436+
// not exist at all.
1437+
td := tempDir(t)
1438+
copy.CopyDir(testFixturePath("init-internal-invalid"), td)
1439+
defer os.RemoveAll(td)
1440+
defer testChdir(t, td)()
1441+
1442+
// An empty provider source
1443+
providerSource, close := newMockProviderSource(t, nil)
1444+
defer close()
1445+
1446+
ui := cli.NewMockUi()
1447+
m := Meta{
1448+
testingOverrides: metaOverridesForProvider(testProvider()),
1449+
Ui: ui,
1450+
ProviderSource: providerSource,
1451+
}
1452+
1453+
c := &InitCommand{
1454+
Meta: m,
1455+
}
1456+
1457+
if code := c.Run(nil); code == 0 {
1458+
t.Fatalf("succeeded, but was expecting error\nstdout:\n%s\nstderr:\n%s", ui.OutputWriter, ui.ErrorWriter)
1459+
}
1460+
1461+
errStr := ui.ErrorWriter.String()
1462+
if subStr := "Cannot use terraform.io/builtin/terraform: built-in"; !strings.Contains(errStr, subStr) {
1463+
t.Errorf("error output should mention the terraform provider\nwant substr: %s\ngot:\n%s", subStr, errStr)
1464+
}
1465+
if subStr := "Cannot use terraform.io/builtin/nonexist: this Terraform release"; !strings.Contains(errStr, subStr) {
1466+
t.Errorf("error output should mention the 'nonexist' provider\nwant substr: %s\ngot:\n%s", subStr, errStr)
1467+
}
14251468
}
14261469

14271470
// The module in this test uses terraform 0.11-style syntax. We expect that the

command/meta_providers.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ func (m *Meta) providerInstallerCustomSource(source getproviders.Source) *provid
5858
if globalCacheDir != nil {
5959
inst.SetGlobalCacheDir(globalCacheDir)
6060
}
61+
var builtinProviderTypes []string
62+
for ty := range m.internalProviders() {
63+
builtinProviderTypes = append(builtinProviderTypes, ty)
64+
}
65+
inst.SetBuiltInProviderTypes(builtinProviderTypes)
6166
return inst
6267
}
6368

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
terraform {
2+
required_providers {
3+
nonexist = {
4+
source = "terraform.io/builtin/nonexist"
5+
}
6+
terraform = {
7+
version = "1.2.0"
8+
}
9+
}
10+
}

internal/providercache/installer.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ type Installer struct {
3535
// both the disk space and the download time for a particular provider
3636
// version between different configurations on the same system.
3737
globalCacheDir *Dir
38+
39+
// builtInProviderTypes is an optional set of types that should be
40+
// considered valid to appear in the special terraform.io/builtin/...
41+
// namespace, which we use for providers that are built in to Terraform
42+
// and thus do not need any separate installation step.
43+
builtInProviderTypes []string
3844
}
3945

4046
// NewInstaller constructs and returns a new installer with the given target
@@ -70,6 +76,24 @@ func (i *Installer) SetGlobalCacheDir(cacheDir *Dir) {
7076
i.globalCacheDir = cacheDir
7177
}
7278

79+
// SetBuiltInProviderTypes tells the receiver to consider the type names in the
80+
// given slice to be valid as providers in the special special
81+
// terraform.io/builtin/... namespace that we use for providers that are
82+
// built in to Terraform and thus do not need a separate installation step.
83+
//
84+
// If a caller requests installation of a provider in that namespace, the
85+
// installer will treat it as a no-op if its name exists in this list, but
86+
// will produce an error if it does not.
87+
//
88+
// The default, if this method isn't called, is for there to be no valid
89+
// builtin providers.
90+
//
91+
// Do not modify the buffer under the given slice after passing it to this
92+
// method.
93+
func (i *Installer) SetBuiltInProviderTypes(types []string) {
94+
i.builtInProviderTypes = types
95+
}
96+
7397
// EnsureProviderVersions compares the given provider requirements with what
7498
// is already available in the installer's target directory and then takes
7599
// appropriate installation actions to ensure that suitable packages
@@ -113,6 +137,42 @@ func (i *Installer) EnsureProviderVersions(ctx context.Context, reqs getprovider
113137
mightNeed := map[addrs.Provider]getproviders.VersionSet{}
114138
MightNeedProvider:
115139
for provider, versionConstraints := range reqs {
140+
if provider.IsBuiltIn() {
141+
// Built in providers do not require installation but we'll still
142+
// verify that the requested provider name is valid.
143+
valid := false
144+
for _, name := range i.builtInProviderTypes {
145+
if name == provider.Type {
146+
valid = true
147+
break
148+
}
149+
}
150+
var err error
151+
if valid {
152+
if len(versionConstraints) == 0 {
153+
// Other than reporting an event for the outcome of this
154+
// provider, we'll do nothing else with it: it's just
155+
// automatically available for use.
156+
if cb := evts.BuiltInProviderAvailable; cb != nil {
157+
cb(provider)
158+
}
159+
} else {
160+
// A built-in provider is not permitted to have an explicit
161+
// version constraint, because we can only use the version
162+
// that is built in to the current Terraform release.
163+
err = fmt.Errorf("built-in providers do not support explicit version constraints")
164+
}
165+
} else {
166+
err = fmt.Errorf("this Terraform release has no built-in provider named %q", provider.Type)
167+
}
168+
if err != nil {
169+
errs[provider] = err
170+
if cb := evts.BuiltInProviderFailure; cb != nil {
171+
cb(provider, err)
172+
}
173+
}
174+
continue
175+
}
116176
acceptableVersions := versions.MeetingConstraints(versionConstraints)
117177
if mode.forceQueryAllProviders() {
118178
// If our mode calls for us to look for newer versions regardless

internal/providercache/installer_events.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@ type InstallerEvents struct {
4040
// available version.
4141
ProviderAlreadyInstalled func(provider addrs.Provider, selectedVersion getproviders.Version)
4242

43+
// The BuiltInProvider... family of events describe the outcome for any
44+
// requested providers that are built in to Terraform. Only one of these
45+
// methods will be called for each such provider, and no other method
46+
// will be called for them except that they are included in the
47+
// aggregate PendingProviders map.
48+
//
49+
// The "Available" event reports that the requested builtin provider is
50+
// available in this release of Terraform. The "Failure" event reports
51+
// either that the provider is unavailable or that the request for it
52+
// is invalid somehow.
53+
BuiltInProviderAvailable func(provider addrs.Provider)
54+
BuiltInProviderFailure func(provider addrs.Provider, err error)
55+
4356
// The QueryPackages... family of events delimit the operation of querying
4457
// a provider source for information about available packages matching
4558
// a particular version constraint, prior to selecting a single version

0 commit comments

Comments
 (0)