github.com/sl1pm4t/terraform@v0.6.4-0.20170725213156-870617d22df3/command/e2etest/main_test.go (about)

     1  package e2etest
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"testing"
    12  
    13  	tfcore "github.com/hashicorp/terraform/terraform"
    14  )
    15  
    16  var terraformBin string
    17  
    18  func TestMain(m *testing.M) {
    19  	teardown := setup()
    20  	code := m.Run()
    21  	teardown()
    22  	os.Exit(code)
    23  }
    24  
    25  func setup() func() {
    26  	if terraformBin != "" {
    27  		// this is pre-set when we're running in a binary produced from
    28  		// the make-archive.sh script, since that builds a ready-to-go
    29  		// binary into the archive. However, we do need to turn it into
    30  		// an absolute path so that we can find it when we change the
    31  		// working directory during tests.
    32  		var err error
    33  		terraformBin, err = filepath.Abs(terraformBin)
    34  		if err != nil {
    35  			panic(fmt.Sprintf("failed to find absolute path of terraform executable: %s", err))
    36  		}
    37  		return func() {}
    38  	}
    39  
    40  	tmpFile, err := ioutil.TempFile("", "terraform")
    41  	if err != nil {
    42  		panic(err)
    43  	}
    44  	tmpFilename := tmpFile.Name()
    45  	if err = tmpFile.Close(); err != nil {
    46  		panic(err)
    47  	}
    48  
    49  	cmd := exec.Command(
    50  		"go", "build",
    51  		"-o", tmpFilename,
    52  		"github.com/hashicorp/terraform",
    53  	)
    54  	cmd.Stderr = os.Stderr
    55  	cmd.Stdout = os.Stdout
    56  
    57  	err = cmd.Run()
    58  	if err != nil {
    59  		// The go compiler will have already produced some error messages
    60  		// on stderr by the time we get here.
    61  		panic(fmt.Sprintf("failed to build terraform executable: %s", err))
    62  	}
    63  
    64  	// Make the executable available for use in tests
    65  	terraformBin = tmpFilename
    66  
    67  	return func() {
    68  		os.Remove(tmpFilename)
    69  	}
    70  }
    71  
    72  func canAccessNetwork() bool {
    73  	// We re-use the flag normally used for acceptance tests since that's
    74  	// established as a way to opt-in to reaching out to real systems that
    75  	// may suffer transient errors.
    76  	return os.Getenv("TF_ACC") != ""
    77  }
    78  
    79  func skipIfCannotAccessNetwork(t *testing.T) {
    80  	if !canAccessNetwork() {
    81  		t.Skip("network access not allowed; use TF_ACC=1 to enable")
    82  	}
    83  }
    84  
    85  // Type terraform represents the combination of a compiled Terraform binary
    86  // and a temporary working directory to run it in.
    87  //
    88  // This is the main harness for tests in this package.
    89  type terraform struct {
    90  	bin string
    91  	dir string
    92  }
    93  
    94  // newTerraform prepares a temporary directory containing the files from the
    95  // given fixture and returns an instance of type terraform that can run
    96  // the generated Terraform binary in that directory.
    97  //
    98  // If the temporary directory cannot be created, a fixture of the given name
    99  // cannot be found, or if an error occurs while _copying_ the fixture files,
   100  // this function will panic. Tests should be written to assume that this
   101  // function always succeeds.
   102  func newTerraform(fixtureName string) *terraform {
   103  	tmpDir, err := ioutil.TempDir("", "terraform-e2etest")
   104  	if err != nil {
   105  		panic(err)
   106  	}
   107  
   108  	// For our purposes here we do a very simplistic file copy that doesn't
   109  	// attempt to preserve file permissions, attributes, alternate data
   110  	// streams, etc. Since we only have to deal with our own fixtures in
   111  	// the test-fixtures subdir, we know we don't need to deal with anything
   112  	// of this nature.
   113  	srcDir := filepath.Join("test-fixtures", fixtureName)
   114  	err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
   115  		if err != nil {
   116  			return err
   117  		}
   118  		if path == srcDir {
   119  			// nothing to do at the root
   120  			return nil
   121  		}
   122  
   123  		srcFn := path
   124  
   125  		path, err = filepath.Rel(srcDir, path)
   126  		if err != nil {
   127  			return err
   128  		}
   129  
   130  		dstFn := filepath.Join(tmpDir, path)
   131  
   132  		if info.IsDir() {
   133  			return os.Mkdir(dstFn, os.ModePerm)
   134  		}
   135  
   136  		src, err := os.Open(srcFn)
   137  		if err != nil {
   138  			return err
   139  		}
   140  		dst, err := os.OpenFile(dstFn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.ModePerm)
   141  		if err != nil {
   142  			return err
   143  		}
   144  
   145  		_, err = io.Copy(dst, src)
   146  		if err != nil {
   147  			return err
   148  		}
   149  
   150  		if err := src.Close(); err != nil {
   151  			return err
   152  		}
   153  		if err := dst.Close(); err != nil {
   154  			return err
   155  		}
   156  
   157  		return nil
   158  	})
   159  	if err != nil {
   160  		panic(err)
   161  	}
   162  
   163  	return &terraform{
   164  		bin: terraformBin,
   165  		dir: tmpDir,
   166  	}
   167  }
   168  
   169  // Cmd returns an exec.Cmd pre-configured to run the generated Terraform
   170  // binary with the given arguments in the temporary working directory.
   171  //
   172  // The returned object can be mutated by the caller to customize how the
   173  // process will be run, before calling Run.
   174  func (t *terraform) Cmd(args ...string) *exec.Cmd {
   175  	cmd := exec.Command(t.bin, args...)
   176  	cmd.Dir = t.dir
   177  	cmd.Env = os.Environ()
   178  
   179  	// Disable checkpoint since we don't want to harass that service when
   180  	// our tests run. (This does, of course, mean we can't actually do
   181  	// end-to-end testing of our Checkpoint interactions.)
   182  	cmd.Env = append(cmd.Env, "CHECKPOINT_DISABLE=1")
   183  
   184  	return cmd
   185  }
   186  
   187  // Run executes the generated Terraform binary with the given arguments
   188  // and returns the bytes that it wrote to both stdout and stderr.
   189  //
   190  // This is a simple way to run Terraform for non-interactive commands
   191  // that don't need any special environment variables. For more complex
   192  // situations, use Cmd and customize the command before running it.
   193  func (t *terraform) Run(args ...string) (stdout, stderr string, err error) {
   194  	cmd := t.Cmd(args...)
   195  	cmd.Stdin = nil
   196  	cmd.Stdout = &bytes.Buffer{}
   197  	cmd.Stderr = &bytes.Buffer{}
   198  	err = cmd.Run()
   199  	stdout = cmd.Stdout.(*bytes.Buffer).String()
   200  	stderr = cmd.Stderr.(*bytes.Buffer).String()
   201  	return
   202  }
   203  
   204  // Path returns a file path within the temporary working directory by
   205  // appending the given arguments as path segments.
   206  func (t *terraform) Path(parts ...string) string {
   207  	args := make([]string, len(parts)+1)
   208  	args[0] = t.dir
   209  	args = append(args, parts...)
   210  	return filepath.Join(args...)
   211  }
   212  
   213  // OpenFile is a helper for easily opening a file from the working directory
   214  // for reading.
   215  func (t *terraform) OpenFile(path ...string) (*os.File, error) {
   216  	flatPath := t.Path(path...)
   217  	return os.Open(flatPath)
   218  }
   219  
   220  // ReadFile is a helper for easily reading a whole file from the working
   221  // directory.
   222  func (t *terraform) ReadFile(path ...string) ([]byte, error) {
   223  	flatPath := t.Path(path...)
   224  	return ioutil.ReadFile(flatPath)
   225  }
   226  
   227  // FileExists is a helper for easily testing whether a particular file
   228  // exists in the working directory.
   229  func (t *terraform) FileExists(path ...string) bool {
   230  	flatPath := t.Path(path...)
   231  	_, err := os.Stat(flatPath)
   232  	return !os.IsNotExist(err)
   233  }
   234  
   235  // LocalState is a helper for easily reading the local backend's state file
   236  // terraform.tfstate from the working directory.
   237  func (t *terraform) LocalState() (*tfcore.State, error) {
   238  	f, err := t.OpenFile("terraform.tfstate")
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  	defer f.Close()
   243  	return tfcore.ReadState(f)
   244  }
   245  
   246  // Plan is a helper for easily reading a plan file from the working directory.
   247  func (t *terraform) Plan(path ...string) (*tfcore.Plan, error) {
   248  	f, err := t.OpenFile(path...)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	defer f.Close()
   253  	return tfcore.ReadPlan(f)
   254  }
   255  
   256  // SetLocalState is a helper for easily writing to the file the local backend
   257  // uses for state in the working directory. This does not go through the
   258  // actual local backend code, so processing such as management of serials
   259  // does not apply and the given state will simply be written verbatim.
   260  func (t *terraform) SetLocalState(state *tfcore.State) error {
   261  	path := t.Path("terraform.tfstate")
   262  	f, err := os.OpenFile(path, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, os.ModePerm)
   263  	if err != nil {
   264  		return err
   265  	}
   266  	defer func() {
   267  		err := f.Close()
   268  		if err != nil {
   269  			panic(fmt.Sprintf("failed to close state file after writing: %s", err))
   270  		}
   271  	}()
   272  
   273  	return tfcore.WriteState(state, f)
   274  }
   275  
   276  // Close cleans up the temporary resources associated with the object,
   277  // including its working directory. It is not valid to call Cmd or Run
   278  // after Close returns.
   279  //
   280  // This method does _not_ stop any running child processes. It's the
   281  // caller's responsibility to also terminate those _before_ closing the
   282  // underlying terraform object.
   283  //
   284  // This function is designed to run under "defer", so it doesn't actually
   285  // do any error handling and will leave dangling temporary files on disk
   286  // if any errors occur while cleaning up.
   287  func (t *terraform) Close() {
   288  	os.RemoveAll(t.dir)
   289  }