stacks: remove support for deprecated .tfstack extension (#37617)

* stacks: remove support for deprecated .tfstack extension

* also remove from comments and readme
pull/37616/head
Liam Cervante 7 months ago committed by GitHub
parent 3ae4616912
commit 0b15151513
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -14,51 +14,6 @@ import (
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestLoadConfigDirDeprecated(t *testing.T) {
bundle, err := sourcebundle.OpenDir("testdata/basics-bundle")
if err != nil {
t.Fatal(err)
}
rootAddr := sourceaddrs.MustParseSource("git::https://example.com/deprecated.git").(sourceaddrs.RemoteSource)
_, gotDiags := LoadConfigDir(rootAddr, bundle)
wantDiags := tfdiags.Diagnostics{
tfdiags.Sourceless(tfdiags.Warning, "Deprecated filename usage", "This configuration is using the deprecated .tfstack.hcl or .tfstack.json file extensions. This will not be supported in a future version of Terraform, please update your files to use the latest .tfcomponent.hcl or .tfcomponent.json file extensions."),
}
count := len(wantDiags)
if len(gotDiags) > count {
count = len(gotDiags)
}
for i := 0; i < count; i++ {
if i >= len(wantDiags) {
t.Errorf("unexpected diagnostic:\n%s", gotDiags[i])
continue
}
if i >= len(gotDiags) {
t.Errorf("missing diagnostic:\n%s", wantDiags[i])
continue
}
got, want := gotDiags[i], wantDiags[i]
if got, want := got.Severity(), want.Severity(); got != want {
t.Errorf("diagnostics[%d] severity\ngot: %s\nwant: %s", i, got, want)
}
if got, want := got.Description().Summary, want.Description().Summary; got != want {
t.Errorf("diagnostics[%d] summary\ngot: %s\nwant: %s", i, got, want)
}
if got, want := got.Description().Detail, want.Description().Detail; got != want {
t.Errorf("diagnostics[%d] detail\ngot: %s\nwant: %s", i, got, want)
}
}
}
func TestLoadConfigDirErrors(t *testing.T) {
bundle, err := sourcebundle.OpenDir("testdata/basics-bundle")
if err != nil {

@ -155,34 +155,28 @@ func DecodeFileBody(body hcl.Body, fileAddr sourceaddrs.FinalSource) (*File, tfd
return ret, diags
}
// ParseFileSource parses the given source code as the content of a Stacks
// component file and then delegates the result to [DecodeFileBody] for
// analysis, returning that final result.
//
// For now, we support both the .tfstack. and .tfcomponent. file suffixes while
// the former is still in the process of being deprecated. It is not valid for
// a single stack to mix and match the two options. We are, therefore, accepting
// a "prior suffix" argument which lets us know which suffix was used by other
// files in this stack.
// ParseFileSource parses the given source code as the content of either a
// .tfcomponent.hcl or .tfcomponent.json file, and then delegates the result to
// [DecodeFileBody] for analysis, returning that final result.
//
// ParseFileSource chooses between native vs. JSON syntax based on the suffix
// of the filename in the given source address, which must be either
// ".[tfstack|tfcomponent].hcl" or ".[tfstack|tfcomponent].json".
func ParseFileSource(src []byte, suffix string, fileAddr sourceaddrs.FinalSource) (*File, tfdiags.Diagnostics) {
// ".tfcomponent.hcl" or ".tfcomponent.json".
func ParseFileSource(src []byte, fileAddr sourceaddrs.FinalSource) (*File, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
filename := sourceaddrs.FinalSourceFilename(fileAddr)
var body hcl.Body
switch {
case strings.HasSuffix(filename, fmt.Sprintf(".%s.hcl", suffix)):
switch validFilenameSuffix(filename) {
case ".tfcomponent.hcl":
hclFile, hclDiags := hclsyntax.ParseConfig(src, fileAddr.String(), hcl.InitialPos)
diags = diags.Append(hclDiags)
if diags.HasErrors() {
return nil, diags
}
body = hclFile.Body
case strings.HasSuffix(filename, fmt.Sprintf(".%s.json", suffix)):
case ".tfcomponent.json":
hclFile, hclDiags := hcljson.Parse(src, fileAddr.String())
diags = diags.Append(hclDiags)
if diags.HasErrors() {
@ -194,8 +188,8 @@ func ParseFileSource(src []byte, suffix string, fileAddr sourceaddrs.FinalSource
tfdiags.Error,
"Unsupported file type",
fmt.Sprintf(
"Cannot load %s as a stack configuration file: filename must have either a .%s.hcl or .%s.json suffix.",
fileAddr, suffix, suffix,
"Cannot load %s as a stack configuration file: filename must have either a .tfcomponent.hcl or .tfcomponent.json suffix.",
fileAddr,
),
))
return nil, diags
@ -209,23 +203,15 @@ func ParseFileSource(src []byte, suffix string, fileAddr sourceaddrs.FinalSource
// validFilenameSuffix returns ".tfcomponent.hcl" or ".tfcomponent.json" if the
// given filename ends with that suffix, and otherwise returns an empty
// string to indicate that the suffix was invalid.
//
// We still support the deprecated .tfstack suffix for the time being.
func validFilenameSuffix(filename string) string {
const nativeSuffix = ".tfcomponent.hcl"
const jsonSuffix = ".tfcomponent.json"
const deprecatedNativeSuffix = ".tfstack.hcl"
const deprecatedJsonSuffix = ".tfstack.json"
switch {
case strings.HasSuffix(filename, deprecatedNativeSuffix):
return "tfstack"
case strings.HasSuffix(filename, deprecatedJsonSuffix):
return "tfstack"
case strings.HasSuffix(filename, nativeSuffix):
return "tfcomponent"
return nativeSuffix
case strings.HasSuffix(filename, jsonSuffix):
return "tfcomponent"
return jsonSuffix
default:
return ""
}

@ -90,25 +90,12 @@ func LoadSingleStackConfig(sourceAddr sourceaddrs.FinalSource, sources *sourcebu
Declarations: makeDeclarations(),
}
var priorSuffix string
for _, entry := range allEntries {
suffix := validFilenameSuffix(entry.Name())
if suffix == "" {
if suffix := validFilenameSuffix(entry.Name()); suffix == "" {
// not a file we're interested in, then
continue
}
if priorSuffix == "" {
priorSuffix = suffix
} else if priorSuffix != suffix {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid stack configuration directory",
"This stack configuration has mixed the deprecated .tfstack.hcl or .tfstack.json file extensions with the latest .tfcomponent.hcl or .tfcomponent.json file extensions. Update all files to the latest .tfcomponent file extension.",
))
continue
}
asLocalSourcePath := "./" + filepath.Base(entry.Name())
relSource, err := sourceaddrs.ParseLocalSource(asLocalSourcePath)
if err != nil {
@ -143,7 +130,7 @@ func LoadSingleStackConfig(sourceAddr sourceaddrs.FinalSource, sources *sourcebu
))
}
file, moreDiags := ParseFileSource(src, priorSuffix, fileSourceAddr)
file, moreDiags := ParseFileSource(src, fileSourceAddr)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
// We'll still try to analyze other files, so we can gather up
@ -176,13 +163,5 @@ func LoadSingleStackConfig(sourceAddr sourceaddrs.FinalSource, sources *sourcebu
pc.ProviderAddr = providerAddr
}
if priorSuffix == "tfstack" {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated filename usage",
Detail: "This configuration is using the deprecated .tfstack.hcl or .tfstack.json file extensions. This will not be supported in a future version of Terraform, please update your files to use the latest .tfcomponent.hcl or .tfcomponent.json file extensions.",
})
}
return ret, diags
}

@ -1,17 +0,0 @@
variable "name" {
type = string
}
resource "null_resource" "example" {
triggers = {
name = var.name
}
}
output "greeting" {
value = "Hello, ${var.name}!"
}
output "resource_id" {
value = null_resource.example.id
}

@ -1,41 +0,0 @@
required_providers {
null = {
source = "hashicorp/null"
version = "3.2.1"
}
}
variable "name" {
type = string
}
provider "null" "a" {}
component "a" {
source = "./component"
inputs = {
name = var.name
}
providers = {
null = var.provider
}
}
removed {
from = component.b
source = "./component"
providers = {
null = var.provider
}
lifecycle {
destroy = true
}
}
output "greeting" {
type = string
value = component.a.greeting
}

@ -39,9 +39,9 @@ There are various pairs of types in this package that represent a static object
in the configuration and dynamic instances of that object respectively.
For example, `InputVariableConfig` directly represents a `variable` block
from a `.tfstack.hcl` file, while `InputVariable` represents the possibly-many
dynamic instances of that object that can be caused by being within a stack
that was called using `for_each`.
from a `.tfcomponent.hcl` file, while `InputVariable` represents the
possibly-many dynamic instances of that object that can be caused by being
within a stack that was called using `for_each`.
In general, the static types are responsible for "static validation"-type tasks,
such as checking whether expressions refer to instances of other configuration

Loading…
Cancel
Save