Skip to content

stacks: Don't require built-in providers to be listed under required providers. #37234

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 18, 2025
17 changes: 17 additions & 0 deletions internal/builtin/providers/factories.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package providers

import (
terraformProvider "github.com/hashicorp/terraform/internal/builtin/providers/terraform"
provider "github.com/hashicorp/terraform/internal/providers"
)

func BuiltInProviders() map[string]provider.Factory {
return map[string]provider.Factory{
"terraform": func() (provider.Interface, error) {
return terraformProvider.NewProvider(), nil
},
}
}
10 changes: 3 additions & 7 deletions internal/command/meta_providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import (
"os/exec"
"strings"

plugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/go-plugin"

"github.com/hashicorp/terraform/internal/addrs"
terraformProvider "github.com/hashicorp/terraform/internal/builtin/providers/terraform"
builtinProviders "github.com/hashicorp/terraform/internal/builtin/providers"
"github.com/hashicorp/terraform/internal/getproviders"
"github.com/hashicorp/terraform/internal/logging"
tfplugin "github.com/hashicorp/terraform/internal/plugin"
Expand Down Expand Up @@ -365,11 +365,7 @@ func (m *Meta) providerFactories() (map[addrs.Provider]providers.Factory, error)
}

func (m *Meta) internalProviders() map[string]providers.Factory {
return map[string]providers.Factory{
"terraform": func() (providers.Interface, error) {
return terraformProvider.NewProvider(), nil
},
}
return builtinProviders.BuiltInProviders()
}

// providerFactory produces a provider factory that runs up the executable
Expand Down
38 changes: 38 additions & 0 deletions internal/stacks/stackconfig/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,41 @@ func TestLoadConfigDirBasics(t *testing.T) {
})
// TODO: More thorough testing!
}

func TestOmittingBuiltInProviders(t *testing.T) {
bundle, err := sourcebundle.OpenDir("testdata/basics-bundle")
if err != nil {
t.Fatal(err)
}

rootAddr := sourceaddrs.MustParseSource("git::https://example.com/builtin.git").(sourceaddrs.RemoteSource)
config, diags := LoadConfigDir(rootAddr, bundle)
if len(diags) != 0 {
t.Fatalf("unexpected diagnostics:\n%s", diags.NonFatalErr().Error())
}

t.Run("built-in providers do NOT have to be listed in required providers", func(t *testing.T) {
if got, want := len(config.Root.Stack.OutputValues), 1; got != want {
t.Errorf("wrong number of output values %d; want %d", got, want)
}

t.Run("greeting", func(t *testing.T) {
cfg, ok := config.Root.Stack.OutputValues["greeting"]
if !ok {
t.Fatal("Root stack config has no output value named \"greeting\".")
}
if got, want := cfg.Name, "greeting"; got != want {
t.Errorf("wrong name\ngot: %s\nwant: %s", got, want)
}
if got, want := cfg.Type.Constraint, cty.String; got != want {
t.Errorf("wrong name\ngot: %#v\nwant: %#v", got, want)
}
if got, want := cfg.Sensitive, false; got != want {
t.Errorf("wrong sensitive\ngot: %#v\nwant: %#v", got, want)
}
if got, want := cfg.Ephemeral, false; got != want {
t.Errorf("wrong ephemeral\ngot: %#v\nwant: %#v", got, want)
}
})
})
}
31 changes: 28 additions & 3 deletions internal/stacks/stackconfig/provider_requirements.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/internal/addrs"
builtinProviders "github.com/hashicorp/terraform/internal/builtin/providers"
"github.com/hashicorp/terraform/internal/tfdiags"
)

