Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
9 changes: 5 additions & 4 deletions internal/verify/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@ func (e WorkingExecutable) Init(ctx context.Context) error {
return e.TF.Init(ctx, tfexec.Upgrade(true))
}

func (e WorkingExecutable) Plan(ctx context.Context, outPath string) (bool, error) {
changes, err := e.TF.Plan(ctx, tfexec.Out(outPath))
func (e WorkingExecutable) Plan(ctx context.Context, outPath string, opts ...tfexec.PlanOption) (bool, error) {
opts = append(opts, tfexec.Out(outPath))
changes, err := e.TF.Plan(ctx, opts...)
return changes, err
}

func (e WorkingExecutable) Apply(ctx context.Context) ([]byte, error) {
func (e WorkingExecutable) Apply(ctx context.Context, opts ...tfexec.ApplyOption) ([]byte, error) {
var out bytes.Buffer
err := e.TF.ApplyJSON(ctx, &out)
err := e.TF.ApplyJSON(ctx, &out, opts...)
return out.Bytes(), err
}

Expand Down
62 changes: 24 additions & 38 deletions preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import (
"fmt"
"io/fs"
"log/slog"
"path/filepath"

"github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"

"github.com/coder/preview/hclext"
"github.com/coder/preview/tfvars"
"github.com/coder/preview/types"
)

Expand All @@ -26,6 +26,9 @@ type Input struct {
ParameterValues map[string]string
Owner types.WorkspaceOwner
Logger *slog.Logger
// TFVars will override any variables set in '.tfvars' files.
// The value set must be a cty.Value, as the type can be anything.
TFVars map[string]cty.Value
}

type Output struct {
Expand Down Expand Up @@ -80,12 +83,7 @@ func Preview(ctx context.Context, input Input, dir fs.FS) (output *Output, diagn
}
}()

// TODO: Fix logging. There is no way to pass in an instanced logger to
// the parser.
// slog.SetLogLoggerLevel(slog.LevelDebug)
// slog.SetDefault(slog.New(log.NewHandler(os.Stderr, nil)))

varFiles, err := tfVarFiles("", dir)
varFiles, err := tfvars.TFVarFiles("", dir)
if err != nil {
return nil, hcl.Diagnostics{
{
Expand All @@ -96,6 +94,17 @@ func Preview(ctx context.Context, input Input, dir fs.FS) (output *Output, diagn
}
}

variableValues, err := tfvars.LoadTFVars(dir, varFiles)
if err != nil {
return nil, hcl.Diagnostics{
{
Severity: hcl.DiagError,
Summary: "Failed to load tfvars from files",
Detail: err.Error(),
},
}
}

planHook, err := planJSONHook(dir, input)
if err != nil {
return nil, hcl.Diagnostics{
Expand Down Expand Up @@ -123,17 +132,24 @@ func Preview(ctx context.Context, input Input, dir fs.FS) (output *Output, diagn
logger = slog.New(slog.DiscardHandler)
}

// Override with user-supplied variables
for k, v := range input.TFVars {
variableValues[k] = v
}

// moduleSource is "" for a local module
p := parser.New(dir, "",
parser.OptionWithLogger(logger),
parser.OptionStopOnHCLError(false),
parser.OptionWithDownloads(false),
parser.OptionWithSkipCachedModules(true),
parser.OptionWithTFVarsPaths(varFiles...),
parser.OptionWithEvalHook(planHook),
parser.OptionWithEvalHook(ownerHook),
parser.OptionWithWorkingDirectoryPath("/"),
parser.OptionWithEvalHook(parameterContextsEvalHook(input)),
// 'OptionsWithTfVars' cannot be set with 'OptionWithTFVarsPaths'. So load the
// tfvars from the files ourselves and merge with the user-supplied tf vars.
parser.OptionsWithTfVars(variableValues),
)

err = p.ParseFS(ctx, ".")
Expand Down Expand Up @@ -179,33 +195,3 @@ func (i Input) RichParameterValue(key string) (string, bool) {
p, ok := i.ParameterValues[key]
return p, ok
}

// tfVarFiles extracts any .tfvars files from the given directory.
// TODO: Test nested directories and how that should behave.
func tfVarFiles(path string, dir fs.FS) ([]string, error) {
dp := "."
entries, err := fs.ReadDir(dir, dp)
if err != nil {
return nil, fmt.Errorf("read dir %q: %w", dp, err)
}

files := make([]string, 0)
for _, entry := range entries {
if entry.IsDir() {
subD, err := fs.Sub(dir, entry.Name())
if err != nil {
return nil, fmt.Errorf("sub dir %q: %w", entry.Name(), err)
}
newFiles, err := tfVarFiles(filepath.Join(path, entry.Name()), subD)
if err != nil {
return nil, err
}
files = append(files, newFiles...)
}

if filepath.Ext(entry.Name()) == ".tfvars" {
files = append(files, filepath.Join(path, entry.Name()))
}
}
return files, nil
}
32 changes: 32 additions & 0 deletions preview_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/hashicorp/hcl/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zclconf/go-cty/cty"

"github.com/coder/preview"
"github.com/coder/preview/types"
Expand Down Expand Up @@ -471,6 +472,37 @@ func Test_Extract(t *testing.T) {
optNames("GoLand 2024.3", "IntelliJ IDEA Ultimate 2024.3", "PyCharm Professional 2024.3"),
},
},
{
name: "tfvars_from_file",
dir: "tfvars",
expTags: map[string]string{},
input: preview.Input{
ParameterValues: map[string]string{},
},
unknownTags: []string{},
params: map[string]assertParam{
"variable_values": ap().
def("alex").optVals("alex", "bob", "claire", "jason"),
},
},
{
name: "tfvars_from_input",
dir: "tfvars",
expTags: map[string]string{},
input: preview.Input{
ParameterValues: map[string]string{},
TFVars: map[string]cty.Value{
"one": cty.StringVal("andrew"),
"two": cty.StringVal("bill"),
"three": cty.StringVal("carter"),
},
},
unknownTags: []string{},
params: map[string]assertParam{
"variable_values": ap().
def("andrew").optVals("andrew", "bill", "carter", "jason"),
},
},
{
name: "unknownoption",
dir: "unknownoption",
Expand Down
26 changes: 19 additions & 7 deletions previewe2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import (
"testing"
"time"

"github.com/hashicorp/terraform-exec/tfexec"
"github.com/stretchr/testify/require"

"github.com/coder/preview"
"github.com/coder/preview/internal/verify"
"github.com/coder/preview/tfvars"
"github.com/coder/preview/types"
)

Expand Down Expand Up @@ -102,11 +104,11 @@ func Test_VerifyE2E(t *testing.T) {

entryWrkPath := t.TempDir()

for _, tfexec := range tfexecs {
tfexec := tfexec
for _, tfexecutable := range tfexecs {
tfexecutable := tfexecutable

t.Run(tfexec.Version, func(t *testing.T) {
wp := filepath.Join(entryWrkPath, tfexec.Version)
t.Run(tfexecutable.Version, func(t *testing.T) {
wp := filepath.Join(entryWrkPath, tfexecutable.Version)
err := os.MkdirAll(wp, 0755)
require.NoError(t, err, "creating working dir")

Expand All @@ -118,17 +120,27 @@ func Test_VerifyE2E(t *testing.T) {
err = verify.CopyTFFS(wp, subFS)
require.NoError(t, err, "copying test data to working dir")

exe, err := tfexec.WorkingDir(wp)
exe, err := tfexecutable.WorkingDir(wp)
require.NoError(t, err, "creating working executable")

ctx, cancel := context.WithTimeout(context.Background(), time.Minute*2)
defer cancel()
err = exe.Init(ctx)
require.NoError(t, err, "terraform init")

tfVarFiles, err := tfvars.TFVarFiles("", subFS)
require.NoError(t, err, "loading tfvars files")

planOpts := make([]tfexec.PlanOption, 0)
applyOpts := make([]tfexec.ApplyOption, 0)
for _, varFile := range tfVarFiles {
planOpts = append(planOpts, tfexec.VarFile(varFile))
applyOpts = append(applyOpts, tfexec.VarFile(varFile))
}

planOutFile := "tfplan"
planOutPath := filepath.Join(wp, planOutFile)
_, err = exe.Plan(ctx, planOutPath)
_, err = exe.Plan(ctx, planOutPath, planOpts...)
require.NoError(t, err, "terraform plan")

plan, err := exe.ShowPlan(ctx, planOutPath)
Expand All @@ -141,7 +153,7 @@ func Test_VerifyE2E(t *testing.T) {
err = os.WriteFile(filepath.Join(wp, "plan.json"), pd, 0644)
require.NoError(t, err, "writing plan.json")

_, err = exe.Apply(ctx)
_, err = exe.Apply(ctx, applyOpts...)
require.NoError(t, err, "terraform apply")

state, err := exe.Show(ctx)
Expand Down
1 change: 1 addition & 0 deletions testdata/tfvars/.auto.tfvars.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"four":"jason"}
61 changes: 61 additions & 0 deletions testdata/tfvars/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Base case for workspace tags + parameters.
terraform {
required_providers {
coder = {
source = "coder/coder"
}
docker = {
source = "kreuzwerker/docker"
version = "3.0.2"
}
}
}

variable "one" {
default = "alice"
type = string
}

variable "two" {
default = "bob"
type = string
}

variable "three" {
default = "charlie"
type = string
}

variable "four" {
default = "jack"
type = string
}


data "coder_parameter" "variable_values" {
name = "variable_values"
description = "Just to show the variable values"
type = "string"
default = var.one


option {
name = "one"
value = var.one
}

option {
name = "two"
value = var.two
}

option {
name = "three"
value = var.three
}

option {
name = "four"
value = var.four
}
}
2 changes: 2 additions & 0 deletions testdata/tfvars/values.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
one="alex"
three="claire"
Loading
Loading