mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 18:01:01 -06:00
bafa44b958
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.
321 lines
6.7 KiB
Go
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,
|
|
})
|
|
})
|
|
}
|
|
}
|