github.com/ddev/ddev@v1.23.2-0.20240519125000-d824ffe36ff3/pkg/ddevapp/task.go (about) 1 package ddevapp 2 3 import ( 4 "fmt" 5 "os" 6 "runtime" 7 "strings" 8 9 "github.com/ddev/ddev/pkg/exec" 10 "github.com/ddev/ddev/pkg/nodeps" 11 "github.com/ddev/ddev/pkg/util" 12 "github.com/mattn/go-isatty" 13 ) 14 15 // YAMLTask defines tasks like Exec to be run in hooks 16 type YAMLTask map[string]interface{} 17 18 // Task is the interface defining methods we'll use in various tasks 19 type Task interface { 20 Execute() error 21 GetDescription() string 22 } 23 24 // ExecTask is the struct that defines "exec" tasks for hooks, commands 25 // to be run in containers. 26 type ExecTask struct { 27 service string // Name of service, defaults to web 28 execRaw []string // Use execRaw if configured instead of exec 29 exec string // Actual command to be executed. 30 app *DdevApp 31 } 32 33 // ExecHostTask is the struct that defines "exec-host" tasks for hooks, 34 // commands that get run on the host. 35 type ExecHostTask struct { 36 exec string 37 app *DdevApp 38 } 39 40 // ComposerTask is the struct that defines "composer" tasks for hooks, commands 41 // to be run in containers. 42 type ComposerTask struct { 43 execRaw []string 44 app *DdevApp 45 } 46 47 // Execute executes an ExecTask 48 func (c ExecTask) Execute() error { 49 opts := &ExecOpts{ 50 Service: c.service, 51 Cmd: c.exec, 52 RawCmd: c.execRaw, 53 Tty: isatty.IsTerminal(os.Stdin.Fd()), 54 NoCapture: true, 55 } 56 _, _, err := c.app.Exec(opts) 57 58 return err 59 } 60 61 // GetDescription returns a human-readable description of the task 62 func (c ExecTask) GetDescription() string { 63 s := c.exec 64 if c.execRaw != nil { 65 s = fmt.Sprintf("%v (raw)", c.execRaw) 66 } 67 68 return fmt.Sprintf("Exec command '%s' in container/service '%s'", s, c.service) 69 } 70 71 // GetDescription returns a human-readable description of the task 72 func (c ExecHostTask) GetDescription() string { 73 hostname, _ := os.Hostname() 74 return fmt.Sprintf("Exec command '%s' on the host (%s)", c.exec, hostname) 75 } 76 77 // Execute (HostTask) executes a command in a container, by default the web container, 78 // and returns stdout, stderr, err 79 func (c ExecHostTask) Execute() error { 80 cwd, _ := os.Getwd() 81 err := os.Chdir(c.app.GetAppRoot()) 82 if err != nil { 83 return err 84 } 85 86 bashPath := "bash" 87 if runtime.GOOS == "windows" { 88 bashPath = util.FindBashPath() 89 } 90 91 args := []string{ 92 "-c", 93 c.exec, 94 } 95 96 err = exec.RunInteractiveCommand(bashPath, args) 97 98 _ = os.Chdir(cwd) 99 100 return err 101 } 102 103 // Execute (ComposerTask) runs a Composer command in the web container 104 // and returns stdout, stderr, err 105 func (c ComposerTask) Execute() error { 106 _, _, err := c.app.Composer(c.execRaw) 107 108 return err 109 } 110 111 // GetDescription returns a human-readable description of the task 112 func (c ComposerTask) GetDescription() string { 113 return fmt.Sprintf("Composer command '%v' in web container", c.execRaw) 114 } 115 116 // NewTask is the factory method to create whatever kind of task 117 // we need using the yaml description of the task. 118 // Returns a task (of various types) or nil 119 func NewTask(app *DdevApp, ytask YAMLTask) Task { 120 if e, ok := ytask["exec-host"]; ok { 121 if v, ok := e.(string); ok { 122 t := ExecHostTask{app: app, exec: v} 123 return t 124 } 125 util.Warning("Invalid exec-host value, not executing it: %v", e) 126 } else if e, ok = ytask["composer"]; ok { 127 // Handle the old-style `composer: install` 128 if v, ok := e.(string); ok { 129 t := ComposerTask{app: app, execRaw: strings.Split(v, " ")} 130 return t 131 } 132 133 // Handle the new-style `composer: [install]` 134 if v, ok := ytask["exec_raw"]; ok { 135 raw, err := util.InterfaceSliceToStringSlice(v.([]interface{})) 136 if err != nil { 137 util.Warning("Invalid composer/exec_raw value, not executing it: %v", e) 138 return nil 139 } 140 141 t := ComposerTask{app: app, execRaw: raw} 142 return t 143 } 144 util.Warning("Invalid Composer value, not executing it: %v", e) 145 } else if e, ok = ytask["exec"]; ok { 146 if v, ok := e.(string); ok { 147 t := ExecTask{app: app, exec: v} 148 if t.service, ok = ytask["service"].(string); !ok { 149 t.service = nodeps.WebContainer 150 } 151 return t 152 } 153 154 if v, ok := ytask["exec_raw"]; ok { 155 raw, err := util.InterfaceSliceToStringSlice(v.([]interface{})) 156 if err != nil { 157 util.Warning("Invalid exec/exec_raw value, not executing it: %v", e) 158 return nil 159 } 160 161 t := ExecTask{app: app, execRaw: raw} 162 if t.service, ok = ytask["service"].(string); !ok { 163 t.service = nodeps.WebContainer 164 } 165 return t 166 } 167 util.Warning("Invalid exec_raw value, not executing it: %v", e) 168 169 } else if e, ok = ytask["exec"]; ok { 170 if v, ok := e.(string); ok { 171 t := ExecTask{app: app, exec: v} 172 if t.service, ok = ytask["service"].(string); !ok { 173 t.service = nodeps.WebContainer 174 } 175 return t 176 } 177 util.Warning("Invalid exec value, not executing it: %v", e) 178 } 179 return nil 180 }