Allow cloud block overrides
These changes allow cloud blocks to be overridden by backend blocks and vice versa; the logic follows the current backend behavior of a block overriding a preceding block in full, with no merges.
This commit is contained in:
parent
07b3d015d1
commit
18d54c1129
|
@ -379,6 +379,7 @@ func (m *Module) mergeFile(file *File) hcl.Diagnostics {
|
||||||
if len(file.Backends) != 0 {
|
if len(file.Backends) != 0 {
|
||||||
switch len(file.Backends) {
|
switch len(file.Backends) {
|
||||||
case 1:
|
case 1:
|
||||||
|
m.CloudConfig = nil // A backend block is mutually exclusive with a cloud one, and overwrites any cloud config
|
||||||
m.Backend = file.Backends[0]
|
m.Backend = file.Backends[0]
|
||||||
default:
|
default:
|
||||||
// An override file with multiple backends is still invalid, even
|
// An override file with multiple backends is still invalid, even
|
||||||
|
@ -392,17 +393,22 @@ func (m *Module) mergeFile(file *File) hcl.Diagnostics {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This restriction is temporary. Overrides should be allowed, but have the added
|
if len(file.CloudConfigs) != 0 {
|
||||||
// complexity of needing to also override a 'backend' block, so this work is being deferred
|
switch len(file.CloudConfigs) {
|
||||||
// for now.
|
case 1:
|
||||||
for _, m := range file.CloudConfigs {
|
m.Backend = nil // A cloud block is mutually exclusive with a backend one, and overwrites any backend
|
||||||
|
m.CloudConfig = file.CloudConfigs[0]
|
||||||
|
default:
|
||||||
|
// An override file with multiple cloud blocks is still invalid, even
|
||||||
|
// though it can override cloud/backend blocks from _other_ files.
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: "Cannot override 'cloud' configuration",
|
Summary: "Duplicate Terraform Cloud configurations",
|
||||||
Detail: "Terraform Cloud configuration blocks can appear only in normal files, not in override files.",
|
Detail: fmt.Sprintf("A module may have only one 'cloud' block configuring Terraform Cloud. Terraform Cloud was previously configured at %s.", file.CloudConfigs[0].DeclRange),
|
||||||
Subject: m.DeclRange.Ptr(),
|
Subject: &file.CloudConfigs[1].DeclRange,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, pc := range file.ProviderConfigs {
|
for _, pc := range file.ProviderConfigs {
|
||||||
key := pc.moduleUniqueKey()
|
key := pc.moduleUniqueKey()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/internal/addrs"
|
"github.com/hashicorp/terraform/internal/addrs"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestNewModule_provider_fqns exercises module.gatherProviderLocalNames()
|
// TestNewModule_provider_fqns exercises module.gatherProviderLocalNames()
|
||||||
|
@ -309,3 +310,105 @@ func TestImpliedProviderForUnqualifiedType(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestModule_backend_override(t *testing.T) {
|
||||||
|
mod, diags := testModuleFromDir("testdata/valid-modules/override-backend")
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
gotType := mod.Backend.Type
|
||||||
|
wantType := "bar"
|
||||||
|
|
||||||
|
if gotType != wantType {
|
||||||
|
t.Errorf("wrong result for backend type: got %#v, want %#v\n", gotType, wantType)
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs, _ := mod.Backend.Config.JustAttributes()
|
||||||
|
|
||||||
|
gotAttr, diags := attrs["path"].Expr.Value(nil)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
wantAttr := cty.StringVal("CHANGED/relative/path/to/terraform.tfstate")
|
||||||
|
|
||||||
|
if !gotAttr.RawEquals(wantAttr) {
|
||||||
|
t.Errorf("wrong result for backend 'path': got %#v, want %#v\n", gotAttr, wantAttr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlike most other overrides, backend blocks do not require a base configuration in a primary
|
||||||
|
// configuration file, as an omitted backend there implies the local backend.
|
||||||
|
func TestModule_backend_override_no_base(t *testing.T) {
|
||||||
|
mod, diags := testModuleFromDir("testdata/valid-modules/override-backend-no-base")
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if mod.Backend == nil {
|
||||||
|
t.Errorf("expected module Backend not to be nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModule_cloud_override_backend(t *testing.T) {
|
||||||
|
mod, diags := testModuleFromDir("testdata/valid-modules/override-backend-with-cloud")
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if mod.Backend != nil {
|
||||||
|
t.Errorf("expected module Backend to be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if mod.CloudConfig == nil {
|
||||||
|
t.Errorf("expected module CloudConfig not to be nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlike most other overrides, cloud blocks do not require a base configuration in a primary
|
||||||
|
// configuration file, as an omitted backend there implies the local backend and cloud blocks
|
||||||
|
// override backends.
|
||||||
|
func TestModule_cloud_override_no_base(t *testing.T) {
|
||||||
|
mod, diags := testModuleFromDir("testdata/valid-modules/override-cloud-no-base")
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if mod.CloudConfig == nil {
|
||||||
|
t.Errorf("expected module CloudConfig not to be nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModule_cloud_override(t *testing.T) {
|
||||||
|
mod, diags := testModuleFromDir("testdata/valid-modules/override-cloud")
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs, _ := mod.CloudConfig.Config.JustAttributes()
|
||||||
|
|
||||||
|
gotAttr, diags := attrs["organization"].Expr.Value(nil)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
wantAttr := cty.StringVal("CHANGED")
|
||||||
|
|
||||||
|
if !gotAttr.RawEquals(wantAttr) {
|
||||||
|
t.Errorf("wrong result for Cloud 'organization': got %#v, want %#v\n", gotAttr, wantAttr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The override should have completely replaced the cloud block in the primary file, no merging
|
||||||
|
if attrs["should_not_be_present_with_override"] != nil {
|
||||||
|
t.Errorf("expected 'should_not_be_present_with_override' attribute to be nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModule_cloud_duplicate_overrides(t *testing.T) {
|
||||||
|
_, diags := testModuleFromDir("testdata/invalid-modules/override-cloud-duplicates")
|
||||||
|
want := `Duplicate Terraform Cloud configurations`
|
||||||
|
if got := diags.Error(); !strings.Contains(got, want) {
|
||||||
|
t.Fatalf("expected module error to contain %q\nerror was:\n%s", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
terraform {
|
||||||
|
cloud {
|
||||||
|
organization = "foo"
|
||||||
|
should_not_be_present_with_override = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
ami = "ami-1234"
|
||||||
|
security_groups = [
|
||||||
|
"foo",
|
||||||
|
"bar",
|
||||||
|
]
|
||||||
|
}
|
11
internal/configs/testdata/invalid-modules/override-cloud-duplicates/override.tf
vendored
Normal file
11
internal/configs/testdata/invalid-modules/override-cloud-duplicates/override.tf
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
terraform {
|
||||||
|
cloud {
|
||||||
|
organization = "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
terraform {
|
||||||
|
cloud {
|
||||||
|
organization = "bar"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
ami = "ami-1234"
|
||||||
|
security_groups = [
|
||||||
|
"foo",
|
||||||
|
"bar",
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
terraform {
|
||||||
|
backend "bar" {
|
||||||
|
path = "CHANGED/relative/path/to/terraform.tfstate"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
terraform {
|
||||||
|
backend "foo" {
|
||||||
|
path = "relative/path/to/terraform.tfstate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
ami = "ami-1234"
|
||||||
|
security_groups = [
|
||||||
|
"foo",
|
||||||
|
"bar",
|
||||||
|
]
|
||||||
|
}
|
5
internal/configs/testdata/valid-modules/override-backend-with-cloud/override.tf
vendored
Normal file
5
internal/configs/testdata/valid-modules/override-backend-with-cloud/override.tf
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
terraform {
|
||||||
|
cloud {
|
||||||
|
organization = "foo"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
terraform {
|
||||||
|
backend "foo" {
|
||||||
|
path = "relative/path/to/terraform.tfstate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
ami = "ami-1234"
|
||||||
|
security_groups = [
|
||||||
|
"foo",
|
||||||
|
"bar",
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
terraform {
|
||||||
|
backend "bar" {
|
||||||
|
path = "CHANGED/relative/path/to/terraform.tfstate"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
ami = "ami-1234"
|
||||||
|
security_groups = [
|
||||||
|
"foo",
|
||||||
|
"bar",
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
terraform {
|
||||||
|
cloud {
|
||||||
|
organization = "foo"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
terraform {
|
||||||
|
cloud {
|
||||||
|
organization = "foo"
|
||||||
|
should_not_be_present_with_override = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
ami = "ami-1234"
|
||||||
|
security_groups = [
|
||||||
|
"foo",
|
||||||
|
"bar",
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
terraform {
|
||||||
|
cloud {
|
||||||
|
organization = "CHANGED"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue