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 }