blob: d1560d48ee7aef496f69eccf4a30d8648e847bae [file] [log] [blame]
package localexec
import (
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"time"
"github.com/hashicorp/terraform/internal/provisioners"
"github.com/mitchellh/cli"
"github.com/zclconf/go-cty/cty"
)
func TestResourceProvider_Apply(t *testing.T) {
defer os.Remove("test_out")
output := cli.NewMockUi()
p := New()
schema := p.GetSchema().Provisioner
c, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
"command": cty.StringVal("echo foo > test_out"),
}))
if err != nil {
t.Fatal(err)
}
resp := p.ProvisionResource(provisioners.ProvisionResourceRequest{
Config: c,
UIOutput: output,
})
if resp.Diagnostics.HasErrors() {
t.Fatalf("err: %v", resp.Diagnostics.Err())
}
// Check the file
raw, err := ioutil.ReadFile("test_out")
if err != nil {
t.Fatalf("err: %v", err)
}
actual := strings.TrimSpace(string(raw))
expected := "foo"
if actual != expected {
t.Fatalf("bad: %#v", actual)
}
}
func TestResourceProvider_stop(t *testing.T) {
output := cli.NewMockUi()
p := New()
schema := p.GetSchema().Provisioner
c, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
// bash/zsh/ksh will exec a single command in the same process. This
// makes certain there's a subprocess in the shell.
"command": cty.StringVal("sleep 30; sleep 30"),
}))
if err != nil {
t.Fatal(err)
}
doneCh := make(chan struct{})
startTime := time.Now()
go func() {
defer close(doneCh)
// The functionality of p.Apply is tested in TestResourceProvider_Apply.
// Because p.Apply is called in a goroutine, trying to t.Fatal() on its
// result would be ignored or would cause a panic if the parent goroutine
// has already completed.
_ = p.ProvisionResource(provisioners.ProvisionResourceRequest{
Config: c,
UIOutput: output,
})
}()
mustExceed := (50 * time.Millisecond)
select {
case <-doneCh:
t.Fatalf("expected to finish sometime after %s finished in %s", mustExceed, time.Since(startTime))
case <-time.After(mustExceed):
t.Logf("correctly took longer than %s", mustExceed)
}
// Stop it
stopTime := time.Now()
p.Stop()
maxTempl := "expected to finish under %s, finished in %s"
finishWithin := (2 * time.Second)
select {
case <-doneCh:
t.Logf(maxTempl, finishWithin, time.Since(stopTime))
case <-time.After(finishWithin):
t.Fatalf(maxTempl, finishWithin, time.Since(stopTime))
}
}
func TestResourceProvider_ApplyCustomInterpreter(t *testing.T) {
output := cli.NewMockUi()
p := New()
schema := p.GetSchema().Provisioner
c, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
"interpreter": cty.ListVal([]cty.Value{cty.StringVal("echo"), cty.StringVal("is")}),
"command": cty.StringVal("not really an interpreter"),
}))
if err != nil {
t.Fatal(err)
}
resp := p.ProvisionResource(provisioners.ProvisionResourceRequest{
Config: c,
UIOutput: output,
})
if resp.Diagnostics.HasErrors() {
t.Fatal(resp.Diagnostics.Err())
}
got := strings.TrimSpace(output.OutputWriter.String())
want := `Executing: ["echo" "is" "not really an interpreter"]
is not really an interpreter`
if got != want {
t.Errorf("wrong output\ngot: %s\nwant: %s", got, want)
}
}
func TestResourceProvider_ApplyCustomWorkingDirectory(t *testing.T) {
testdir := "working_dir_test"
os.Mkdir(testdir, 0755)
defer os.Remove(testdir)
output := cli.NewMockUi()
p := New()
schema := p.GetSchema().Provisioner
c, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
"working_dir": cty.StringVal(testdir),
"command": cty.StringVal("echo `pwd`"),
}))
if err != nil {
t.Fatal(err)
}
resp := p.ProvisionResource(provisioners.ProvisionResourceRequest{
Config: c,
UIOutput: output,
})
if resp.Diagnostics.HasErrors() {
t.Fatal(resp.Diagnostics.Err())
}
dir, err := os.Getwd()
if err != nil {
t.Fatalf("err: %v", err)
}
got := strings.TrimSpace(output.OutputWriter.String())
want := "Executing: [\"/bin/sh\" \"-c\" \"echo `pwd`\"]\n" + dir + "/" + testdir
if got != want {
t.Errorf("wrong output\ngot: %s\nwant: %s", got, want)
}
}
func TestResourceProvider_ApplyCustomEnv(t *testing.T) {
output := cli.NewMockUi()
p := New()
schema := p.GetSchema().Provisioner
c, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
"command": cty.StringVal("echo $FOO $BAR $BAZ"),
"environment": cty.MapVal(map[string]cty.Value{
"FOO": cty.StringVal("BAR"),
"BAR": cty.StringVal("1"),
"BAZ": cty.StringVal("true"),
}),
}))
if err != nil {
t.Fatal(err)
}
resp := p.ProvisionResource(provisioners.ProvisionResourceRequest{
Config: c,
UIOutput: output,
})
if resp.Diagnostics.HasErrors() {
t.Fatal(resp.Diagnostics.Err())
}
got := strings.TrimSpace(output.OutputWriter.String())
want := `Executing: ["/bin/sh" "-c" "echo $FOO $BAR $BAZ"]
BAR 1 true`
if got != want {
t.Errorf("wrong output\ngot: %s\nwant: %s", got, want)
}
}
// Validate that Stop can Close can be called even when not provisioning.
func TestResourceProvisioner_StopClose(t *testing.T) {
p := New()
p.Stop()
p.Close()
}
func TestResourceProvisioner_nullsInOptionals(t *testing.T) {
output := cli.NewMockUi()
p := New()
schema := p.GetSchema().Provisioner
for i, cfg := range []cty.Value{
cty.ObjectVal(map[string]cty.Value{
"command": cty.StringVal("echo OK"),
"environment": cty.MapVal(map[string]cty.Value{
"FOO": cty.NullVal(cty.String),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"command": cty.StringVal("echo OK"),
"environment": cty.NullVal(cty.Map(cty.String)),
}),
cty.ObjectVal(map[string]cty.Value{
"command": cty.StringVal("echo OK"),
"interpreter": cty.ListVal([]cty.Value{cty.NullVal(cty.String)}),
}),
cty.ObjectVal(map[string]cty.Value{
"command": cty.StringVal("echo OK"),
"interpreter": cty.NullVal(cty.List(cty.String)),
}),
cty.ObjectVal(map[string]cty.Value{
"command": cty.StringVal("echo OK"),
"working_dir": cty.NullVal(cty.String),
}),
} {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
cfg, err := schema.CoerceValue(cfg)
if err != nil {
t.Fatal(err)
}
// verifying there are no panics
p.ProvisionResource(provisioners.ProvisionResourceRequest{
Config: cfg,
UIOutput: output,
})
})
}
}