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  }