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  }