github.com/kubeshop/testkube@v1.17.23/cmd/kubectl-testkube/commands/testsuites/common.go (about)

     1  package testsuites
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/spf13/cobra"
    12  
    13  	"github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common"
    14  	"github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/render"
    15  	"github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/tests"
    16  	apiclientv1 "github.com/kubeshop/testkube/pkg/api/v1/client"
    17  	"github.com/kubeshop/testkube/pkg/api/v1/testkube"
    18  	"github.com/kubeshop/testkube/pkg/ui"
    19  )
    20  
    21  func printExecution(execution testkube.TestSuiteExecution, startTime time.Time) {
    22  	if execution.TestSuite != nil {
    23  		ui.Warn("Name          :", execution.TestSuite.Name)
    24  	}
    25  
    26  	if execution.Id != "" {
    27  		ui.Warn("Execution ID  :", execution.Id)
    28  		ui.Warn("Execution name:", execution.Name)
    29  	}
    30  
    31  	if execution.Status != nil {
    32  		ui.Warn("Status        :", string(*execution.Status))
    33  	}
    34  
    35  	if execution.Id != "" {
    36  		ui.Warn("Duration:", execution.CalculateDuration().String()+"\n")
    37  		ui.Table(execution, os.Stdout)
    38  	}
    39  
    40  	ui.NL()
    41  	ui.NL()
    42  }
    43  
    44  func uiPrintExecutionStatus(client apiclientv1.Client, execution testkube.TestSuiteExecution) error {
    45  	if execution.Status == nil {
    46  		return nil
    47  	}
    48  
    49  	switch true {
    50  	case execution.IsQueued():
    51  		ui.Warn("Test Suite queued for execution")
    52  
    53  	case execution.IsRunning():
    54  		ui.Warn("Test Suite execution started")
    55  
    56  	case execution.IsPassed():
    57  		ui.Success("Test Suite execution completed with sucess in " + execution.Duration)
    58  
    59  		info, err := client.GetServerInfo()
    60  		ui.ExitOnError("getting server info", err)
    61  
    62  		render.PrintTestSuiteExecutionURIs(&execution, info.DashboardUri)
    63  
    64  	case execution.IsFailed():
    65  		ui.UseStderr()
    66  		ui.Errf("Test Suite execution failed")
    67  
    68  		info, err := client.GetServerInfo()
    69  		ui.ExitOnError("getting server info", err)
    70  
    71  		render.PrintTestSuiteExecutionURIs(&execution, info.DashboardUri)
    72  		return errors.New("failed test suite")
    73  	}
    74  
    75  	ui.NL()
    76  	return nil
    77  }
    78  
    79  func uiShellTestSuiteGetCommandBlock(id string) {
    80  	ui.ShellCommand(
    81  		"Use following command to get test execution details",
    82  		"kubectl testkube get tse "+id,
    83  	)
    84  
    85  	ui.NL()
    86  }
    87  
    88  func uiShellTestSuiteWatchCommandBlock(id string) {
    89  	ui.ShellCommand(
    90  		"Use following command to get test execution details",
    91  		"kubectl testkube watch tse "+id,
    92  	)
    93  
    94  	ui.NL()
    95  }
    96  
    97  // NewTestSuiteUpsertOptionsFromFlags creates test suite upsert options from command flags
    98  func NewTestSuiteUpsertOptionsFromFlags(cmd *cobra.Command) (options apiclientv1.UpsertTestSuiteOptions, err error) {
    99  	data, err := common.NewDataFromFlags(cmd)
   100  	if err != nil {
   101  		return options, err
   102  	}
   103  
   104  	if data == nil {
   105  		return options, fmt.Errorf("empty test suite content")
   106  	}
   107  
   108  	if err = json.Unmarshal([]byte(*data), &options); err != nil {
   109  		ui.Debug("json unmarshaling", err.Error())
   110  	}
   111  
   112  	emptyBatch := true
   113  	for _, step := range options.Steps {
   114  		if len(step.Execute) != 0 {
   115  			emptyBatch = false
   116  			break
   117  		}
   118  	}
   119  
   120  	if emptyBatch {
   121  		var testSuite testkube.TestSuiteUpsertRequestV2
   122  		err = json.Unmarshal([]byte(*data), &testSuite)
   123  		if err != nil {
   124  			return options, err
   125  		}
   126  
   127  		options = apiclientv1.UpsertTestSuiteOptions(*testSuite.ToTestSuiteUpsertRequest())
   128  		if len(options.Steps) == 0 {
   129  			return options, fmt.Errorf("no test suite batch steps provided")
   130  		}
   131  	}
   132  
   133  	for _, step := range options.Steps {
   134  		if len(step.Execute) == 0 {
   135  			return options, fmt.Errorf("no steps defined for batch step")
   136  		}
   137  	}
   138  
   139  	name := cmd.Flag("name").Value.String()
   140  	if name != "" {
   141  		options.Name = name
   142  	}
   143  
   144  	labels, err := cmd.Flags().GetStringToString("label")
   145  	if err != nil {
   146  		return options, err
   147  	}
   148  
   149  	options.Namespace = cmd.Flag("namespace").Value.String()
   150  	options.Labels = labels
   151  
   152  	crdOnly, err := cmd.Flags().GetBool("crd-only")
   153  	if err != nil {
   154  		return options, err
   155  	}
   156  
   157  	disableSecretCreation := false
   158  	if !crdOnly {
   159  		client, _, err := common.GetClient(cmd)
   160  		if err != nil {
   161  			return options, err
   162  		}
   163  
   164  		info, err := client.GetServerInfo()
   165  		if err != nil {
   166  			return options, err
   167  		}
   168  
   169  		disableSecretCreation = info.DisableSecretCreation
   170  	}
   171  
   172  	variables, err := common.CreateVariables(cmd, disableSecretCreation)
   173  	if err != nil {
   174  		return options, fmt.Errorf("invalid variables %w", err)
   175  	}
   176  
   177  	timeout, err := cmd.Flags().GetInt32("timeout")
   178  	if err != nil {
   179  		return options, err
   180  	}
   181  
   182  	schedule := cmd.Flag("schedule").Value.String()
   183  	if err = validateSchedule(schedule); err != nil {
   184  		return options, fmt.Errorf("validating schedule %w", err)
   185  	}
   186  
   187  	jobTemplateReference := cmd.Flag("job-template-reference").Value.String()
   188  	cronJobTemplateReference := cmd.Flag("cronjob-template-reference").Value.String()
   189  	scraperTemplateReference := cmd.Flag("scraper-template-reference").Value.String()
   190  	pvcTemplateReference := cmd.Flag("pvc-template-reference").Value.String()
   191  
   192  	options.Schedule = schedule
   193  	options.ExecutionRequest = &testkube.TestSuiteExecutionRequest{
   194  		Variables:                variables,
   195  		Name:                     cmd.Flag("execution-name").Value.String(),
   196  		HttpProxy:                cmd.Flag("http-proxy").Value.String(),
   197  		HttpsProxy:               cmd.Flag("https-proxy").Value.String(),
   198  		Timeout:                  timeout,
   199  		JobTemplateReference:     jobTemplateReference,
   200  		CronJobTemplateReference: cronJobTemplateReference,
   201  		ScraperTemplateReference: scraperTemplateReference,
   202  		PvcTemplateReference:     pvcTemplateReference,
   203  	}
   204  
   205  	var fields = []struct {
   206  		source      string
   207  		destination *string
   208  	}{
   209  		{
   210  			cmd.Flag("job-template").Value.String(),
   211  			&options.ExecutionRequest.JobTemplate,
   212  		},
   213  		{
   214  			cmd.Flag("cronjob-template").Value.String(),
   215  			&options.ExecutionRequest.CronJobTemplate,
   216  		},
   217  		{
   218  			cmd.Flag("scraper-template").Value.String(),
   219  			&options.ExecutionRequest.ScraperTemplate,
   220  		},
   221  		{
   222  			cmd.Flag("pvc-template").Value.String(),
   223  			&options.ExecutionRequest.PvcTemplate,
   224  		},
   225  	}
   226  
   227  	for _, field := range fields {
   228  		if field.source != "" {
   229  			b, err := os.ReadFile(field.source)
   230  			if err != nil {
   231  				return options, err
   232  			}
   233  
   234  			*field.destination = string(b)
   235  		}
   236  	}
   237  
   238  	return options, nil
   239  }
   240  
   241  // NewTestSuiteUpdateOptionsFromFlags creates test suite update options from command flags
   242  func NewTestSuiteUpdateOptionsFromFlags(cmd *cobra.Command) (options apiclientv1.UpdateTestSuiteOptions, err error) {
   243  	data, err := common.NewDataFromFlags(cmd)
   244  	if err != nil {
   245  		return options, err
   246  	}
   247  
   248  	if data != nil {
   249  		if err = json.Unmarshal([]byte(*data), &options); err != nil {
   250  			ui.Debug("json unmarshaling", err.Error())
   251  		}
   252  
   253  		if options.Steps != nil {
   254  			emptyBatch := true
   255  			for _, step := range *options.Steps {
   256  				if len(step.Execute) != 0 {
   257  					emptyBatch = false
   258  					break
   259  				}
   260  			}
   261  
   262  			if emptyBatch {
   263  				var testSuite testkube.TestSuiteUpdateRequestV2
   264  				err = json.Unmarshal([]byte(*data), &testSuite)
   265  				if err != nil {
   266  					return options, err
   267  				}
   268  
   269  				options = apiclientv1.UpdateTestSuiteOptions(*testSuite.ToTestSuiteUpdateRequest())
   270  			}
   271  
   272  		}
   273  	}
   274  
   275  	var fields = []struct {
   276  		name        string
   277  		destination **string
   278  	}{
   279  		{
   280  			"name",
   281  			&options.Name,
   282  		},
   283  		{
   284  			"namespace",
   285  			&options.Namespace,
   286  		},
   287  	}
   288  
   289  	for _, field := range fields {
   290  		if cmd.Flag(field.name).Changed {
   291  			value := cmd.Flag(field.name).Value.String()
   292  			*field.destination = &value
   293  		}
   294  	}
   295  
   296  	if cmd.Flag("schedule").Changed {
   297  		schedule := cmd.Flag("schedule").Value.String()
   298  		if err = validateSchedule(schedule); err != nil {
   299  			return options, fmt.Errorf("validating schedule %w", err)
   300  		}
   301  
   302  		options.Schedule = &schedule
   303  	}
   304  
   305  	if cmd.Flag("label").Changed {
   306  		labels, err := cmd.Flags().GetStringToString("label")
   307  		if err != nil {
   308  			return options, err
   309  		}
   310  
   311  		options.Labels = &labels
   312  	}
   313  
   314  	var executionRequest testkube.TestSuiteExecutionUpdateRequest
   315  	var nonEmpty bool
   316  	if cmd.Flag("variable").Changed || cmd.Flag("secret-variable").Changed || cmd.Flag("secret-variable-reference").Changed {
   317  		client, _, err := common.GetClient(cmd)
   318  		if err != nil {
   319  			return options, err
   320  		}
   321  
   322  		info, err := client.GetServerInfo()
   323  		if err != nil {
   324  			return options, err
   325  		}
   326  
   327  		variables, err := common.CreateVariables(cmd, info.DisableSecretCreation)
   328  		if err != nil {
   329  			return options, fmt.Errorf("invalid variables %w", err)
   330  		}
   331  
   332  		executionRequest.Variables = &variables
   333  		nonEmpty = true
   334  	}
   335  
   336  	if cmd.Flag("timeout").Changed {
   337  		timeout, err := cmd.Flags().GetInt32("timeout")
   338  		if err != nil {
   339  			return options, err
   340  		}
   341  
   342  		executionRequest.Timeout = &timeout
   343  		nonEmpty = true
   344  	}
   345  
   346  	var values = []struct {
   347  		source      string
   348  		destination **string
   349  	}{
   350  		{
   351  			"job-template",
   352  			&executionRequest.JobTemplate,
   353  		},
   354  		{
   355  			"cronjob-template",
   356  			&executionRequest.CronJobTemplate,
   357  		},
   358  		{
   359  			"scraper-template",
   360  			&executionRequest.ScraperTemplate,
   361  		},
   362  		{
   363  			"pvc-template",
   364  			&executionRequest.PvcTemplate,
   365  		},
   366  	}
   367  
   368  	for _, value := range values {
   369  		if cmd.Flag(value.source).Changed {
   370  			data := ""
   371  			name := cmd.Flag(value.source).Value.String()
   372  			if name != "" {
   373  				b, err := os.ReadFile(name)
   374  				if err != nil {
   375  					return options, err
   376  				}
   377  
   378  				data = string(b)
   379  			}
   380  
   381  			*value.destination = &data
   382  			nonEmpty = true
   383  		}
   384  	}
   385  
   386  	var executionFields = []struct {
   387  		name        string
   388  		destination **string
   389  	}{
   390  		{
   391  			"execution-name",
   392  			&executionRequest.Name,
   393  		},
   394  		{
   395  			"http-proxy",
   396  			&executionRequest.HttpProxy,
   397  		},
   398  		{
   399  			"https-proxy",
   400  			&executionRequest.HttpsProxy,
   401  		},
   402  		{
   403  			"job-template-reference",
   404  			&executionRequest.JobTemplateReference,
   405  		},
   406  		{
   407  			"cronjob-template-reference",
   408  			&executionRequest.CronJobTemplateReference,
   409  		},
   410  		{
   411  			"scraper-template-reference",
   412  			&executionRequest.ScraperTemplateReference,
   413  		},
   414  		{
   415  			"pvc-template-reference",
   416  			&executionRequest.PvcTemplateReference,
   417  		},
   418  	}
   419  
   420  	for _, field := range executionFields {
   421  		if cmd.Flag(field.name).Changed {
   422  			value := cmd.Flag(field.name).Value.String()
   423  			*field.destination = &value
   424  			nonEmpty = true
   425  		}
   426  	}
   427  
   428  	if nonEmpty {
   429  		value := (&executionRequest)
   430  		options.ExecutionRequest = &value
   431  	}
   432  
   433  	return options, nil
   434  }
   435  
   436  func DownloadArtifacts(id, dir, format string, masks []string, client apiclientv1.Client) {
   437  	testSuiteExecution, err := client.GetTestSuiteExecution(id)
   438  	ui.ExitOnError("getting test suite execution ", err)
   439  
   440  	for _, execution := range testSuiteExecution.ExecuteStepResults {
   441  		for _, step := range execution.Execute {
   442  			if step.Execution != nil && step.Step != nil && step.Step.Test != "" {
   443  				if step.Execution.IsPassed() || step.Execution.IsFailed() {
   444  					tests.DownloadTestArtifacts(step.Execution.Id, filepath.Join(dir, step.Execution.TestName+"-"+step.Execution.Id), format, masks, client)
   445  				}
   446  			}
   447  		}
   448  	}
   449  }