Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make binary testing compatible with Terraform 0.15 #694

Merged
merged 3 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ require (
github.com/hashicorp/go-version v1.2.1
github.com/hashicorp/hcl/v2 v2.3.0
github.com/hashicorp/logutils v1.0.0
github.com/hashicorp/terraform-exec v0.12.0
github.com/hashicorp/terraform-exec v0.13.0
github.com/hashicorp/terraform-json v0.8.0
github.com/hashicorp/terraform-plugin-go v0.2.1
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,8 @@ github.com/hashicorp/hcl/v2 v2.3.0 h1:iRly8YaMwTBAKhn1Ybk7VSdzbnopghktCD031P8ggU
github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/terraform-exec v0.12.0 h1:Tb1VC2gqArl9EJziJjoazep2MyxMk00tnNKV/rgMba0=
github.com/hashicorp/terraform-exec v0.12.0/go.mod h1:SGhto91bVRlgXQWcJ5znSz+29UZIa8kpBbkGwQ+g9E8=
github.com/hashicorp/terraform-exec v0.13.0 h1:1Pth+pdWJAufJuWWjaVOVNEkoRTOjGn3hQpAqj4aPdg=
github.com/hashicorp/terraform-exec v0.13.0/go.mod h1:SGhto91bVRlgXQWcJ5znSz+29UZIa8kpBbkGwQ+g9E8=
github.com/hashicorp/terraform-json v0.8.0 h1:XObQ3PgqU52YLQKEaJ08QtUshAfN3yu4u8ebSW0vztc=
github.com/hashicorp/terraform-json v0.8.0/go.mod h1:3defM4kkMfttwiE7VakJDwCd4R+umhSQnvJwORXbprE=
github.com/hashicorp/terraform-plugin-go v0.2.1 h1:EW/R8bB2Zbkjmugzsy1d27yS8/0454b3MtYHkzOknqA=
Expand Down
11 changes: 10 additions & 1 deletion internal/plugintest/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"io/ioutil"
"os"

"github.com/hashicorp/terraform-exec/tfexec"
)

const subprocessCurrentSigil = "4acd63807899403ca4859f5bb948d2c6"
Expand Down Expand Up @@ -107,14 +109,21 @@ func (h *Helper) NewWorkingDir() (*WorkingDir, error) {
return nil, err
}

// symlink the provider source files into the base directory
// symlink the provider source files into the config directory
// e.g. testdata
err = symlinkDirectoriesOnly(h.sourceDir, dir)
if err != nil {
return nil, err
}

tf, err := tfexec.NewTerraform(dir, h.terraformExec)
if err != nil {
return nil, err
}

return &WorkingDir{
h: h,
tf: tf,
baseDir: dir,
terraformExec: h.terraformExec,
}, nil
Expand Down
74 changes: 24 additions & 50 deletions internal/plugintest/working_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import (
tfjson "github.com/hashicorp/terraform-json"
)

const (
ConfigFileName = "terraform_plugin_test.tf"
PlanFileName = "tfplan"
)

