github.com/upcmd/up@v0.8.1-0.20230108151705-ad8b797bf04f/biz/impl/tasker.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 "bufio" 12 "bytes" 13 "github.com/fatih/color" 14 "github.com/imdario/mergo" 15 ms "github.com/mitchellh/mapstructure" 16 "github.com/mohae/deepcopy" 17 "github.com/spf13/viper" 18 "github.com/upcmd/up/model" 19 "github.com/upcmd/up/model/core" 20 "github.com/upcmd/up/model/stack" 21 u "github.com/upcmd/up/utils" 22 "github.com/xlab/treeprint" 23 "io/ioutil" 24 "os" 25 "os/exec" 26 "path" 27 "path/filepath" 28 "strconv" 29 "strings" 30 ) 31 32 func InitDefaultSkeleton() { 33 filepath := path.Join(".", "upconfig.yml") 34 ioutil.WriteFile(filepath, []byte(u.DEFAULT_CONFIG), 0644) 35 filepath = path.Join(".", "up.yml") 36 ioutil.WriteFile(filepath, []byte(u.DEFAULT_UP_TASK_YML), 0644) 37 } 38 39 type Tasker struct { 40 TaskYmlRoot *viper.Viper 41 Tasks *model.Tasks 42 InstanceName string 43 ExecProfilename string 44 Dryrun bool 45 TaskStack *stack.ExecStack 46 StepStack *stack.ExecStack 47 BlockStack *stack.ExecStack 48 FinallyStack *stack.ExecStack 49 TaskBreak bool 50 Config *u.UpConfig 51 GroupMembersList []string 52 MemberGroupMap map[string]string 53 //expanded context only contains group and global scope, but not each instance vars 54 ExpandedContext ScopeContext 55 ScopeProfiles *Scopes 56 ExecProfiles *ExecProfiles 57 //this is the merged vars from within scope: global, groups level (if there is), instance varss, then global runtime vars 58 RuntimeVarsMerged *core.Cache 59 ExecProfileEnvVars *core.Cache 60 SecretVars *core.Cache 61 //this is the merged vars and dvars to a vars cache from within scope: global, groups level (if there is), instance varss, then global runtime vars 62 //this vars should be used instead of RuntimeVarsMerged as it include both runtime vars and dvars except the local vars and dvars 63 RuntimeVarsAndDvarsMerged *core.Cache 64 RuntimeGlobalVars *core.Cache 65 RuntimeGlobalDvars *Dvars 66 InFinalExec bool 67 } 68 69 type ScopeContext map[string]*core.Cache 70 type ContextInstances []ScopeContext 71 72 type TaskerRuntimeContext struct { 73 Tasker *Tasker 74 TaskerCaller *Tasker 75 } 76 77 func NewTasker(instanceId string, eprofiename string, cfg *u.UpConfig) *Tasker { 78 priorityLoadingTaskFile := filepath.Join(".", cfg.TaskFile) 79 refDir := "." 80 if _, err := os.Stat(priorityLoadingTaskFile); err != nil { 81 refDir = cfg.RefDir 82 } 83 84 taskYmlRoot := u.YamlLoader("Task", refDir, cfg.TaskFile) 85 tasker := &Tasker{ 86 TaskYmlRoot: taskYmlRoot, 87 MemberGroupMap: map[string]string{}, 88 GroupMembersList: []string{}, 89 ExpandedContext: ScopeContext{}, 90 } 91 tasker.Config = cfg 92 tasker.initRuntime() 93 94 taskerContext := TaskerRuntimeContext{ 95 Tasker: tasker, 96 } 97 98 TaskerStack.Push(&taskerContext) 99 100 tasker.initSecureVault() 101 tasker.loadPureEnv() 102 tasker.loadExecProfiles() 103 tasker.setInstanceName(instanceId, eprofiename) 104 tasker.loadExecProfileEnvVars() 105 tasker.loadScopes() 106 tasker.loadInstancesContext() 107 tasker.loadRuntimeGlobalVars() 108 tasker.loadRuntimeGlobalDvars() 109 tasker.MergeUptoRuntimeGlobalVars() 110 tasker.MergeRuntimeGlobalDvars() 111 tasker.loadTasks() 112 tasker.validateTasks() 113 return tasker 114 } 115 116 /* 117 Get the merged vars for specific scope instance 118 Validate the scopes 119 1. for the scope name equal to global, there should be no value for members, otherwise errors 120 2. for the scope with group members, it is a group itself 121 3. for the scope with no members and name is not global, it is a final instance 122 */ 123 124 func (t *Tasker) loadInstancesContext() { 125 ss := t.ScopeProfiles 126 //validation 127 for idx, s := range *ss { 128 129 if s.Ref != "" && s.Vars != nil { 130 u.Dvvvvv(s) 131 u.InvalidAndPanic("verify scope ref and member coexistence", "ref and members can not both exist") 132 } 133 refdir := ConfigRuntime().RefDir 134 if s.Ref != "" { 135 if s.RefDir != "" { 136 refdir = s.RefDir 137 } 138 yamlvarsroot := u.YamlLoader("ref vars", refdir, s.Ref) 139 vars := *loadRefVars(yamlvarsroot) 140 u.Pvvvv("loading vars from:", s.Ref) 141 u.Ppmsgvvvv(vars) 142 (*ss)[idx].Vars = vars 143 } 144 145 } 146 147 u.Pvvvvv("-------full vars in scopes------") 148 u.Dvvvvv(ss) 149 150 var globalScope *Scope 151 for idx, s := range *ss { 152 if s.Name == "global" { 153 if s.Members != nil { 154 u.InvalidAndPanic("scope expand", "global scope should not contains members") 155 } 156 globalScope = &(*ss)[idx] 157 } 158 } 159 160 //expand dvars into global scope's vars space 161 var globalvarsMergedWithDvars *core.Cache 162 if globalScope != nil { 163 globalvarsMergedWithDvars = GlobalVarsMergedWithDvars(globalScope) 164 } else { 165 globalvarsMergedWithDvars = core.NewCache() 166 } 167 168 for idx, s := range *ss { 169 if s.Members != nil { 170 for _, m := range s.Members { 171 if u.Contains(t.GroupMembersList, m) { 172 u.InvalidAndPanic("scope expand", u.Spfv("duplicated member: %s\n", m)) 173 } 174 t.GroupMembersList = append(t.GroupMembersList, m) 175 t.MemberGroupMap[m] = s.Name 176 } 177 178 var groupvars core.Cache = deepcopy.Copy(*globalvarsMergedWithDvars).(core.Cache) 179 mergo.Merge(&groupvars, s.Vars, mergo.WithOverride) 180 181 //expand dvars into group scope's vars space 182 groupScope := &(*ss)[idx] 183 var groupvarsMergedWithDvars *core.Cache = ScopeVarsMergedWithDvars(groupScope, &groupvars) 184 185 t.ExpandedContext[s.Name] = groupvarsMergedWithDvars 186 } 187 } 188 189 t.ExpandedContext["global"] = globalvarsMergedWithDvars 190 func() { 191 u.Pvvvv("---------group vars----------") 192 for k, v := range t.ExpandedContext { 193 u.Pfvvvv("%s: %s", k, u.Sppmsg(secureCache(v))) 194 } 195 u.Pfvvvv("groups members:%s\n", t.GroupMembersList) 196 197 }() 198 199 } 200 201 func (t *Tasker) MergeRuntimeGlobalDvars() { 202 var mergedVars core.Cache 203 mergedVars = deepcopy.Copy(*t.RuntimeVarsMerged).(core.Cache) 204 205 expandedVars := t.RuntimeGlobalDvars.Expand("runtime global", t.RuntimeVarsMerged) 206 207 if t.RuntimeGlobalDvars != nil { 208 mergo.Merge(&mergedVars, *expandedVars, mergo.WithOverride) 209 } 210 211 t.RuntimeVarsAndDvarsMerged = &mergedVars 212 u.Ppmsgvvvvhint("-------runtime global final merged with dvars-------", secureCache(&mergedVars)) 213 } 214 215 func (t *Tasker) loadExecProfileEnvVars() { 216 var envVars *core.Cache = core.NewCache() 217 var evars *EnvVars 218 if p := t.getExecProfile(t.ExecProfilename); p != nil { 219 220 if p.Ref != "" && p.Evars != nil { 221 u.InvalidAndPanic("exec proile validation", "You can only setup either ref file to load the env vars or use evars tag to config env vars, but not both") 222 } 223 224 refdir := ConfigRuntime().RefDir 225 226 if p.Ref != "" { 227 if p.RefDir != "" { 228 refdir = p.RefDir 229 } 230 yamlevarsroot := u.YamlLoader("ref evars", refdir, p.Ref) 231 evars = loadRefEvars(yamlevarsroot) 232 u.Pvvvv("loading vars from:", p.Ref) 233 u.Ppmsgvvvv(evars) 234 } 235 236 if p.Evars != nil { 237 evars = &p.Evars 238 } 239 240 if p.Taskname != "" { 241 u.MainConfig.EntryTask = p.Taskname 242 } 243 244 alternativeEntryTaskname := os.Getenv(UP_ENTRY_TASK_NAME) 245 if alternativeEntryTaskname != "" { 246 u.PlnBlue(u.Spf("entry task: %s", alternativeEntryTaskname)) 247 u.MainConfig.EntryTask = alternativeEntryTaskname 248 } 249 250 if p.Pure { 251 pureEnv() 252 } 253 254 if p.Verbose != "" { 255 u.MainConfig.Verbose = p.Verbose 256 } 257 258 if evars != nil { 259 for _, v := range *evars { 260 envvarName := u.Spf("%s_%s", "envVar", v.Name) 261 envVars.Put(envvarName, v.Value) 262 os.Setenv(v.Name, v.Value) 263 } 264 } 265 } 266 267 t.ExecProfileEnvVars = envVars 268 u.Ppmsgvvvhint(u.Spf("profile - %s envVars:", t.ExecProfilename), envVars) 269 } 270 271 func pureEnv() { 272 sourceContent := ` 273 set -e 274 echo '<<<ENVIRONMENT>>>' 275 env 276 ` 277 278 cmd := exec.Command(u.MainConfig.ShellType, "-c", sourceContent) 279 bs, err := cmd.CombinedOutput() 280 if err != nil { 281 u.LogErrorAndExit("set pure env", err, "something is wrong obtaining system env vars") 282 } 283 venv := func() model.Venv { 284 s := bufio.NewScanner(bytes.NewReader(bs)) 285 start := false 286 output := bytes.NewBufferString("") 287 venv := model.Venv{} 288 for s.Scan() { 289 if s.Text() == "<<<ENVIRONMENT>>>" { 290 start = true 291 } else if start { 292 kv := strings.SplitN(s.Text(), "=", 2) 293 if len(kv) == 2 { 294 k := kv[0] 295 v := kv[1] 296 os.Setenv(k, v) 297 venv = append(venv, model.Env{ 298 Name: k, 299 Value: v, 300 }) 301 } 302 } else if !start { 303 output.WriteString(s.Text() + "\n") 304 } 305 } 306 return venv 307 }() 308 309 for _, x := range venv { 310 os.Unsetenv(x.Name) 311 } 312 u.PlnInfoHighlight("-set pure env context done.") 313 } 314 315 //clear up everything in scope and cache 316 func (t *Tasker) Unset() { 317 t.ExpandedContext = ScopeContext{} 318 t.GroupMembersList = []string{} 319 t.MemberGroupMap = map[string]string{} 320 t.ScopeProfiles = nil 321 t.RuntimeVarsMerged = nil 322 t.RuntimeVarsAndDvarsMerged = nil 323 t.RuntimeGlobalVars = nil 324 t.RuntimeGlobalDvars = nil 325 TaskerStack = stack.New("tasker") 326 } 327 328 /* 329 This will generate a one off vars merged from top level down to runtime 330 global and merge them all together,the result vars will be used to finally 331 merge with local func vars to be used in runtime execution time 332 333 pass in runtime id, if runtime id is in member list, eg dev -> nonprod 334 then merge runtimevars to group(nonprod)'s varss, 335 336 if runtime id (nonname) is not in member list, 337 then merge runtimevars to global varss, 338 339 This has chained dvar expansion through global to group then to instance level 340 and finally merge with global var, except the global dvars 341 */ 342 func (t *Tasker) MergeUptoRuntimeGlobalVars() { 343 344 var runtimevars core.Cache 345 runtimevars = deepcopy.Copy(*t.ExpandedContext["global"]).(core.Cache) 346 347 if u.Contains(t.GroupMembersList, t.InstanceName) { 348 groupname := t.MemberGroupMap[t.InstanceName] 349 //TODO: t.ExpandedContext[groupname] should have already merge to global vars, double check to confirm 350 mergo.Merge(&runtimevars, *t.ExpandedContext[groupname], mergo.WithOverride) 351 instanceVars := t.ScopeProfiles.GetInstanceVars(t.InstanceName) 352 if instanceVars != nil { 353 mergo.Merge(&runtimevars, instanceVars, mergo.WithOverride) 354 } 355 } 356 357 //merge dvars for the instance 358 //TODO: is this a duplication of: GetInstanceVars above? 359 var instanceScope *Scope 360 for idx, s := range *t.ScopeProfiles { 361 if s.Name == t.InstanceName { 362 instanceScope = &(*t.ScopeProfiles)[idx] 363 } 364 } 365 366 var instanceVarsMergedWithDvars *core.Cache 367 if instanceScope != nil { 368 instanceVarsMergedWithDvars = VarsMergedWithDvars(instanceScope.Name, &instanceScope.Vars, &instanceScope.Dvars, &runtimevars) 369 //merge back the expanded merged scope vars and dvars 370 mergo.Merge(&runtimevars, *instanceVarsMergedWithDvars, mergo.WithOverride) 371 } 372 373 //merge with global vars 374 mergo.Merge(&runtimevars, *t.RuntimeGlobalVars, mergo.WithOverride) 375 376 u.Pfvvvv("merged[ %s ] runtime vars:", t.InstanceName) 377 u.Ppmsgvvvv(secureCache(&runtimevars)) 378 u.Dvvvvv(secureCache(&runtimevars)) 379 380 t.RuntimeVarsMerged = &runtimevars 381 } 382 383 func (t *Tasker) setInstanceName(id, eprofilename string) { 384 t.ExecProfilename = eprofilename 385 instanceName := "nonamed" 386 if id != "" { 387 instanceName = id 388 } else { 389 if p := t.getExecProfile(eprofilename); p != nil { 390 if p.Instance != "" { 391 instanceName = p.Instance 392 } 393 } 394 } 395 396 t.InstanceName = instanceName 397 u.Pf("module: [%s], instance id: [%s], exec profile: [%s]\n", ConfigRuntime().ModuleName, t.InstanceName, t.ExecProfilename) 398 if t.InstanceName == "nonamed" && t.ExecProfilename == "" { 399 u.LogWarn("*be aware*", "both instance id and exec profile are not set") 400 } 401 } 402 403 func (t *Tasker) initRuntime() { 404 t.TaskStack = stack.New("task") 405 t.StepStack = stack.New("step") 406 t.BlockStack = stack.New("block") 407 t.FinallyStack = stack.New("block") 408 } 409 410 func (t *Tasker) ListTasks() { 411 caps := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 412 u.Pln("-task list") 413 maxlen := 0 414 for _, task := range *t.Tasks { 415 tasknamelen := len(task.Name) 416 if tasknamelen > maxlen { 417 maxlen = tasknamelen 418 } 419 } 420 format := " %4d | %" + u.Spf("%d", maxlen) + "s |%9s| %s " 421 for idx, task := range *t.Tasks { 422 start := task.Name[0:1] 423 desc := strings.Split(task.Desc, "\n")[0] 424 if strings.Contains(caps, start) { 425 color.HiGreen("%s", u.Spf(format, idx+1, task.Name, "public", desc)) 426 } else { 427 color.Yellow("%s", u.Spf(format, idx+1, task.Name, "protected", desc)) 428 } 429 430 u.Ppmsgvvvv(task) 431 } 432 u.Pln("-\n") 433 } 434 435 func (t *Tasker) GetUiTasks() []model.Task { 436 caps := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 437 tasks := []model.Task{} 438 for _, task := range *t.Tasks { 439 start := task.Name[0:1] 440 if strings.Contains(caps, start) { 441 task.Public = true 442 } else { 443 task.Public = false 444 } 445 tasks = append(tasks, task) 446 } 447 448 return tasks 449 } 450 451 func (t *Tasker) ListAllTasks() { 452 u.Pln("-inspect all tasks:") 453 for _, task := range *t.Tasks { 454 t.ListTask(task.Name) 455 } 456 } 457 458 func (t *Tasker) LockModules() { 459 if !t.ValidateAllModules() { 460 u.InvalidAndPanic("modules configuration is not valid", "please fix the problem and try again") 461 } 462 u.Pln("-lock repos:") 463 464 lockMap := u.ModuleLockMap{} 465 466 mlist := (*ConfigRuntime()).Modules 467 if mlist != nil { 468 for _, m := range mlist { 469 m.Normalize() 470 m.ShowDetails() 471 gitdir := path.Join(m.Dir, ".git") 472 if _, err := os.Stat(gitdir); !os.IsNotExist(err) { 473 rev := u.GetHeadRev(m.Dir) 474 lockMap[m.Alias] = rev 475 } 476 } 477 } 478 479 u.Pln("versions:") 480 u.Ppmsg(lockMap) 481 lockYml := core.ObjToYaml(lockMap) 482 ioutil.WriteFile("./modlock.yml", []byte(lockYml), 0644) 483 u.Pf("Please check in: [%s] into code repo", "modlock.yml") 484 } 485 486 func (t *Tasker) CleanModules() { 487 488 if !t.ValidateAllModules() { 489 u.InvalidAndPanic("modules configuration is not valid", "please fix the problem and try again") 490 } 491 u.Pln("-clean repos:") 492 } 493 494 func (t *Tasker) PullModules() { 495 if !t.ValidateAllModules() { 496 u.InvalidAndPanic("modules configuration is not valid", "please fix the problem and try again") 497 } 498 499 u.Pln("-pull repos:") 500 501 mainMods := listModules("-main direct modules:", "%s/%s") 502 clonedMainModNames := mainMods.PullMainModules() 503 clonedSubModNames := append(clonedMainModNames, []string{}...) 504 mainMods.PullCascadedModules(&clonedMainModNames, &clonedSubModNames) 505 } 506 507 func (t *Tasker) ValidateAllModules() bool { 508 u.Pln("-validate all modules:") 509 mlist := (*ConfigRuntime()).Modules 510 511 namelist := []string{} 512 policies := []string{"manual", "always", "skip"} 513 errCnt := 0 514 for idx, m := range mlist { 515 m.Normalize() 516 if u.Contains(namelist, m.Alias) { 517 u.LogErrorMsg("alias duplication error", u.Spf("%d:%s", idx+1, m.Alias)) 518 errCnt += 1 519 } else { 520 namelist = append(namelist, m.Alias) 521 } 522 523 if m.Repo != "" && !u.Contains(policies, m.PullPolicy) { 524 u.LogErrorMsg("pullpolicy error", u.Spf("%d:%s", idx+1, "must be one of: manual | always | skip")) 525 errCnt += 1 526 } 527 528 if m.Repo != "" && m.Subdir != "" && m.Alias == "" { 529 u.LogErrorMsg("alias must be set", u.Spf("%d:%s", idx+1, "alias is needed to avoid confusion")) 530 errCnt += 1 531 } 532 } 533 534 if errCnt == 0 { 535 return true 536 } else { 537 return false 538 } 539 540 } 541 542 //list tasker modules 543 func (t *Tasker) ListMainModules() { 544 u.Pln("-list all modules:") 545 mlist := ConfigRuntime().Modules 546 mlist.ReportModules() 547 t.ValidateAllModules() 548 } 549 550 //probing modules list all modules, including the main direct modules and the all indirect modules 551 func ListAllModules() { 552 u.Pln("-list all modules:") 553 mods := listModules("-main direct modules:", "%s/%s") 554 u.Pln("- Insights:") 555 mods.ReportModules() 556 u.Pln("") 557 mods = listModules("-indirect sub modules:", "%s/.upmodules/*/%s") 558 u.Pln("- Insights:") 559 mods.ReportModules() 560 } 561 562 func listModules(desc, pattern string) *u.Modules { 563 cfgname := "upconfig.yml" 564 filelist := []string{} 565 match := u.Spfv(pattern, u.MainConfig.AbsWorkDir, cfgname) 566 files, err := filepath.Glob(match) 567 u.LogError("list upconfig.yml", err) 568 filelist = append(filelist, files...) 569 570 modlist := u.Modules{} 571 for _, f := range filelist { 572 cfg := u.NewUpConfig(path.Dir(f), cfgname) 573 modlist = append(modlist, cfg.Modules...) 574 } 575 u.Pf("\n%s\n", desc) 576 yml := core.ObjToYaml(modlist) 577 u.Pln(yml) 578 579 return &modlist 580 } 581 582 func (tasker *Tasker) ListTask(taskname string) { 583 var tree = treeprint.New() 584 //u.Pln("\ninspect task:") 585 level := 0 586 for _, task := range *tasker.Tasks { 587 if task.Name == taskname { 588 desc := strings.Split(task.Desc, "\n")[0] 589 u.Pf("%s: %s", color.BlueString("%s", task.Name), desc) 590 var steps Steps 591 err := ms.Decode(task.Task, &steps) 592 u.LogErrorAndExit("decode steps:", err, "please fix data type in yaml config") 593 steps.InspectSteps(tree, &level) 594 } 595 } 596 u.Pln(tree.String()) 597 } 598 599 func (tasker *Tasker) InspectTask(taskname string, branch treeprint.Tree, level *int) bool { 600 *level += 1 601 maxLayers, _ := strconv.Atoi(ConfigRuntime().MaxCallLayers) 602 if *level > maxLayers { 603 u.LogWarn("evaluate max task stack layer", "please setup max MaxCallLayers correctly, or fix recursive cycle calls") 604 return false 605 } 606 for _, task := range *tasker.Tasks { 607 if task.Name == taskname { 608 desc := strings.Split(task.Desc, "\n")[0] 609 br := branch.AddMetaBranch(color.BlueString("%s", task.Name), desc) 610 var steps Steps 611 err := ms.Decode(task.Task, &steps) 612 u.LogErrorAndExit("decode steps:", err, "please fix data type in yaml config") 613 614 for _, step := range steps { 615 descRaw := strings.Split(step.Desc, "\n")[0] 616 desc := Render(descRaw, TaskerRuntime().Tasker.RuntimeVarsAndDvarsMerged) 617 if step.Func == FUNC_CALL { 618 var callee string 619 switch t := step.Do.(type) { 620 case string: 621 622 brnode := br.AddMetaBranch(func() string { 623 if step.Loop != nil { 624 return step.Name + color.HiYellowString("%s", " /loop..") 625 } else { 626 return step.Name 627 } 628 }(), desc) 629 630 callee = step.Do.(string) 631 tasker.InspectTask(callee, brnode, level) 632 case []interface{}: 633 calleeTasknames := step.Do.([]interface{}) 634 for _, x := range calleeTasknames { 635 brnode := br.AddMetaBranch(func() string { 636 if step.Loop != "" { 637 return step.Name + color.HiYellowString("%s", " /loop..") 638 } else { 639 return step.Name 640 } 641 }(), desc) 642 643 callee = x.(string) 644 tasker.InspectTask(callee, brnode, level) 645 } 646 default: 647 u.Pf("type: %T", t) 648 } 649 } else if step.Func == FUNC_BLOCK { 650 branch := branch.AddMetaBranch(func() string { 651 if step.Loop != "" { 652 return step.Name + color.HiYellowString("%s", " /block.") 653 } else { 654 return step.Name 655 } 656 }(), desc) 657 658 switch t := step.Do.(type) { 659 case string: 660 rawFlowname := step.Do.(string) 661 branch.AddNode(u.Spf("%s %s", color.HiYellowString("%s", " ..flow ->"), rawFlowname)) 662 663 case []interface{}: 664 //detailed steps 665 var steps Steps 666 err := ms.Decode(step.Do, &steps) 667 u.LogErrorAndExit("load steps", err, "configuration problem, please fix it") 668 steps.InspectSteps(branch, level) 669 670 default: 671 u.Pf("type: %T", t) 672 } 673 674 } else { 675 br.AddNode(u.Spf("%s: %s", step.Name, desc)) 676 } 677 } 678 } 679 } 680 return true 681 } 682 683 func (t *Tasker) DryrunTask(taskname string) { 684 SetDryrun() 685 t.ExecTask(taskname, nil, false) 686 } 687 688 func ExecTask(fulltaskname string, callerVars *core.Cache) { 689 var modname string 690 var taskname string 691 692 func() { 693 subnames := strings.Split(fulltaskname, ".") 694 if len(subnames) > 2 { 695 u.InvalidAndPanic("task name validation", "task naming pattern: modulename.taskname") 696 } 697 698 if len(subnames) == 1 { 699 modname = "self" 700 taskname = subnames[0] 701 } else if len(subnames) == 2 { 702 modname = subnames[0] 703 taskname = subnames[1] 704 } 705 }() 706 707 if modname == "self" { 708 TaskerRuntime().Tasker.ExecTask(taskname, callerVars, false) 709 } else { 710 if modname == GetBaseModuleName() { 711 u.InvalidAndPanic("module name should not be the same as the main caller", "please check your task configuration") 712 } else { 713 714 cwd, err := os.Getwd() 715 716 if err != nil { 717 u.LogErrorAndPanic("cwd", err, "working directory error") 718 } 719 720 mods := TaskerRuntime().Tasker.Config.Modules 721 if mods != nil { 722 mod := TaskerRuntime().Tasker.Config.Modules.LocateModule(modname) 723 724 if mod != nil { 725 func() { 726 //TODO: exclude the subdir case 727 var modpath string 728 if path.IsAbs(mod.Dir) { 729 modpath = mod.Dir 730 } else { 731 modpath = path.Clean(path.Join(BaseDir, mod.Dir)) 732 } 733 os.Chdir(modpath) 734 if _, err := os.Stat(modpath); !os.IsNotExist(err) { 735 /* 736 in module loading, since you can not pass in the cli options, so: 737 version: will not be used at all 738 Verbose: determined by caller, so not relevant 739 MaxCallLayers: determined by caller 740 RefDir: applied 741 TaskFile: applied 742 ConfigDir: will not be used at all since no cli option to override this, it will be always be current dir . 743 ConfigFile: will not be used at all since no cli option to override this, it will be always be upconfig.yml from default 744 */ 745 mcfg := u.NewUpConfig("", "") 746 mcfg.SetModulename(modname) 747 mcfg.InitConfig() 748 taskerCaller := TaskerRuntime().Tasker 749 mTasker := NewTasker(mod.Iid, "", mcfg) 750 TaskerRuntime().TaskerCaller = taskerCaller 751 u.Pf("=>call module: [%s] task: [%s]\n", modname, taskname) 752 //u.Ptmpdebug("55", callerVars) 753 754 func() { 755 taskerLayer := TaskerStack.GetLen() 756 UpRunTimeVars.Put(UP_RUNTIME_TASKER_LAYER_NUMBER, taskerLayer) 757 u.Pvvvv("Executing tasker layer:", taskerLayer) 758 maxLayers, err := strconv.Atoi(u.MainConfig.MaxModuelCallLayers) 759 u.LogErrorAndPanic("evaluate max tasker module call layer", err, "please setup max MaxModuelCallLayers properly for your case") 760 761 if maxLayers != 0 && taskerLayer > maxLayers { 762 u.InvalidAndPanic("Module call layer check:", u.Spf("Too many layers of recursive module executions, max allowed(%d), please fix your recursive call", maxLayers)) 763 } 764 }() 765 766 mTasker.ExecTask(taskname, callerVars, true) 767 TaskerStack.Pop() 768 os.Chdir(cwd) 769 } else { 770 //TODO: put the reasoning into the doco: not to auto update to avoid evil code injection problem 771 u.InvalidAndPanic(u.Spf("module dir: [%s] does not exist under: [%s]\n", mod.Dir, cwd), "double check if you have change your module configuration, then you will probably need to update module again") 772 } 773 }() 774 } else { 775 u.LogWarn("locating module name failed", u.Spf("module name: [%s] does not exist", modname)) 776 TaskerRuntime().Tasker.ListMainModules() 777 } 778 779 } else { 780 callerName := TaskerRuntime().Tasker.Config.ModuleName 781 u.InvalidAndPanic(u.Spf("caller Module [%s] is not configured,", callerName), u.Spf("module: [%s], task: [%s]", modname, taskname)) 782 } 783 } 784 785 } 786 787 } 788 789 func (t *Tasker) validateTasks() { 790 for _, x := range *t.Tasks { 791 if x.Name == "" { 792 continue 793 } 794 var cnt int = 0 795 for _, y := range *t.Tasks { 796 if y.Name == x.Name { 797 cnt += 1 798 } 799 } 800 801 if cnt > 1 { 802 u.InvalidAndPanic("Duplicated task name", u.Spf("task name:[%s]", x.Name)) 803 } 804 } 805 } 806 807 func execTask(t *Tasker, taskname string, idx int, task model.Task, callerVars *core.Cache, isExternalCall bool) { 808 var rtContextFinal *TaskRuntimeContext 809 u.TaskPanicCount += 1 810 defer func(currentTask *model.Task) { 811 TaskerRuntime().Tasker.InFinalExec = true 812 TaskFinallyStack().Push(rtContextFinal) 813 if currentTask.Finally != nil && currentTask.Finally != "" { 814 u.PlnBlue("task Finally:") 815 } 816 817 paniced := false 818 var panicInfo interface{} 819 if r := recover(); r != nil { 820 u.Pvvvvv(u.Spf("Recovered from: %s", r)) 821 paniced = true 822 panicInfo = r 823 } 824 825 if currentTask.Finally != nil && currentTask.Finally != "" { 826 execFinally(currentTask.Finally, core.NewCache()) 827 } 828 829 if paniced && currentTask.Rescue == false { 830 u.LogWarn("Not rescued in task level", "please assess the panic problem and cause, fix it before re-run the task") 831 u.PanicExit("task finally", panicInfo) 832 } else if paniced { 833 u.LogWarn("Rescued in task 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") 834 } 835 TaskerRuntime().Tasker.InFinalExec = false 836 TaskFinallyStack().Pop() 837 u.TaskPanicCount -= 1 838 }(&task) 839 840 u.Pfvvvv(" located task-> %d [%s]: \n", idx+1, task.Name) 841 842 var ctxCallerTaskname string 843 844 if isExternalCall { 845 ctxCallerTaskname = "TODO: Main Caller Taskname" 846 } else { 847 if IsAtRootTaskLevel() { 848 ctxCallerTaskname = taskname 849 } else { 850 ctxCallerTaskname = TaskRuntime().TasknameLayered 851 } 852 } 853 854 taskLayerCnt := TaskerRuntime().Tasker.TaskStack.GetLen() 855 desc := Render(task.Desc, TaskerRuntime().Tasker.RuntimeVarsAndDvarsMerged) 856 857 u.LogDesc("task", idx+1, taskLayerCnt, u.Spf("%s ==> %s", ctxCallerTaskname, taskname), desc) 858 859 var steps Steps 860 err := ms.Decode(task.Task, &steps) 861 862 u.LogErrorAndPanic("decode steps:", err, "please fix data type in yaml config") 863 func() { 864 //step name validation 865 invalidNames := []string{} 866 for _, step := range steps { 867 if strings.Contains(step.Name, "-") { 868 invalidNames = append(invalidNames, step.Name) 869 } 870 } 871 872 if len(invalidNames) > 0 { 873 u.InvalidAndPanic(u.Spf("validating step name fails: %s ", invalidNames), "task name can not contain '-', please use '_' instead, failed names:") 874 } 875 }() 876 877 func() { 878 rtContext := TaskRuntimeContext{ 879 Taskname: taskname, 880 TaskVars: core.NewCache(), 881 IsCalledExternally: isExternalCall, 882 } 883 rtContextFinal = &rtContext 884 885 u.Pdebugvvvvvvv(callerVars) 886 if isExternalCall { 887 var passinvars core.Cache 888 passinvars = deepcopy.Copy(*t.RuntimeVarsAndDvarsMerged).(core.Cache) 889 mergo.Merge(&passinvars, callerVars, mergo.WithOverride) 890 rtContext.ExecbaseVars = &passinvars 891 rtContext.TasknameLayered = u.Spf("%s/%s", "TODO: Main Caller Taskname", taskname) 892 } else { 893 if IsAtRootTaskLevel() { 894 rtContext.ExecbaseVars = t.RuntimeVarsAndDvarsMerged 895 rtContext.TasknameLayered = taskname 896 } else { 897 rtContext.ExecbaseVars = func() *core.Cache { 898 if *callerVars == nil { 899 return core.NewCache() 900 } else { 901 return callerVars 902 } 903 }() 904 905 rtContext.TasknameLayered = u.Spf("%s/%s", TaskRuntime().TasknameLayered, taskname) 906 } 907 } 908 u.Pdebugvvvvvvv(rtContext.ExecbaseVars) 909 910 func() { 911 UpRunTimeVars.Put(UP_RUNTIME_TASK_LAYER_NUMBER, TaskerRuntime().Tasker.TaskStack.GetLen()) 912 TaskerRuntime().Tasker.TaskStack.Push(&rtContext) 913 u.Pvvvv("Executing task stack layer:", TaskerRuntime().Tasker.TaskStack.GetLen()) 914 maxLayers, err := strconv.Atoi(ConfigRuntime().MaxCallLayers) 915 u.LogErrorAndPanic("evaluate max task stack layer", err, "please setup max MaxCallLayers correctly") 916 917 if maxLayers != 0 && TaskerRuntime().Tasker.TaskStack.GetLen() > maxLayers { 918 u.InvalidAndPanic("Task exec stack layer check:", u.Spf("Too many layers of task executions, max allowed(%d), please fix your recursive call", maxLayers)) 919 } 920 }() 921 922 steps.Exec(false) 923 924 returnVars := TaskRuntime().ReturnVars 925 926 TaskerRuntime().Tasker.TaskStack.Pop() 927 func() { 928 //this will ensure the local caller vars are synced with return values, typically useful for chained tasks in call func 929 if returnVars != nil { 930 mergo.Merge(callerVars, returnVars, mergo.WithOverride) 931 } 932 933 if isExternalCall { 934 if returnVars != nil { 935 callerExecBaseVars := TaskerRuntime().TaskerCaller.TaskStack.GetTop().(*TaskRuntimeContext).ExecbaseVars 936 mergo.Merge(callerExecBaseVars, returnVars, mergo.WithOverride) 937 } 938 } else { 939 if !IsAtRootTaskLevel() && returnVars != nil { 940 mergo.Merge(TaskRuntime().ExecbaseVars, returnVars, mergo.WithOverride) 941 } else if IsAtRootTaskLevel() && returnVars != nil { 942 mergo.Merge(t.RuntimeVarsAndDvarsMerged, returnVars, mergo.WithOverride) 943 } 944 } 945 946 }() 947 948 }() 949 950 } 951 952 func (t *Tasker) ExecTask(taskname string, callerVars *core.Cache, isExternalCall bool) { 953 found := false 954 for idx, task := range *t.Tasks { 955 if taskname == task.Name { 956 found = true 957 execTask(t, taskname, idx, task, callerVars, isExternalCall) 958 } 959 } 960 961 if !found { 962 u.Pferror("Task %s is not defined!", taskname) 963 t.ListTasks() 964 u.InvalidAndPanic("Task call failed", "Task does not exist") 965 } 966 } 967 968 func (t *Tasker) validateAndLoadTaskRef() { 969 //validation 970 971 invalidNames := []string{} 972 for idx, task := range *t.Tasks { 973 start := task.Name[0:1] 974 if strings.Contains(u.CapsChars, start) { 975 task.Public = true 976 } else { 977 task.Public = false 978 } 979 980 if strings.Contains(task.Name, "-") { 981 invalidNames = append(invalidNames, task.Name) 982 } 983 984 if task.Task != nil && task.Ref != "" { 985 u.InvalidAndPanic("validate task node and ref", "task and ref can not coexist") 986 } 987 988 //load ref task 989 refdir := ConfigRuntime().RefDir 990 991 if task.Ref != "" { 992 if task.RefDir != "" { 993 rawdir := task.RefDir 994 refdir = Render(rawdir, t.RuntimeVarsAndDvarsMerged) 995 } 996 997 rawref := task.Ref 998 ref := Render(rawref, t.RuntimeVarsAndDvarsMerged) 999 1000 yamlflowroot := u.YamlLoader("flow ref", refdir, ref) 1001 flow := loadRefFlow(yamlflowroot) 1002 (*t.Tasks)[idx].Task = flow 1003 } 1004 } 1005 1006 if len(invalidNames) > 0 { 1007 u.InvalidAndPanic(u.Spf("validating task name fails: %s ", invalidNames), "task name can not contain '-', please use '_' instead, failed names:") 1008 } 1009 } 1010 1011 func (t *Tasker) loadRefTasks() { 1012 tasksRefList := t.TaskYmlRoot.Get("tasksref") 1013 if tasksRefList != nil { 1014 for _, ref := range tasksRefList.([]interface{}) { 1015 tasksYamlName := ref.(string) 1016 tasksYmlRoot := u.YamlLoader(tasksYamlName, ConfigRuntime().RefDir, tasksYamlName) 1017 1018 var tasks model.Tasks 1019 tasksData := tasksYmlRoot.Get("tasks") 1020 err := ms.Decode(tasksData, &tasks) 1021 u.LogErrorAndPanic(u.Spf("decode tasks:%s", tasksYamlName), err, "please fix configuration in tasks yaml file") 1022 for _, task := range tasks { 1023 *t.Tasks = append(*t.Tasks, task) 1024 } 1025 } 1026 } 1027 } 1028 1029 func (t *Tasker) loadTasks() error { 1030 tasksData := t.TaskYmlRoot.Get("tasks") 1031 var tasks model.Tasks 1032 err := ms.Decode(tasksData, &tasks) 1033 u.LogErrorAndPanic("decode tasks:main", err, "please fix configuration in tasks yaml file") 1034 t.Tasks = &tasks 1035 t.loadRefTasks() 1036 t.validateAndLoadTaskRef() 1037 return err 1038 } 1039 1040 func loadRefFlow(yamlroot *viper.Viper) *Steps { 1041 flowData := yamlroot.Get("flow") 1042 var flow Steps 1043 err := ms.Decode(flowData, &flow) 1044 u.LogErrorAndPanic("load ref flow", err, "flow of the steps has configuration problem, please fix it") 1045 return &flow 1046 } 1047 1048 func (t *Tasker) loadScopes() { 1049 scopesData := t.TaskYmlRoot.Get("scopes") 1050 var scopes Scopes 1051 err := ms.Decode(scopesData, &scopes) 1052 t.ScopeProfiles = &scopes 1053 1054 u.LogErrorAndPanic("load full scopes", err, "please assess your scope configuration carefully") 1055 } 1056 1057 func (t *Tasker) loadExecProfiles() { 1058 eprofileData := t.TaskYmlRoot.Get("eprofiles") 1059 var eprofiles ExecProfiles 1060 err := ms.Decode(eprofileData, &eprofiles) 1061 t.ExecProfiles = &eprofiles 1062 1063 u.LogErrorAndPanic("load exec profiles", err, "please assess your exec profiles configuration carefully") 1064 } 1065 1066 func (t *Tasker) loadPureEnv() { 1067 if u.MainConfig.Pure { 1068 pureEnv() 1069 } 1070 1071 } 1072 1073 func (t *Tasker) initSecureVault() { 1074 t.SecretVars = core.NewCache() 1075 } 1076 1077 func (t *Tasker) getExecProfile(pname string) *ExecProfile { 1078 var ep *ExecProfile 1079 if t.ExecProfiles != nil { 1080 for _, p := range *t.ExecProfiles { 1081 if p.Name == pname { 1082 ep = &p 1083 break 1084 } 1085 } 1086 } 1087 return ep 1088 } 1089 1090 func (t *Tasker) reportContextualEnvVars(vars *core.Cache) string { 1091 var envs EnvVars = EnvVars{} 1092 pname := TaskerRuntime().Tasker.ExecProfilename 1093 eprofile := TaskerRuntime().Tasker.getExecProfile(pname) 1094 if eprofile != nil { 1095 evars := eprofile.Evars 1096 for _, x := range evars { 1097 envs = append(envs, EnvVar{ 1098 Name: x.Name, 1099 Value: x.Value, 1100 }) 1101 } 1102 } 1103 1104 for k, v := range *vars { 1105 if strings.HasPrefix(k, "envVar_") { 1106 envs = append(envs, EnvVar{ 1107 Name: strings.TrimPrefix(k, "envVar_"), 1108 Value: v.(string), 1109 }) 1110 } 1111 } 1112 1113 var maxlen int 1114 for _, x := range envs { 1115 if l := len(x.Name); l > maxlen { 1116 maxlen = l 1117 } 1118 } 1119 1120 fs := `%3d: %` + strconv.Itoa(maxlen) + `s = %s` 1121 var expStr = bytes.NewBufferString("") 1122 for idx, x := range envs { 1123 u.PlnInfoHighlight(u.Spf(fs, idx+1, x.Name, x.Value)) 1124 expStr.WriteString(u.Spf("export %s=\"%s\"\n", x.Name, x.Value)) 1125 } 1126 1127 return expStr.String() 1128 } 1129 1130 func (t *Tasker) loadRuntimeGlobalVars() { 1131 varsData := t.TaskYmlRoot.Get("vars") 1132 var vars core.Cache 1133 err := ms.Decode(varsData, &vars) 1134 u.LogError("loadRuntimeGlobalVars", err) 1135 t.RuntimeGlobalVars = &vars 1136 } 1137 1138 func (t *Tasker) loadRuntimeGlobalDvars() { 1139 dvarsData := t.TaskYmlRoot.Get("dvars") 1140 var dvars Dvars 1141 err := ms.Decode(dvarsData, &dvars) 1142 u.LogErrorAndPanic("loadRuntimeGlobalDvars", 1143 err, 1144 "You must fix the data type to be\n string for a dvar value and try again. possible problems:\nthe name can not be single character 'y' or 'n' ", 1145 ) 1146 t.RuntimeGlobalDvars = &dvars 1147 }