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

     1  package runner
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"path/filepath"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/pkg/errors"
    11  
    12  	"github.com/kubeshop/testkube/pkg/api/v1/client"
    13  	"github.com/kubeshop/testkube/pkg/api/v1/testkube"
    14  	"github.com/kubeshop/testkube/pkg/envs"
    15  	"github.com/kubeshop/testkube/pkg/executor"
    16  	"github.com/kubeshop/testkube/pkg/executor/containerexecutor"
    17  	"github.com/kubeshop/testkube/pkg/executor/content"
    18  	"github.com/kubeshop/testkube/pkg/executor/output"
    19  	"github.com/kubeshop/testkube/pkg/executor/runner"
    20  	"github.com/kubeshop/testkube/pkg/storage/minio"
    21  	"github.com/kubeshop/testkube/pkg/ui"
    22  )
    23  
    24  const (
    25  	defaultShell      = "/bin/sh"
    26  	preRunScriptName  = "prerun.sh"
    27  	commandScriptName = "command.sh"
    28  	postRunScriptName = "postrun.sh"
    29  )
    30  
    31  // NewRunner creates init runner
    32  func NewRunner(params envs.Params) *InitRunner {
    33  	return &InitRunner{
    34  		Fetcher: content.NewFetcher(params.DataDir),
    35  		Params:  params,
    36  	}
    37  }
    38  
    39  // InitRunner prepares data for executor
    40  type InitRunner struct {
    41  	Fetcher content.ContentFetcher
    42  	Params  envs.Params
    43  }
    44  
    45  var _ runner.Runner = &InitRunner{}
    46  
    47  // Run prepares data for executor
    48  func (r *InitRunner) Run(ctx context.Context, execution testkube.Execution) (result testkube.ExecutionResult, err error) {
    49  	output.PrintLogf("%s Initializing...", ui.IconTruck)
    50  
    51  	gitUsername := r.Params.GitUsername
    52  	gitToken := r.Params.GitToken
    53  
    54  	if gitUsername != "" || gitToken != "" {
    55  		if execution.Content != nil && execution.Content.Repository != nil {
    56  			execution.Content.Repository.Username = gitUsername
    57  			execution.Content.Repository.Token = gitToken
    58  		}
    59  	}
    60  
    61  	if execution.VariablesFile != "" {
    62  		output.PrintLogf("%s Creating variables file...", ui.IconWorld)
    63  		file := filepath.Join(r.Params.DataDir, "params-file")
    64  		if err = os.WriteFile(file, []byte(execution.VariablesFile), 0666); err != nil {
    65  			output.PrintLogf("%s Could not create variables file %s: %s", ui.IconCross, file, err.Error())
    66  			return result, errors.Errorf("could not create variables file %s: %v", file, err)
    67  		}
    68  		output.PrintLogf("%s Variables file created", ui.IconCheckMark)
    69  	}
    70  
    71  	_, err = r.Fetcher.Fetch(execution.Content)
    72  	if err != nil {
    73  		output.PrintLogf("%s Could not fetch test content: %s", ui.IconCross, err.Error())
    74  		return result, errors.Errorf("could not fetch test content: %v", err)
    75  	}
    76  
    77  	if execution.PreRunScript != "" || execution.PostRunScript != "" {
    78  		shell := defaultShell
    79  		if execution.ContainerShell != "" {
    80  			shell = execution.ContainerShell
    81  		}
    82  
    83  		shebang := "#!" + shell + "\nset -e\n"
    84  		// No set -e so that we can run the post-run script even if the command fails
    85  		entrypoint := "#!" + shell + "\n"
    86  		command := shebang
    87  		preRunScript := shebang
    88  		postRunScript := shebang
    89  
    90  		if execution.PreRunScript != "" {
    91  			if execution.SourceScripts {
    92  				entrypoint += ". "
    93  			}
    94  
    95  			entrypoint += strconv.Quote(filepath.Join(r.Params.DataDir, preRunScriptName)) + "\n"
    96  			entrypoint += "prerun_exit_code=$?\nif [ $prerun_exit_code -ne 0 ]; then\n  exit $prerun_exit_code\nfi\n"
    97  			preRunScript += execution.PreRunScript
    98  		}
    99  
   100  		if len(execution.Command) != 0 {
   101  			if execution.SourceScripts {
   102  				entrypoint += ". "
   103  			}
   104  
   105  			entrypoint += strconv.Quote(filepath.Join(r.Params.DataDir, commandScriptName)) + " $@\n"
   106  			entrypoint += "command_exit_code=$?\n"
   107  			command += strings.Join(execution.Command, " ")
   108  			command += " \"$@\"\n"
   109  		}
   110  
   111  		if execution.PostRunScript != "" {
   112  			if execution.SourceScripts {
   113  				entrypoint += ". "
   114  			}
   115  
   116  			entrypoint += strconv.Quote(filepath.Join(r.Params.DataDir, postRunScriptName)) + "\n"
   117  			entrypoint += "postrun_exit_code=$?\n"
   118  			postRunScript += execution.PostRunScript
   119  		}
   120  
   121  		if len(execution.Command) != 0 {
   122  			entrypoint += "if [ $command_exit_code -ne 0 ]; then\n  exit $command_exit_code\nfi\n"
   123  		}
   124  
   125  		if execution.PostRunScript != "" {
   126  			entrypoint += "exit $postrun_exit_code\n"
   127  		}
   128  		var scripts = []struct {
   129  			dir     string
   130  			file    string
   131  			data    string
   132  			comment string
   133  		}{
   134  			{r.Params.DataDir, preRunScriptName, preRunScript, "prerun"},
   135  			{r.Params.DataDir, commandScriptName, command, "command"},
   136  			{r.Params.DataDir, postRunScriptName, postRunScript, "postrun"},
   137  			{r.Params.DataDir, containerexecutor.EntrypointScriptName, entrypoint, "entrypoint"},
   138  		}
   139  
   140  		for _, script := range scripts {
   141  			if script.data == "" {
   142  				continue
   143  			}
   144  
   145  			file := filepath.Join(script.dir, script.file)
   146  			output.PrintLogf("%s Creating %s script...", ui.IconWorld, script.comment)
   147  			if err = os.WriteFile(file, []byte(script.data), 0755); err != nil {
   148  				output.PrintLogf("%s Could not create %s script %s: %s", ui.IconCross, script.comment, file, err.Error())
   149  				return result, errors.Errorf("could not create %s script %s: %v", script.comment, file, err)
   150  			}
   151  			output.PrintLogf("%s %s script created", ui.IconCheckMark, script.comment)
   152  		}
   153  	}
   154  
   155  	// TODO: write a proper cloud implementation
   156  	if r.Params.Endpoint != "" && !r.Params.ProMode {
   157  		output.PrintLogf("%s Fetching uploads from object store %s...", ui.IconFile, r.Params.Endpoint)
   158  		opts := minio.GetTLSOptions(r.Params.Ssl, r.Params.SkipVerify, r.Params.CertFile, r.Params.KeyFile, r.Params.CAFile)
   159  		minioClient := minio.NewClient(r.Params.Endpoint, r.Params.AccessKeyID, r.Params.SecretAccessKey, r.Params.Region, r.Params.Token, r.Params.Bucket, opts...)
   160  		fp := content.NewCopyFilesPlacer(minioClient)
   161  		fp.PlaceFiles(ctx, execution.TestName, execution.BucketName)
   162  	} else if r.Params.ProMode {
   163  		output.PrintLogf("%s Copy files functionality is currently not supported in cloud mode", ui.IconWarning)
   164  	}
   165  
   166  	output.PrintLogf("%s Setting up access to files in %s", ui.IconFile, r.Params.DataDir)
   167  	_, err = executor.Run(r.Params.DataDir, "chmod", nil, []string{"-R", "777", "."}...)
   168  	if err != nil {
   169  		output.PrintLogf("%s Could not chmod for data dir: %s", ui.IconCross, err.Error())
   170  	}
   171  
   172  	if execution.ArtifactRequest != nil &&
   173  		(execution.ArtifactRequest.StorageClassName != "" || execution.ArtifactRequest.UseDefaultStorageClassName) {
   174  		mountPath := filepath.Join(r.Params.DataDir, "artifacts")
   175  		if execution.ArtifactRequest.VolumeMountPath != "" {
   176  			mountPath = execution.ArtifactRequest.VolumeMountPath
   177  		}
   178  
   179  		_, err = executor.Run(mountPath, "chmod", nil, []string{"-R", "777", "."}...)
   180  		if err != nil {
   181  			output.PrintLogf("%s Could not chmod for artifacts dir: %s", ui.IconCross, err.Error())
   182  		}
   183  	}
   184  	output.PrintLogf("%s Access to files enabled", ui.IconCheckMark)
   185  
   186  	if len(execution.DownloadArtifactExecutionIDs) != 0 || len(execution.DownloadArtifactTestNames) != 0 {
   187  		downloadedArtifacts := filepath.Join(r.Params.DataDir, "downloaded-artifacts")
   188  		options := client.Options{
   189  			ApiUri: r.Params.APIURI,
   190  		}
   191  
   192  		c, err := client.GetClient(client.ClientDirect, options)
   193  		if err != nil {
   194  			output.PrintLogf("%s Could not get client: %s", ui.IconCross, err.Error())
   195  		} else {
   196  			for _, id := range execution.DownloadArtifactExecutionIDs {
   197  				execution, err := c.GetExecution(id)
   198  				if err != nil {
   199  					output.PrintLogf("%s Could not get execution: %s", ui.IconCross, err.Error())
   200  					continue
   201  				}
   202  
   203  				if err = downloadArtifacts(id, filepath.Join(downloadedArtifacts, execution.TestName+"-"+id), c); err != nil {
   204  					output.PrintLogf("%s Could not download execution artifact: %s", ui.IconCross, err.Error())
   205  				}
   206  			}
   207  
   208  			for _, name := range execution.DownloadArtifactTestNames {
   209  				test, err := c.GetTestWithExecution(name)
   210  				if err != nil {
   211  					output.PrintLogf("%s Could not get test with execution: %s", ui.IconCross, err.Error())
   212  					continue
   213  				}
   214  
   215  				if test.LatestExecution != nil {
   216  					id := test.LatestExecution.Id
   217  					if err = downloadArtifacts(id, filepath.Join(downloadedArtifacts, name+"-"+id), c); err != nil {
   218  						output.PrintLogf("%s Could not download test artifact: %s", ui.IconCross, err.Error())
   219  					}
   220  				}
   221  			}
   222  		}
   223  	}
   224  
   225  	output.PrintLogf("%s Initialization successful", ui.IconCheckMark)
   226  	return testkube.NewPendingExecutionResult(), nil
   227  }
   228  
   229  func downloadArtifacts(id, dir string, c client.Client) error {
   230  	artifacts, err := c.GetExecutionArtifacts(id)
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	if err = os.MkdirAll(dir, os.ModePerm); err != nil {
   236  		return err
   237  	}
   238  
   239  	if len(artifacts) > 0 {
   240  		output.PrintLogf("%s Getting %d artifacts...", ui.IconWorld, len(artifacts))
   241  		for _, artifact := range artifacts {
   242  			f, err := c.DownloadFile(id, artifact.Name, dir)
   243  			if err != nil {
   244  				return err
   245  			}
   246  
   247  			output.PrintLogf("%s Downloading file %s...", ui.IconWorld, f)
   248  		}
   249  	}
   250  
   251  	return nil
   252  }
   253  
   254  // GetType returns runner type
   255  func (r *InitRunner) GetType() runner.Type {
   256  	return runner.TypeInit
   257  }