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 }