github.com/dnephin/dobi@v0.15.0/tasks/tasks.go (about) 1 package tasks 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "github.com/dnephin/dobi/config" 9 "github.com/dnephin/dobi/execenv" 10 "github.com/dnephin/dobi/logging" 11 "github.com/dnephin/dobi/tasks/alias" 12 "github.com/dnephin/dobi/tasks/client" 13 "github.com/dnephin/dobi/tasks/compose" 14 "github.com/dnephin/dobi/tasks/context" 15 "github.com/dnephin/dobi/tasks/env" 16 "github.com/dnephin/dobi/tasks/image" 17 "github.com/dnephin/dobi/tasks/job" 18 "github.com/dnephin/dobi/tasks/mount" 19 "github.com/dnephin/dobi/tasks/task" 20 "github.com/dnephin/dobi/tasks/types" 21 log "github.com/sirupsen/logrus" 22 ) 23 24 // TaskCollection is a collection of Task objects 25 type TaskCollection struct { 26 tasks []types.TaskConfig 27 } 28 29 func (c *TaskCollection) add(task types.TaskConfig) { 30 c.tasks = append(c.tasks, task) 31 } 32 33 // All returns all the tasks in the dependency order 34 func (c *TaskCollection) All() []types.TaskConfig { 35 return c.tasks 36 } 37 38 // Get returns the TaskConfig for the Name 39 func (c *TaskCollection) Get(name task.Name) types.TaskConfig { 40 for _, task := range c.tasks { 41 if task.Name().Equal(name) { 42 return task 43 } 44 } 45 return nil 46 } 47 48 func newTaskCollection() *TaskCollection { 49 return &TaskCollection{} 50 } 51 52 func collectTasks(options RunOptions) (*TaskCollection, error) { 53 return collect(options, &collectionState{ 54 newTaskCollection(), 55 task.NewStack(), 56 }) 57 } 58 59 type collectionState struct { 60 tasks *TaskCollection 61 taskStack *task.Stack 62 } 63 64 func collect(options RunOptions, state *collectionState) (*TaskCollection, error) { 65 for _, taskname := range options.Tasks { 66 taskname := task.ParseName(taskname) 67 resourceName := taskname.Resource() 68 resource, ok := options.Config.Resources[resourceName] 69 if !ok { 70 return nil, fmt.Errorf("resource %q does not exist", resourceName) 71 } 72 73 taskConfig, err := buildTaskConfig(resourceName, taskname.Action(), resource) 74 if err != nil { 75 return nil, err 76 } 77 78 if state.taskStack.Contains(taskConfig.Name()) { 79 return nil, fmt.Errorf( 80 "Invalid dependency cycle: %s", strings.Join(state.taskStack.Names(), ", ")) 81 } 82 state.taskStack.Push(taskConfig.Name()) 83 84 options.Tasks = taskConfig.Dependencies() 85 if _, err := collect(options, state); err != nil { 86 return nil, err 87 } 88 state.tasks.add(taskConfig) 89 state.taskStack.Pop() // nolint: errcheck 90 } 91 return state.tasks, nil 92 } 93 94 // TODO: some way to make this a registry 95 func buildTaskConfig(name, action string, resource config.Resource) (types.TaskConfig, error) { 96 switch conf := resource.(type) { 97 case *config.ImageConfig: 98 return image.GetTaskConfig(name, action, conf) 99 case *config.JobConfig: 100 return job.GetTaskConfig(name, action, conf) 101 case *config.MountConfig: 102 return mount.GetTaskConfig(name, action, conf) 103 case *config.AliasConfig: 104 return alias.GetTaskConfig(name, action, conf) 105 case *config.EnvConfig: 106 return env.GetTaskConfig(name, action, conf) 107 case *config.ComposeConfig: 108 return compose.GetTaskConfig(name, action, conf) 109 default: 110 panic(fmt.Sprintf("Unexpected config type %T", conf)) 111 } 112 } 113 114 func reversed(tasks []types.Task) []types.Task { 115 reversed := []types.Task{} 116 for i := len(tasks) - 1; i >= 0; i-- { 117 reversed = append(reversed, tasks[i]) 118 } 119 return reversed 120 } 121 122 func executeTasks(ctx *context.ExecuteContext, tasks *TaskCollection) error { 123 startedTasks := []types.Task{} 124 125 defer func() { 126 logging.Log.Debug("stopping tasks") 127 for _, startedTask := range reversed(startedTasks) { 128 if err := startedTask.Stop(ctx); err != nil { 129 logging.Log.Warnf("Failed to stop task %q: %s", startedTask.Name(), err) 130 } 131 } 132 }() 133 134 logging.Log.Debug("executing tasks") 135 for _, taskConfig := range tasks.All() { 136 resource, err := taskConfig.Resource().Resolve(ctx.Env) 137 if err != nil { 138 return err 139 } 140 ctx.Resources.Add(taskConfig.Name().Resource(), resource) 141 142 currentTask := taskConfig.Task(resource) 143 startedTasks = append(startedTasks, currentTask) 144 start := time.Now() 145 logging.Log.WithFields(log.Fields{"time": start, "task": currentTask}).Debug("Start") 146 147 depsModified := hasModifiedDeps(ctx, taskConfig.Dependencies()) 148 modified, err := currentTask.Run(ctx, depsModified) 149 if err != nil { 150 return fmt.Errorf("failed to execute task %q: %s", currentTask.Name(), err) 151 } 152 if modified { 153 ctx.SetModified(currentTask.Name()) 154 } 155 logging.Log.WithFields(log.Fields{ 156 "elapsed": time.Since(start), 157 "task": currentTask, 158 }).Debug("Complete") 159 } 160 return nil 161 } 162 163 func hasModifiedDeps(ctx *context.ExecuteContext, deps []string) bool { 164 for _, dep := range deps { 165 taskName := task.ParseName(dep) 166 if ctx.IsModified(taskName) { 167 return true 168 } 169 } 170 return false 171 } 172 173 // RunOptions are the options supported by Run 174 type RunOptions struct { 175 Client client.DockerClient 176 Config *config.Config 177 Tasks []string 178 Quiet bool 179 BindMount bool 180 } 181 182 func getNames(options RunOptions) []string { 183 if len(options.Tasks) > 0 { 184 return options.Tasks 185 } 186 187 if options.Config.Meta.Default != "" { 188 return []string{options.Config.Meta.Default} 189 } 190 191 return options.Tasks 192 } 193 194 // Run one or more tasks 195 func Run(options RunOptions) error { 196 options.Tasks = getNames(options) 197 if len(options.Tasks) == 0 { 198 return fmt.Errorf("no task to run, and no default task defined") 199 } 200 201 execEnv, err := execenv.NewExecEnvFromConfig( 202 options.Config.Meta.ExecID, 203 options.Config.Meta.Project, 204 options.Config.WorkingDir, 205 ) 206 if err != nil { 207 return err 208 } 209 210 tasks, err := collectTasks(options) 211 if err != nil { 212 return err 213 } 214 215 ctx := context.NewExecuteContext( 216 options.Config, 217 options.Client, 218 execEnv, 219 context.NewSettings(options.Quiet, options.BindMount)) 220 return executeTasks(ctx, tasks) 221 }