Skip to content

Commit

Permalink
add runtime pterraform vars through IMAGETEST_TF_VAR_ (#174)
Browse files Browse the repository at this point in the history
provide another option for setting variables to the `pterraform` harness
through the use of an `IMAGETEST_TF_VAR_` option, which operates
identically to `TF_VAR_`, except is only parsed by the `pterraform`
harness.

This ensures the runtime can configure `pterraform` directly **without**
relying on conflicting runtime variables for the "outer" terraform.

`pterraform` will inherit all other environment variables from the host,
but will ignore any `TF_VAR_`s and exclusively use `IMAGETEST_TF_VAR_`s
  • Loading branch information
joshrwolf authored Aug 26, 2024
1 parent d1d5b70 commit 947be95
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 1 deletion.
52 changes: 51 additions & 1 deletion internal/harness/pterraform/pterraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ func New(source fs.FS, opts ...Option) (*pterraform, error) {
p.tf = tf
p.tf.SetStdout(io.Discard)

// Use the host variables but ignore any host TF_VAR_
envs := make(map[string]string)
for _, e := range os.Environ() {
if strings.HasPrefix(e, "TF_VAR_") {
continue
}
parts := strings.SplitN(e, "=", 2)
if len(parts) != 2 {
continue
}
envs[parts[0]] = parts[1]
}
if err := p.tf.SetEnv(envs); err != nil {
return nil, fmt.Errorf("setting environment variables: %w", err)
}

return p, nil
}

Expand Down Expand Up @@ -201,6 +217,10 @@ func (p *pterraform) Create(ctx context.Context) error {

applyopts := []tfexec.ApplyOption{}

for _, opt := range p.evars() {
applyopts = append(applyopts, opt)
}

if p.vars != nil {
// Write the vars as a vars.tf.json file
vdata, err := json.Marshal(p.vars)
Expand All @@ -218,7 +238,11 @@ func (p *pterraform) Create(ctx context.Context) error {
}

if err := p.stack.Add(func(ctx context.Context) error {
return p.tf.Destroy(ctx)
destroyopts := []tfexec.DestroyOption{}
for _, opt := range p.evars() {
destroyopts = append(destroyopts, opt)
}
return p.tf.Destroy(ctx, destroyopts...)
}); err != nil {
return fmt.Errorf("adding terraform destroy to stack: %w", err)
}
Expand Down Expand Up @@ -313,6 +337,32 @@ func (p *pterraform) Destroy(ctx context.Context) error {
return p.stack.Teardown(ctx)
}

// evars slurps any IMAGETEST_TF_VAR_* environment variables and adds them to
// the pterraform executor as -var="key=value".
func (p *pterraform) evars() []*tfexec.VarOption {
opts := make([]*tfexec.VarOption, 0)

for _, e := range os.Environ() {
if strings.HasPrefix(e, "TF_VAR_") {
continue
}

parts := strings.SplitN(e, "=", 2)
if len(parts) != 2 {
continue
}

k, v := parts[0], parts[1]

if strings.HasPrefix(k, "IMAGETEST_TF_VAR_") {
k = strings.TrimPrefix(k, "IMAGETEST_TF_VAR_")
opts = append(opts, tfexec.Var(fmt.Sprintf("%s=%s", k, v)))
}
}

return opts
}

type Option func(*pterraform) error

// WithWorkspace sets the path to the terraform workspace to use.
Expand Down
162 changes: 162 additions & 0 deletions internal/harness/pterraform/pterraform_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package pterraform

import (
"context"
"fmt"
"io/fs"
"os"
"path/filepath"
"testing"
)

func TestPterraform(t *testing.T) {
tests := []struct {
name string
content string
envVars map[string]string
want Connection
expectError bool
}{
{
name: "Test with an IMAGETEST_TF_VAR_ variable",
content: fmt.Sprintf(`
variable "foo" {}
resource "terraform_data" "foo" {
provisioner "local-exec" {
command = "docker run -d --name ${var.foo} cgr.dev/chainguard/wolfi-base:latest tail -f /dev/null"
when = "create"
}
}
resource "terraform_data" "foo-down" {
provisioner "local-exec" {
command = "docker rm -f %s"
when = "destroy"
}
}
output "connection" {
value = {
docker = {
cid = var.foo
}
}
}
`, "foo"),
envVars: map[string]string{
"IMAGETEST_TF_VAR_foo": "foo",
},
},
{
name: "Ensure TF_VAR_ variables are ignored",
content: fmt.Sprintf(`
variable "foo" {
default = "foo"
}
resource "terraform_data" "foo" {
provisioner "local-exec" {
command = "docker run -d --name ${var.foo} cgr.dev/chainguard/wolfi-base:latest tail -f /dev/null"
when = "create"
}
}
resource "terraform_data" "foo-down" {
provisioner "local-exec" {
command = "docker rm -f %s"
when = "destroy"
}
}
output "connection" {
value = {
docker = {
cid = var.foo
}
}
}
`, "foo"),
envVars: map[string]string{
"TF_VAR_foo": "bar",
},
},
{
name: "Ensure TF_VAR_ don't pollute IMAGETEST_TF_VAR_ variables",
content: fmt.Sprintf(`
variable "foo" {}
resource "terraform_data" "foo" {
provisioner "local-exec" {
command = "docker run -d --name ${var.foo} cgr.dev/chainguard/wolfi-base:latest tail -f /dev/null"
when = "create"
}
}
resource "terraform_data" "foo-down" {
provisioner "local-exec" {
command = "docker rm -f %s"
when = "destroy"
}
}
output "connection" {
value = {
docker = {
cid = var.foo
}
}
}
`, "foo"),
envVars: map[string]string{
"IMAGETEST_TF_VAR_foo": "foo",
"TF_VAR_foo": "bar",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()

// Set environment variables
for key, value := range tt.envVars {
os.Setenv(key, value)
}

// Clean up environment variables after the test
defer func() {
for key := range tt.envVars {
os.Unsetenv(key)
}
}()

p, err := New(sourceFs(t, tt.content))
if err != nil {
t.Fatalf("unexpected error creating new pterraform: %v", err)
}

err = p.Create(ctx)
if (err != nil) != tt.expectError {
t.Errorf("expected error: %v, got: %v", tt.expectError, err)
}

err = p.Destroy(ctx)
if (err != nil) != tt.expectError {
t.Errorf("expected error: %v, got: %v", tt.expectError, err)
}
})
}
}

func sourceFs(t *testing.T, content string) fs.FS {
dir := t.TempDir()

t.Logf("using temp dir %s", dir)

if err := os.WriteFile(filepath.Join(dir, "main.tf"), []byte(content), 0644); err != nil {
t.Fatal(err)
}

return os.DirFS(dir)
}

0 comments on commit 947be95

Please sign in to comment.