github.com/Kevinklinger/open_terraform@v0.11.12-beta1/e2e/e2e.go (about)

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