// WorkingDir represents a distinct working directory that can be used for
// running tests. Each test should construct its own WorkingDir by calling
// NewWorkingDir or RequireNewWorkingDir on its package's singleton
Expand All @@ -26,9 +31,6 @@ type WorkingDir struct {
// baseArgs is arguments that should be appended to all commands
baseArgs []string

// configDir contains the singular config file generated for each test
configDir string

// tf is the instance of tfexec.Terraform used for running Terraform commands
tf *tfexec.Terraform

Expand Down Expand Up @@ -75,55 +77,32 @@ func (wd *WorkingDir) GetHelper() *Helper {
return wd.h
}

func (wd *WorkingDir) relativeConfigDir() (string, error) {
relPath, err := filepath.Rel(wd.baseDir, wd.configDir)
if err != nil {
return "", fmt.Errorf("Error determining relative path of configuration directory: %w", err)
}
return relPath, nil
}

// SetConfig sets a new configuration for the working directory.
//
// This must be called at least once before any call to Init, Plan, Apply, or
// Destroy to establish the configuration. Any previously-set configuration is
// discarded and any saved plan is cleared.
func (wd *WorkingDir) SetConfig(cfg string) error {
// Each call to SetConfig creates a new directory under our baseDir.
// We create them within so that our final cleanup step will delete them
// automatically without any additional tracking.
configDir, err := ioutil.TempDir(wd.baseDir, "config")
if err != nil {
return err
}
configFilename := filepath.Join(configDir, "terraform_plugin_test.tf")
err = ioutil.WriteFile(configFilename, []byte(cfg), 0700)
if err != nil {
return err
}

tf, err := tfexec.NewTerraform(wd.baseDir, wd.terraformExec)
configFilename := filepath.Join(wd.baseDir, ConfigFileName)
err := ioutil.WriteFile(configFilename, []byte(cfg), 0700)
if err != nil {
return err
}

var mismatch *tfexec.ErrVersionMismatch
err = tf.SetDisablePluginTLS(true)
err = wd.tf.SetDisablePluginTLS(true)
if err != nil && !errors.As(err, &mismatch) {
return err
}
err = tf.SetSkipProviderVerify(true)
err = wd.tf.SetSkipProviderVerify(true)
if err != nil && !errors.As(err, &mismatch) {
return err
}

if p := os.Getenv("TF_ACC_LOG_PATH"); p != "" {
tf.SetLogPath(p)
wd.tf.SetLogPath(p)
}

wd.configDir = configDir
wd.tf = tf

// Changing configuration invalidates any saved plan.
err = wd.ClearPlan()
if err != nil {
Expand Down Expand Up @@ -156,28 +135,32 @@ func (wd *WorkingDir) ClearPlan() error {
// Init runs "terraform init" for the given working directory, forcing Terraform
// to use the current version of the plugin under test.
func (wd *WorkingDir) Init() error {
if wd.configDir == "" {
if _, err := os.Stat(wd.configFilename()); err != nil {
return fmt.Errorf("must call SetConfig before Init")
}

return wd.tf.Init(context.Background(), tfexec.Reattach(wd.reattachInfo), tfexec.Dir(wd.configDir))
return wd.tf.Init(context.Background(), tfexec.Reattach(wd.reattachInfo))
}

func (wd *WorkingDir) configFilename() string {
return filepath.Join(wd.baseDir, ConfigFileName)
}

func (wd *WorkingDir) planFilename() string {
return filepath.Join(wd.baseDir, "tfplan")
return filepath.Join(wd.baseDir, PlanFileName)
}

// CreatePlan runs "terraform plan" to create a saved plan file, which if successful
// will then be used for the next call to Apply.
func (wd *WorkingDir) CreatePlan() error {
_, err := wd.tf.Plan(context.Background(), tfexec.Reattach(wd.reattachInfo), tfexec.Refresh(false), tfexec.Out("tfplan"), tfexec.Dir(wd.configDir))
_, err := wd.tf.Plan(context.Background(), tfexec.Reattach(wd.reattachInfo), tfexec.Refresh(false), tfexec.Out(PlanFileName))
return err
}

// CreateDestroyPlan runs "terraform plan -destroy" to create a saved plan
// file, which if successful will then be used for the next call to Apply.
func (wd *WorkingDir) CreateDestroyPlan() error {
_, err := wd.tf.Plan(context.Background(), tfexec.Reattach(wd.reattachInfo), tfexec.Refresh(false), tfexec.Out("tfplan"), tfexec.Destroy(true), tfexec.Dir(wd.configDir))
_, err := wd.tf.Plan(context.Background(), tfexec.Reattach(wd.reattachInfo), tfexec.Refresh(false), tfexec.Out(PlanFileName), tfexec.Destroy(true))
return err
}

Expand All @@ -188,18 +171,9 @@ func (wd *WorkingDir) CreateDestroyPlan() error {
func (wd *WorkingDir) Apply() error {
args := []tfexec.ApplyOption{tfexec.Reattach(wd.reattachInfo), tfexec.Refresh(false)}
if wd.HasSavedPlan() {
args = append(args, tfexec.DirOrPlan("tfplan"))
} else {
// we need to use a relative config dir here or we get an
// error about Terraform not having any configuration. See
// https://github.com/hashicorp/terraform-plugin-sdk/issues/495
// for more info.
configDir, err := wd.relativeConfigDir()
if err != nil {
return err
}
args = append(args, tfexec.DirOrPlan(configDir))
args = append(args, tfexec.DirOrPlan(PlanFileName))
}

return wd.tf.Apply(context.Background(), args...)
}

Expand All @@ -209,7 +183,7 @@ func (wd *WorkingDir) Apply() error {
// If destroy fails then remote objects might still exist, and continue to
// exist after a particular test is concluded.
func (wd *WorkingDir) Destroy() error {
return wd.tf.Destroy(context.Background(), tfexec.Reattach(wd.reattachInfo), tfexec.Refresh(false), tfexec.Dir(wd.configDir))
return wd.tf.Destroy(context.Background(), tfexec.Reattach(wd.reattachInfo), tfexec.Refresh(false))
}

// HasSavedPlan returns true if there is a saved plan in the working directory. If
Expand Down Expand Up @@ -262,12 +236,12 @@ func (wd *WorkingDir) State() (*tfjson.State, error) {

// Import runs terraform import
func (wd *WorkingDir) Import(resource, id string) error {
return wd.tf.Import(context.Background(), resource, id, tfexec.Config(wd.configDir), tfexec.Reattach(wd.reattachInfo))
return wd.tf.Import(context.Background(), resource, id, tfexec.Config(wd.baseDir), tfexec.Reattach(wd.reattachInfo))
}

// Refresh runs terraform refresh
func (wd *WorkingDir) Refresh() error {
return wd.tf.Refresh(context.Background(), tfexec.Reattach(wd.reattachInfo), tfexec.State(filepath.Join(wd.baseDir, "terraform.tfstate")), tfexec.Dir(wd.configDir))
return wd.tf.Refresh(context.Background(), tfexec.Reattach(wd.reattachInfo), tfexec.State(filepath.Join(wd.baseDir, "terraform.tfstate")))
}

// Schemas returns an object describing the provider schemas.
Expand Down