Skip to content
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

earlydecoder: Decode backend block #78

Merged
merged 1 commit into from
Oct 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions backend/backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package backend

type BackendData interface {
Copy() BackendData
}

type UnknownBackendData struct{}

func (*UnknownBackendData) Copy() BackendData {
return &UnknownBackendData{}
}

type Remote struct {
Hostname string
}

func (r *Remote) Copy() BackendData {
return &Remote{
Hostname: r.Hostname,
}
}
29 changes: 29 additions & 0 deletions earlydecoder/backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package earlydecoder

import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform-schema/backend"
"github.com/zclconf/go-cty/cty"
)

func decodeBackendsBlock(block *hcl.Block) (backend.BackendData, hcl.Diagnostics) {
bType := block.Labels[0]
attrs, diags := block.Body.JustAttributes()

switch bType {
case "remote":
if attr, ok := attrs["hostname"]; ok {
val, vDiags := attr.Expr.Value(nil)
diags = append(diags, vDiags...)
if val.IsWhollyKnown() && val.Type() == cty.String {
return &backend.Remote{
Hostname: val.AsString(),
}, nil
}
}

return &backend.Remote{}, nil
}

return &backend.UnknownBackendData{}, diags
}
22 changes: 22 additions & 0 deletions earlydecoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,27 @@ func LoadModule(path string, files map[string]*hcl.File) (*module.Meta, hcl.Diag
coreRequirements = append(coreRequirements, c...)
}

var backend *module.Backend
if len(mod.Backends) == 1 {
for bType, data := range mod.Backends {
backend = &module.Backend{
Type: bType,
Data: data,
}
}
} else if len(mod.Backends) > 1 {
backendTypes := make([]string, len(mod.Backends))
for bType := range mod.Backends {
backendTypes = append(backendTypes, bType)
}

diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unable to parse backend configuration",
Detail: fmt.Sprintf("Multiple backend definitions: %q", backendTypes),
})
}

