Skip to content

Commit

Permalink
feat: auto load workspace tfvars file (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
leg100 authored Apr 30, 2024
1 parent 6072d03 commit 95ebc7e
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 1 deletion.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ Environment variables are specified by prefixing the value with `PUG_` and appen

The config file by default is expected to be found in the current working directory in which you invoke pug, and by default it's expected to be named `pug.yaml`. Override the default using the flag `-c` or environment variable `PUG_CONFIG`.

## Workspace Variables

Pug automatically loads variables from a .tfvars file. It looks for a file named `<workspace>.tfvars` in the module directory, where `<workspace>` is the name of the workspace. For example, if the workspace is named `dev` then it'll look for `dev.tfvars`. If the file exists then it'll pass the name to `terraform plan`, e.g. for a workspace named `dev`, it'll invoke `terraform plan -vars-file=dev.tfvars`.

## Resource hierarchy

There are several types of resources in pug:
Expand Down
2 changes: 1 addition & 1 deletion internal/app/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ type testLogger struct {
func (l *testLogger) Write(b []byte) (int, error) {
l.t.Helper()

//l.t.Log(string(b))
l.t.Log(string(b))
return len(b), nil
}

Expand Down
52 changes: 52 additions & 0 deletions internal/app/run_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package app

import (
"strings"
"testing"

"github.com/leg100/pug/internal"
)

func TestRun(t *testing.T) {
Expand All @@ -10,3 +13,52 @@ func TestRun(t *testing.T) {
// Initialize and apply run on modules/a
initAndApplyModuleA(t, tm)
}

func TestRun_WithVars(t *testing.T) {
tm := setup(t, "./testdata/run_with_vars")

// Wait for module to be loaded
waitFor(t, tm, func(s string) bool {
return strings.Contains(s, "modules/a")
})

// Initialize module
tm.Type("i")
waitFor(t, tm, func(s string) bool {
return strings.Contains(s, "Terraform has been successfully initialized!")
})

// Go to module page
tm.Type("m")

// Wait for default workspace to be loaded
waitFor(t, tm, func(s string) bool {
return strings.Contains(s, "default")
})

// Create plan for default workspace
tm.Type("p")

// User should now be taken to the run page...

// Expect to see summary of changes
waitFor(t, tm, func(s string) bool {
// Remove formatting
s = internal.StripAnsi(s)
return strings.Contains(s, "Changes to Outputs:") &&
strings.Contains(s, `+ foo = "override"`)
})

// Apply plan and provide confirmation
tm.Type("a")
waitFor(t, tm, func(s string) bool {
return strings.Contains(s, "Proceed with apply (y/N)?")
})
tm.Type("y")

// Wait for apply to complete
waitFor(t, tm, func(s string) bool {
return strings.Contains(s, "Apply complete! Resources: 0 added, 0 changed, 0 destroyed.") &&
strings.Contains(s, `foo = "override"`)
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo = "override"
11 changes: 11 additions & 0 deletions internal/app/testdata/run_with_vars/modules/a/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
terraform {
backend "local" {}
}

variable "foo" {
default = "bar"
}

output "foo" {
value = var.foo
}
15 changes: 15 additions & 0 deletions internal/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ type Run struct {

// Call this function after every status update
afterUpdate func(run *Run)

// Name of tfvars file to pass to terraform plan. An empty string means
// there is no vars file.
varsFilename string
}

type CreateOptions struct {
Expand Down Expand Up @@ -84,6 +88,14 @@ func newRun(mod *module.Module, ws *workspace.Workspace, opts CreateOptions) (*R
return nil, err
}

// Check if a tfvars file exists for the workspace. If so then use it for
// the plan.
varsFilename := fmt.Sprintf("%s.tfvars", ws.Name)
tfvars := filepath.Join(mod.FullPath(), varsFilename)
if _, err := os.Stat(tfvars); err == nil {
run.varsFilename = varsFilename
}

return run, nil
}

Expand All @@ -106,6 +118,9 @@ func (r *Run) PlanArgs() []string {
if r.Destroy {
args = append(args, "-destroy")
}
if r.varsFilename != "" {
args = append(args, fmt.Sprintf("-var-file=%s", r.varsFilename))
}
return args
}

Expand Down
23 changes: 23 additions & 0 deletions internal/run/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package run

import (
"fmt"
"os"
"path/filepath"
"testing"

"github.com/leg100/pug/internal"
Expand All @@ -12,6 +14,27 @@ import (
"github.com/stretchr/testify/require"
)

// TestRun_VarsFile tests creating a run with a workspace tfvars file.
func TestRun_VarsFile(t *testing.T) {
workdir := internal.NewTestWorkdir(t)
testutils.ChTempDir(t, workdir.String())

mod := module.New(workdir, "a/b/c")
ws, err := workspace.New(mod, "dev")
require.NoError(t, err)

// Create a workspace tfvars file for dev
os.MkdirAll(mod.FullPath(), 0o755)
_, err = os.Create(filepath.Join(mod.FullPath(), "dev.tfvars"))
require.NoError(t, err)

run, err := newRun(mod, ws, CreateOptions{})
require.NoError(t, err)

assert.Equal(t, "dev.tfvars", run.varsFilename)
assert.Contains(t, run.PlanArgs(), "-var-file=dev.tfvars")
}

func TestRun_MakePugDirectory(t *testing.T) {
workdir := internal.NewTestWorkdir(t)
testutils.ChTempDir(t, workdir.String())
Expand Down

0 comments on commit 95ebc7e

Please sign in to comment.