github.com/tooploox/oya@v0.0.21-0.20230524103240-1cda1861aad6/oya_test.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"regexp"
    10  	"runtime"
    11  	"strings"
    12  
    13  	"github.com/cucumber/godog"
    14  	"github.com/pkg/errors"
    15  	log "github.com/sirupsen/logrus"
    16  	"github.com/tooploox/oya/cmd"
    17  	"github.com/tooploox/oya/pkg/secrets"
    18  	"github.com/tooploox/oya/testutil/gherkin"
    19  )
    20  
    21  const sopsPgpKey = "317D 6971 DD80 4501 A6B8  65B9 0F1F D46E 2E8C 7202"
    22  
    23  type SuiteContext struct {
    24  	projectDir string
    25  
    26  	lastCommandErr      error
    27  	lastCommandExitCode int
    28  	stdout              *bytes.Buffer
    29  }
    30  
    31  func (c *SuiteContext) MustSetUp() {
    32  	projectDir, err := ioutil.TempDir("", "oya")
    33  	if err != nil {
    34  		panic(err)
    35  	}
    36  
    37  	overrideOyaCmd(projectDir)
    38  	setEnv(projectDir)
    39  
    40  	log.SetLevel(log.DebugLevel)
    41  	c.projectDir = projectDir
    42  	c.stdout = bytes.NewBuffer(nil)
    43  }
    44  
    45  func (c *SuiteContext) MustTearDown() {
    46  	if err := removePGPKeys(c.projectDir); err != nil {
    47  		panic(err)
    48  	}
    49  
    50  	if err := os.RemoveAll(c.projectDir); err != nil {
    51  		panic(err)
    52  	}
    53  }
    54  
    55  func setEnv(projectDir string) {
    56  	err := os.Setenv("OYA_HOME", projectDir)
    57  	if err != nil {
    58  		panic(err)
    59  	}
    60  	err = os.Setenv("SOPS_PGP_FP", sopsPgpKey)
    61  	if err != nil {
    62  		panic(err)
    63  	}
    64  }
    65  
    66  // removePGPKeys removes PGP keys based on fingerprints(s) in .sops.yaml, NOT sopsPgpKey ^.
    67  func removePGPKeys(projectDir string) error {
    68  	if err := os.Chdir(projectDir); err != nil {
    69  		return err
    70  	}
    71  	sops, err := secrets.LoadPGPSopsYaml()
    72  	if err != nil {
    73  		if os.IsNotExist(err) {
    74  			return nil
    75  		}
    76  
    77  		return err
    78  	}
    79  	fingerprints := make([]string, 0)
    80  
    81  	for _, rule := range sops.CreationRules {
    82  		fingerprints = append(fingerprints, strings.Split(rule.PGP, ",")...)
    83  	}
    84  	return secrets.RemovePGPKeypairs(fingerprints)
    85  }
    86  
    87  // overrideOyaCmd overrides `oya` command used by $Tasks in templates
    88  // to run oya tasks.
    89  // It builds oya to a temporary directory and use it to launch Oya in scripts.
    90  func overrideOyaCmd(projectDir string) {
    91  	executablePath := filepath.Join(projectDir, "_bin/oya")
    92  	oyaCmdOverride := fmt.Sprintf(
    93  		"function oya() { (cd %v && go build -o %v oya.go) && %v $@; }",
    94  		sourceFileDirectory(), executablePath, executablePath)
    95  	os.Setenv("OYA_CMD", oyaCmdOverride)
    96  }
    97  
    98  func (c *SuiteContext) writeFile(path, contents string) error {
    99  	targetPath := c.resolvePath(path)
   100  	dir := filepath.Dir(targetPath)
   101  	err := os.MkdirAll(dir, 0700)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	return ioutil.WriteFile(targetPath, []byte(contents), 0600)
   106  }
   107  
   108  func (c *SuiteContext) readFile(path string) (string, error) {
   109  	sourcePath := c.resolvePath(path)
   110  	contents, err := ioutil.ReadFile(sourcePath)
   111  	if err != nil {
   112  		return "", err
   113  	}
   114  	return string(contents), err
   115  }
   116  
   117  func (c *SuiteContext) resolvePath(path string) string {
   118  	if filepath.IsAbs(path) {
   119  		return path
   120  	}
   121  	return filepath.Join(c.projectDir, path)
   122  }
   123  
   124  func (c *SuiteContext) iAmInProjectDir() error {
   125  	return os.Chdir(c.projectDir)
   126  }
   127  
   128  func (c *SuiteContext) imInDir(subdir string) error {
   129  	return os.Chdir(subdir)
   130  }
   131  
   132  func (c *SuiteContext) fileContaining(path string, contents *gherkin.DocString) error {
   133  	return c.writeFile(path, contents.Content)
   134  }
   135  
   136  func (c *SuiteContext) environmentVariableSet(name, value string) error {
   137  	return os.Setenv(name, value)
   138  }
   139  
   140  func (c *SuiteContext) fileContains(path string, contents *gherkin.DocString) error {
   141  	actual, err := c.readFile(path)
   142  	if err != nil {
   143  		return err
   144  	}
   145  	if actual != contents.Content {
   146  		return fmt.Errorf("unexpected file %v contents: %q expected: %q", path, actual, contents.Content)
   147  	}
   148  	return nil
   149  }
   150  
   151  func (c *SuiteContext) fileDoesNotContain(path string, contents *gherkin.DocString) error {
   152  	actual, err := c.readFile(path)
   153  	if err != nil {
   154  		return err
   155  	}
   156  	re := regexp.MustCompile(".*" + contents.Content + ".*")
   157  	if len(re.FindString(actual)) > 0 {
   158  		return fmt.Errorf("unexpected file %v contents: %q NOT expected: %q", path, actual, contents.Content)
   159  	}
   160  	return nil
   161  }
   162  
   163  func (c *SuiteContext) fileExists(path string) error {
   164  	_, err := os.Stat(c.resolvePath(path))
   165  	return err
   166  }
   167  
   168  func (c *SuiteContext) fileDoesNotExist(path string) error {
   169  	_, err := os.Stat(path)
   170  	if os.IsNotExist(err) {
   171  		return nil
   172  	}
   173  	if err != nil {
   174  		return err
   175  	}
   176  	return errors.Errorf("expected %v to not exist", path)
   177  }
   178  
   179  func (c *SuiteContext) execute(command string) error {
   180  	c.stdout.Reset()
   181  	cmd.ResetFlags()
   182  
   183  	oldArgs := os.Args
   184  	os.Args = parseCommand(command)
   185  	defer func() {
   186  		os.Args = oldArgs
   187  	}()
   188  	cmd.SetOutput(c.stdout)
   189  	c.lastCommandExitCode, c.lastCommandErr = cmd.ExecuteE()
   190  	return nil
   191  }
   192  
   193  func parseCommand(command string) []string {
   194  	argv := make([]string, 0)
   195  	r := regexp.MustCompile(`([^\s"']+)|"([^"]*)"|'([^']*)'`)
   196  	matches := r.FindAllStringSubmatch(command, -1)
   197  	for _, match := range matches {
   198  		for _, group := range match[1:] {
   199  			if group != "" {
   200  				argv = append(argv, group)
   201  			}
   202  		}
   203  	}
   204  	return argv
   205  }
   206  
   207  func (c *SuiteContext) iRunOya(command string) error {
   208  	return c.execute("oya " + command)
   209  }
   210  
   211  func (c *SuiteContext) modifyFileToContain(path string, contents *gherkin.DocString) error {
   212  	return c.writeFile(path, contents.Content)
   213  }
   214  
   215  func (c *SuiteContext) theCommandSucceeds() error {
   216  	if c.lastCommandErr != nil {
   217  		log.Println(c.stdout.String())
   218  		return errors.Wrap(c.lastCommandErr, "command unexpectedly failed")
   219  	}
   220  	return nil
   221  }
   222  
   223  func (c *SuiteContext) theCommandFails() error {
   224  	if c.lastCommandErr == nil {
   225  		return errors.Errorf("last command unexpectedly succeeded")
   226  	}
   227  	return nil
   228  }
   229  
   230  func (c *SuiteContext) theCommandFailsWithError(errMsg *gherkin.DocString) error {
   231  	errMsg.Content = fmt.Sprintf("^%v$", errMsg.Content)
   232  	return c.theCommandFailsWithErrorMatching(errMsg)
   233  }
   234  
   235  func (c *SuiteContext) theCommandFailsWithErrorMatching(errMsg *gherkin.DocString) error {
   236  	if c.lastCommandErr == nil {
   237  		return errors.Errorf("last command unexpectedly succeeded")
   238  	}
   239  
   240  	rx := regexp.MustCompile(errMsg.Content)
   241  	if !rx.MatchString(c.lastCommandErr.Error()) {
   242  		return errors.Wrap(c.lastCommandErr,
   243  			fmt.Sprintf("unexpected error %q; expected to match %q", c.lastCommandErr, errMsg.Content))
   244  	}
   245  	return nil
   246  }
   247  
   248  func (c *SuiteContext) theCommandOutputs(expected *gherkin.DocString) error {
   249  	actual := c.stdout.String()
   250  	if actual != expected.Content {
   251  		return fmt.Errorf("unexpected %v; expected: %q", actual, expected.Content)
   252  	}
   253  	return nil
   254  }
   255  
   256  func (c *SuiteContext) theCommandOutputsTextMatching(expected *gherkin.DocString) error {
   257  	actual := c.stdout.String()
   258  	rx := regexp.MustCompile(expected.Content)
   259  	if !rx.MatchString(actual) {
   260  		return fmt.Errorf("unexpected %v; expected to match: %q", actual, expected.Content)
   261  	}
   262  	return nil
   263  }
   264  
   265  func (c *SuiteContext) theCommandExitCodeIs(expectedExitCode int) error {
   266  	if c.lastCommandExitCode != expectedExitCode {
   267  		return errors.Errorf("unexpected exit code from the last command: %v; expected: %v", c.lastCommandExitCode, expectedExitCode)
   268  	}
   269  	return nil
   270  }
   271  
   272  func (c *SuiteContext) oyafileIsEncryptedUsingKeyInSopsyaml(oyafilePath string) error {
   273  	sops, err := secrets.LoadPGPSopsYaml()
   274  	if err != nil {
   275  		return err
   276  	}
   277  	contents, err := ioutil.ReadFile(oyafilePath)
   278  	if err != nil {
   279  		return err
   280  	}
   281  	for _, rule := range sops.CreationRules {
   282  		fingerprint := rule.PGP
   283  		if strings.Contains(string(contents), fingerprint) {
   284  			return nil
   285  		}
   286  	}
   287  
   288  	return errors.Errorf("%q not encrypted using key is .sops.yaml", oyafilePath)
   289  }
   290  
   291  func FeatureContext(s *godog.Suite) {
   292  	c := SuiteContext{}
   293  	s.Step(`^I'm in project dir$`, c.iAmInProjectDir)
   294  	s.Step(`^I\'m in the (.+) dir$`, c.imInDir)
   295  	s.Step(`^file (.+) containing$`, c.fileContaining)
   296  	s.Step(`^I run "oya (.+)"$`, c.iRunOya)
   297  	s.Step(`^I modify file (.+) to contain$`, c.modifyFileToContain)
   298  	s.Step(`^file (.+) contains$`, c.fileContains)
   299  	s.Step(`^file (.+) does not contain$`, c.fileDoesNotContain)
   300  	s.Step(`^file (.+) exists$`, c.fileExists)
   301  	s.Step(`^file (.+) does not exist$`, c.fileDoesNotExist)
   302  	s.Step(`^the command succeeds$`, c.theCommandSucceeds)
   303  	s.Step(`^the command fails$`, c.theCommandFails)
   304  	s.Step(`^the command fails with error$`, c.theCommandFailsWithError)
   305  	s.Step(`^the command fails with error matching$`, c.theCommandFailsWithErrorMatching)
   306  	s.Step(`^the command outputs$`, c.theCommandOutputs)
   307  	s.Step(`^the command outputs text matching$`, c.theCommandOutputsTextMatching)
   308  	s.Step(`^the command exit code is (.+)$`, c.theCommandExitCodeIs)
   309  	s.Step(`^the ([^ ]+) environment variable set to "([^"]*)"$`, c.environmentVariableSet)
   310  	s.Step(`^([^ ]+) is encrypted using PGP key in .sops.yaml$`, c.oyafileIsEncryptedUsingKeyInSopsyaml)
   311  	s.BeforeScenario(func(*gherkin.Scenario) { c.MustSetUp() })
   312  	s.AfterScenario(func(*gherkin.Scenario, error) { c.MustTearDown() })
   313  }
   314  
   315  // sourceFileDirectory returns the current .go source file directory.
   316  func sourceFileDirectory() string {
   317  	_, filename, _, _ := runtime.Caller(1)
   318  	return filepath.Dir(filename)
   319  }