github.com/Benchkram/bob@v0.0.0-20220321080157-7c8f3876e225/bob/run.go (about) 1 package bob 2 3 import ( 4 "context" 5 "errors" 6 7 "github.com/Benchkram/bob/bob/bobfile" 8 "github.com/Benchkram/bob/pkg/ctl" 9 "github.com/Benchkram/errz" 10 ) 11 12 // Examples of possible interactive usecase 13 // 14 // 1: [Done] executable requiring a database to run properly. 15 // Database is setup in a docker-compose file. 16 // 17 // 2: [Done] plain docker-compose run with dependcies to build-cmds 18 // containing instructions how to build the container image. 19 // 20 // TODO: 21 // 3: init script requiring a executable to run before 22 // containing a health endpoint (REST?). So the init script can be 23 // sure about the service to be functional. 24 // 25 26 // Run builds dependent tasks for a run cmd and starts it. 27 // A control is returned to interact with the run cmd. 28 // 29 // Canceling the cmd from the outside must be done through the context. 30 // 31 // TODO: Forbid circular dependecys. 32 func (b *B) Run(ctx context.Context, runName string) (_ ctl.Commander, err error) { 33 defer errz.Recover(&err) 34 35 aggregate, err := b.Aggregate() 36 errz.Fatal(err) 37 38 b.PrintVersionCompatibility(aggregate) 39 40 runTask, ok := aggregate.RTasks[runName] 41 if !ok { 42 return nil, ErrRunDoesNotExist 43 } 44 45 // gather interactive tasks 46 childInteractiveTasks := b.interactiveTasksInChain(runName, aggregate) 47 interactiveTasks := []string{runTask.Name()} 48 interactiveTasks = append(interactiveTasks, childInteractiveTasks...) 49 50 // build dependencies & main runTask 51 for _, task := range interactiveTasks { 52 err = buildNonInteractive(ctx, task, aggregate) 53 errz.Fatal(err) 54 } 55 56 // generate run controls to steer the run cmd. 57 runCtls := []ctl.Command{} 58 for _, name := range interactiveTasks { 59 interactiveTask := aggregate.RTasks[name] 60 61 rc, err := interactiveTask.Run(ctx) 62 errz.Fatal(err) 63 64 runCtls = append(runCtls, rc) 65 } 66 67 builder := NewBuilder(b, runName, aggregate, buildNonInteractive) 68 commander := ctl.NewCommander(ctx, builder, runCtls...) 69 70 return commander, nil 71 } 72 73 // interactiveTasksInChain returns run tasks in the dependency chain. 74 // Task on a higher level in the tree appear at the front of the slice.. 75 // 76 // It will not error but return a empty error in case the runName 77 // does not exists. 78 func (b *B) interactiveTasksInChain(runName string, aggregate *bobfile.Bobfile) []string { 79 runTasks := []string{} 80 81 run, ok := aggregate.RTasks[runName] 82 if !ok { 83 return nil 84 } 85 86 for _, task := range run.DependsOn { 87 if !isInteractive(task, aggregate) { 88 continue 89 } 90 runTasks = append(runTasks, task) 91 92 // assure all it's dependent runTasks are also added. 93 childs := b.interactiveTasksInChain(task, aggregate) 94 runTasks = append(runTasks, childs...) 95 } 96 97 return normalize(runTasks) 98 } 99 100 // normalize removes duplicated entrys from the run task list. 101 // The duplicate closest to the top of the chain is removed 102 // so that child tasks are started first. 103 func normalize(tasks []string) []string { 104 sanitized := []string{} 105 106 for i, task := range tasks { 107 keep := true 108 109 // last element can always be added safely 110 if i < len(tasks) { 111 for _, jtask := range tasks[i+1:] { 112 if task == jtask { 113 keep = false 114 break 115 } 116 } 117 } 118 119 if keep { 120 sanitized = append(sanitized, task) 121 } 122 } 123 124 return sanitized 125 } 126 127 func isInteractive(name string, aggregate *bobfile.Bobfile) bool { 128 _, ok := aggregate.RTasks[name] 129 return ok 130 } 131 132 // func isNonInteractive(name string, aggregate *bobfile.Bobfile) bool { 133 // _, ok := aggregate.Tasks[name] 134 // return ok 135 // } 136 137 // buildNonInteractive takes a interactive task to build it's non-interactive children. 138 func buildNonInteractive(ctx context.Context, runname string, aggregate *bobfile.Bobfile) (err error) { 139 defer errz.Recover(&err) 140 141 interactive, ok := aggregate.RTasks[runname] 142 if !ok { 143 return ErrRunDoesNotExist 144 } 145 146 // Run dependent build tasks 147 // before starting the run task 148 for _, child := range interactive.DependsOn { 149 if isInteractive(child, aggregate) { 150 continue 151 } 152 153 playbook, err := aggregate.Playbook(child) 154 if err != nil { 155 if errors.Is(err, ErrTaskDoesNotExist) { 156 continue 157 } 158 errz.Fatal(err) 159 } 160 161 err = playbook.Build(ctx) 162 errz.Fatal(err) 163 } 164 165 return nil 166 }