func NewExecWithConfig(pathConfig string) (*Exec, error) {
	e, err := NewExec()
	if err != nil {
		return nil, err
	}
	v := viper.New()
	v.SetConfigFile(pathConfig)
	if err := v.ReadInConfig(); err != nil {
		return nil, err
	}
	err = e.AddToViper(v)
	if err != nil {
		return nil, err
	}
	e.configPath = pathConfig
	return e, nil
}
NewExecWithConfig will create a new Exec using the NewExec method, and will
use viper.Viper to apply the configuration located at pathConfig.
package exec
import (
	"database/sql"
	"errors"
	"fmt"
	"github.com/vitessio/arewefastyet/go/storage"
	"github.com/vitessio/arewefastyet/go/storage/psdb"
	"github.com/vitessio/arewefastyet/go/tools/macrobench"
	"io"
	"os"
	"path"
	"strconv"
	"time"
	"github.com/google/uuid"
	"github.com/spf13/viper"
	"github.com/vitessio/arewefastyet/go/exec/stats"
	"github.com/vitessio/arewefastyet/go/infra"
	"github.com/vitessio/arewefastyet/go/infra/ansible"
	"github.com/vitessio/arewefastyet/go/infra/construct"
	"github.com/vitessio/arewefastyet/go/infra/equinix"
	"github.com/vitessio/arewefastyet/go/tools/git"
)
const (
	keyExecUUID = "arewefastyet_exec_uuid"
	keyExecSource = "arewefastyet_source"
	keyVitessVersion = "vitess_git_version"
	keyExecutionType = "arewefastyet_execution_type"
	keyVtgatePlanner = "planner_version"
	keyGoVersion = "golang_gover"
	stderrFile = "exec-stderr.log"
	stdoutFile = "exec-stdout.log"
	ErrorNotPrepared      = "exec is not prepared"
	ErrorExecutionTimeout = "execution timeout"
)
type Exec struct {
	UUID          uuid.UUID
	InfraConfig   infra.Config
	AnsibleConfig ansible.Config
	Infra         infra.Infra
	Source        string
	GitRef        string
	Status string
	StartedAt  *time.Time
	FinishedAt *time.Time
	TypeOf string
	PullNB int
	configDB *psdb.Config
	clientDB *psdb.Client
	statsRemoteDBConfig stats.RemoteDBConfig
	rootDir string
	dirPath string
	stdout io.Writer
	stderr io.Writer
	createdInDB bool
	prepared    bool
	configPath  string
	VtgatePlannerVersion string
	GolangVersion string
}
const (
	SourceCron            = "cron"
	SourcePullRequest     = "cron_pr"
	SourcePullRequestBase = "cron_pr_base"
	SourceTag             = "cron_tags_"
	SourceReleaseBranch   = "cron_"
)
func (e *Exec) SetStdout(stdout *os.File) {
	e.stdout = stdout
	e.AnsibleConfig.SetStdout(stdout)
}
func (e *Exec) SetStderr(stderr *os.File) {
	e.stderr = stderr
	e.AnsibleConfig.SetStderr(stderr)
}
func (e *Exec) SetOutputToDefaultPath() error {
	if !e.prepared {
		return errors.New(ErrorNotPrepared)
	}
	outFile, err := os.OpenFile(path.Join(e.dirPath, stdoutFile), os.O_RDWR|os.O_CREATE, 0755)
	if err != nil {
		return err
	}
	errFile, err := os.OpenFile(path.Join(e.dirPath, stderrFile), os.O_RDWR|os.O_CREATE, 0755)
	if err != nil {
		return err
	}
	e.stdout = outFile
	e.stderr = errFile
	e.AnsibleConfig.SetOutputs(outFile, errFile)
	return nil
}
func (e *Exec) Prepare() error {
	if e.prepared {
		return nil
	}
	var err error
	defer func() {
		if !e.createdInDB {
			return
		}
		e.handleStepEnd(err)
	}()
	e.clientDB, err = e.configDB.NewClient()
	if err != nil {
		return err
	}
	if _, err = e.clientDB.Insert(
		"INSERT INTO execution(uuid, status, source, git_ref, type, pull_nb, go_version) VALUES(?, ?, ?, ?, ?, ?, ?)",
		e.UUID.String(),
		StatusCreated,
		e.Source,
		e.GitRef,
		e.TypeOf,
		e.PullNB,
		e.GolangVersion,
	); err != nil {
		return err
	}
	e.createdInDB = true
	e.Infra.SetTags(map[string]string{
		"execution_git_ref":         git.ShortenSHA(e.GitRef),
		"execution_source":          e.Source,
		"execution_type":            e.TypeOf,
		"execution_planner_version": e.VtgatePlannerVersion,
	})
	err = e.prepareDirectories()
	if err != nil {
		return err
	}
	err = e.Infra.Prepare()
	if err != nil {
		return err
	}
	if e.configPath == "" {
		e.configPath = viper.ConfigFileUsed()
	}
	e.AnsibleConfig.ExtraVars = map[string]interface{}{}
	e.statsRemoteDBConfig.AddToAnsible(&e.AnsibleConfig)
	if e.PullNB != 0 {
		e.AnsibleConfig.ExtraVars["vitess_git_version_fetch_pr"] = "refs/pull/" + strconv.Itoa(e.PullNB) + "/head"
		e.AnsibleConfig.ExtraVars["vitess_git_version_pr_nb"] = e.PullNB
	}
	if e.Source == SourceCron && e.TypeOf != "micro" {
		e.AnsibleConfig.ExtraVars["vitess_schema_tracking"] = 1
	}
	e.prepared = true
	return nil
}
func (e Exec) ExecuteWithTimeout(timeout time.Duration) (err error) {
	defer func() {
		e.handleStepEnd(err)
	}()
	errs := make(chan error)
	go func() {
		errs <- e.Execute()
	}()
	select {
	case err = <-errs:
		return
	case <-time.After(timeout):
		err = errors.New(ErrorExecutionTimeout)
		return
	}
}
func (e *Exec) Execute() (err error) {
	defer func() {
		e.handleStepEnd(err)
	}()
	if !e.prepared {
		return errors.New(ErrorNotPrepared)
	}
	if _, err := e.clientDB.Insert("UPDATE execution SET started_at = CURRENT_TIME, status = ? WHERE uuid = ?", StatusStarted, e.UUID.String()); err != nil {
		return err
	}
	IPs, err := e.provision()
	if err != nil {
		return err
	}
	err = ansible.AddIPsToFiles(IPs, e.AnsibleConfig)
	if err != nil {
		return err
	}
	err = ansible.AddLocalConfigPathToFiles(e.configPath, e.AnsibleConfig)
	if err != nil {
		return err
	}
	e.prepareAnsibleForExecution()
	err = e.Infra.Run(&e.AnsibleConfig)
	if err != nil {
		return err
	}
	return nil
}
func (e *Exec) prepareAnsibleForExecution() {
	e.AnsibleConfig.ExtraVars[keyExecUUID] = e.UUID.String()
	e.AnsibleConfig.ExtraVars[keyVitessVersion] = e.GitRef
	e.AnsibleConfig.ExtraVars[keyExecSource] = e.Source
	e.AnsibleConfig.ExtraVars[keyExecutionType] = e.TypeOf
	e.AnsibleConfig.ExtraVars[keyGoVersion] = e.GolangVersion
	if e.VtgatePlannerVersion == string(macrobench.Gen4FallbackPlanner) {
		e.AnsibleConfig.ExtraVars[keyVtgatePlanner] = e.VtgatePlannerVersion
	}
}
func (e *Exec) Success() error {
	rows, err := e.clientDB.Select("SELECT uuid FROM execution WHERE uuid = ? AND status = ?", e.UUID.String(), StatusFailed)
	if err != nil {
		return err
	}
	defer rows.Close()
	if rows.Next() {
		return nil
	}
	_, err = e.clientDB.Insert("UPDATE execution SET finished_at = CURRENT_TIME, status = ? WHERE uuid = ?", StatusFinished, e.UUID.String())
	return err
}
func (e *Exec) handleStepEnd(err error) {
	if err != nil {
		_, _ = e.clientDB.Insert("UPDATE execution SET finished_at = CURRENT_TIME, status = ? WHERE uuid = ?", StatusFailed, e.UUID.String())
	}
}
func (e Exec) CleanUp() (err error) {
	defer func() {
		e.handleStepEnd(err)
	}()
	err = e.Infra.CleanUp()
	if err != nil {
		return err
	}
	return nil
}
func NewExec() (*Exec, error) {
	inf, err := construct.NewInfra(equinix.Name)
	if err != nil {
		return nil, err
	}
	ex := Exec{
		UUID:  uuid.New(),
		Infra: inf,
		stdout: os.Stdout,
		stderr: os.Stderr,
		configDB:   &psdb.Config{},
		clientDB:   nil,
		configPath: viper.ConfigFileUsed(),
	}
	ex.Infra.SetConfig(&ex.InfraConfig)
	ex.Infra.SetExecUUID(ex.UUID)
	return &ex, nil
}