stacks: check providers blocks in components during validatation (#34707)

pull/34719/head
Liam Cervante 2 years ago committed by GitHub
parent bced645a4d
commit b3abff5750
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -323,7 +323,23 @@ func (c *ComponentConfig) CheckProviders(ctx context.Context, phase EvalPhase) (
continue
}
// TODO: Also validate the provider types are the same.
// TODO: It's not currently possible to assign a provider configuration
// with a different local name even if the types match. Find out if
// this is deliberate. Note, the component_instance CheckProviders
// function also enforces this.
//
// In theory you should be able to do this:
// provider_one = provider.provider_two.default
//
// Assuming the underlying types of the providers are the same, even if
// the local names are not. This is not possible at the moment, the
// local names must match up.
//
// We'll have to partially parse the reference here to get the local
// configuration block (uninstanced), and then resolve the underlying
// type. And then make sure it matches the type of the provider we're
// assigning it to in the module. Also, we should fix the equivalent
// function in component_instance at the same time.
ret.Add(inCalleeAddr)
}
@ -403,9 +419,14 @@ func (c *ComponentConfig) checkValid(ctx context.Context, phase EvalPhase) tfdia
}
decl := c.Declaration(ctx)
// TODO: Also check if the providers are valid.
// TODO: Also check if the input variables are valid.
_, providerDiags := c.CheckProviders(ctx, phase)
diags = diags.Append(providerDiags)
if providerDiags.HasErrors() {
return diags, nil
}
providerSchemas, moreDiags := c.neededProviderSchemas(ctx, phase)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {

@ -0,0 +1,26 @@
required_providers {
testing = {
// The source is wrong, so validate should complain.
source = "hashicorp/wrong"
version = "0.1.0"
}
}
provider "testing" "default" {}
variable "input" {
type = string
}
component "self" {
source = "../"
providers = {
// Everything looks okay here, but the provider types are actually wrong.
testing = provider.testing.default
}
inputs = {
input = var.input
}
}

@ -0,0 +1,23 @@
required_providers {
testing = {
source = "hashicorp/testing"
version = "0.1.0"
}
}
provider "testing" "default" {}
variable "input" {
type = string
}
component "self" {
source = "../"
# We do actually require a provider here, Validate() should warn us.
providers = {}
inputs = {
input = var.input
}
}

@ -0,0 +1,26 @@
required_providers {
other = {
source = "hashicorp/testing"
version = "0.1.0"
}
}
provider "other" "default" {}
variable "input" {
type = string
}
component "self" {
source = "../"
providers = {
// Even though the names are wrong, the underlying types are the same
// so this should be okay.
testing = provider.other.default
}
inputs = {
input = var.input
}
}

@ -0,0 +1,16 @@
variable "input" {
type = string
}
component "self" {
source = "../"
providers = {
# We haven't provided a definition for this anywhere.
testing = provider.testing.default
}
inputs = {
input = var.input
}
}

@ -5,6 +5,7 @@ package stackruntime
import (
"context"
"path/filepath"
"testing"
"time"
@ -25,14 +26,24 @@ import (
// potentially be included in here unless it depends on provider plugins
// to complete validation, since this test cannot supply provider plugins.
func TestValidate_valid(t *testing.T) {
validConfigDirs := []string{
"empty",
"variable-output-roundtrip",
"variable-output-roundtrip-nested",
validConfigDirs := map[string]struct {
skip bool
}{
"empty": {},
"variable-output-roundtrip": {},
"variable-output-roundtrip-nested": {},
filepath.Join("with-single-input", "provider-name-clash"): {
skip: true,
},
}
for _, name := range validConfigDirs {
for name, tc := range validConfigDirs {
t.Run(name, func(t *testing.T) {
if tc.skip {
// We've added this test before the implementation was ready.
t.SkipNow()
}
ctx := context.Background()
cfg := loadMainBundleConfigForTest(t, name)
@ -55,6 +66,7 @@ func TestValidate_valid(t *testing.T) {
func TestValidate_invalid(t *testing.T) {
tcs := map[string]struct {
diags func() tfdiags.Diagnostics
skip bool
}{
"validate-undeclared-variable": {
diags: func() tfdiags.Diagnostics {
@ -88,10 +100,53 @@ func TestValidate_invalid(t *testing.T) {
return diags
},
},
filepath.Join("with-single-input", "undeclared-provider"): {
diags: func() tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Component requires undeclared provider",
Detail: "The root module for component.self requires a configuration for provider \"hashicorp/testing\", which isn't declared as a dependency of this stack configuration.\n\nDeclare this provider in the stack's required_providers block, and then assign a configuration for that provider in this component's \"providers\" argument.",
Subject: &hcl.Range{
Filename: mainBundleSourceAddrStr("with-single-input/undeclared-provider/undeclared-provider.tfstack.hcl"),
Start: hcl.Pos{Line: 5, Column: 1, Byte: 38},
End: hcl.Pos{Line: 5, Column: 17, Byte: 54},
},
})
return diags
},
},
filepath.Join("with-single-input", "missing-provider"): {
diags: func() tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing required provider configuration",
Detail: "The root module for component.self requires a provider configuration named \"testing\" for provider \"hashicorp/testing\", which is not assigned in the component's \"providers\" argument.",
Subject: &hcl.Range{
Filename: mainBundleSourceAddrStr("with-single-input/missing-provider/missing-provider.tfstack.hcl"),
Start: hcl.Pos{Line: 14, Column: 1, Byte: 169},
End: hcl.Pos{Line: 14, Column: 17, Byte: 185},
},
})
return diags
},
},
filepath.Join("with-single-input", "invalid-provider"): {
// TODO: Enable this test case, when we have a good error message
// for provider type mismatches. Currently, we return the same
// error as for missing provider, which is not ideal.
skip: true,
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
if tc.skip {
// We've added this test before the implementation was ready.
t.SkipNow()
}
ctx := context.Background()
cfg := loadMainBundleConfigForTest(t, name)

Loading…
Cancel
Save