github.com/informationsea/shellflow@v0.1.3/executer.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"os/exec"
     9  	"os/user"
    10  	"path"
    11  	"path/filepath"
    12  	"regexp"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/google/uuid"
    18  )
    19  
    20  type GeneratedScript struct {
    21  	JobRoot       string
    22  	StdoutPath    string
    23  	StderrPath    string
    24  	RunScriptPath string
    25  	ScriptPath    string
    26  	Skip          bool
    27  }
    28  
    29  type TaskScripts struct {
    30  	workflowRoot string
    31  	jobName      string
    32  	scripts      map[int]*GeneratedScript
    33  	env          *Environment
    34  	builder      *ShellTaskBuilder
    35  }
    36  
    37  type Execute func(ge *TaskScripts) error
    38  type FollowUp func(log *JobLog) error
    39  
    40  type WorkflowMetaData struct {
    41  	Env           map[string]string
    42  	Shellflow     string
    43  	Args          []string
    44  	WorkDir       string
    45  	Date          time.Time
    46  	User          *user.User
    47  	Workflow      string
    48  	WorkflowPath  string
    49  	Tasks         []*ShellTask
    50  	Parameters    map[string]interface{}
    51  	ParameterFile string
    52  }
    53  
    54  var jobNameRegexp = regexp.MustCompile("(\\w+).*")
    55  
    56  func GenerateTaskScripts(scriptPath string, paramPath string, env *Environment, builder *ShellTaskBuilder) (*TaskScripts, error) {
    57  	originalWorkDir, err := os.Getwd()
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	err = os.Chdir(env.workDir)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	defer os.Chdir(originalWorkDir)
    66  
    67  	basename := path.Base(scriptPath)
    68  	uuidObj := uuid.New()
    69  	workflowDir := path.Join(env.workflowRoot, fmt.Sprintf("%s-%s-%s", time.Now().Format("20060102-150405.000"), basename, uuidObj))
    70  	os.MkdirAll(workflowDir, 0755)
    71  
    72  	var shellflowPath string
    73  	_, err = Stat(os.Args[0])
    74  	if os.IsNotExist(err) {
    75  		shellflowPath, err = exec.LookPath(os.Args[0])
    76  		if err != nil {
    77  			return nil, fmt.Errorf("Cannot find shellflow binary absolute path: %s", err.Error())
    78  		}
    79  	} else if err == nil {
    80  		shellflowPath = Abs(os.Args[0])
    81  	} else {
    82  		return nil, fmt.Errorf("Cannot find shellflow binary absolute path: %s", err.Error())
    83  	}
    84  
    85  	jobName := path.Base(scriptPath)
    86  	if paramPath != "" {
    87  		jobName += " " + path.Base(paramPath)
    88  	}
    89  
    90  	ret := TaskScripts{
    91  		workflowRoot: workflowDir,
    92  		jobName:      jobName,
    93  		scripts:      make(map[int]*GeneratedScript),
    94  		env:          env,
    95  		builder:      builder,
    96  	}
    97  
    98  	{
    99  		// create input file list
   100  		fileList, err := os.OpenFile(path.Join(workflowDir, "input.json"), os.O_CREATE|os.O_WRONLY, 0644)
   101  		if err != nil {
   102  			return nil, err
   103  		}
   104  		defer fileList.Close()
   105  
   106  		encoder := json.NewEncoder(fileList)
   107  		encoder.SetIndent("", "  ")
   108  		files, err := CreateFileLog(builder.MissingCreatorFiles.Array(), false, MaximumContentLogSize)
   109  		if err != nil {
   110  			return nil, err
   111  		}
   112  		err = encoder.Encode(files)
   113  		if err != nil {
   114  			return nil, err
   115  		}
   116  
   117  		//		for _, x := range files {
   118  		//			tmp, _ := json.MarshalIndent(x, "", "  ")
   119  		//			changed, _ := x.IsChanged()
   120  		//			fmt.Printf("input files: %s %v\n", tmp, changed)
   121  		//		}
   122  	}
   123  
   124  	pathEnv := os.ExpandEnv("${PATH}")
   125  	ldLibraryPathEnv := os.ExpandEnv("${LD_LIBRARY_PATH}")
   126  
   127  	{
   128  		// create runtime information
   129  		runtimeFile, err := os.OpenFile(path.Join(workflowDir, "runtime.json"), os.O_CREATE|os.O_WRONLY, 0644)
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  		defer runtimeFile.Close()
   134  
   135  		envMap := make(map[string]string)
   136  		envMap["PATH"] = pathEnv
   137  		envMap["LD_LIBRARY_PATH"] = ldLibraryPathEnv
   138  
   139  		wd, err := os.Getwd()
   140  		if err != nil {
   141  			return nil, err
   142  		}
   143  
   144  		user, err := user.Current()
   145  		if err != nil {
   146  			return nil, err
   147  		}
   148  
   149  		var absParamPath string
   150  		if paramPath != "" {
   151  			absParamPath = Abs(paramPath)
   152  		}
   153  
   154  		runtime := WorkflowMetaData{
   155  			Env:           envMap,
   156  			Shellflow:     os.Args[0],
   157  			Args:          os.Args,
   158  			WorkDir:       wd,
   159  			Date:          time.Now(),
   160  			User:          user,
   161  			Workflow:      builder.WorkflowContent,
   162  			WorkflowPath:  Abs(scriptPath),
   163  			Tasks:         builder.Tasks,
   164  			Parameters:    env.parameters,
   165  			ParameterFile: absParamPath,
   166  		}
   167  
   168  		encoder := json.NewEncoder(runtimeFile)
   169  		encoder.SetIndent("", "  ")
   170  		encoder.Encode(runtime)
   171  	}
   172  
   173  	{
   174  		// create job scripts
   175  		for _, v := range builder.Tasks {
   176  			jobDir := path.Join(workflowDir, fmt.Sprintf("job%03d", v.ID))
   177  			err := os.MkdirAll(jobDir, 0755)
   178  			if err != nil {
   179  				return nil, err
   180  			}
   181  
   182  			absScriptPath := Abs(path.Join(jobDir, "script.sh"))
   183  			absRunScriptPath := Abs(path.Join(jobDir, "run.sh"))
   184  			absStdoutPath := Abs(path.Join(jobDir, "script.stdout"))
   185  			absStderrPath := Abs(path.Join(jobDir, "script.stderr"))
   186  			absResultPath := Abs(path.Join(jobDir, "rc"))
   187  			absInputPath := Abs(path.Join(jobDir, "input.json"))
   188  			absOutputPath := Abs(path.Join(jobDir, "output.json"))
   189  
   190  			if v.ShouldSkip {
   191  				copyFiles := []string{"script.sh", "run.sh", "script.stdout", "script.stderr", "rc", "input.json", "output.json"}
   192  				for _, x := range copyFiles {
   193  					srcFile, err := os.Open(path.Join(v.ReuseLog.JobLogRoot, x))
   194  					if err != nil {
   195  						return nil, err
   196  					}
   197  					defer srcFile.Close()
   198  					dstFile, err := os.OpenFile(path.Join(jobDir, x), os.O_CREATE|os.O_WRONLY, 0644)
   199  					if err != nil {
   200  						return nil, err
   201  					}
   202  					defer dstFile.Close()
   203  					_, err = io.Copy(dstFile, srcFile)
   204  					if err != nil {
   205  						return nil, err
   206  					}
   207  				}
   208  				rel, err := filepath.Rel(Abs(jobDir), Abs(v.ReuseLog.JobLogRoot))
   209  				if err != nil {
   210  					return nil, err
   211  				}
   212  				err = os.Symlink(rel, path.Join(jobDir, "original"))
   213  				if err != nil {
   214  					return nil, err
   215  				}
   216  			} else {
   217  
   218  				scriptFile, err := os.OpenFile(path.Join(jobDir, "script.sh"), os.O_CREATE|os.O_WRONLY, 0755)
   219  				if err != nil {
   220  					return nil, err
   221  				}
   222  				defer scriptFile.Close()
   223  				_, err = scriptFile.WriteString(v.ShellScript)
   224  				if err != nil {
   225  					return nil, err
   226  				}
   227  
   228  				var absDependentFilesBuilder strings.Builder
   229  				for _, v := range v.DependentFiles.Array() {
   230  					//str := Abs(v)
   231  					absDependentFilesBuilder.WriteString(strconv.Quote(v))
   232  					absDependentFilesBuilder.WriteString(" ")
   233  				}
   234  				absDependentFiles := absDependentFilesBuilder.String()
   235  
   236  				var absCreatingFilesBuilder strings.Builder
   237  				for _, v := range v.CreatingFiles.Array() {
   238  					//str := Abs(v)
   239  					absCreatingFilesBuilder.WriteString(strconv.Quote(v))
   240  					absCreatingFilesBuilder.WriteString(" ")
   241  				}
   242  				absCreatingFiles := absCreatingFilesBuilder.String()
   243  
   244  				runFile, err := os.OpenFile(path.Join(jobDir, "run.sh"), os.O_CREATE|os.O_WRONLY, 0755)
   245  				if err != nil {
   246  					return nil, err
   247  				}
   248  				defer runFile.Close()
   249  				fmt.Fprintf(runFile, `#/bin/bash
   250  #set -x
   251  cd %s
   252  `, env.workDir)
   253  
   254  				if !v.CommandConfiguration.DontInheirtPath {
   255  					fmt.Fprintf(runFile, `#/bin/bash
   256  export PATH="%s"
   257  export LD_LIBRARY_PATH="%s"
   258  `, pathEnv, ldLibraryPathEnv)
   259  				}
   260  
   261  				skipSha := ""
   262  				if env.skipSha {
   263  					skipSha = " -skipSha "
   264  				}
   265  
   266  				fmt.Fprintf(runFile, "%s filelog %s -output %s %s || exit 1\n", shellflowPath, skipSha, absInputPath, absDependentFiles)
   267  
   268  				fmt.Fprintf(runFile, `/bin/bash -o pipefail -e "%s" > %s 2> %s
   269  EXIT_CODE=$?
   270  `, absScriptPath, absStdoutPath, absStderrPath)
   271  
   272  				skipSha = ""
   273  				if env.skipSha {
   274  					skipSha = " -skipSha "
   275  				}
   276  				fmt.Fprintf(runFile, "%s filelog %s -output %s %s || exit 1\n", shellflowPath, skipSha, absOutputPath, absCreatingFiles)
   277  				fmt.Fprintf(runFile, "echo $EXIT_CODE > \"%s\"\n", absResultPath)
   278  				fmt.Fprintf(runFile, "exit $EXIT_CODE\n")
   279  
   280  			}
   281  
   282  			ret.scripts[v.ID] = &GeneratedScript{
   283  				JobRoot:       jobDir,
   284  				StdoutPath:    absStdoutPath,
   285  				StderrPath:    absStderrPath,
   286  				ScriptPath:    absScriptPath,
   287  				RunScriptPath: absRunScriptPath,
   288  				Skip:          v.ShouldSkip,
   289  			}
   290  		}
   291  	}
   292  
   293  	// print workflow directory
   294  	{
   295  		cwd, err := os.Getwd()
   296  		if err != nil {
   297  			return &ret, nil
   298  		}
   299  		r, err := filepath.Rel(cwd, workflowDir)
   300  		if err != nil {
   301  			return &ret, nil
   302  		}
   303  		fmt.Printf("Workflow Log: %s\n", r)
   304  	}
   305  
   306  	return &ret, nil
   307  }
   308  
   309  func Abs(path string) string {
   310  	abs, err := filepath.Abs(path)
   311  	if err != nil {
   312  		panic(err)
   313  	}
   314  	return abs
   315  }