var (
providerRequirements = make(map[tfaddr.Provider]version.Constraints, 0)
refs = make(map[module.ProviderRef]tfaddr.Provider, 0)
Expand Down Expand Up @@ -139,6 +160,7 @@ func LoadModule(path string, files map[string]*hcl.File) (*module.Meta, hcl.Diag

return &module.Meta{
Path: path,
Backend: backend,
ProviderReferences: refs,
ProviderRequirements: providerRequirements,
CoreRequirements: coreRequirements,
Expand Down
119 changes: 116 additions & 3 deletions earlydecoder/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
tfaddr "github.com/hashicorp/terraform-registry-address"
"github.com/hashicorp/terraform-schema/backend"
"github.com/hashicorp/terraform-schema/module"
"github.com/zclconf/go-cty-debug/ctydebug"
"github.com/zclconf/go-cty/cty"
Expand Down Expand Up @@ -536,7 +537,7 @@ resource "google_something" "test" {
},
}

executeTestCases(testCases, t, path)
runTestCases(testCases, t, path)
}

func TestLoadModule_Variables(t *testing.T) {
Expand Down Expand Up @@ -787,11 +788,123 @@ output "name" {
nil,
},
}
executeTestCases(testCases, t, path)

runTestCases(testCases, t, path)
}
func executeTestCases(testCases []testCase, t *testing.T, path string) {

func TestLoadModule_backend(t *testing.T) {
path := t.TempDir()

testCases := []testCase{
{
"no backend",
`
terraform {

}`,
&module.Meta{
Path: path,
Backend: nil,
ProviderReferences: map[module.ProviderRef]tfaddr.Provider{},
ProviderRequirements: map[tfaddr.Provider]version.Constraints{},
Variables: map[string]module.Variable{},
Outputs: map[string]module.Output{},
},
nil,
},
{
"s3 backend",
`
terraform {
backend "s3" {
blah = "test"
}
}`,
&module.Meta{
Path: path,
Backend: &module.Backend{
Type: "s3",
Data: &backend.UnknownBackendData{},
},
ProviderReferences: map[module.ProviderRef]tfaddr.Provider{},
ProviderRequirements: map[tfaddr.Provider]version.Constraints{},
Variables: map[string]module.Variable{},
Outputs: map[string]module.Output{},
},
nil,
},
{
"empty remote backend",
`
terraform {
backend "remote" {}
}`,
&module.Meta{
Path: path,
Backend: &module.Backend{
Type: "remote",
Data: &backend.Remote{},
},
ProviderReferences: map[module.ProviderRef]tfaddr.Provider{},
ProviderRequirements: map[tfaddr.Provider]version.Constraints{},
Variables: map[string]module.Variable{},
Outputs: map[string]module.Output{},
},
nil,
},
{
"remote backend with hostname",
`
terraform {
backend "remote" {
hostname = "app.terraform.io"
}
}`,
&module.Meta{
Path: path,
Backend: &module.Backend{
Type: "remote",
Data: &backend.Remote{Hostname: "app.terraform.io"},
},
ProviderReferences: map[module.ProviderRef]tfaddr.Provider{},
ProviderRequirements: map[tfaddr.Provider]version.Constraints{},
Variables: map[string]module.Variable{},
Outputs: map[string]module.Output{},
},
nil,
},
{
"remote backend with hostname and more attributes",
`
terraform {
backend "remote" {
hostname = "app.terraform.io"
organization = "test"

workspaces {
name = "test"
}
}
}`,
&module.Meta{
Path: path,
Backend: &module.Backend{
Type: "remote",
Data: &backend.Remote{Hostname: "app.terraform.io"},
},
ProviderReferences: map[module.ProviderRef]tfaddr.Provider{},
ProviderRequirements: map[tfaddr.Provider]version.Constraints{},
Variables: map[string]module.Variable{},
Outputs: map[string]module.Output{},
},
nil,
},
}

runTestCases(testCases, t, path)
}

func runTestCases(testCases []testCase, t *testing.T, path string) {
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) {
f, diags := hclsyntax.ParseConfig([]byte(tc.cfg), "test.tf", hcl.InitialPos)
Expand Down
21 changes: 21 additions & 0 deletions earlydecoder/load_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform-schema/backend"
"github.com/hashicorp/terraform-schema/internal/typeexpr"
"github.com/hashicorp/terraform-schema/module"
"github.com/zclconf/go-cty/cty"
Expand All @@ -15,6 +16,7 @@ import (
// decodedModule is the type representing a decoded Terraform module.
type decodedModule struct {
RequiredCore []string
Backends map[string]backend.BackendData
ProviderRequirements map[string]*providerRequirement
ProviderConfigs map[string]*providerConfig
Resources map[string]*resource
Expand All @@ -26,6 +28,7 @@ type decodedModule struct {
func newDecodedModule() *decodedModule {
return &decodedModule{
RequiredCore: make([]string, 0),
Backends: make(map[string]backend.BackendData),
ProviderRequirements: make(map[string]*providerRequirement),
ProviderConfigs: make(map[string]*providerConfig),
Resources: make(map[string]*resource),
Expand Down Expand Up @@ -68,6 +71,24 @@ func loadModuleFromFile(file *hcl.File, mod *decodedModule) hcl.Diagnostics {

for _, innerBlock := range content.Blocks {
switch innerBlock.Type {
case "backend":
bType := innerBlock.Labels[0]

data, bDiags := decodeBackendsBlock(innerBlock)
diags = append(diags, bDiags...)

if _, exists := mod.Backends[bType]; exists {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Multiple backend definitions",
Detail: fmt.Sprintf("Found multiple backend definitions for %q. Only one is allowed.", bType),
Subject: &innerBlock.DefRange,
})
continue
}

mod.Backends[bType] = data

case "required_providers":
reqs, reqsDiags := decodeRequiredProvidersBlock(innerBlock)
diags = append(diags, reqsDiags...)
Expand Down
4 changes: 4 additions & 0 deletions earlydecoder/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ var terraformBlockSchema = &hcl.BodySchema{
{
Type: "required_providers",
},
{
Type: "backend",
LabelNames: []string{"type"},
},
},
}

Expand Down
7 changes: 7 additions & 0 deletions module/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@ package module
import (
"github.com/hashicorp/go-version"
tfaddr "github.com/hashicorp/terraform-registry-address"
"github.com/hashicorp/terraform-schema/backend"
)

type Meta struct {
Path string

Backend *Backend
ProviderReferences map[ProviderRef]tfaddr.Provider
ProviderRequirements map[tfaddr.Provider]version.Constraints
CoreRequirements version.Constraints
Variables map[string]Variable
Outputs map[string]Output
}

type Backend struct {
Type string
Data backend.BackendData
}

type ProviderRef struct {
LocalName string

Expand Down