opentofu/internal/builtin/provisioners/remote-exec/resource_provisioner_test.go
James Bardin bafa44b958 return diagnostics from provisioners
Do not convert provisioner diagnostics to errors so that users can get
context from provisioner failures.

Return diagnostics from the builtin provisioners that can be annotated
with configuration context and instance addresses.
2021-05-19 11:24:54 -04:00

321 lines
6.7 KiB
Go

package remoteexec
import (
"bytes"
"context"
"fmt"
"io"
"log"
"testing"
"time"
"strings"
"github.com/hashicorp/terraform/internal/communicator"
"github.com/hashicorp/terraform/internal/communicator/remote"
"github.com/hashicorp/terraform/internal/provisioners"
"github.com/mitchellh/cli"
"github.com/zclconf/go-cty/cty"
)
func TestResourceProvider_Validate_good(t *testing.T) {
c := cty.ObjectVal(map[string]cty.Value{
"inline": cty.ListVal([]cty.Value{cty.StringVal("echo foo")}),
})
resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{
Config: c,
})
if len(resp.Diagnostics) > 0 {
t.Fatal(resp.Diagnostics.ErrWithWarnings())
}
}
func TestResourceProvider_Validate_bad(t *testing.T) {
c := cty.ObjectVal(map[string]cty.Value{
"invalid": cty.StringVal("nope"),
})
resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{
Config: c,
})
if !resp.Diagnostics.HasErrors() {
t.Fatalf("Should have errors")
}
}
var expectedScriptOut = `cd /tmp
wget http://foobar
exit 0
`
func TestResourceProvider_generateScript(t *testing.T) {
inline := cty.ListVal([]cty.Value{
cty.StringVal("cd /tmp"),
cty.StringVal("wget http://foobar"),
cty.StringVal("exit 0"),
})
out, err := generateScripts(inline)
if err != nil {
t.Fatalf("err: %v", err)
}
if len(out) != 1 {
t.Fatal("expected 1 out")
}
if out[0] != expectedScriptOut {
t.Fatalf("bad: %v", out)
}
}
func TestResourceProvider_generateScriptEmptyInline(t *testing.T) {
inline := cty.ListVal([]cty.Value{cty.StringVal("")})
_, err := generateScripts(inline)
if err == nil {
t.Fatal("expected error, got none")
}
if !strings.Contains(err.Error(), "empty string") {
t.Fatalf("expected empty string error, got: %s", err)
}
}
func TestResourceProvider_CollectScripts_inline(t *testing.T) {
conf := map[string]cty.Value{
"inline": cty.ListVal([]cty.Value{
cty.StringVal("cd /tmp"),
cty.StringVal("wget http://foobar"),
cty.StringVal("exit 0"),
}),
}
scripts, err := collectScripts(cty.ObjectVal(conf))
if err != nil {
t.Fatalf("err: %v", err)
}
if len(scripts) != 1 {
t.Fatalf("bad: %v", scripts)
}
var out bytes.Buffer
_, err = io.Copy(&out, scripts[0])
if err != nil {
t.Fatalf("err: %v", err)
}
if out.String() != expectedScriptOut {
t.Fatalf("bad: %v", out.String())
}
}
func TestResourceProvider_CollectScripts_script(t *testing.T) {
p := New()
schema := p.GetSchema().Provisioner
conf, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
"scripts": cty.ListVal([]cty.Value{
cty.StringVal("testdata/script1.sh"),
}),
}))
if err != nil {
t.Fatal(err)
}
scripts, err := collectScripts(conf)
if err != nil {
t.Fatalf("err: %v", err)
}
if len(scripts) != 1 {
t.Fatalf("bad: %v", scripts)
}
var out bytes.Buffer
_, err = io.Copy(&out, scripts[0])
if err != nil {
t.Fatalf("err: %v", err)
}
if out.String() != expectedScriptOut {
t.Fatalf("bad: %v", out.String())
}
}
func TestResourceProvider_CollectScripts_scripts(t *testing.T) {
p := New()
schema := p.GetSchema().Provisioner
conf, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
"scripts": cty.ListVal([]cty.Value{
cty.StringVal("testdata/script1.sh"),
cty.StringVal("testdata/script1.sh"),
cty.StringVal("testdata/script1.sh"),
}),
}))
if err != nil {
log.Fatal(err)
}
scripts, err := collectScripts(conf)
if err != nil {
t.Fatalf("err: %v", err)
}
if len(scripts) != 3 {
t.Fatalf("bad: %v", scripts)
}
for idx := range scripts {
var out bytes.Buffer
_, err = io.Copy(&out, scripts[idx])
if err != nil {
t.Fatalf("err: %v", err)
}
if out.String() != expectedScriptOut {
t.Fatalf("bad: %v", out.String())
}
}
}
func TestResourceProvider_CollectScripts_scriptsEmpty(t *testing.T) {
p := New()
schema := p.GetSchema().Provisioner
conf, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
"scripts": cty.ListVal([]cty.Value{cty.StringVal("")}),
}))
if err != nil {
t.Fatal(err)
}
_, err = collectScripts(conf)
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), "empty string") {
t.Fatalf("Expected empty string error, got: %s", err)
}
}
func TestProvisionerTimeout(t *testing.T) {
o := cli.NewMockUi()
c := new(communicator.MockCommunicator)
disconnected := make(chan struct{})
c.DisconnectFunc = func() error {
close(disconnected)
return nil
}
completed := make(chan struct{})
c.CommandFunc = func(cmd *remote.Cmd) error {
defer close(completed)
cmd.Init()
time.Sleep(2 * time.Second)
cmd.SetExitStatus(0, nil)
return nil
}
c.ConnTimeout = time.Second
c.UploadScripts = map[string]string{"hello": "echo hello"}
c.RemoteScriptPath = "hello"
conf := map[string]cty.Value{
"inline": cty.ListVal([]cty.Value{cty.StringVal("echo hello")}),
}
scripts, err := collectScripts(cty.ObjectVal(conf))
if err != nil {
t.Fatal(err)
}
ctx := context.Background()
done := make(chan struct{})
var runErr error
go func() {
defer close(done)
runErr = runScripts(ctx, o, c, scripts)
}()
select {
case <-disconnected:
t.Fatal("communicator disconnected before command completed")
case <-completed:
}
<-done
if runErr != nil {
t.Fatal(err)
}
}
// 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_connectionRequired(t *testing.T) {
p := New()
resp := p.ProvisionResource(provisioners.ProvisionResourceRequest{})
if !resp.Diagnostics.HasErrors() {
t.Fatal("expected error")
}
got := resp.Diagnostics.Err().Error()
if !strings.Contains(got, "Missing connection") {
t.Fatalf("expected 'Missing connection' error: got %q", got)
}
}
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{
"script": cty.StringVal("echo"),
"inline": cty.NullVal(cty.List(cty.String)),
}),
cty.ObjectVal(map[string]cty.Value{
"inline": cty.ListVal([]cty.Value{
cty.NullVal(cty.String),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"script": cty.NullVal(cty.String),
}),
cty.ObjectVal(map[string]cty.Value{
"scripts": cty.NullVal(cty.List(cty.String)),
}),
cty.ObjectVal(map[string]cty.Value{
"scripts": cty.ListVal([]cty.Value{
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,
})
})
}
}