2020-12-09 15:31:34 +00:00
|
|
|
package acceptance
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2021-01-05 15:40:45 +00:00
|
|
|
"path"
|
2020-12-09 15:31:34 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/cloudskiff/driftctl/pkg/analyser"
|
2020-12-14 09:49:12 +00:00
|
|
|
"github.com/pkg/errors"
|
2020-12-09 15:31:34 +00:00
|
|
|
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
|
|
|
|
"github.com/cloudskiff/driftctl/test"
|
|
|
|
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
|
|
|
|
"github.com/cloudskiff/driftctl/logger"
|
|
|
|
"github.com/cloudskiff/driftctl/pkg/cmd"
|
|
|
|
)
|
|
|
|
|
|
|
|
type AccCheck struct {
|
|
|
|
PreExec func()
|
|
|
|
PostExec func()
|
2021-01-07 16:53:17 +00:00
|
|
|
Env map[string]string
|
2020-12-09 15:31:34 +00:00
|
|
|
Check func(result *ScanResult, stdout string, err error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type AccTestCase struct {
|
|
|
|
Path string
|
|
|
|
Args []string
|
|
|
|
OnStart func()
|
|
|
|
OnEnd func()
|
|
|
|
Checks []AccCheck
|
|
|
|
tmpResultFilePath string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *AccTestCase) createResultFile(t *testing.T) error {
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
file, err := ioutil.TempFile(tmpDir, "result")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
c.tmpResultFilePath = file.Name()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *AccTestCase) validate() error {
|
|
|
|
if c.Checks == nil || len(c.Checks) == 0 {
|
|
|
|
return fmt.Errorf("checks attribute must be defined")
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Path == "" {
|
|
|
|
return fmt.Errorf("path attribute must be defined")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, arg := range c.Args {
|
|
|
|
if arg == "--output" || arg == "-o" {
|
|
|
|
return fmt.Errorf("--output flag should not be defined in test case, it is automatically tested")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *AccTestCase) getResultFilePath() string {
|
|
|
|
return c.tmpResultFilePath
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *AccTestCase) getResult(t *testing.T) *ScanResult {
|
|
|
|
analysis := analyser.Analysis{}
|
|
|
|
result, err := ioutil.ReadFile(c.getResultFilePath())
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal(result, &analysis); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return NewScanResult(t, analysis)
|
|
|
|
}
|
|
|
|
|
2021-01-05 15:40:45 +00:00
|
|
|
func (c *AccTestCase) terraformInit() error {
|
|
|
|
_, err := os.Stat(path.Join(c.Path, ".terraform"))
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
logrus.Debug("Running terraform init ...")
|
|
|
|
cmd := exec.Command("terraform", "init", "-input=false")
|
|
|
|
cmd.Dir = c.Path
|
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, string(out))
|
|
|
|
}
|
|
|
|
logrus.Debug("Terraform init done")
|
2020-12-09 15:31:34 +00:00
|
|
|
}
|
|
|
|
|
2021-01-05 15:40:45 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *AccTestCase) terraformApply() error {
|
2020-12-09 15:31:34 +00:00
|
|
|
logrus.Debug("Running terraform apply ...")
|
2021-01-05 15:40:45 +00:00
|
|
|
cmd := exec.Command("terraform", "apply", "-auto-approve")
|
|
|
|
cmd.Dir = c.Path
|
|
|
|
out, err := cmd.CombinedOutput()
|
2020-12-09 15:31:34 +00:00
|
|
|
if err != nil {
|
2020-12-14 09:49:12 +00:00
|
|
|
return errors.Wrap(err, string(out))
|
2020-12-09 15:31:34 +00:00
|
|
|
}
|
|
|
|
logrus.Debug("Terraform apply done")
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-01-05 15:40:45 +00:00
|
|
|
func (c *AccTestCase) terraformDestroy() error {
|
2020-12-09 15:31:34 +00:00
|
|
|
logrus.Debug("Running terraform destroy ...")
|
2021-01-05 15:40:45 +00:00
|
|
|
cmd := exec.Command("terraform", "destroy", "-auto-approve")
|
|
|
|
cmd.Dir = c.Path
|
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, string(out))
|
|
|
|
}
|
2020-12-09 15:31:34 +00:00
|
|
|
logrus.Debug("Terraform destroy done")
|
2021-01-05 15:40:45 +00:00
|
|
|
|
|
|
|
return nil
|
2020-12-09 15:31:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func runDriftCtlCmd(driftctlCmd *cmd.DriftctlCmd) (*cobra.Command, string, error) {
|
|
|
|
old := os.Stdout // keep backup of the real stdout
|
|
|
|
r, w, _ := os.Pipe()
|
|
|
|
os.Stdout = w
|
|
|
|
cmd, cmdErr := driftctlCmd.ExecuteC()
|
|
|
|
outC := make(chan string)
|
|
|
|
// copy the output in a separate goroutine so printing can't block indefinitely
|
|
|
|
go func() {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
_, _ = io.Copy(&buf, r)
|
|
|
|
outC <- buf.String()
|
|
|
|
}()
|
|
|
|
|
|
|
|
// back to normal state
|
|
|
|
w.Close()
|
|
|
|
os.Stdout = old // restoring the real stdout
|
|
|
|
out := <-outC
|
|
|
|
return cmd, out, cmdErr
|
|
|
|
}
|
|
|
|
|
|
|
|
func Run(t *testing.T, c AccTestCase) {
|
|
|
|
|
|
|
|
if os.Getenv("DRIFTCTL_ACC") == "" {
|
|
|
|
t.Skip()
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := c.validate(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.OnStart != nil {
|
|
|
|
c.OnStart()
|
|
|
|
}
|
2021-01-05 15:40:45 +00:00
|
|
|
|
|
|
|
// Disable terraform version checks
|
|
|
|
// @link https://www.terraform.io/docs/commands/index.html#upgrade-and-security-bulletin-checks
|
|
|
|
checkpoint := os.Getenv("CHECKPOINT_DISABLE")
|
|
|
|
os.Setenv("CHECKPOINT_DISABLE", "true")
|
|
|
|
|
|
|
|
// Execute terraform init if .terraform folder is not found in test folder
|
|
|
|
err := c.terraformInit()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = c.terraformApply()
|
2020-12-09 15:31:34 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2021-01-05 15:40:45 +00:00
|
|
|
|
|
|
|
defer func() {
|
|
|
|
err := c.terraformDestroy()
|
|
|
|
os.Setenv("CHECKPOINT_DISABLE", checkpoint)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}()
|
2020-12-09 15:31:34 +00:00
|
|
|
|
|
|
|
logger.Init(logger.GetConfig())
|
|
|
|
|
|
|
|
driftctlCmd := cmd.NewDriftctlCmd(test.Build{})
|
|
|
|
|
|
|
|
err = c.createResultFile(t)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if c.Args != nil {
|
|
|
|
c.Args = append([]string{""}, c.Args...)
|
2021-01-05 15:40:45 +00:00
|
|
|
c.Args = append(c.Args,
|
|
|
|
"--from", fmt.Sprintf("tfstate://%s", path.Join(c.Path, "terraform.tfstate")),
|
|
|
|
"--output", fmt.Sprintf("json://%s", c.getResultFilePath()),
|
|
|
|
)
|
2020-12-09 15:31:34 +00:00
|
|
|
}
|
|
|
|
os.Args = c.Args
|
|
|
|
|
|
|
|
for _, check := range c.Checks {
|
|
|
|
if check.Check == nil {
|
|
|
|
t.Fatal("Check attribute must be defined")
|
|
|
|
}
|
|
|
|
if check.PreExec != nil {
|
|
|
|
check.PreExec()
|
|
|
|
}
|
2021-01-07 16:53:17 +00:00
|
|
|
if len(check.Env) > 0 {
|
|
|
|
for key, value := range check.Env {
|
|
|
|
os.Setenv(key, value)
|
|
|
|
}
|
|
|
|
}
|
2020-12-09 15:31:34 +00:00
|
|
|
_, out, cmdErr := runDriftCtlCmd(driftctlCmd)
|
2021-01-07 16:53:17 +00:00
|
|
|
if len(check.Env) > 0 {
|
|
|
|
for key := range check.Env {
|
|
|
|
_ = os.Unsetenv(key)
|
|
|
|
}
|
|
|
|
}
|
2020-12-09 15:31:34 +00:00
|
|
|
check.Check(c.getResult(t), out, cmdErr)
|
|
|
|
if check.PostExec != nil {
|
|
|
|
check.PostExec()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if c.OnEnd != nil {
|
|
|
|
c.OnEnd()
|
|
|
|
}
|
|
|
|
}
|