Skip to content

Commit

Permalink
fix(cli): Adding run by id (#3775)
Browse files Browse the repository at this point in the history
* fix(cli): Adding run by id

* feat(cli): Adding support for multiple IDs

* feat(cli): Adding support for multiple IDs

* fix(cli): improving run time

* fix(cli): improving run time
  • Loading branch information
xoscar committed Apr 5, 2024
1 parent 45c065d commit aecf0ab
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 27 deletions.
2 changes: 2 additions & 0 deletions cli/cloud/cmd/run_cmd.go
Expand Up @@ -33,6 +33,8 @@ func RunMultipleFiles(ctx context.Context, httpClient *resourcemanager.HTTPClien
)

return orchestrator.Run(ctx, runner.RunOptions{
IDs: runParams.IDs,
ResourceName: runParams.ResourceName,
DefinitionFiles: runParams.DefinitionFiles,
VarsID: runParams.VarsID,
SkipResultWait: runParams.SkipResultWait,
Expand Down
99 changes: 76 additions & 23 deletions cli/cloud/runner/multifile_orchestrator.go
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"net/http"
"os"
"sync"

"github.com/kubeshop/tracetest/cli/formatters"
"github.com/kubeshop/tracetest/cli/metadata"
Expand All @@ -22,6 +23,10 @@ import (
// RunOptions defines options for running a resource
// IDs and DefinitionFiles are mutually exclusive and the only required options
type RunOptions struct {
// if IDs is used it needs to have the ResourceName defined
IDs []string
ResourceName string

// path to the file with resource definition
// the file will be applied before running
DefinitionFiles []string
Expand Down Expand Up @@ -101,36 +106,27 @@ func (o orchestrator) Run(ctx context.Context, opts RunOptions, outputFormat str
}
o.logger.Debug("env resolved", zap.String("ID", varsID))

hasDefinitionFilesDefined := opts.DefinitionFiles != nil && len(opts.DefinitionFiles) > 0
resourceFetcher := runner.GetResourceFetcher(o.logger, o.runnerRegistry)

if !hasDefinitionFilesDefined {
return ExitCodeGeneralError, fmt.Errorf("you must define at least two files to use the multifile orchestrator")
}

vars := varset.VarSets{}
var resources []any
resourceFetcher := runner.GetResourceFetcher(o.logger, o.runnerRegistry)

runGroupID := opts.RunGroupID
if runGroupID == "" {
runGroupID = id.GenerateID().String()
}
runsResults := make([]runner.RunResult, 0)
definitionFiles, err := o.getDefinitionFiles(opts.DefinitionFiles)

if err != nil {
return ExitCodeGeneralError, fmt.Errorf("cannot read definition files: %w", err)
}
var resources []any
var runsResults []runner.RunResult

// 1. create runs
for _, definitionFile := range definitionFiles {
result, resource, err := o.createRun(ctx, resourceFetcher, &vars, opts.RequiredGates, definitionFile, varsID, runGroupID)
if len(opts.DefinitionFiles) > 0 {
resources, runsResults, err = o.runByFiles(ctx, opts, resourceFetcher, &vars, varsID, runGroupID)
if err != nil {
return ExitCodeGeneralError, fmt.Errorf("cannot run test: %w", err)
return ExitCodeGeneralError, fmt.Errorf("cannot run files: %w", err)
}
} else {
resources, runsResults, err = o.runByIDs(ctx, opts, resourceFetcher, &vars, varsID, runGroupID)
if err != nil {
return ExitCodeGeneralError, fmt.Errorf("cannot run by id: %w", err)
}

runsResults = append(runsResults, result)
resources = append(resources, resource)
}

runnerGetter := func(resource any) (formatters.Runner[runner.RunResult], error) {
Expand Down Expand Up @@ -224,12 +220,69 @@ func (o orchestrator) getDefinitionFiles(file []string) ([]string, error) {
return files, nil
}

func (o orchestrator) createRun(ctx context.Context, resourceFetcher runner.ResourceFetcher, vars *varset.VarSets, requiredGates []string, definitionFile string, varsID string, runGroupID string) (runner.RunResult, any, error) {
resource, err := resourceFetcher.FetchWithDefinitionFile(ctx, definitionFile)
func (o orchestrator) runByFiles(ctx context.Context, opts RunOptions, resourceFetcher runner.ResourceFetcher, vars *varset.VarSets, varsID string, runGroupID string) ([]any, []runner.RunResult, error) {
resources := make([]any, 0)
runsResults := make([]runner.RunResult, 0)
var mainErr error

hasDefinitionFilesDefined := opts.DefinitionFiles != nil && len(opts.DefinitionFiles) > 0
if !hasDefinitionFilesDefined {
return resources, runsResults, fmt.Errorf("no definition files defined")
}

definitionFiles, err := o.getDefinitionFiles(opts.DefinitionFiles)
if err != nil {
return runner.RunResult{}, nil, err
return resources, runsResults, fmt.Errorf("cannot get definition files: %w", err)
}

var wg sync.WaitGroup
wg.Add(len(definitionFiles))
for _, definitionFile := range definitionFiles {
go func(def string) {
defer wg.Done()
resource, err := resourceFetcher.FetchWithDefinitionFile(ctx, def)
if err != nil {
mainErr = fmt.Errorf("cannot fetch resource from definition file: %w", err)
return
}
result, resource, err := o.createRun(ctx, resource, vars, opts.RequiredGates, varsID, runGroupID)
if err != nil {
mainErr = fmt.Errorf("cannot run test: %w", err)
return
}

runsResults = append(runsResults, result)
resources = append(resources, resource)
}(definitionFile)
}

wg.Wait()
return resources, runsResults, mainErr
}

func (o orchestrator) runByIDs(ctx context.Context, opts RunOptions, resourceFetcher runner.ResourceFetcher, vars *varset.VarSets, varsID string, runGroupID string) ([]any, []runner.RunResult, error) {
resources := make([]any, 0)
runsResults := make([]runner.RunResult, 0)

for _, id := range opts.IDs {
resource, err := resourceFetcher.FetchWithID(ctx, opts.ResourceName, id)
if err != nil {
return resources, runsResults, err
}

result, resource, err := o.createRun(ctx, resource, vars, opts.RequiredGates, varsID, runGroupID)
if err != nil {
return resources, runsResults, fmt.Errorf("cannot run test: %w", err)
}

runsResults = append(runsResults, result)
resources = append(resources, resource)
}

return resources, runsResults, nil
}

func (o orchestrator) createRun(ctx context.Context, resource any, vars *varset.VarSets, requiredGates []string, varsID, runGroupID string) (runner.RunResult, any, error) {
resourceType, err := resourcemanager.GetResourceType(resource)
if err != nil {
return runner.RunResult{}, nil, fmt.Errorf("cannot extract type from resource: %w", err)
Expand Down
10 changes: 8 additions & 2 deletions cli/cmd/resource_run_cmd.go
Expand Up @@ -27,6 +27,7 @@ func init() {
Long: "Run tests and test suites",
PreRun: setupCommand(WithOptionalResourceName()),
Run: WithResourceMiddleware(func(ctx context.Context, _ *cobra.Command, args []string) (string, error) {
runParams.ResourceName = resourceParams.ResourceName
if cliConfig.Jwt != "" {
exitCode, err := cloudCmd.RunMultipleFiles(ctx, httpClient, runParams, &cliConfig, runnerRegistry, output)
ExitCLI(exitCode)
Expand All @@ -40,7 +41,7 @@ func init() {
}

runCmd.Flags().StringSliceVarP(&runParams.DefinitionFiles, "file", "f", []string{}, "path to the definition file (can be defined multiple times)")
runCmd.Flags().StringVarP(&runParams.ID, "id", "", "", "id of the resource to run (can be defined multiple times)")
runCmd.Flags().StringSliceVarP(&runParams.IDs, "id", "", []string{}, "id of the resource to run (can be defined multiple times)")
runCmd.Flags().StringVarP(&runParams.VarsID, "vars", "", "", "variable set file or ID to be used")
runCmd.Flags().StringVarP(&runParams.RunGroupID, "group", "g", "", "Sets the Run Group ID for the run. This is used to group multiple runs together.")
runCmd.Flags().BoolVarP(&runParams.SkipResultWait, "skip-result-wait", "W", false, "do not wait for results. exit immediately after test run started")
Expand Down Expand Up @@ -72,8 +73,13 @@ func runSingleFile(ctx context.Context) (string, error) {
definitionFile = runParams.DefinitionFiles[0]
}

ID := ""
if len(runParams.IDs) > 0 {
ID = runParams.IDs[0]
}

runParams := runner.RunOptions{
ID: runParams.ID,
ID: ID,
DefinitionFile: definitionFile,
VarsID: runParams.VarsID,
SkipResultWait: runParams.SkipResultWait,
Expand Down
13 changes: 11 additions & 2 deletions cli/cmdutil/run_params.go
Expand Up @@ -9,21 +9,22 @@ import (
)

type RunParameters struct {
ID string
IDs []string
DefinitionFiles []string
VarsID string
EnvID string
SkipResultWait bool
JUnitOuptutFile string
RequiredGates []string
RunGroupID string
ResourceName string
}

func (p RunParameters) Validate(cmd *cobra.Command, args []string) []error {
errs := []error{}

hasDefinitionFilesSpecified := p.DefinitionFiles != nil && len(p.DefinitionFiles) > 0
hasFileIDsSpecified := p.ID != "" && len(p.ID) > 0
hasFileIDsSpecified := len(p.IDs) > 0

if !hasDefinitionFilesSpecified && !hasFileIDsSpecified {
errs = append(errs, ParamError{
Expand All @@ -32,6 +33,14 @@ func (p RunParameters) Validate(cmd *cobra.Command, args []string) []error {
})
}

isResourceNameSpecified := len(args) > 0
if hasFileIDsSpecified && !isResourceNameSpecified {
errs = append(errs, ParamError{
Parameter: "resource",
Message: "you must specify a resource name (test|testsuite) when providing a resource IDs",
})
}

if hasDefinitionFilesSpecified && hasFileIDsSpecified {
errs = append(errs, ParamError{
Parameter: "resource",
Expand Down

0 comments on commit aecf0ab

Please sign in to comment.