github.com/swaros/contxt/module/taskrun@v0.0.0-20240305083542-3dbd4436ac40/cmdflow.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 "context" 26 "fmt" 27 "os" 28 "strconv" 29 "strings" 30 "sync" 31 "time" 32 33 "github.com/imdario/mergo" 34 "github.com/sirupsen/logrus" 35 "github.com/swaros/contxt/module/awaitgroup" 36 "github.com/swaros/contxt/module/dirhandle" 37 "github.com/swaros/contxt/module/trigger" 38 "github.com/swaros/manout" 39 40 "github.com/swaros/contxt/module/systools" 41 42 "github.com/swaros/contxt/module/configure" 43 ) 44 45 const ( 46 EventAllLines = "ExecuteScriptLine" // EventAllLines is the event name for sending all lines 47 EventTaskStatusUpdate = "TaskStatus" // EventTaskStatusUpdate is the event name for sending task status updates 48 ) 49 50 type EventScriptLine struct { 51 Line string 52 Target string 53 Error error 54 } 55 56 type EventTaskStatus struct { 57 Target string 58 Running bool 59 Finished bool 60 RunCount int 61 Error error 62 ExitCode int 63 Pid int 64 } 65 66 // SharedFolderExecuter runs shared .contxt.yml files directly without merging them into 67 // the current contxt file 68 func SharedFolderExecuter(template configure.RunConfig, locationHandle func(string, string)) { 69 if len(template.Config.Use) > 0 { 70 GetLogger().WithField("uses", template.Config.Use).Info("shared executer") 71 for _, shared := range template.Config.Use { 72 externalPath := HandleUsecase(shared) 73 GetLogger().WithField("path", externalPath).Info("shared contxt location") 74 currentDir, _ := dirhandle.Current() 75 os.Chdir(externalPath) 76 locationHandle(externalPath, currentDir) 77 os.Chdir(currentDir) 78 } 79 } 80 } 81 82 func RunShared(targets string) { 83 84 allTargets := strings.Split(targets, ",") 85 template, templatePath, exists, terr := GetTemplate() 86 if terr != nil { 87 CtxOut(manout.MessageCln(manout.ForeRed, "Error ", manout.CleanTag, terr.Error())) 88 return 89 } 90 if !exists { 91 return 92 } 93 94 if template.Config.Loglevel != "" { // set logger level by template definition 95 setLogLevelByString(template.Config.Loglevel) 96 } 97 98 GetLogger().WithField("targets", allTargets).Info("SHARED START run targets...") 99 100 // handle all shared usages. these usages are set 101 // in the template by the string map named Use in the config section 102 // Config: 103 // Use: 104 // - shared_task_1 105 // - shared_task_2 106 if len(template.Config.Use) > 0 { 107 GetLogger().WithField("uses", template.Config.Use).Info("found external dependecy") 108 CtxOut(manout.MessageCln(manout.ForeCyan, "[SHARED loop]")) 109 for _, shared := range template.Config.Use { 110 CtxOut(manout.MessageCln(manout.ForeCyan, "[SHARED CONTXT][", manout.ForeBlue, shared, manout.ForeCyan, "] ")) 111 externalPath := HandleUsecase(shared) 112 GetLogger().WithField("path", externalPath).Info("shared contxt location") 113 currentDir, _ := dirhandle.Current() 114 os.Chdir(externalPath) 115 for _, runTarget := range allTargets { 116 CtxOut(manout.MessageCln(manout.ForeCyan, manout.ForeGreen, runTarget, manout.ForeYellow, " [ external:", manout.ForeWhite, externalPath, manout.ForeYellow, "] ", manout.ForeDarkGrey, templatePath)) 117 RunTargets(runTarget, false) 118 CtxOut(manout.MessageCln(manout.ForeCyan, "["+manout.ForeBlue, shared+"] ", manout.ForeGreen, runTarget, " DONE")) 119 } 120 os.Chdir(currentDir) 121 } 122 CtxOut(manout.MessageCln(manout.ForeCyan, "[SHARED done]")) 123 } 124 GetLogger().WithField("targets", allTargets).Info(" SHARED DONE run targets...") 125 } 126 127 // RunTargets executes multiple targets 128 // the targets string can have multiple targets 129 // seperated by comma 130 func RunTargets(targets string, sharedRun bool) { 131 132 // validate first 133 if err := TestTemplate(); err != nil { 134 CtxOut("found issues in the current template. ", err) 135 systools.Exit(systools.ErrorTemplate) 136 return 137 } 138 139 SetPH("CTX_TARGETS", targets) 140 141 // this flag should only true on the first execution 142 if sharedRun { 143 // do it here makes sure we are not in the shared scope 144 currentDir, _ := dirhandle.Current() 145 SetPH("CTX_PWD", currentDir) 146 // run shared use 147 RunShared(targets) 148 } 149 150 allTargets := strings.Split(targets, ",") 151 template, templatePath, exists, terr := GetTemplate() 152 if terr != nil { 153 CtxOut(manout.MessageCln(manout.ForeRed, "Error ", manout.CleanTag, terr.Error())) 154 systools.Exit(systools.ErrorTemplateReading) 155 return 156 } 157 GetLogger().WithField("targets", allTargets).Info("run targets...") 158 var runSequencially = false // default is async mode 159 if exists { // TODO: the exists check just for this config reading seems wrong 160 runSequencially = template.Config.Sequencially 161 if template.Config.Coloroff { 162 manout.ColorEnabled = false 163 } 164 } 165 166 if template.Config.Loglevel != "" { // loglevel by config 167 setLogLevelByString(template.Config.Loglevel) 168 } 169 170 var wg sync.WaitGroup // the main waitgroup 171 172 // handle all imports. 173 // these are yaml or json files, they can be accessed for reading by the gson doted format 174 if len(template.Config.Imports) > 0 { 175 GetLogger().WithField("Import", template.Config.Imports).Info("import variables from Files") 176 handleFileImportsToVars(template.Config.Imports) 177 } else { 178 GetLogger().Info("No Imports defined") 179 } 180 181 // experimental usage of taskrunner 182 183 if runSequencially { // non async run 184 for _, trgt := range allTargets { 185 SetPH("CTX_TARGET", trgt) 186 CtxOut(LabelFY("exec"), InfoMinor("execute target in sequence"), ValF(trgt), manout.ForeLightCyan, " ", templatePath) 187 ExecPathFile(&wg, !runSequencially, template, trgt) 188 } 189 } else { 190 var futuresExecs []awaitgroup.FutureStack 191 for _, trgt := range allTargets { // iterate all targets 192 CtxOut(LabelFY("exec"), InfoMinor("execute target in Async"), ValF(trgt), manout.ForeLightCyan, " ", templatePath) 193 futuresExecs = append(futuresExecs, awaitgroup.FutureStack{ 194 AwaitFunc: func(ctx context.Context) interface{} { 195 ctxTarget := ctx.Value(awaitgroup.CtxKey{}).(string) // get the target from context 196 SetPH("CTX_TARGET", ctxTarget) // update global target. TODO: makes this any sense in async? 197 return ExecPathFile(&wg, !runSequencially, template, ctxTarget) // execute target 198 }, 199 Argument: trgt, 200 }) 201 } 202 futures := awaitgroup.ExecFutureGroup(futuresExecs) // execute all async task 203 CtxOut(LabelFY("exec"), "all targets started ", len(targets)) // just info 204 awaitgroup.WaitAtGroup(futures) // wait until all task are done 205 CtxOut(LabelFY("exec"), "all targets done ", len(targets)) // also just info for the user 206 } 207 208 CtxOut(manout.MessageCln(manout.ForeBlue, "[done] ", manout.BoldTag, targets)) 209 GetLogger().Info("target task execution done") 210 } 211 212 func setLogLevelByString(loglevel string) { 213 level, err := logrus.ParseLevel(loglevel) 214 if err != nil { 215 GetLogger().Error("Invalid loglevel in task defined.", err) 216 } else { 217 GetLogger().SetLevel(level) 218 } 219 220 } 221 222 func listenerWatch(script configure.Task, target, logLine string, e error, waitGroup *sync.WaitGroup, useWaitGroup bool, runCfg configure.RunConfig) { 223 if script.Listener != nil { 224 225 GetLogger().WithFields(logrus.Fields{ 226 "count": len(script.Listener), 227 "listener": script.Listener, 228 "target": target, 229 }).Debug("testing Listener") 230 231 for _, listener := range script.Listener { 232 triggerFound, triggerMessage := checkReason(listener.Trigger, logLine, e) // check if a trigger have a match 233 if triggerFound { 234 GetLogger().WithFields(logrus.Fields{"target": target, "reason": triggerMessage}).Debug("Trigger Found") 235 SetPH("RUN."+target+".LOG.HIT", logLine) 236 if script.Options.Displaycmd { 237 CtxOut(manout.MessageCln(manout.ForeCyan, "[trigger]\t", manout.ForeYellow, triggerMessage, manout.Dim, " ", logLine)) 238 } 239 240 someReactionTriggered := false // did this trigger something? used as flag 241 actionDef := configure.Action(listener.Action) // extract action 242 243 if len(actionDef.Script) > 0 { // script are directs executes without any async or other executes out of scope 244 someReactionTriggered = true 245 var dummyArgs map[string]string = make(map[string]string) // create empty arguments as scoped values 246 for _, triggerScript := range actionDef.Script { // run any line of script 247 GetLogger().WithFields(logrus.Fields{ 248 "cmd": triggerScript, 249 }).Debug("TRIGGER SCRIPT ACTION") 250 lineExecuter(waitGroup, useWaitGroup, script.Stopreasons, runCfg, "93", "46", triggerScript, target, dummyArgs, script) 251 } 252 253 } 254 255 if actionDef.Target != "" { // here we have a target defined thats needs to be started 256 someReactionTriggered = true 257 GetLogger().WithFields(logrus.Fields{ 258 "target": actionDef.Target, 259 }).Debug("TRIGGER ACTION") 260 261 if script.Options.Displaycmd { 262 CtxOut(manout.MessageCln(manout.ForeCyan, "[trigger]\t ", manout.ForeGreen, "target:", manout.ForeLightGreen, actionDef.Target)) 263 } 264 265 // TODO: i can't remember why i am doing this placeholder thing 266 hitKeyTargets := "RUN.LISTENER." + target + ".HIT.TARGETS" // compose the placeholder key 267 lastHitTargets := GetPH(hitKeyTargets) // get the last stored value if exists 268 if !strings.Contains(lastHitTargets, "("+actionDef.Target+")") { 269 lastHitTargets = lastHitTargets + "(" + actionDef.Target + ")" 270 SetPH(hitKeyTargets, lastHitTargets) 271 } 272 273 hitKeyCnt := "RUN.LISTENER." + actionDef.Target + ".HIT.CNT" 274 lastCnt := GetPH(hitKeyCnt) 275 if lastCnt == "" { 276 SetPH(hitKeyCnt, "1") 277 } else { 278 iCnt, err := strconv.Atoi(lastCnt) 279 if err != nil { 280 GetLogger().Fatal("fail converting trigger count") 281 } 282 iCnt++ 283 SetPH(hitKeyCnt, strconv.Itoa(iCnt)) 284 } 285 286 GetLogger().WithFields(logrus.Fields{ 287 "trigger": triggerMessage, 288 "target": actionDef.Target, 289 "waitgroup": useWaitGroup, 290 "RUN.LISTENER." + target + ".HIT.TARGETS": lastHitTargets, 291 }).Info("TRIGGER Called") 292 var scopeVars map[string]string = make(map[string]string) 293 294 GetLogger().WithFields(logrus.Fields{ 295 "target": actionDef.Target, 296 }).Info("RUN Triggered target (not async)") 297 298 // because we are anyway in a async scope, we should no longer 299 // try to run this target too async. 300 // also the target is triggered by an specific log entriy, it makes 301 // sence to stop the execution of the parent, til this target is executed 302 CtxOut("running target ", manout.ForeCyan, actionDef.Target, manout.ForeLightCyan, " trigger action") 303 executeTemplate(waitGroup, useWaitGroup, runCfg, actionDef.Target, scopeVars) 304 305 } 306 if !someReactionTriggered { 307 GetLogger().WithFields(logrus.Fields{ 308 "trigger": triggerMessage, 309 "output": logLine, 310 }).Warn("trigger defined without any action") 311 } 312 } else { 313 GetLogger().WithFields(logrus.Fields{ 314 "output": logLine, 315 }).Debug("no trigger found") 316 } 317 } 318 } 319 } 320 321 // create the event for the trigger 322 // this is a workaround for the trigger package 323 // we have to create the event before we can use it 324 // but we can't create it in the init function, because 325 // the trigger package is not initialized at this time 326 // so we have to create it on the first call 327 // and we have to handle the listener here 328 func createOrGetExecuteEvent(eventName string) *trigger.Event { 329 var execEvent *trigger.Event 330 execEvent, _ = trigger.GetEvent(eventName) 331 // we have to handle the listener here 332 if execEvent == nil { 333 var eventErr error 334 execEvent, eventErr = trigger.NewEvent(eventName) 335 336 if eventErr != nil { 337 GetLogger().Error("fail to create event") 338 return nil 339 } 340 } 341 return execEvent 342 } 343 344 // the main script handler 345 func lineExecuter( 346 waitGroup *sync.WaitGroup, // the main waitgoup 347 useWaitGroup bool, // flag if we have to use the waitgroup. also means we run in async mode 348 stopReason configure.Trigger, // configuration for the stop reasons 349 runCfg configure.RunConfig, // the runtime configuration 350 colorCode, bgCode, // colorcodes for the left panel 351 codeLine, // the script that have to be processed 352 target string, // the actual target 353 arguments map[string]string, // the arguments for the current scope 354 script configure.Task) (int, bool) { 355 panelSize := 12 // default panelsize 356 if script.Options.Panelsize > 0 { // overwrite panel size if set 357 panelSize = script.Options.Panelsize 358 } 359 var mainCommand = defaultString(script.Options.Maincmd, DefaultCommandFallBack) // get the maincommand by default first 360 if configure.GetOs() == "windows" { // handle windows behavior depending default commands 361 mainCommand = defaultString(script.Options.Maincmd, DefaultCommandFallBackWindows) // setup windows default command 362 } 363 replacedLine := HandlePlaceHolderWithScope(codeLine, arguments) // placeholders 364 if script.Options.Displaycmd { // do we show the argument? 365 CtxOut(LabelFY("cmd"), ValF(target), InfoF(replacedLine)) 366 } 367 368 SetPH("RUN.SCRIPT_LINE", replacedLine) // overwrite the current scriptline. this is only reliable if we not in async mode 369 // composing the label for the output 370 var targetLabel CtxTargetOut = CtxTargetOut{ 371 ForeCol: defaultString(script.Options.Colorcode, colorCode), 372 BackCol: defaultString(script.Options.Bgcolorcode, bgCode), 373 PanelSize: panelSize, 374 } 375 // here we execute the current script line 376 execCode, realExitCode, execErr := ExecuteScriptLine(mainCommand, script.Options.Mainparams, replacedLine, 377 func(logLine string, err error) bool { // callback for any logline 378 379 execEvent := createOrGetExecuteEvent(EventAllLines) // create or get the event for all lines 380 if execEvent != nil { 381 event := EventScriptLine{ 382 Target: target, 383 Line: logLine, 384 Error: err, 385 } 386 execEvent.SetArguments(event) 387 trigger.UpdateEvents() 388 execEvent.Send() 389 } 390 391 SetPH("RUN."+target+".LOG.LAST", logLine) // set or overwrite the last script output for the target 392 393 if script.Listener != nil { // do we have listener? 394 GetLogger().WithFields(logrus.Fields{ 395 "cnt": len(script.Listener), 396 "listener": script.Listener, 397 }).Debug("CHECK Listener") 398 listenerWatch(script, target, logLine, err, waitGroup, useWaitGroup, runCfg) // listener handler 399 } 400 targetLabel.Target = target 401 // The whole output can be ignored by configuration 402 // if this is not enabled then we handle all these here 403 if !script.Options.Hideout { 404 // the background color 405 if script.Options.Format != "" { // do we have a specific format for the label, then we use them instead 406 format := HandlePlaceHolderWithScope(script.Options.Format, script.Variables) // handle placeholder in the label 407 fomatedOutStr := manout.Message(format) // first just get the format as it is 408 if strings.Contains(format, "%s") { // do we have the string placeholder? 409 fomatedOutStr = manout.Message(fmt.Sprintf(format, target)) // ... then format the message depending format codes 410 } 411 targetLabel.Alternative = fomatedOutStr 412 } 413 414 //outStr := systools.LabelPrintWithArg(logLine, colorCode, "39", 2) // hardcoded format for the logoutput iteself 415 outStr := manout.MessageCln(logLine) 416 if script.Options.Stickcursor { // optional set back the cursor to the beginning 417 fmt.Print("\033[G\033[K") // done by escape codes 418 } 419 420 CtxOut(targetLabel, outStr) // prints the codeline 421 if script.Options.Stickcursor { // cursor stick handling 422 fmt.Print("\033[A") 423 } 424 } 425 426 stopReasonFound, message := checkReason(stopReason, logLine, err) // do we found a defined reason to stop execution 427 if stopReasonFound { 428 if script.Options.Displaycmd { 429 CtxOut(LabelFY("stop-reason"), ValF(message)) 430 } 431 return false 432 } 433 return true 434 }, func(process *os.Process) { // callback if the process started and we got the process id 435 pidStr := fmt.Sprintf("%d", process.Pid) // we use them as info for the user only 436 SetPH("RUN.PID", pidStr) 437 SetPH("RUN."+target+".PID", pidStr) 438 if script.Options.Displaycmd { 439 CtxOut(LabelFY("pid"), ValF(process.Pid)) 440 } 441 taskUpdate := createOrGetExecuteEvent(EventTaskStatusUpdate) 442 event := EventTaskStatus{ 443 Target: target, 444 Running: true, 445 Finished: false, 446 Error: nil, 447 Pid: process.Pid, 448 } 449 if taskUpdate != nil { 450 taskUpdate.SetArguments(event) 451 taskUpdate.Send() 452 } 453 454 }) 455 if execErr != nil { 456 if script.Options.Displaycmd { 457 CtxOut(LabelErrF("exec error"), ValF(execErr)) 458 } 459 460 // send errors as event too 461 execEvent := createOrGetExecuteEvent(EventAllLines) 462 if execEvent != nil { 463 event := EventScriptLine{ 464 Target: target, 465 Line: execErr.Error(), 466 Error: execErr, 467 } 468 execEvent.SetArguments(event) 469 trigger.UpdateEvents() 470 execEvent.Send() 471 472 } 473 } 474 // check execution codes 475 switch execCode { 476 case systools.ExitByStopReason: 477 return systools.ExitByStopReason, true 478 case systools.ExitCmdError: 479 if script.Options.IgnoreCmdError { 480 if script.Stopreasons.Onerror { 481 return systools.ExitByStopReason, true 482 } 483 CtxOut(manout.MessageCln(manout.ForeYellow, "NOTE!\t", manout.BackLightYellow, manout.ForeDarkGrey, " a script execution was failing. no stopreason is set so execution will continued ")) 484 CtxOut(manout.MessageCln("\t", manout.BackLightYellow, manout.ForeDarkGrey, " if this is expected you can ignore this message. ")) 485 CtxOut(manout.MessageCln("\t", manout.BackLightYellow, manout.ForeDarkGrey, " but you should handle error cases ")) 486 CtxOut("\ttarget :\t", manout.MessageCln(manout.ForeBlue, target)) 487 CtxOut("\tcommand:\t", manout.MessageCln(manout.ForeYellow, codeLine)) 488 489 } else { 490 errMsg := " = exit code from command: " 491 lastMessage := manout.MessageCln(manout.BackRed, manout.ForeYellow, realExitCode, manout.CleanTag, manout.ForeLightRed, errMsg, manout.ForeWhite, codeLine) 492 CtxOut("\t Exit ", lastMessage) 493 CtxOut() 494 CtxOut("\t check the command. if this command can fail you may fit the execution rules. see options:") 495 CtxOut("\t you may disable a hard exit on error by setting ignoreCmdError: true") 496 CtxOut("\t if you do so, a Note will remind you, that a error is happend in this case.") 497 CtxOut() 498 //GetLogger().Error("runtime error:", execErr, "exit", realExitCode) 499 systools.Exit(realExitCode) 500 // returns the error code 501 return systools.ExitCmdError, true 502 } 503 case systools.ExitOk: 504 return systools.ExitOk, false 505 } 506 return systools.ExitNoCode, true 507 } 508 509 func generateFuturesByTargetListAndExec(RunTargets []string, waitGroup *sync.WaitGroup, runCfg configure.RunConfig) []awaitgroup.Future { 510 if len(RunTargets) < 1 { 511 return []awaitgroup.Future{} 512 } 513 var runTargetExecs []awaitgroup.FutureStack 514 for _, needTarget := range RunTargets { 515 GetLogger().Debug("runTarget name should be added " + needTarget) 516 runTargetExecs = append(runTargetExecs, awaitgroup.FutureStack{ 517 AwaitFunc: func(ctx context.Context) interface{} { 518 argTask := ctx.Value(awaitgroup.CtxKey{}).(string) 519 _, argmap := StringSplitArgs(argTask, "arg") 520 GetLogger().Debug("add runTarget task " + argTask) 521 return executeTemplate(waitGroup, true, runCfg, argTask, argmap) 522 }, 523 Argument: needTarget}) 524 525 } 526 CtxOut(LabelFY("async targets"), InfoF("count"), len(runTargetExecs), InfoF(" targets")) 527 return awaitgroup.ExecFutureGroup(runTargetExecs) 528 } 529 530 // merge a list of task to an single task. 531 func mergeTargets(target string, runCfg configure.RunConfig) configure.Task { 532 var checkTasks configure.Task 533 first := true 534 if len(runCfg.Task) > 0 { 535 for _, script := range runCfg.Task { 536 if strings.EqualFold(target, script.ID) { 537 canRun, failMessage := checkRequirements(script.Requires) 538 if canRun { 539 // update task variables 540 for keyName, variable := range script.Variables { 541 SetPH(keyName, HandlePlaceHolder(variable)) 542 } 543 if first { 544 checkTasks = script 545 first = false 546 } else { 547 mergo.Merge(&checkTasks, script, mergo.WithOverride, mergo.WithAppendSlice) 548 } 549 } else { 550 GetLogger().Debug(failMessage) 551 } 552 } 553 } 554 } 555 return checkTasks 556 } 557 558 func executeTemplate(waitGroup *sync.WaitGroup, runAsync bool, runCfg configure.RunConfig, target string, scopeVars map[string]string) int { 559 if runAsync { 560 waitGroup.Add(1) 561 defer waitGroup.Done() 562 } 563 // check if task is already running 564 // this check depends on the target name. 565 if !runCfg.Config.AllowMutliRun && TaskRunning(target) { 566 GetLogger().WithField("task", target).Warning("task would be triggered again while is already running. IGNORED") 567 return systools.ExitAlreadyRunning 568 } 569 570 // increment task counter 571 targetTaskCount := incTaskCount(target) 572 defer incTaskDoneCount(target) 573 574 GetLogger().WithFields(logrus.Fields{ 575 "target": target, 576 }).Info("executeTemplate LOOKING for target") 577 578 // create the task update event 579 taskUpdate := createOrGetExecuteEvent(EventTaskStatusUpdate) 580 event := EventTaskStatus{ 581 Target: target, 582 Running: false, 583 RunCount: targetTaskCount, 584 Finished: false, 585 Error: nil, 586 } 587 if taskUpdate != nil { 588 taskUpdate.SetArguments(event) 589 taskUpdate.Send() 590 } 591 592 // Checking if the Tasklist have something 593 // to handle 594 if len(runCfg.Task) > 0 { 595 returnCode := systools.ExitOk 596 597 // the main variables will be set at first 598 // but only if the they not already exists 599 // from other task or by start argument 600 for keyName, variable := range runCfg.Config.Variables { 601 SetIfNotExists(keyName, HandlePlaceHolder(variable)) 602 } 603 // set the colorcodes for the labels on left side of screen 604 colorCode, bgCode := systools.CreateColorCode() 605 606 // updates global variables 607 SetPH("RUN.TARGET", target) 608 609 // this flag is only used 610 // for a "target not found" message later 611 targetFound := false 612 613 // oure tasklist that will use later 614 var taskList []configure.Task 615 616 // depending on the config 617 // we merge the tasks and handle them as one task, 618 // or we keep them as a list of tasks what would 619 // keep more flexibility. 620 // by merging task we can loose runtime definitions 621 if runCfg.Config.MergeTasks { 622 mergedScript := mergeTargets(target, runCfg) 623 taskList = append(taskList, mergedScript) 624 } else { 625 for _, script := range runCfg.Task { 626 if strings.EqualFold(target, script.ID) { 627 taskList = append(taskList, script) 628 } 629 } 630 } 631 632 // check if we have found the target 633 for curTIndex, script := range taskList { 634 if strings.EqualFold(target, script.ID) { 635 GetLogger().WithFields(logrus.Fields{ 636 "target": target, 637 "scopeVars": scopeVars, 638 }).Info("executeTemplate EXECUTE target") 639 targetFound = true 640 641 stopReason := script.Stopreasons 642 643 var messageCmdCtrl CtxOutCtrl = CtxOutCtrl{ // define a controll hook, depending on the display comand option 644 IgnoreCase: !script.Options.Displaycmd, // we ignore thie message, as long the display command is NOT set 645 } 646 647 // check requirements 648 canRun, message := checkRequirements(script.Requires) 649 if !canRun { 650 GetLogger().WithFields(logrus.Fields{ 651 "target": target, 652 "reason": message, 653 }).Info("executeTemplate IGNORE because requirements not matching") 654 if script.Options.Displaycmd { 655 CtxOut(messageCmdCtrl, LabelFY("require"), ValF(message), InfoF("Task-Block "), curTIndex+1, " of ", len(taskList), " skipped") 656 } 657 // ---- return ExitByRequirement 658 continue 659 } 660 661 // get the task related variables 662 for keyName, variable := range script.Variables { 663 SetPH(keyName, HandlePlaceHolder(variable)) 664 scopeVars[keyName] = variable 665 } 666 backToDir := "" 667 // if working dir is set change to them 668 if script.Options.WorkingDir != "" { 669 backToDir, _ = dirhandle.Current() 670 chDirError := os.Chdir(HandlePlaceHolderWithScope(script.Options.WorkingDir, scopeVars)) 671 if chDirError != nil { 672 manout.Error("Workspace setting seems invalid ", chDirError) 673 systools.Exit(systools.ErrorBySystem) 674 } 675 } 676 677 // just the abort flag. 678 abort := false 679 680 // experimental usage of needs 681 682 // -- NEEDS 683 // needs are task, the have to be startet once 684 // before we continue. 685 // any need can have his own needs they needs to 686 // be executed 687 if len(script.Needs) > 0 { 688 689 CtxOut(messageCmdCtrl, LabelFY("target"), ValF(target), InfoF("require"), ValF(len(script.Needs)), InfoF("needs. async?"), ValF(runAsync)) 690 GetLogger().WithField("needs", script.Needs).Debug("Needs for the script") 691 if runAsync { 692 var needExecs []awaitgroup.FutureStack 693 for _, needTarget := range script.Needs { 694 if TaskRunsAtLeast(needTarget, 1) { 695 CtxOut(messageCmdCtrl, LabelFY("need check"), ValF(target), InfoRed("already executed"), ValF(needTarget)) 696 GetLogger().Debug("need already handled " + needTarget) 697 } else { 698 GetLogger().Debug("need name should be added " + needTarget) 699 CtxOut(messageCmdCtrl, LabelFY("need check"), ValF(target), InfoF("executing"), ValF(needTarget)) 700 needExecs = append(needExecs, awaitgroup.FutureStack{ 701 AwaitFunc: func(ctx context.Context) interface{} { 702 argNeed := ctx.Value(awaitgroup.CtxKey{}).(string) 703 _, argmap := StringSplitArgs(argNeed, "arg") 704 GetLogger().Debug("add need task " + argNeed) 705 return executeTemplate(waitGroup, true, runCfg, argNeed, argmap) 706 }, 707 Argument: needTarget}) 708 } 709 } 710 futures := awaitgroup.ExecFutureGroup(needExecs) // create the futures and start the tasks 711 results := awaitgroup.WaitAtGroup(futures) // wait until any task is executed 712 713 GetLogger().WithField("result", results).Debug("needs result") 714 } else { 715 for _, needTarget := range script.Needs { 716 if TaskRunsAtLeast(needTarget, 1) { // do not run needs the already runs 717 GetLogger().Debug("need already handled " + needTarget) 718 } else { 719 _, argmap := StringSplitArgs(needTarget, "arg") 720 executeTemplate(waitGroup, false, runCfg, needTarget, argmap) 721 } 722 } 723 } 724 CtxOut(LabelFY("target"), ValF(target), InfoF("needs done")) 725 } 726 727 // targets that should be started as well 728 // these targets running at the same time 729 // so different to scope, we dont need to wait 730 // right now until they ends 731 runTargetfutures := generateFuturesByTargetListAndExec(script.RunTargets, waitGroup, runCfg) 732 733 // check if we have script lines. 734 // if not, we need at least to check 735 // 'now' listener 736 if len(script.Script) < 1 { 737 GetLogger().Debug("no script lines defined. run listener anyway") 738 listenerWatch(script, target, "", nil, waitGroup, runAsync, runCfg) 739 // workaround til the async runnig is refactored 740 // now we need to give the subtask time to run and update the waitgroup 741 duration := time.Second 742 time.Sleep(duration) 743 } 744 745 // now we update all the listeners 746 // and start the script 747 if taskUpdate != nil { 748 event.Target = target 749 event.Running = true 750 taskUpdate.SetArguments(event) 751 taskUpdate.Send() 752 } 753 754 // preparing codelines by execute second level commands 755 // that can affect the whole script 756 abort, returnCode, _ = TryParse(script.Script, func(codeLine string) (bool, int) { 757 lineAbort, lineExitCode := lineExecuter(waitGroup, runAsync, stopReason, runCfg, colorCode, bgCode, codeLine, target, scopeVars, script) 758 return lineExitCode, lineAbort 759 }) 760 if abort { 761 GetLogger().Debug("abort reason found ") 762 } 763 // here we update the listeners again, after scripts was running 764 if taskUpdate != nil { 765 event.Running = false 766 event.ExitCode = returnCode 767 event.Finished = true 768 taskUpdate.SetArguments(event) 769 taskUpdate.Send() 770 } 771 772 // waitin until the any target that runns also is done 773 if len(runTargetfutures) > 0 { 774 CtxOut(messageCmdCtrl, LabelFY("wait targets"), "waiting until beside running targets are done") 775 trgtRes := awaitgroup.WaitAtGroup(runTargetfutures) 776 CtxOut(messageCmdCtrl, LabelFY("wait targets"), "waiting done", trgtRes) 777 } 778 // next are tarets they runs afterwards the regular 779 // script os done 780 GetLogger().WithFields(logrus.Fields{ 781 "current-target": target, 782 "nexts": script.Next, 783 }).Debug("executeTemplate next definition") 784 785 nextfutures := generateFuturesByTargetListAndExec(script.Next, waitGroup, runCfg) 786 nextRes := awaitgroup.WaitAtGroup(nextfutures) 787 CtxOut(messageCmdCtrl, LabelFY("wait next"), "waiting done", nextRes) 788 789 //return returnCode 790 // back to old dir if workpace usage was set 791 if backToDir != "" { 792 os.Chdir(backToDir) 793 } 794 } 795 796 } 797 // we have at least none of the possible task executed. 798 if !targetFound { 799 CtxOut(manout.MessageCln(manout.ForeYellow, "target not defined or matching any requirement: ", manout.ForeWhite, target)) 800 GetLogger().Error("Target can not be found: ", target) 801 return systools.ExitByNoTargetExists 802 } 803 804 GetLogger().WithFields(logrus.Fields{ 805 "target": target, 806 }).Info("executeTemplate. target do not contains tasks") 807 return returnCode 808 } 809 return systools.ExitNoCode 810 } 811 812 func defaultString(line string, defaultString string) string { 813 if line == "" { 814 return defaultString 815 } 816 return line 817 } 818 819 func handleFileImportsToVars(imports []string) { 820 for _, filenameFull := range imports { 821 var keyname string 822 parts := strings.Split(filenameFull, " ") 823 filename := parts[0] 824 if len(parts) > 1 { 825 keyname = parts[1] 826 } 827 828 dirhandle.FileTypeHandler(filename, func(jsonBaseName string) { 829 GetLogger().Debug("loading json File as second level variables:", filename) 830 if keyname == "" { 831 keyname = jsonBaseName 832 } 833 if err := ImportDataFromJSONFile(keyname, filename); err != nil { 834 GetLogger().Error("error while loading import: ", filename) 835 manout.Error("error loading json file base import:", filename, " ", err) 836 systools.Exit(systools.ErrorOnConfigImport) 837 } 838 839 }, func(yamlBaseName string) { 840 GetLogger().Debug("loading yaml File: as second level variables", filename) 841 if keyname == "" { 842 keyname = yamlBaseName 843 } 844 if err := ImportDataFromYAMLFile(keyname, filename); err != nil { 845 GetLogger().Error("error while loading import", filename) 846 manout.Error("error loading yaml based import:", filename, " ", err) 847 systools.Exit(systools.ErrorOnConfigImport) 848 } 849 }, func(filenameBase string, ext string) { 850 if keyname == "" { 851 keyname = filename 852 } 853 GetLogger().Debug("loading File: as plain named variable", filename, ext) 854 855 if str, err := ParseFileAsTemplate(filename); err != nil { 856 GetLogger().Error("error while loading import", filename) 857 manout.Error("error loading text file import:", filename, " ", err) 858 systools.Exit(systools.ErrorOnConfigImport) 859 } else { 860 SetPH(keyname, str) 861 } 862 863 }, func(path string, err error) { 864 GetLogger().Errorln("file not exists:", err) 865 manout.Error("varibales file not exists:", path, err) 866 systools.Exit(1) 867 }) 868 } 869 }