terraform-provider-gitea/vendor/github.com/hashicorp/terraform-plugin-test/helper.go
Malar Invention 00ebcd295e add vendor
2022-04-03 09:37:16 +05:30

291 lines
9.8 KiB
Go

package tftest
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
getter "github.com/hashicorp/go-getter"
)
const subprocessCurrentSigil = "4acd63807899403ca4859f5bb948d2c6"
const subprocessPreviousSigil = "2279afb8cf71423996be1fd65d32f13b"
// AutoInitProviderHelper is the main entrypoint for testing provider plugins
// using this package. It is intended to be called during TestMain to prepare
// for provider testing.
//
// AutoInitProviderHelper will discover the location of a current Terraform CLI
// executable to test against, detect whether a prior version of the plugin is
// available for upgrade tests, and then will return an object containing the
// results of that initialization which can then be stored in a global variable
// for use in other tests.
func AutoInitProviderHelper(name string, sourceDir string) *Helper {
helper, err := AutoInitHelper("terraform-provider-"+name, sourceDir)
if err != nil {
fmt.Fprintf(os.Stderr, "cannot run Terraform provider tests: %s\n", err)
os.Exit(1)
}
return helper
}
// Helper is intended as a per-package singleton created in TestMain which
// other tests in a package can use to create Terraform execution contexts
type Helper struct {
baseDir string
// sourceDir is the dir containing the provider source code, needed
// for tests that use fixture files.
sourceDir string
pluginName string
terraformExec string
thisPluginDir, prevPluginDir string
}
// AutoInitHelper uses the auto-discovery behavior of DiscoverConfig to prepare
// a configuration and then calls InitHelper with it. This is a convenient
// way to get the standard init behavior based on environment variables, and
// callers should use this unless they have an unusual requirement that calls
// for constructing a config in a different way.
func AutoInitHelper(pluginName string, sourceDir string) (*Helper, error) {
config, err := DiscoverConfig(pluginName, sourceDir)
if err != nil {
return nil, err
}
return InitHelper(config)
}
// InitHelper prepares a testing helper with the given configuration.
//
// For most callers it is sufficient to call AutoInitHelper instead, which
// will construct a configuration automatically based on certain environment
// variables.
//
// If this function returns an error then it may have left some temporary files
// behind in the system's temporary directory. There is currently no way to
// automatically clean those up.
func InitHelper(config *Config) (*Helper, error) {
tempDir := os.Getenv("TF_ACC_TEMP_DIR")
baseDir, err := ioutil.TempDir(tempDir, "tftest-"+config.PluginName)
if err != nil {
return nil, fmt.Errorf("failed to create temporary directory for test helper: %s", err)
}
var thisPluginDir, prevPluginDir string
if config.CurrentPluginExec != "" {
thisPluginDir, err = ioutil.TempDir(baseDir, "plugins-current")
if err != nil {
return nil, fmt.Errorf("failed to create temporary directory for -plugin-dir: %s", err)
}
currentExecPath := filepath.Join(thisPluginDir, config.PluginName)
err = symlinkFile(config.CurrentPluginExec, currentExecPath)
if err != nil {
return nil, fmt.Errorf("failed to create symlink at %s to %s: %s", currentExecPath, config.CurrentPluginExec, err)
}
err = symlinkAuxiliaryProviders(thisPluginDir)
if err != nil {
return nil, fmt.Errorf("failed to symlink auxiliary providers: %s", err)
}
} else {
return nil, fmt.Errorf("CurrentPluginExec is not set")
}
if config.PreviousPluginExec != "" {
prevPluginDir, err = ioutil.TempDir(baseDir, "plugins-previous")
if err != nil {
return nil, fmt.Errorf("failed to create temporary directory for previous -plugin-dir: %s", err)
}
prevExecPath := filepath.Join(prevPluginDir, config.PluginName)
err = symlinkFile(config.PreviousPluginExec, prevExecPath)
if err != nil {
return nil, fmt.Errorf("failed to create symlink at %s to %s: %s", prevExecPath, config.PreviousPluginExec, err)
}
err = symlinkAuxiliaryProviders(prevPluginDir)
if err != nil {
return nil, fmt.Errorf("failed to symlink auxiliary providers: %s", err)
}
}
return &Helper{
baseDir: baseDir,
sourceDir: config.SourceDir,
pluginName: config.PluginName,
terraformExec: config.TerraformExec,
thisPluginDir: thisPluginDir,
prevPluginDir: prevPluginDir,
}, nil
}
// symlinkAuxiliaryProviders discovers auxiliary provider binaries, used in
// multi-provider tests, and symlinks them to the plugin directory.
//
// Auxiliary provider binaries should be included in the provider source code
// directory, under the path terraform.d/plugins/$GOOS_$GOARCH/provider-name.
//
// The environment variable TF_ACC_PROVIDER_ROOT_DIR must be set to the path of
// the provider source code directory root in order to use this feature.
func symlinkAuxiliaryProviders(pluginDir string) error {
providerRootDir := os.Getenv("TF_ACC_PROVIDER_ROOT_DIR")
if providerRootDir == "" {
// common case; assume intentional and do not log
return nil
}
_, err := os.Stat(filepath.Join(providerRootDir, "terraform.d", "plugins"))
if os.IsNotExist(err) {
fmt.Printf("No terraform.d/plugins directory found: continuing. Unset TF_ACC_PROVIDER_ROOT_DIR or supply provider binaries in terraform.d/plugins/$GOOS_$GOARCH to disable this message.")
return nil
} else if err != nil {
return fmt.Errorf("Unexpected error: %s", err)
}
auxiliaryProviderDir := filepath.Join(providerRootDir, "terraform.d", "plugins", runtime.GOOS+"_"+runtime.GOARCH)
// If we can't os.Stat() terraform.d/plugins/$GOOS_$GOARCH, however,
// assume the omission was unintentional, and error.
_, err = os.Stat(auxiliaryProviderDir)
if os.IsNotExist(err) {
return fmt.Errorf("error finding auxiliary provider dir %s: %s", auxiliaryProviderDir, err)
} else if err != nil {
return fmt.Errorf("Unexpected error: %s", err)
}
// now find all the providers in that dir and symlink them to the plugin dir
providers, err := ioutil.ReadDir(auxiliaryProviderDir)
if err != nil {
return fmt.Errorf("error reading auxiliary providers: %s", err)
}
zipDecompressor := new(getter.ZipDecompressor)
for _, provider := range providers {
filename := provider.Name()
filenameExt := filepath.Ext(filename)
name := strings.TrimSuffix(filename, filenameExt)
path := filepath.Join(auxiliaryProviderDir, name)
symlinkPath := filepath.Join(pluginDir, name)
// exit early if we have already symlinked this provider
_, err := os.Stat(symlinkPath)
if err == nil {
continue
}
// if filename ends in .zip, assume it is a zip and extract it
// otherwise assume it is a provider binary
if filenameExt == ".zip" {
_, err = os.Stat(path)
if os.IsNotExist(err) {
zipDecompressor.Decompress(path, filepath.Join(auxiliaryProviderDir, filename), false)
} else if err != nil {
return fmt.Errorf("Unexpected error: %s", err)
}
}
err = symlinkFile(path, symlinkPath)
if err != nil {
return fmt.Errorf("error symlinking auxiliary provider %s: %s", name, err)
}
}
return nil
}
// GetPluginName returns the configured plugin name.
func (h *Helper) GetPluginName() string {
return h.pluginName
}
// Close cleans up temporary files and directories created to support this
// helper, returning an error if any of the cleanup fails.
//
// Call this before returning from TestMain to minimize the amount of detritus
// left behind in the filesystem after the tests complete.
func (h *Helper) Close() error {
return os.RemoveAll(h.baseDir)
}
// NewWorkingDir creates a new working directory for use in the implementation
// of a single test, returning a WorkingDir object representing that directory.
//
// If the working directory object is not itself closed by the time the test
// program exits, the Close method on the helper itself will attempt to
// delete it.
func (h *Helper) NewWorkingDir() (*WorkingDir, error) {
dir, err := ioutil.TempDir(h.baseDir, "work")
if err != nil {
return nil, err
}
// symlink the provider source files into the base directory
err = symlinkDirectoriesOnly(h.sourceDir, dir)
if err != nil {
return nil, err
}
// symlink the provider binaries into the base directory
err = symlinkDir(h.thisPluginDir, dir)
if err != nil {
return nil, err
}
return &WorkingDir{
h: h,
baseArgs: []string{"-no-color"},
baseDir: dir,
}, nil
}
// RequireNewWorkingDir is a variant of NewWorkingDir that takes a TestControl
// object and will immediately fail the running test if the creation of the
// working directory fails.
func (h *Helper) RequireNewWorkingDir(t TestControl) *WorkingDir {
t.Helper()
wd, err := h.NewWorkingDir()
if err != nil {
t := testingT{t}
t.Fatalf("failed to create new working directory: %s", err)
return nil
}
return wd
}
// HasPreviousVersion returns true if and only if the receiving helper has a
// previous plugin version available for use in tests.
func (h *Helper) HasPreviousVersion() bool {
return h.prevPluginDir != ""
}
// TerraformExecPath returns the location of the Terraform CLI executable that
// should be used when running tests.
func (h *Helper) TerraformExecPath() string {
return h.terraformExec
}
// PluginDir returns the directory that should be used as the -plugin-dir when
// running "terraform init" in order to make Terraform detect the current
// version of the plugin.
func (h *Helper) PluginDir() string {
return h.thisPluginDir
}
// PreviousPluginDir returns the directory that should be used as the -plugin-dir
// when running "terraform init" in order to make Terraform detect the previous
// version of the plugin, if available.
//
// If no previous version is available, this method will panic. Use
// RequirePreviousVersion or HasPreviousVersion to ensure a previous version is
// available before calling this.
func (h *Helper) PreviousPluginDir() string {
if h.prevPluginDir != "" {
panic("PreviousPluginDir not available")
}
return h.prevPluginDir
}