github.com/kubeshop/testkube@v1.17.23/cmd/tcl/testworkflow-init/run/run.go (about)

     1  // Copyright 2024 Testkube.
     2  //
     3  // Licensed as a Testkube Pro file under the Testkube Community
     4  // License (the "License"); you may not use this file except in compliance with
     5  // the License. You may obtain a copy of the License at
     6  //
     7  //	https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt
     8  
     9  package run
    10  
    11  import (
    12  	"fmt"
    13  	"os"
    14  	"os/exec"
    15  
    16  	"github.com/kubeshop/testkube/cmd/tcl/testworkflow-init/data"
    17  	"github.com/kubeshop/testkube/cmd/tcl/testworkflow-init/utils"
    18  )
    19  
    20  const (
    21  	defaultBinPath = "/.tktw/bin"
    22  )
    23  
    24  func getProcessStatus(err error) (bool, uint8) {
    25  	if err == nil {
    26  		return true, 0
    27  	}
    28  	if e, ok := err.(*exec.ExitError); ok {
    29  		if e.ProcessState != nil {
    30  			return false, uint8(e.ProcessState.ExitCode())
    31  		}
    32  		return false, 1
    33  	}
    34  	fmt.Println(err.Error())
    35  	return false, 1
    36  }
    37  
    38  // TODO: Obfuscate Stdout/Stderr streams
    39  func createCommand(cmd string, args ...string) (c *exec.Cmd) {
    40  	c = exec.Command(cmd, args...)
    41  	out := utils.NewOutputProcessor(data.Step.Ref, os.Stdout)
    42  	c.Stdout = out
    43  	c.Stderr = os.Stderr
    44  	c.Stdin = os.Stdin
    45  	return
    46  }
    47  
    48  func execute(cmd string, args ...string) {
    49  	data.Step.Cmd = createCommand(cmd, args...)
    50  	success, exitCode := getProcessStatus(data.Step.Cmd.Run())
    51  	data.Step.ExitCode = exitCode
    52  
    53  	actualSuccess := success
    54  	if data.Config.Negative {
    55  		actualSuccess = !success
    56  	}
    57  
    58  	if actualSuccess {
    59  		data.Step.Status = data.StepStatusPassed
    60  	} else {
    61  		data.Step.Status = data.StepStatusFailed
    62  	}
    63  
    64  	if data.Config.Negative {
    65  		fmt.Printf("Expected to fail: finished with exit code %d.\n", exitCode)
    66  	} else if data.Config.Debug {
    67  		fmt.Printf("Exit code: %d.\n", exitCode)
    68  	}
    69  }
    70  
    71  func Run(cmd string, args []string) {
    72  	// Ensure the built-in binaries are available
    73  	if os.Getenv("PATH") == "" {
    74  		_ = os.Setenv("PATH", defaultBinPath)
    75  	} else {
    76  		_ = os.Setenv("PATH", fmt.Sprintf("%s:%s", os.Getenv("PATH"), defaultBinPath))
    77  	}
    78  
    79  	// Instantiate the command and run
    80  	execute(cmd, args...)
    81  
    82  	// Retry if it's expected
    83  	// TODO: Support nested retries
    84  	step := data.State.GetStep(data.Step.Ref)
    85  	for step.Iteration <= uint64(data.Config.RetryCount) {
    86  		expr, err := data.Expression(data.Config.RetryUntil, data.LocalMachine)
    87  		if err != nil {
    88  			fmt.Printf("Failed to execute retry condition: %s: %s\n", data.Config.RetryUntil, err.Error())
    89  			data.Finish()
    90  		}
    91  		v, _ := expr.BoolValue()
    92  		if v {
    93  			break
    94  		}
    95  		step.Next()
    96  		fmt.Printf("\nExit code: %d • Retrying: attempt #%d (of %d):\n", data.Step.ExitCode, step.Iteration-1, data.Config.RetryCount)
    97  		execute(cmd, args...)
    98  	}
    99  
   100  	// Finish
   101  	data.Finish()
   102  }