github.com/swaros/contxt/module/taskrun@v0.0.0-20240305083542-3dbd4436ac40/cmdhandl.go (about)

     1  // Copyright (c) 2020 Thomas Ziegler <thomas.zglr@googlemail.com>. All rights reserved.
     2  //
     3  // # Licensed under the MIT License
     4  //
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  //
    12  // The above copyright notice and this permission notice shall be included in all
    13  // copies or substantial portions of the Software.
    14  //
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    21  // SOFTWARE.
    22  package taskrun
    23  
    24  import (
    25  	"bufio"
    26  	"fmt"
    27  
    28  	"os"
    29  	"os/exec"
    30  	"strings"
    31  	"sync"
    32  	"syscall"
    33  
    34  	"github.com/sirupsen/logrus"
    35  	"github.com/swaros/contxt/module/configure"
    36  	"github.com/swaros/contxt/module/dirhandle"
    37  	"github.com/swaros/contxt/module/systools"
    38  )
    39  
    40  const (
    41  	// DefaultExecFile is the filename of the script defaut file
    42  	DefaultExecFile = string(os.PathSeparator) + ".context.json"
    43  
    44  	// DefaultExecYaml is the default yaml configuration file
    45  	DefaultExecYaml     = string(os.PathSeparator) + ".contxt.yml"
    46  	defaultExecYamlName = ".contxt.yml"
    47  
    48  	// TargetScript is script default target
    49  	TargetScript = "script"
    50  
    51  	// InitScript is script default target
    52  	InitScript = "init"
    53  
    54  	// ClearScript is script default target
    55  	ClearScript = "clear"
    56  
    57  	// TestScript is teh test target
    58  	TestScript = "test"
    59  
    60  	// DefaultCommandFallBack is used if no command is defined
    61  	DefaultCommandFallBack = "bash"
    62  
    63  	// On windows we have a different default
    64  	DefaultCommandFallBackWindows = "powershell"
    65  )
    66  
    67  // ExecuteTemplateWorker runs ExecCurrentPathTemplate in context of a waitgroup
    68  func ExecuteTemplateWorker(waitGroup *sync.WaitGroup, useWaitGroup bool, target string, template configure.RunConfig) {
    69  	if useWaitGroup {
    70  		defer waitGroup.Done()
    71  	}
    72  	//ExecCurrentPathTemplate(path)
    73  	exitCode := ExecPathFile(waitGroup, useWaitGroup, template, target)
    74  	GetLogger().WithField("exitcode", exitCode).Info("ExecuteTemplateWorker done with exitcode")
    75  
    76  }
    77  
    78  func GetExecDefaults() (string, []string) {
    79  	cmd := GetDefaultCmd()
    80  	var args []string
    81  	args = GetDefaultCmdOpts(cmd, args)
    82  	return cmd, args
    83  }
    84  
    85  func GetDefaultCmd() string {
    86  
    87  	envCmd := os.Getenv("CTX_DEFAULT_CMD")
    88  	if envCmd != "" {
    89  		GetLogger().WithField("defaultcmd", envCmd).Info("Got default cmd from environment")
    90  		return envCmd
    91  	}
    92  
    93  	if configure.GetOs() == "windows" {
    94  		return DefaultCommandFallBackWindows
    95  	}
    96  	return DefaultCommandFallBack
    97  }
    98  
    99  func GetDefaultCmdOpts(ShellToUse string, cmdArg []string) []string {
   100  	if configure.GetOs() == "windows" {
   101  		if envCmd := os.Getenv("CTX_DEFAULT_CMD_ARGUMENTS"); envCmd != "" {
   102  			GetLogger().WithField("arguments", envCmd).Info("Got cmd arguments form environment")
   103  			return strings.Split(envCmd, " ")
   104  		}
   105  		if cmdArg == nil && ShellToUse == DefaultCommandFallBackWindows {
   106  			cmdArg = []string{"-nologo", "-noprofile"}
   107  		}
   108  	} else {
   109  		if cmdArg == nil && ShellToUse == DefaultCommandFallBack {
   110  			cmdArg = []string{"-c"}
   111  		}
   112  	}
   113  	return cmdArg
   114  }
   115  
   116  // ExecuteScriptLine executes a simple shell script
   117  // returns internal exitsCode, process existcode, error
   118  func ExecuteScriptLine(ShellToUse string, cmdArg []string, command string, callback func(string, error) bool, startInfo func(*os.Process)) (int, int, error) {
   119  	cmdArg = GetDefaultCmdOpts(ShellToUse, cmdArg)
   120  	cmdArg = append(cmdArg, command)
   121  	cmd := exec.Command(ShellToUse, cmdArg...)
   122  	stdoutPipe, _ := cmd.StdoutPipe()
   123  	cmd.Stderr = cmd.Stdout
   124  
   125  	err := cmd.Start()
   126  	if err != nil {
   127  		GetLogger().Warn("execution error: ", err)
   128  		return systools.ExitCmdError, 0, err
   129  	}
   130  
   131  	GetLogger().WithFields(logrus.Fields{
   132  		"env":        cmd.Env,
   133  		"args":       cmd.Args,
   134  		"dir":        cmd.Dir,
   135  		"extrafiles": cmd.ExtraFiles,
   136  		"pid":        cmd.Process.Pid,
   137  	}).Info(":::EXEC")
   138  
   139  	startInfo(cmd.Process)
   140  	scanner := bufio.NewScanner(stdoutPipe)
   141  
   142  	scanner.Split(bufio.ScanLines)
   143  	for scanner.Scan() {
   144  		m := scanner.Text()
   145  
   146  		keepRunning := callback(m, nil)
   147  
   148  		GetLogger().WithFields(logrus.Fields{
   149  			"keep-running": keepRunning,
   150  			"out":          m,
   151  		}).Info("handle-result")
   152  		if !keepRunning {
   153  			cmd.Process.Kill()
   154  			return systools.ExitByStopReason, 0, err
   155  		}
   156  
   157  	}
   158  	err = cmd.Wait()
   159  	if err != nil {
   160  		callback(err.Error(), err)
   161  		errRealCode := 0
   162  		if exiterr, ok := err.(*exec.ExitError); ok {
   163  			if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
   164  				GetLogger().Warn("(maybe expected...) Exit Status reported: ", status.ExitStatus())
   165  				errRealCode = status.ExitStatus()
   166  			}
   167  
   168  		} else {
   169  			GetLogger().Warn("execution error: ", err)
   170  		}
   171  		return systools.ExitCmdError, errRealCode, err
   172  	}
   173  
   174  	return systools.ExitOk, 0, err
   175  }
   176  
   177  // WriteTemplate create path based execution file
   178  func WriteTemplate() {
   179  	dir, error := dirhandle.Current()
   180  	if error != nil {
   181  		fmt.Println("error getting current directory")
   182  		return
   183  	}
   184  
   185  	var path = dir + DefaultExecYaml
   186  	already, errEx := dirhandle.Exists(path)
   187  	if errEx != nil {
   188  		fmt.Println("error ", errEx)
   189  		return
   190  	}
   191  
   192  	if already {
   193  		fmt.Println("file already exists.", path, " aborted")
   194  		return
   195  	}
   196  
   197  	fmt.Println("write execution template to ", path)
   198  
   199  	var demoContent = `task:
   200    - id: script
   201      script:
   202        - echo "hello world"
   203  `
   204  	err := os.WriteFile(path, []byte(demoContent), 0644)
   205  	if err != nil {
   206  		fmt.Println(err)
   207  	}
   208  }
   209  
   210  // ExecPathFile executes the default exec file
   211  func ExecPathFile(waitGroup *sync.WaitGroup, useWaitGroup bool, template configure.RunConfig, target string) int {
   212  	var scopeVars map[string]string = make(map[string]string)
   213  	return executeTemplate(waitGroup, useWaitGroup, template, target, scopeVars)
   214  }