github.com/kubeshop/testkube@v1.17.23/contrib/executor/maven/pkg/runner/runner.go (about) 1 package runner 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "os/user" 8 "path/filepath" 9 "strings" 10 11 "github.com/joshdk/go-junit" 12 "github.com/pkg/errors" 13 14 "github.com/kubeshop/testkube/pkg/api/v1/testkube" 15 "github.com/kubeshop/testkube/pkg/envs" 16 "github.com/kubeshop/testkube/pkg/executor" 17 "github.com/kubeshop/testkube/pkg/executor/agent" 18 "github.com/kubeshop/testkube/pkg/executor/content" 19 "github.com/kubeshop/testkube/pkg/executor/env" 20 outputPkg "github.com/kubeshop/testkube/pkg/executor/output" 21 "github.com/kubeshop/testkube/pkg/executor/runner" 22 "github.com/kubeshop/testkube/pkg/executor/scraper" 23 "github.com/kubeshop/testkube/pkg/executor/scraper/factory" 24 "github.com/kubeshop/testkube/pkg/ui" 25 ) 26 27 func NewRunner(ctx context.Context, params envs.Params) (*MavenRunner, error) { 28 outputPkg.PrintLogf("%s Preparing test runner", ui.IconTruck) 29 30 var err error 31 r := &MavenRunner{ 32 params: params, 33 } 34 35 r.Scraper, err = factory.TryGetScrapper(ctx, params) 36 if err != nil { 37 return nil, err 38 } 39 40 return r, nil 41 } 42 43 type MavenRunner struct { 44 params envs.Params 45 Scraper scraper.Scraper 46 } 47 48 var _ runner.Runner = &MavenRunner{} 49 50 func (r *MavenRunner) Run(ctx context.Context, execution testkube.Execution) (result testkube.ExecutionResult, err error) { 51 if r.Scraper != nil { 52 defer r.Scraper.Close() 53 } 54 55 outputPkg.PrintLogf("%s Preparing for test run", ui.IconTruck) 56 err = r.Validate(execution) 57 if err != nil { 58 return result, err 59 } 60 61 // check that the datadir exists 62 _, err = os.Stat(r.params.DataDir) 63 if errors.Is(err, os.ErrNotExist) { 64 outputPkg.PrintLogf("%s Datadir %s does not exist", ui.IconCross, r.params.DataDir) 65 return result, err 66 } 67 68 // check that pom.xml file exists 69 directory := filepath.Join(r.params.DataDir, "repo", execution.Content.Repository.Path) 70 71 fileInfo, err := os.Stat(directory) 72 if err != nil { 73 return result, err 74 } 75 76 if !fileInfo.IsDir() { 77 outputPkg.PrintLogf("%s passing maven test as single file not implemented yet", ui.IconCross) 78 return result, errors.New("passing maven test as single file not implemented yet") 79 } 80 81 pomXml := filepath.Join(directory, "pom.xml") 82 83 _, pomXmlErr := os.Stat(pomXml) 84 if errors.Is(pomXmlErr, os.ErrNotExist) { 85 outputPkg.PrintLogf("%s No pom.xml found", ui.IconCross) 86 return *result.Err(errors.New("no pom.xml found")), nil 87 } 88 89 // determine the Maven command to use 90 // pass additional executor arguments/flags to Maven 91 mavenCommand, args := executor.MergeCommandAndArgs(execution.Command, execution.Args) 92 mavenWrapper := filepath.Join(directory, "mvnw") 93 _, err = os.Stat(mavenWrapper) 94 if mavenCommand == "mvn" && err == nil { 95 // then we use the wrapper instead 96 mavenCommand = "./mvnw" 97 } 98 99 envManager := env.NewManagerWithVars(execution.Variables) 100 envManager.GetReferenceVars(envManager.Variables) 101 102 var settingsXML string 103 if execution.VariablesFile != "" { 104 outputPkg.PrintLogf("%s Creating settings.xml file", ui.IconWorld) 105 settingsXML, err = createSettingsXML(directory, execution.VariablesFile, execution.IsVariablesFileUploaded) 106 if err != nil { 107 outputPkg.PrintLogf("%s Could not create settings.xml", ui.IconCross) 108 return *result.Err(errors.New("could not create settings.xml")), nil 109 } 110 outputPkg.PrintLogf("%s Successfully created settings.xml", ui.IconCheckMark) 111 } 112 113 goal := strings.Split(execution.TestType, "/")[1] 114 var goalName string 115 if !strings.EqualFold(goal, "project") { 116 // use the test subtype as goal or phase when != project 117 // in case of project there is need to pass additional args 118 goalName = goal 119 } 120 121 // workaround for https://github.com/eclipse/che/issues/13926 122 os.Unsetenv("MAVEN_CONFIG") 123 124 currentUser, err := user.Current() 125 var mavenHome string 126 if err == nil && currentUser.Name == "maven" { 127 mavenHome = "/home/maven" 128 } 129 130 runPath := directory 131 if execution.Content.Repository != nil && execution.Content.Repository.WorkingDir != "" { 132 runPath = filepath.Join(r.params.DataDir, "repo", execution.Content.Repository.WorkingDir) 133 } 134 135 for i := len(args) - 1; i >= 0; i-- { 136 if goalName == "" && args[i] == "<goalName>" { 137 args = append(args[:i], args[i+1:]...) 138 continue 139 } 140 141 if settingsXML == "" && (args[i] == "--settings" || args[i] == "<settingsFile>") { 142 args = append(args[:i], args[i+1:]...) 143 continue 144 } 145 146 if mavenHome == "" && (args[i] == "--Duser.home" || args[i] == "<mavenHome>") { 147 args = append(args[:i], args[i+1:]...) 148 continue 149 } 150 151 if args[i] == "<goalName>" { 152 args[i] = goalName 153 } 154 155 if args[i] == "<settingsFile>" { 156 args[i] = settingsXML 157 } 158 159 if args[i] == "<mavenHome>" { 160 args[i] = mavenHome 161 } 162 163 args[i] = os.ExpandEnv(args[i]) 164 } 165 166 outputPkg.PrintEvent("Running goal: "+goal, mavenHome, mavenCommand, envManager.ObfuscateStringSlice(args)) 167 output, err := executor.Run(runPath, mavenCommand, envManager, args...) 168 output = envManager.ObfuscateSecrets(output) 169 170 if err == nil { 171 result.Status = testkube.ExecutionStatusPassed 172 outputPkg.PrintLogf("%s Test run successful", ui.IconCheckMark) 173 } else { 174 result.Status = testkube.ExecutionStatusFailed 175 result.ErrorMessage = err.Error() 176 outputPkg.PrintLogf("%s Test run failed: %s", ui.IconCross, err.Error()) 177 if strings.Contains(result.ErrorMessage, "exit status 1") { 178 // probably some tests have failed 179 result.ErrorMessage = "build failed with an exception" 180 } else { 181 // Gradle was unable to run at all 182 return result, nil 183 } 184 } 185 186 var rerr error 187 if execution.PostRunScript != "" && execution.ExecutePostRunScriptBeforeScraping { 188 outputPkg.PrintLog(fmt.Sprintf("%s Running post run script...", ui.IconCheckMark)) 189 190 if runPath == "" { 191 runPath = r.params.WorkingDir 192 } 193 194 if rerr = agent.RunScript(execution.PostRunScript, runPath); rerr != nil { 195 outputPkg.PrintLogf("%s Failed to execute post run script %s", ui.IconWarning, rerr) 196 } 197 } 198 199 // scrape artifacts first even if there are errors above 200 if r.params.ScrapperEnabled && execution.ArtifactRequest != nil && len(execution.ArtifactRequest.Dirs) != 0 { 201 outputPkg.PrintLogf("Scraping directories: %v with masks: %v", execution.ArtifactRequest.Dirs, execution.ArtifactRequest.Masks) 202 203 if err := r.Scraper.Scrape(ctx, execution.ArtifactRequest.Dirs, execution.ArtifactRequest.Masks, execution); err != nil { 204 return *result.WithErrors(err), nil 205 } 206 } 207 208 result.Output = string(output) 209 result.OutputType = "text/plain" 210 211 junitReportPath := filepath.Join(directory, "target", "surefire-reports") 212 err = filepath.Walk(junitReportPath, func(path string, info os.FileInfo, err error) error { 213 if err != nil { 214 return err 215 } 216 if !info.IsDir() && filepath.Ext(path) == ".xml" { 217 suites, _ := junit.IngestFile(path) 218 for _, suite := range suites { 219 for _, test := range suite.Tests { 220 result.Steps = append( 221 result.Steps, 222 testkube.ExecutionStepResult{ 223 Name: fmt.Sprintf("%s - %s", suite.Name, test.Name), 224 Duration: test.Duration.String(), 225 Status: mapStatus(test.Status), 226 }) 227 } 228 } 229 } 230 231 return nil 232 }) 233 234 if err != nil { 235 return *result.Err(err), nil 236 } 237 238 if rerr != nil { 239 return *result.Err(rerr), nil 240 } 241 242 return result, nil 243 } 244 245 func mapStatus(in junit.Status) (out string) { 246 switch string(in) { 247 case "passed": 248 return string(testkube.PASSED_ExecutionStatus) 249 default: 250 return string(testkube.FAILED_ExecutionStatus) 251 } 252 } 253 254 // createSettingsXML saves the settings.xml to maven config folder and adds it to the list of arguments. 255 // In case it is taken from storage, it will return the path to the file 256 func createSettingsXML(directory string, variablesFile string, isUploaded bool) (string, error) { 257 if isUploaded { 258 return filepath.Join(content.UploadsFolder, variablesFile), nil 259 } 260 261 settingsXML := filepath.Join(directory, "settings.xml") 262 err := os.WriteFile(settingsXML, []byte(variablesFile), 0644) 263 if err != nil { 264 return "", errors.Errorf("could not create settings.xml: %v", err) 265 } 266 267 return settingsXML, nil 268 } 269 270 // GetType returns runner type 271 func (r *MavenRunner) GetType() runner.Type { 272 return runner.TypeMain 273 } 274 275 // Validate checks if Execution has valid data in context of Maven executor 276 func (r *MavenRunner) Validate(execution testkube.Execution) error { 277 278 if execution.Content == nil { 279 outputPkg.PrintLogf("%s Can't find any content to run in execution data", ui.IconCross) 280 return errors.Errorf("can't find any content to run in execution data: %+v", execution) 281 } 282 283 if execution.Content.Repository == nil { 284 outputPkg.PrintLogf("%s Maven executor handles only repository based tests, but repository is nil", ui.IconCross) 285 return errors.New("maven executor handles only repository based tests, but repository is nil") 286 } 287 288 if execution.Content.Repository.Branch == "" && execution.Content.Repository.Commit == "" { 289 outputPkg.PrintLogf("%s Can't find branch or commit in params must use one or the other, repo %+v", ui.IconCross, execution.Content.Repository) 290 return errors.Errorf("can't find branch or commit in params must use one or the other, repo:%+v", execution.Content.Repository) 291 } 292 293 return nil 294 }