Skip to content

Commit 225c06e

Browse files
authored
Switches to use terraform core's stack schema for config parsing, adds support for providers (#139)
1 parent d9a8f43 commit 225c06e

File tree

6 files changed

+200
-4
lines changed

6 files changed

+200
-4
lines changed

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
)
1414

1515
var showJSON = flag.Bool("json", false, "produce JSON-formatted output")
16-
var parseStack = flag.Bool("stack", false, "parse as Terraform stack instead of module")
16+
var parseStack = flag.Bool("stack", false, "parse a Terraform stack")
1717

1818
func main() {
1919
flag.Parse()

tfconfig/load_hcl.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func LoadStack(dir string) (*Stack, Diagnostics) {
8787

8888
func loadStackFromFile(file *hcl.File, stack *Stack) hcl.Diagnostics {
8989
var diags hcl.Diagnostics
90-
content, _, contentDiags := file.Body.PartialContent(rootSchema)
90+
content, _, contentDiags := file.Body.PartialContent(stackSchema)
9191
diags = append(diags, contentDiags...)
9292

9393
for _, block := range content.Blocks {
@@ -243,9 +243,31 @@ func loadStackFromFile(file *hcl.File, stack *Stack) hcl.Diagnostics {
243243
}
244244
}
245245

246+
case "provider":
247+
if len(block.Labels) < 2 {
248+
diags = append(diags, &hcl.Diagnostic{
249+
Severity: hcl.DiagError,
250+
Summary: "Invalid provider block",
251+
Detail: "Provider blocks in stacks must have both a provider name and configuration name",
252+
Subject: &block.DefRange,
253+
})
254+
continue
255+
}
256+
257+
_, _, contentDiags := block.Body.PartialContent(providerConfigSchema)
258+
diags = append(diags, contentDiags...)
259+
260+
providerName := block.Labels[0]
261+
_ = block.Labels[1] // configName - not used for metadata extraction
262+
263+
// For stack providers, we primarily care about tracking that they exist
264+
// The actual provider configuration is handled differently in stacks
265+
if _, exists := stack.RequiredProviders[providerName]; !exists {
266+
stack.RequiredProviders[providerName] = &ProviderRequirement{}
267+
}
268+
246269
default:
247-
// For now, we only handle variable blocks in stacks
248-
// Other block types will be ignored
270+
// Other block types will be ignored for stacks
249271
}
250272
}
251273

tfconfig/load_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,34 @@ func TestLoadStack(t *testing.T) {
8181
return stack
8282
})
8383
}
84+
85+
func TestProviderLabels(t *testing.T) {
86+
// Test that provider blocks with two labels are correctly parsed
87+
stack, diags := LoadStack("testdata-stack/provider-labels")
88+
89+
if diags.HasErrors() {
90+
t.Fatalf("unexpected errors: %v", diags)
91+
}
92+
93+
expectedProviders := map[string]bool{
94+
"aws": true,
95+
"random": true,
96+
"null": true,
97+
}
98+
99+
if len(stack.RequiredProviders) != len(expectedProviders) {
100+
t.Errorf("expected %d required providers, got %d", len(expectedProviders), len(stack.RequiredProviders))
101+
}
102+
103+
for providerName := range expectedProviders {
104+
if _, exists := stack.RequiredProviders[providerName]; !exists {
105+
t.Errorf("expected provider %q to be in required_providers", providerName)
106+
}
107+
}
108+
109+
if awsProvider, exists := stack.RequiredProviders["aws"]; exists {
110+
if awsProvider.Source != "hashicorp/aws" {
111+
t.Errorf("expected aws provider source to be 'hashicorp/aws', got %q", awsProvider.Source)
112+
}
113+
}
114+
}

tfconfig/schema.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,44 @@ var componentSchema = &hcl.BodySchema{
132132
},
133133
},
134134
}
135+
136+
// stackSchema defines the schema for Terraform Stacks files based on Terraform core's rootConfigSchema
137+
// https://github.com/hashicorp/terraform/blob/8b65426ecfac58a6937c1c26297c8e6a0db57a35/internal/stacks/stackconfig/file.go
138+
var stackSchema = &hcl.BodySchema{
139+
Attributes: []hcl.AttributeSchema{
140+
{
141+
Name: "language",
142+
},
143+
},
144+
Blocks: []hcl.BlockHeaderSchema{
145+
{
146+
Type: "stack",
147+
LabelNames: []string{"name"},
148+
},
149+
{
150+
Type: "component",
151+
LabelNames: []string{"name"},
152+
},
153+
{
154+
Type: "variable",
155+
LabelNames: []string{"name"},
156+
},
157+
{
158+
Type: "locals",
159+
},
160+
{
161+
Type: "output",
162+
LabelNames: []string{"name"},
163+
},
164+
{
165+
Type: "provider",
166+
LabelNames: []string{"type", "name"},
167+
},
168+
{
169+
Type: "required_providers",
170+
},
171+
{
172+
Type: "removed",
173+
},
174+
},
175+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"path": "testdata-stack/provider-labels",
3+
"variables": {
4+
"test_var": {
5+
"name": "test_var",
6+
"type": "string",
7+
"description": "Test variable for provider labels test",
8+
"default": "test",
9+
"required": false,
10+
"pos": {
11+
"filename": "testdata-stack/provider-labels/providers.tfstack.hcl",
12+
"line": 42
13+
}
14+
}
15+
},
16+
"outputs": {
17+
"test_output": {
18+
"name": "test_output",
19+
"description": "Test output for provider labels test",
20+
"pos": {
21+
"filename": "testdata-stack/provider-labels/providers.tfstack.hcl",
22+
"line": 48
23+
}
24+
}
25+
},
26+
"required_providers": {
27+
"aws": {
28+
"source": "hashicorp/aws"
29+
},
30+
"null": {
31+
"source": "hashicorp/null"
32+
},
33+
"random": {
34+
"source": "hashicorp/random"
35+
}
36+
},
37+
"components": {
38+
"test_component": {
39+
"name": "test_component",
40+
"source": "./test-module",
41+
"pos": {
42+
"filename": "testdata-stack/provider-labels/providers.tfstack.hcl",
43+
"line": 53
44+
}
45+
}
46+
}
47+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
# Test file for provider blocks with different label configurations
5+
6+
required_providers {
7+
aws = {
8+
source = "hashicorp/aws"
9+
version = "~> 5.0"
10+
}
11+
12+
random = {
13+
source = "hashicorp/random"
14+
version = "~> 3.0"
15+
}
16+
17+
null = {
18+
source = "hashicorp/null"
19+
}
20+
}
21+
22+
# Terraform Stacks provider with two labels (type + name)
23+
provider "aws" "main" {
24+
config {
25+
region = "us-east-1"
26+
}
27+
}
28+
29+
# Another two-label provider configuration
30+
provider "aws" "secondary" {
31+
config {
32+
region = "us-west-2"
33+
}
34+
}
35+
36+
# Two-label provider with for_each
37+
provider "random" "default" {}
38+
39+
# Two-label provider with minimal config
40+
provider "null" "test" {}
41+
42+
variable "test_var" {
43+
type = string
44+
description = "Test variable for provider labels test"
45+
default = "test"
46+
}
47+
48+
output "test_output" {
49+
description = "Test output for provider labels test"
50+
value = var.test_var
51+
}
52+
53+
component "test_component" {
54+
source = "./test-module"
55+
}

0 commit comments

Comments
 (0)