github.com/upcmd/up@v0.8.1-0.20230108151705-ad8b797bf04f/biz/impl/step.go (about)

     1  // Ultimate Provisioner: UP cmd
     2  // Copyright (c) 2019 Stephen Cheng and contributors
     3  
     4  /* This Source Code Form is subject to the terms of the Mozilla Public
     5   * License, v. 2.0. If a copy of the MPL was not distributed with this
     6   * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
     7  
     8  package impl
     9  
    10  import (
    11  	"github.com/fatih/color"
    12  	"github.com/imdario/mergo"
    13  	ms "github.com/mitchellh/mapstructure"
    14  	"github.com/mohae/deepcopy"
    15  	"github.com/upcmd/up/biz"
    16  	"github.com/upcmd/up/model/core"
    17  	u "github.com/upcmd/up/utils"
    18  	ee "github.com/upcmd/up/utils/error"
    19  	"github.com/xlab/treeprint"
    20  	"os"
    21  	"path"
    22  	"reflect"
    23  	"strconv"
    24  	"strings"
    25  )
    26  
    27  type Step struct {
    28  	Name     string
    29  	Do       interface{} //FuncImpl
    30  	Dox      interface{}
    31  	Func     string
    32  	Vars     core.Cache
    33  	Dvars    Dvars
    34  	Desc     string
    35  	Reg      string
    36  	Flags    []string
    37  	If       string
    38  	Else     interface{}
    39  	Loop     interface{}
    40  	Until    string
    41  	RefDir   string
    42  	VarsFile string
    43  	Timeout  int //milli seconds, only for shell func
    44  	Finally  interface{}
    45  	Rescue   bool
    46  }
    47  
    48  type Steps []Step
    49  
    50  /*
    51  ExecbaseVars is the the scope containing passed in caller vars
    52  local vars will be merged depending if it is a callee task
    53  merge taskvars before final merge as task vars will be the calculated result of current stack
    54  */
    55  func (step *Step) getRuntimeExecVars(fromBlock bool) *core.Cache {
    56  	var execvars core.Cache
    57  	var resultVars *core.Cache
    58  
    59  	if u.Contains(step.Flags, "pure") {
    60  		execvars = *core.NewCache()
    61  	} else {
    62  		execvars = deepcopy.Copy(*TaskRuntime().ExecbaseVars).(core.Cache)
    63  	}
    64  
    65  	mergo.Merge(&execvars, UpRunTimeVars, mergo.WithOverride)
    66  
    67  	taskVars := TaskRuntime().TaskVars
    68  	mergo.Merge(&execvars, taskVars, mergo.WithOverride)
    69  
    70  	if step.VarsFile != "" {
    71  		refdir := ConfigRuntime().RefDir
    72  		if step.RefDir != "" {
    73  			raw := step.RefDir
    74  			refdir = Render(raw, execvars)
    75  		}
    76  
    77  		var varsfile string
    78  		if step.VarsFile != "" {
    79  			raw := step.VarsFile
    80  			varsfile = Render(raw, execvars)
    81  		}
    82  
    83  		filepath := path.Join(refdir, varsfile)
    84  		if _, err := os.Stat(filepath); !os.IsNotExist(err) {
    85  			yamlvarsroot := u.YamlLoader("varsfile", refdir, varsfile)
    86  			filevars := loadRefVars(yamlvarsroot)
    87  			mergo.Merge(filevars, &step.Vars, mergo.WithOverride)
    88  			step.Vars = *filevars
    89  		} else {
    90  			u.LogWarn("varsfile is not loaded, ignored", u.Spf("%s does not exist", filepath))
    91  		}
    92  	}
    93  
    94  	if fromBlock {
    95  		blockvars := BlockStack().GetTop().(*BlockRuntimeContext).BlockBaseVars
    96  		mergo.Merge(&execvars, blockvars, mergo.WithOverride)
    97  		mergo.Merge(&execvars, &step.Vars, mergo.WithOverride)
    98  		resultVars = &execvars
    99  	} else {
   100  		if IsCalledTask() {
   101  			//u.Ptmpdebug("if", "if")
   102  			if step.Vars != nil {
   103  				mergo.Merge(&step.Vars, &execvars, mergo.WithOverride)
   104  				resultVars = &step.Vars
   105  			} else {
   106  				resultVars = &execvars
   107  			}
   108  		} else {
   109  			//u.Ptmpdebug("else", "else")
   110  			mergo.Merge(&execvars, &step.Vars, mergo.WithOverride)
   111  			resultVars = &execvars
   112  		}
   113  	}
   114  	u.Pfvvvv("current exec runtime vars:")
   115  	u.Ppmsgvvvv(secureCache(resultVars))
   116  
   117  	StepRuntime().ContextVars = resultVars
   118  	//so far the execvars includes: scope vars + scope dvars + global runtime vars + task vars
   119  	varsWithDvars := VarsMergedWithDvars("local", resultVars, &step.Dvars, resultVars)
   120  
   121  	//the processed varsWithDvars must merge with result vars: from varsWithDvars to resultVars
   122  	//care taken to register new vars to dvar processing phase
   123  	mergo.Merge(resultVars, varsWithDvars, mergo.WithOverride)
   124  
   125  	//so far the resultVars includes: the local vars + dvars rendered using execvars
   126  	u.Ppmsgvvvhint(u.Spf("%s: final context exec vars:", ConfigRuntime().ModuleName), secureCache(resultVars))
   127  	//debugVars()
   128  	return resultVars
   129  }
   130  
   131  type LoopItem struct {
   132  	Index  int
   133  	Index1 int
   134  	Item   interface{}
   135  }
   136  
   137  func chainAction(action *biz.Do) {
   138  	(*action).Adapt()
   139  	(*action).Exec()
   140  }
   141  
   142  func validation(vars *core.Cache) {
   143  	identified := false
   144  
   145  	for k, _ := range *vars {
   146  		if k == "" {
   147  			u.InvalidAndPanic("validating var name", "var name can not be empty")
   148  		}
   149  		if u.CharIsNum(k[0:1]) != -1 {
   150  			identified = true
   151  			u.InvalidAndPanic("validating var name", u.Spf("var name (%s) can not start with number", k))
   152  		}
   153  	}
   154  
   155  	if identified {
   156  		u.InvalidAndPanic("vars validation", "please fix all validation before continue")
   157  	}
   158  }
   159  
   160  func (step *Step) Exec(fromBlock bool) {
   161  	var action biz.Do
   162  	u.StepPanicCount += 1
   163  	defer func() {
   164  		if step.Finally != nil && step.Finally != "" {
   165  			u.PlnBlue("Step Finally:")
   166  			u.Ppmsgvvvvv(StepRuntime().Result)
   167  		}
   168  
   169  		paniced := false
   170  		if step.Vars == nil {
   171  			step.Vars = *core.NewCache()
   172  		}
   173  
   174  		step.Vars.Put(UP_RUNTIME_SHELL_EXEC_RESULT, StepRuntime().Result)
   175  		//debugVars()
   176  		var panicInfo interface{}
   177  		if r := recover(); r != nil {
   178  			u.PlnBlue(u.Spf("Recovered from: %s", r))
   179  			paniced = true
   180  			panicInfo = r
   181  		}
   182  
   183  		if step.Finally != nil && step.Finally != "" {
   184  			execFinally(step.Finally, &step.Vars)
   185  		}
   186  
   187  		step.Vars.Delete(UP_RUNTIME_SHELL_EXEC_RESULT)
   188  
   189  		if paniced && step.Rescue == false {
   190  			u.LogWarn("Not rescued in step level", "please assess the panic problem and cause, fix it before re-run the task")
   191  			panic(panicInfo)
   192  		} else if paniced {
   193  			u.LogWarn("Rescued in step level, but not advised!", "setting rescue to yes/true to continue is not recommended\nit is advised to locate root cause of the problem, fix it and re-run the task again\nit is the best practice to test the execution in your ci pipeline to eliminate problems rather than dynamically fix using rescue")
   194  		}
   195  		u.StepPanicCount -= 1
   196  	}()
   197  
   198  	var bizErr *ee.Error = ee.New()
   199  	var stepExecVars *core.Cache
   200  
   201  	stepExecVars = step.getRuntimeExecVars(fromBlock)
   202  	validation(stepExecVars)
   203  
   204  	if step.Flags != nil && u.Contains(step.Flags, "pause") {
   205  		pause(stepExecVars)
   206  	}
   207  
   208  	routeFuncType := func(loopItem *LoopItem) {
   209  		if loopItem != nil {
   210  			stepExecVars.Put("loopitem", loopItem.Item)
   211  			stepExecVars.Put("loopindex", loopItem.Index)
   212  			stepExecVars.Put("loopindex1", loopItem.Index1)
   213  		}
   214  
   215  		switch step.Func {
   216  		case FUNC_SHELL:
   217  			funcAction := ShellFuncAction{
   218  				Do:   step.Do,
   219  				Vars: stepExecVars,
   220  			}
   221  			action = biz.Do(&funcAction)
   222  
   223  		case FUNC_CALL:
   224  			funcAction := CallFuncAction{
   225  				Do:   step.Do,
   226  				Vars: stepExecVars,
   227  			}
   228  			action = biz.Do(&funcAction)
   229  
   230  		case FUNC_BLOCK:
   231  			funcAction := BlockFuncAction{
   232  				Do:   step.Do,
   233  				Vars: stepExecVars,
   234  			}
   235  			action = biz.Do(&funcAction)
   236  
   237  		case FUNC_CMD:
   238  			funcAction := CmdFuncAction{
   239  				Do:   step.Do,
   240  				Vars: stepExecVars,
   241  			}
   242  			action = biz.Do(&funcAction)
   243  
   244  		case "":
   245  			u.InvalidAndPanic("Step dispatch", "func name is empty and not defined")
   246  			bizErr.Mark = "func name not implemented"
   247  
   248  		default:
   249  			u.InvalidAndPanic("Step dispatch", u.Spf("func name(%s) is not recognised and implemented", step.Func))
   250  			bizErr.Mark = "func name not implemented"
   251  		}
   252  	}
   253  
   254  	dryRunOrContinue := func() {
   255  		//example to stop further steps
   256  		//f := u.MustConditionToContinueFunc(func() bool {
   257  		//	return action != nil
   258  		//})
   259  		//
   260  		//u.DryRunOrExit("Step Exec", f, "func name must be valid")
   261  
   262  		alloweErrors := []string{
   263  			"func name not implemented",
   264  		}
   265  
   266  		DryRunAndSkip(
   267  			bizErr.Mark,
   268  			alloweErrors,
   269  			ContinueFunc(
   270  				func() {
   271  					if step.Loop != nil {
   272  						rawUtil := step.Until
   273  						func() {
   274  							//loop points to a var name which is a slice
   275  							if reflect.TypeOf(step.Loop).Kind() == reflect.String {
   276  								loopVarName := Render(step.Loop.(string), stepExecVars)
   277  								loopObj := stepExecVars.Get(loopVarName)
   278  								if loopObj == nil {
   279  									u.InvalidAndPanic("Evaluating loop var and object", u.Spf("Please use a correct varname:(%s) containing a list of values", loopVarName))
   280  								}
   281  								if reflect.TypeOf(loopObj).Kind() == reflect.Slice {
   282  									switch loopObj.(type) {
   283  									case []interface{}:
   284  										for idx, item := range loopObj.([]interface{}) {
   285  											routeFuncType(&LoopItem{idx, idx + 1, item})
   286  											if rawUtil != "" {
   287  												if TaskRuntime().ReturnVars != nil {
   288  													mergo.Merge(stepExecVars, TaskRuntime().ReturnVars, mergo.WithOverride)
   289  												}
   290  												untilEval := Render(rawUtil, stepExecVars)
   291  												toBreak, err := strconv.ParseBool(untilEval)
   292  												u.LogErrorAndPanic("evaluate until condition", err, u.Spf("please fix until condition evaluation: [%s]", untilEval))
   293  												if toBreak {
   294  													u.Pvvvv("loop util conditional break")
   295  													break
   296  												} else {
   297  													chainAction(&action)
   298  												}
   299  											} else {
   300  												chainAction(&action)
   301  											}
   302  										}
   303  
   304  									case []string:
   305  										for idx, item := range loopObj.([]string) {
   306  											routeFuncType(&LoopItem{idx, idx + 1, item})
   307  											if rawUtil != "" {
   308  												if TaskRuntime().ReturnVars != nil {
   309  													mergo.Merge(stepExecVars, TaskRuntime().ReturnVars, mergo.WithOverride)
   310  												}
   311  												untilEval := Render(rawUtil, stepExecVars)
   312  												toBreak, err := strconv.ParseBool(untilEval)
   313  												u.LogErrorAndPanic("evaluate until condition", err, u.Spf("please fix until condition evaluation: [%s]", untilEval))
   314  												if toBreak {
   315  													u.Pvvvv("loop util conditional break")
   316  													break
   317  												} else {
   318  													chainAction(&action)
   319  												}
   320  											} else {
   321  												chainAction(&action)
   322  											}
   323  										}
   324  
   325  									case []int64:
   326  										for idx, item := range loopObj.([]int64) {
   327  											routeFuncType(&LoopItem{idx, idx + 1, item})
   328  											if rawUtil != "" {
   329  												if TaskRuntime().ReturnVars != nil {
   330  													mergo.Merge(stepExecVars, TaskRuntime().ReturnVars, mergo.WithOverride)
   331  												}
   332  												untilEval := Render(rawUtil, stepExecVars)
   333  												toBreak, err := strconv.ParseBool(untilEval)
   334  												u.LogErrorAndPanic("evaluate until condition", err, u.Spf("please fix until condition evaluation: [%s]", untilEval))
   335  												if toBreak {
   336  													u.Pvvvv("loop util conditional break")
   337  													break
   338  												} else {
   339  													chainAction(&action)
   340  												}
   341  											} else {
   342  												chainAction(&action)
   343  											}
   344  										}
   345  
   346  									default:
   347  										u.LogWarn("loop item evaluation", "Loop item type is not supported yet!")
   348  										u.LogWarn("supported:", "[]interface, []string, []int64")
   349  										u.LogWarn("got:", u.Spf("%T\n", loopObj))
   350  									}
   351  								} else {
   352  									u.InvalidAndPanic("evaluate loop var", "loop var is not a array/list/slice")
   353  								}
   354  							} else if reflect.TypeOf(step.Loop).Kind() == reflect.Slice {
   355  								//loop itself is a slice
   356  								for idx, item := range step.Loop.([]interface{}) {
   357  									routeFuncType(&LoopItem{idx, idx + 1, item})
   358  									if rawUtil != "" {
   359  										//make the return value could be evaluated in until
   360  										if TaskRuntime().ReturnVars != nil {
   361  											mergo.Merge(stepExecVars, TaskRuntime().ReturnVars, mergo.WithOverride)
   362  										}
   363  										untilEval := Render(rawUtil, stepExecVars)
   364  										toBreak, err := strconv.ParseBool(untilEval)
   365  										u.LogErrorAndPanic("evaluate until condition", err, u.Spf("please fix until condition evaluation: [%s]", untilEval))
   366  										if toBreak {
   367  											u.Pvvvv("loop util conditional break")
   368  											break
   369  										} else {
   370  											chainAction(&action)
   371  										}
   372  									} else {
   373  										chainAction(&action)
   374  									}
   375  								}
   376  							} else {
   377  								u.InvalidAndPanic("evaluate loop items", "please either use a list or a template evaluation which could result in a value of a list")
   378  							}
   379  						}()
   380  
   381  					} else {
   382  						routeFuncType(nil)
   383  						chainAction(&action)
   384  					}
   385  
   386  				}),
   387  			nil,
   388  		)
   389  	}
   390  
   391  	func() {
   392  		if step.If != NONE_VALUE && step.If != "" {
   393  			IfEval := Render(step.If, stepExecVars)
   394  			if IfEval != NONE_VALUE {
   395  				goahead, err := strconv.ParseBool(IfEval)
   396  				u.LogErrorAndPanic("evaluate condition", err, u.Spf("please fix if condition evaluation: [%s]", IfEval))
   397  				if goahead {
   398  					dryRunOrContinue()
   399  				} else {
   400  					if step.Else != nil && step.Else != "" {
   401  						doElse(step.Else, stepExecVars)
   402  					} else {
   403  						u.Pvvv("condition failed, skip executing step", step.Name)
   404  					}
   405  				}
   406  			} else {
   407  				u.Pvvv("condition failed, skip executing step", step.Name)
   408  			}
   409  		} else {
   410  			dryRunOrContinue()
   411  		}
   412  
   413  	}()
   414  
   415  }
   416  
   417  func doElse(elseCalls interface{}, execVars *core.Cache) {
   418  	var taskname string
   419  	var tasknames []string
   420  	var flow Steps
   421  
   422  	switch elseCalls.(type) {
   423  	case string:
   424  		taskname = elseCalls.(string)
   425  		tasknames = append(tasknames, taskname)
   426  
   427  	case []interface{}:
   428  		elseStr := u.Spf("%s", elseCalls)
   429  		if strings.Index(elseStr, "map") != -1 && strings.Index(elseStr, "func:") != -1 {
   430  			err := ms.Decode(elseCalls, &flow)
   431  			u.LogErrorAndPanic("load steps in else", err, "steps has configuration problem, please fix it")
   432  			BlockFlowRun(&flow, execVars)
   433  		} else {
   434  			err := ms.Decode(elseCalls, &tasknames)
   435  			u.LogErrorAndPanic("call func alias: else", err, "please ref to a task name only")
   436  		}
   437  
   438  	default:
   439  		u.LogWarn("else ..", "Not implemented or void for no action!")
   440  	}
   441  
   442  	if len(tasknames) > 0 {
   443  		for _, tmptaskname := range tasknames {
   444  			taskname := Render(tmptaskname, execVars)
   445  			u.PpmsgvvvvvhintHigh(u.Spf("else caller vars to task (%s):", taskname), execVars)
   446  			ExecTask(taskname, execVars)
   447  		}
   448  	}
   449  
   450  }
   451  
   452  func execFinally(finally interface{}, execVars *core.Cache) {
   453  	var taskname string
   454  	var tasknames []string
   455  	var flow Steps
   456  	switch finally.(type) {
   457  	case string:
   458  		taskname = finally.(string)
   459  		tasknames = append(tasknames, taskname)
   460  
   461  	case []interface{}:
   462  		elseStr := u.Spf("%s", finally)
   463  		if strings.Index(elseStr, "map") != -1 && strings.Index(elseStr, "func:") != -1 {
   464  			err := ms.Decode(finally, &flow)
   465  			u.LogErrorAndPanic("load steps in finally", err, "steps/flow has configuration problem, please fix it")
   466  			BlockFlowRun(&flow, execVars)
   467  		} else {
   468  			err := ms.Decode(finally, &tasknames)
   469  			u.LogErrorAndPanic("load task names in finally", err, "please ref to a task name only")
   470  		}
   471  
   472  	default:
   473  		u.LogWarn("finally ..", "Not implemented or void for no action!")
   474  	}
   475  
   476  	if len(tasknames) > 0 {
   477  		for _, tmptaskname := range tasknames {
   478  			taskname := Render(tmptaskname, execVars)
   479  			u.PpmsgvvvvvhintHigh(u.Spf("finally caller vars to task (%s):", taskname), execVars)
   480  			ExecTask(taskname, execVars)
   481  		}
   482  	}
   483  
   484  }
   485  
   486  func (steps *Steps) InspectSteps(tree treeprint.Tree, level *int) bool {
   487  	for _, step := range *steps {
   488  		descRaw := strings.Split(step.Desc, "\n")[0]
   489  		desc := Render(descRaw, TaskerRuntime().Tasker.RuntimeVarsAndDvarsMerged)
   490  		if step.Func == FUNC_CALL {
   491  			branch := tree.AddMetaBranch(func() string {
   492  				if step.Loop != "" {
   493  					return step.Name + color.HiYellowString("%s", " /call.")
   494  				} else {
   495  					return step.Name
   496  				}
   497  			}(), desc)
   498  			var callee string
   499  			switch t := step.Do.(type) {
   500  			case string:
   501  				callee = step.Do.(string)
   502  				if !TaskerRuntime().Tasker.InspectTask(callee, branch, level) {
   503  					break
   504  				}
   505  				*level -= 1
   506  			case []interface{}:
   507  				calleeTasknames := step.Do.([]interface{})
   508  				breakFlag := false
   509  				for _, x := range calleeTasknames {
   510  					callee = x.(string)
   511  					if !TaskerRuntime().Tasker.InspectTask(callee, branch, level) {
   512  						breakFlag = true
   513  						break
   514  					}
   515  					*level -= 1
   516  				}
   517  				if breakFlag {
   518  					break
   519  				}
   520  			default:
   521  				u.Pf("type: %T", t)
   522  			}
   523  
   524  		} else if step.Func == FUNC_BLOCK {
   525  			branch := tree.AddMetaBranch(func() string {
   526  				if step.Loop != "" {
   527  					return step.Name + color.HiYellowString("%s", " /block.")
   528  				} else {
   529  					return step.Name
   530  				}
   531  			}(), desc)
   532  
   533  			switch t := step.Do.(type) {
   534  			case string:
   535  				rawFlowname := step.Do.(string)
   536  				tree.AddNode(u.Spf("%s %s", color.HiYellowString("%s", " ..flow ->"), rawFlowname))
   537  
   538  			case []interface{}:
   539  				//detailed steps
   540  				var steps Steps
   541  				err := ms.Decode(step.Do, &steps)
   542  				u.LogErrorAndPanic("load steps", err, "configuration problem, please fix it")
   543  				steps.InspectSteps(branch, level)
   544  
   545  			default:
   546  				u.Pf("type: %T", t)
   547  			}
   548  
   549  		} else {
   550  			tree.AddNode(u.Spf("%s: %s", step.Name, desc))
   551  		}
   552  	}
   553  	return true
   554  }
   555  
   556  func (steps *Steps) Exec(fromBlock bool) {
   557  	if fromBlock {
   558  		if TaskRuntime() != nil && TaskRuntime().ExecbaseVars != nil {
   559  			TaskRuntime().ExecbaseVars.Delete(LAST_RESULT)
   560  		}
   561  		if StepRuntime() != nil && StepRuntime().ContextVars != nil {
   562  			StepRuntime().ContextVars.Delete(LAST_RESULT)
   563  		}
   564  	}
   565  
   566  	for idx, step := range *steps {
   567  		taskLayerCnt := TaskerRuntime().Tasker.TaskStack.GetLen()
   568  		desc := Render(step.Desc, TaskRuntime().ExecbaseVars)
   569  		u.LogDesc("step", idx+1, taskLayerCnt, step.Name, desc)
   570  		u.Ppmsgvvvvv(step)
   571  
   572  		execStep := func() {
   573  			rtContext := StepRuntimeContext{
   574  				Stepname: step.Name,
   575  				Timeout:  step.Timeout,
   576  				Flags:    &step.Flags,
   577  			}
   578  			StepStack().Push(&rtContext)
   579  
   580  			if step.Do == nil && step.Dox != nil {
   581  				u.LogWarn("*", "Step is deactivated!")
   582  			} else {
   583  				step.Exec(fromBlock)
   584  			}
   585  
   586  			result := StepRuntime().Result
   587  			taskname := TaskRuntime().Taskname
   588  
   589  			if u.Contains([]string{FUNC_SHELL, FUNC_CALL}, step.Func) {
   590  				if step.Reg == "auto" {
   591  					if step.Name == "" {
   592  						TaskRuntime().ExecbaseVars.Put(u.Spf("%s_%d_result", taskname, idx), result)
   593  					} else {
   594  						TaskRuntime().ExecbaseVars.Put(u.Spf("%s_%s_result", taskname, step.Name), result)
   595  					}
   596  				} else if step.Reg != "" {
   597  					TaskRuntime().ExecbaseVars.Put(u.Spf("%s", step.Reg), result)
   598  				}
   599  				if step.Func == FUNC_SHELL {
   600  					TaskRuntime().ExecbaseVars.Put(LAST_RESULT, result)
   601  				}
   602  			}
   603  
   604  			func() {
   605  				result := StepRuntime().Result
   606  
   607  				if result != nil && result.Code == 0 {
   608  					u.LogOk(".")
   609  				}
   610  
   611  				if !u.Contains(step.Flags, "ignoreError") {
   612  					if result != nil && result.Code != 0 {
   613  						u.InvalidAndPanic("Failed And Not Ignored!", "You may want to continue and ignore the error")
   614  					}
   615  				} else {
   616  					if result != nil && result.Code != 0 {
   617  						u.LogWarn("ignoreError:", "Error ignored!!!")
   618  					}
   619  				}
   620  
   621  			}()
   622  
   623  			StepStack().Pop()
   624  		}
   625  
   626  		if !TaskerRuntime().Tasker.TaskBreak {
   627  			execStep()
   628  		} else {
   629  			TaskerRuntime().Tasker.TaskBreak = false
   630  			u.LogWarn("break", "client chose to break")
   631  			break
   632  		}
   633  
   634  	}
   635  
   636  }