github.com/kubeshop/testkube@v1.17.23/contrib/executor/kubepug/pkg/runner/runner.go (about)

     1  package runner
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"strings"
     9  
    10  	kubepug "github.com/kubepug/kubepug/pkg/results"
    11  
    12  	"github.com/kubeshop/testkube/pkg/api/v1/testkube"
    13  	"github.com/kubeshop/testkube/pkg/envs"
    14  	"github.com/kubeshop/testkube/pkg/executor"
    15  	"github.com/kubeshop/testkube/pkg/executor/agent"
    16  	"github.com/kubeshop/testkube/pkg/executor/content"
    17  	"github.com/kubeshop/testkube/pkg/executor/env"
    18  	"github.com/kubeshop/testkube/pkg/executor/output"
    19  	"github.com/kubeshop/testkube/pkg/executor/runner"
    20  	"github.com/kubeshop/testkube/pkg/executor/scraper"
    21  	"github.com/kubeshop/testkube/pkg/executor/scraper/factory"
    22  	"github.com/kubeshop/testkube/pkg/ui"
    23  )
    24  
    25  func NewRunner(ctx context.Context, params envs.Params) (*KubepugRunner, error) {
    26  	output.PrintLogf("%s Preparing test runner", ui.IconTruck)
    27  
    28  	var err error
    29  	r := &KubepugRunner{
    30  		params: params,
    31  	}
    32  
    33  	r.Scraper, err = factory.TryGetScrapper(ctx, params)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	return r, nil
    39  }
    40  
    41  // KubepugRunner runs kubepug against cluster
    42  type KubepugRunner struct {
    43  	params  envs.Params
    44  	Scraper scraper.Scraper
    45  }
    46  
    47  var _ runner.Runner = &KubepugRunner{}
    48  
    49  // Run runs the kubepug executable and parses it's output to be Testkube-compatible
    50  func (r *KubepugRunner) Run(ctx context.Context, execution testkube.Execution) (testkube.ExecutionResult, error) {
    51  	if r.Scraper != nil {
    52  		defer r.Scraper.Close()
    53  	}
    54  	output.PrintLogf("%s Preparing for test run", ui.IconTruck)
    55  
    56  	path, workingDir, err := content.GetPathAndWorkingDir(execution.Content, r.params.DataDir)
    57  	if err != nil {
    58  		output.PrintLogf("%s Failed to resolve absolute directory for %s, using the path directly", ui.IconWarning, r.params.DataDir)
    59  	}
    60  
    61  	fileInfo, err := os.Stat(path)
    62  	if err != nil {
    63  		return testkube.ExecutionResult{}, err
    64  	}
    65  
    66  	if !fileInfo.IsDir() {
    67  		output.PrintLogf("%s Using single file: %v", ui.IconFile, execution)
    68  	}
    69  
    70  	if fileInfo.IsDir() {
    71  		output.PrintLogf("%s Using dir: %v", ui.IconFile, execution)
    72  	}
    73  
    74  	args, err := buildArgs(execution.Args, path)
    75  	if err != nil {
    76  		output.PrintLogf("%s Could not build up parameters: %s", ui.IconCross, err.Error())
    77  		return testkube.ExecutionResult{}, fmt.Errorf("could not build up parameters: %w", err)
    78  	}
    79  
    80  	envManager := env.NewManagerWithVars(execution.Variables)
    81  	envManager.GetReferenceVars(envManager.Variables)
    82  	output.PrintLogf("%s Running kubepug with arguments: %v", ui.IconWorld, envManager.ObfuscateStringSlice(args))
    83  
    84  	runPath := workingDir
    85  	command, args := executor.MergeCommandAndArgs(execution.Command, args)
    86  	output.PrintLogf("%s Test run command %s %s", ui.IconRocket, command, strings.Join(envManager.ObfuscateStringSlice(args), " "))
    87  	out, err := executor.Run(runPath, command, envManager, args...)
    88  	out = envManager.ObfuscateSecrets(out)
    89  	if err != nil {
    90  		output.PrintLogf("%s Could not execute kubepug: %s", ui.IconCross, err.Error())
    91  		return testkube.ExecutionResult{}, fmt.Errorf("could not execute kubepug: %w", err)
    92  	}
    93  
    94  	var rerr error
    95  	if execution.PostRunScript != "" && execution.ExecutePostRunScriptBeforeScraping {
    96  		output.PrintLog(fmt.Sprintf("%s Running post run script...", ui.IconCheckMark))
    97  
    98  		if rerr = agent.RunScript(execution.PostRunScript, r.params.WorkingDir); rerr != nil {
    99  			output.PrintLogf("%s Failed to execute post run script %s", ui.IconWarning, rerr)
   100  		}
   101  	}
   102  
   103  	// scrape artifacts first even if there are errors above
   104  	if r.params.ScrapperEnabled && execution.ArtifactRequest != nil && len(execution.ArtifactRequest.Dirs) != 0 {
   105  		output.PrintLogf("Scraping directories: %v with masks: %v", execution.ArtifactRequest.Dirs, execution.ArtifactRequest.Masks)
   106  
   107  		if err := r.Scraper.Scrape(ctx, execution.ArtifactRequest.Dirs, execution.ArtifactRequest.Masks, execution); err != nil {
   108  			return testkube.ExecutionResult{}, fmt.Errorf("could not scrape kubepug directories: %w", err)
   109  		}
   110  	}
   111  
   112  	var kubepugResult kubepug.Result
   113  	err = json.Unmarshal(out, &kubepugResult)
   114  	if err != nil {
   115  		output.PrintLogf("%s could not unmarshal kubepug execution result: %s", ui.IconCross, err.Error())
   116  		return testkube.ExecutionResult{}, fmt.Errorf("could not unmarshal kubepug execution result: %w", err)
   117  	}
   118  
   119  	if rerr != nil {
   120  		return testkube.ExecutionResult{}, rerr
   121  	}
   122  
   123  	deprecatedAPIstep := createDeprecatedAPIsStep(kubepugResult)
   124  	deletedAPIstep := createDeletedAPIsStep(kubepugResult)
   125  	return testkube.ExecutionResult{
   126  		Status: getResultStatus(kubepugResult),
   127  		Output: string(out),
   128  		Steps: []testkube.ExecutionStepResult{
   129  			deprecatedAPIstep,
   130  			deletedAPIstep,
   131  		},
   132  	}, nil
   133  }
   134  
   135  // createDeprecatedAPIsStep checks the kubepug output for deprecated APIs and converts them to Testkube step result
   136  func createDeprecatedAPIsStep(r kubepug.Result) testkube.ExecutionStepResult {
   137  	step := testkube.ExecutionStepResult{
   138  		Name: "Deprecated APIs",
   139  	}
   140  
   141  	if len(r.DeprecatedAPIs) == 0 {
   142  		step.Status = "passed"
   143  		output.PrintLog(fmt.Sprintf("%s No deprecated APIs found", ui.IconCheckMark))
   144  		return step
   145  	}
   146  
   147  	step.Status = "failed"
   148  	output.PrintLog(fmt.Sprintf("%s Found deprecated APIs: %v", ui.IconCross, r.DeletedAPIs))
   149  	for _, api := range r.DeprecatedAPIs {
   150  		step.AssertionResults = append(step.AssertionResults, testkube.AssertionResult{
   151  			Name:         api.Kind,
   152  			Status:       "failed",
   153  			ErrorMessage: fmt.Sprintf("Deprecated API:\n %v", api),
   154  		})
   155  	}
   156  
   157  	return step
   158  }
   159  
   160  // createDeletedAPISstep checks the kubepug output for deleted APIs and converts them to Testkube step result
   161  func createDeletedAPIsStep(r kubepug.Result) testkube.ExecutionStepResult {
   162  	step := testkube.ExecutionStepResult{
   163  		Name: "Deleted APIs",
   164  	}
   165  
   166  	if len(r.DeletedAPIs) == 0 {
   167  		step.Status = "passed"
   168  		output.PrintLogf("%s No deleted APIs found", ui.IconCheckMark)
   169  		return step
   170  	}
   171  
   172  	step.Status = "failed"
   173  	output.PrintLogf("%s Found deleted APIs: %v", ui.IconCross, r.DeletedAPIs)
   174  	for _, api := range r.DeletedAPIs {
   175  		step.AssertionResults = append(step.AssertionResults, testkube.AssertionResult{
   176  			Name:         api.Kind,
   177  			Status:       "failed",
   178  			ErrorMessage: fmt.Sprintf("Deleted API:\n %v", api),
   179  		})
   180  	}
   181  
   182  	return step
   183  }
   184  
   185  // getResultStatus calculates the final result status
   186  func getResultStatus(r kubepug.Result) *testkube.ExecutionStatus {
   187  	if len(r.DeletedAPIs) == 0 && len(r.DeprecatedAPIs) == 0 {
   188  		return testkube.ExecutionStatusPassed
   189  	}
   190  	return testkube.ExecutionStatusFailed
   191  }
   192  
   193  // buildArgs builds up the arguments for
   194  func buildArgs(args []string, inputPath string) ([]string, error) {
   195  	for i := range args {
   196  		if args[i] == "<runPath>" {
   197  			args[i] = inputPath
   198  		}
   199  
   200  		args[i] = os.ExpandEnv(args[i])
   201  	}
   202  	return args, nil
   203  }
   204  
   205  // GetType returns runner type
   206  func (r *KubepugRunner) GetType() runner.Type {
   207  	return runner.TypeMain
   208  }