Expand All @@ -31,15 +32,38 @@ type ProviderRequirement struct {

func decodeProviderRequirementsBlock(block *hcl.Block) (*ProviderRequirements, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
var ret *ProviderRequirements
attrs, hclDiags := block.Body.JustAttributes()
diags = diags.Append(hclDiags)

// Include built-in providers, if not present
includeBuiltInProviders := func(pr *ProviderRequirements) *ProviderRequirements {
if pr == nil {
pr = &ProviderRequirements{
Requirements: make(map[string]ProviderRequirement, len(attrs)),
DeclRange: tfdiags.SourceRangeFromHCL(hcl.Range{}),
}
}

for providerName := range builtinProviders.BuiltInProviders() {
if _, ok := pr.Requirements[providerName]; !ok {
pr.Requirements[providerName] = ProviderRequirement{
LocalName: providerName,
Provider: addrs.NewBuiltInProvider(providerName),
}
}
}

return pr
}

if len(attrs) == 0 {
return nil, diags
return includeBuiltInProviders(ret), diags
}

reverseMap := make(map[addrs.Provider]string)

ret := &ProviderRequirements{
ret = &ProviderRequirements{
Requirements: make(map[string]ProviderRequirement, len(attrs)),
DeclRange: tfdiags.SourceRangeFromHCL(block.DefRange),
}
Expand Down Expand Up @@ -196,7 +220,8 @@ func decodeProviderRequirementsBlock(block *hcl.Block) (*ProviderRequirements, t
}
reverseMap[providerAddr] = name
}
return ret, diags

return includeBuiltInProviders(ret), diags
}

func (pr *ProviderRequirements) ProviderForLocalName(localName string) (addrs.Provider, bool) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
variable "name" {
type = string
}

resource "terraform_data" "example" {
input = {
message = "Hello, ${var.name}!"
}
}

output "greeting" {
value = terraform_data.example.input.message
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
required_providers {
# Built-in providers can be omitted.
}

provider "terraform" "x" {}

component "a" {
source = "./component"

inputs = {
name = var.name
}

providers = {
x = var.provider
}
}

output "greeting" {
type = string
value = component.a.greeting
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
"source": "git::https://example.com/deprecated.git",
"local": "deprecated",
"meta": {}
},
{
"source": "git::https://example.com/builtin.git",
"local": "builtin",
"meta": {}
}
],
"registry": [
Expand Down
12 changes: 12 additions & 0 deletions internal/stacks/stackruntime/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ func TestApply(t *testing.T) {
store *stacks_testing_provider.ResourceStore
cycles []TestCycle
}{
"built-in provider used not present in required": {
path: "with-built-in-provider",
cycles: []TestCycle{
{}, // plan, apply -> no diags
},
},
"built-in provider used and explicitly defined in required providers": {
path: "with-built-in-provider-explicitly-defined",
cycles: []TestCycle{
{}, // plan, apply -> no diags
},
},
"creating inputs and outputs": {
path: "component-input-output",
cycles: []TestCycle{
Expand Down
12 changes: 11 additions & 1 deletion internal/stacks/stackruntime/internal/stackeval/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (
"context"
"fmt"
"log"
"maps"
"sync"
"time"

"github.com/hashicorp/go-slug/sourcebundle"
"github.com/hashicorp/hcl/v2"
builtinProviders "github.com/hashicorp/terraform/internal/builtin/providers"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"

Expand Down Expand Up @@ -347,7 +349,15 @@ func (m *Main) Stack(ctx context.Context, addr stackaddrs.StackInstance, phase E
// ProviderFactories returns the collection of factory functions for providers
// that are available to this instance of the evaluation runtime.
func (m *Main) ProviderFactories() ProviderFactories {
return m.providerFactories
// Built-in provider factories are always present
resultProviderFactories := ProviderFactories{}
for k, v := range builtinProviders.BuiltInProviders() {
resultProviderFactories[addrs.NewBuiltInProvider(k)] = v
}

maps.Copy(resultProviderFactories, m.providerFactories)

return resultProviderFactories
}

// ProviderFunctions returns the collection of externally defined provider
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
variable "name" {
type = string
}

resource "terraform_data" "example" {
input = {
message = "Hello, ${var.name}!"
}
}

output "greeting" {
value = terraform_data.example.input.message
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
required_providers {
terraform = {
source = "terraform.io/builtin/terraform"
}
}

provider "terraform" "x" {}

component "a" {
source = "./component"

inputs = {
name = "test-name"
}

providers = {
terraform = provider.terraform.x
}
}

output "greeting" {
type = string
value = component.a.greeting
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
variable "name" {
type = string
}

resource "terraform_data" "example" {
input = {
message = "Hello, ${var.name}!"
}
}

output "greeting" {
value = terraform_data.example.input.message
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
required_providers {
# Built-in providers can be omitted.
}

provider "terraform" "x" {}

component "a" {
source = "./component"

inputs = {
name = "test-name"
}

providers = {
terraform = provider.terraform.x
}
}

output "greeting" {
type = string
value = component.a.greeting
}