mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
This is part of a general effort to move all of Terraform's non-library package surface under internal in order to reinforce that these are for internal use within Terraform only. If you were previously importing packages under this prefix into an external codebase, you could pin to an earlier release tag as an interim solution until you've make a plan to achieve the same functionality some other way.
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/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,
|
|
})
|
|
})
|
|
}
|
|
}
|