github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/drivers/java/driver.go (about)

     1  package java
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"runtime"
    10  	"time"
    11  
    12  	"github.com/hashicorp/nomad/client/lib/cgutil"
    13  	"github.com/hashicorp/nomad/drivers/shared/capabilities"
    14  
    15  	"github.com/hashicorp/consul-template/signals"
    16  	hclog "github.com/hashicorp/go-hclog"
    17  	"github.com/hashicorp/nomad/drivers/shared/eventer"
    18  	"github.com/hashicorp/nomad/drivers/shared/executor"
    19  	"github.com/hashicorp/nomad/drivers/shared/resolvconf"
    20  	"github.com/hashicorp/nomad/helper/pluginutils/loader"
    21  	"github.com/hashicorp/nomad/plugins/base"
    22  	"github.com/hashicorp/nomad/plugins/drivers"
    23  	"github.com/hashicorp/nomad/plugins/drivers/utils"
    24  	"github.com/hashicorp/nomad/plugins/shared/hclspec"
    25  	pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
    26  )
    27  
    28  const (
    29  	// pluginName is the name of the plugin
    30  	pluginName = "java"
    31  
    32  	// fingerprintPeriod is the interval at which the driver will send fingerprint responses
    33  	fingerprintPeriod = 30 * time.Second
    34  
    35  	// The key populated in Node Attributes to indicate presence of the Java driver
    36  	driverAttr        = "driver.java"
    37  	driverVersionAttr = "driver.java.version"
    38  
    39  	// taskHandleVersion is the version of task handle which this driver sets
    40  	// and understands how to decode driver state
    41  	taskHandleVersion = 1
    42  )
    43  
    44  var (
    45  	// PluginID is the java plugin metadata registered in the plugin
    46  	// catalog.
    47  	PluginID = loader.PluginID{
    48  		Name:       pluginName,
    49  		PluginType: base.PluginTypeDriver,
    50  	}
    51  
    52  	// PluginConfig is the java driver factory function registered in the
    53  	// plugin catalog.
    54  	PluginConfig = &loader.InternalPluginConfig{
    55  		Config:  map[string]interface{}{},
    56  		Factory: func(ctx context.Context, l hclog.Logger) interface{} { return NewDriver(ctx, l) },
    57  	}
    58  
    59  	// pluginInfo is the response returned for the PluginInfo RPC
    60  	pluginInfo = &base.PluginInfoResponse{
    61  		Type:              base.PluginTypeDriver,
    62  		PluginApiVersions: []string{drivers.ApiVersion010},
    63  		PluginVersion:     "0.1.0",
    64  		Name:              pluginName,
    65  	}
    66  
    67  	// configSpec is the hcl specification returned by the ConfigSchema RPC
    68  	configSpec = hclspec.NewObject(map[string]*hclspec.Spec{
    69  		"default_pid_mode": hclspec.NewDefault(
    70  			hclspec.NewAttr("default_pid_mode", "string", false),
    71  			hclspec.NewLiteral(`"private"`),
    72  		),
    73  		"default_ipc_mode": hclspec.NewDefault(
    74  			hclspec.NewAttr("default_ipc_mode", "string", false),
    75  			hclspec.NewLiteral(`"private"`),
    76  		),
    77  		"allow_caps": hclspec.NewDefault(
    78  			hclspec.NewAttr("allow_caps", "list(string)", false),
    79  			hclspec.NewLiteral(capabilities.HCLSpecLiteral),
    80  		),
    81  	})
    82  
    83  	// taskConfigSpec is the hcl specification for the driver config section of
    84  	// a taskConfig within a job. It is returned in the TaskConfigSchema RPC
    85  	taskConfigSpec = hclspec.NewObject(map[string]*hclspec.Spec{
    86  		// It's required for either `class` or `jar_path` to be set,
    87  		// but that's not expressable in hclspec.  Marking both as optional
    88  		// and setting checking explicitly later
    89  		"class":       hclspec.NewAttr("class", "string", false),
    90  		"class_path":  hclspec.NewAttr("class_path", "string", false),
    91  		"jar_path":    hclspec.NewAttr("jar_path", "string", false),
    92  		"jvm_options": hclspec.NewAttr("jvm_options", "list(string)", false),
    93  		"args":        hclspec.NewAttr("args", "list(string)", false),
    94  		"pid_mode":    hclspec.NewAttr("pid_mode", "string", false),
    95  		"ipc_mode":    hclspec.NewAttr("ipc_mode", "string", false),
    96  		"cap_add":     hclspec.NewAttr("cap_add", "list(string)", false),
    97  		"cap_drop":    hclspec.NewAttr("cap_drop", "list(string)", false),
    98  	})
    99  
   100  	// driverCapabilities is returned by the Capabilities RPC and indicates what
   101  	// optional features this driver supports
   102  	driverCapabilities = &drivers.Capabilities{
   103  		SendSignals: false,
   104  		Exec:        false,
   105  		FSIsolation: drivers.FSIsolationNone,
   106  		NetIsolationModes: []drivers.NetIsolationMode{
   107  			drivers.NetIsolationModeHost,
   108  			drivers.NetIsolationModeGroup,
   109  		},
   110  		MountConfigs: drivers.MountConfigSupportNone,
   111  	}
   112  
   113  	_ drivers.DriverPlugin = (*Driver)(nil)
   114  )
   115  
   116  func init() {
   117  	if runtime.GOOS == "linux" {
   118  		driverCapabilities.FSIsolation = drivers.FSIsolationChroot
   119  		driverCapabilities.MountConfigs = drivers.MountConfigSupportAll
   120  	}
   121  }
   122  
   123  // Config is the driver configuration set by the SetConfig RPC call
   124  type Config struct {
   125  	// DefaultModePID is the default PID isolation set for all tasks using
   126  	// exec-based task drivers.
   127  	DefaultModePID string `codec:"default_pid_mode"`
   128  
   129  	// DefaultModeIPC is the default IPC isolation set for all tasks using
   130  	// exec-based task drivers.
   131  	DefaultModeIPC string `codec:"default_ipc_mode"`
   132  
   133  	// AllowCaps configures which Linux Capabilities are enabled for tasks
   134  	// running on this node.
   135  	AllowCaps []string `codec:"allow_caps"`
   136  }
   137  
   138  func (c *Config) validate() error {
   139  	switch c.DefaultModePID {
   140  	case executor.IsolationModePrivate, executor.IsolationModeHost:
   141  	default:
   142  		return fmt.Errorf("default_pid_mode must be %q or %q, got %q", executor.IsolationModePrivate, executor.IsolationModeHost, c.DefaultModePID)
   143  	}
   144  
   145  	switch c.DefaultModeIPC {
   146  	case executor.IsolationModePrivate, executor.IsolationModeHost:
   147  	default:
   148  		return fmt.Errorf("default_ipc_mode must be %q or %q, got %q", executor.IsolationModePrivate, executor.IsolationModeHost, c.DefaultModeIPC)
   149  	}
   150  
   151  	badCaps := capabilities.Supported().Difference(capabilities.New(c.AllowCaps))
   152  	if !badCaps.Empty() {
   153  		return fmt.Errorf("allow_caps configured with capabilities not supported by system: %s", badCaps)
   154  	}
   155  
   156  	return nil
   157  }
   158  
   159  // TaskConfig is the driver configuration of a taskConfig within a job
   160  type TaskConfig struct {
   161  	// Class indicates which class contains the java entry point.
   162  	Class string `codec:"class"`
   163  
   164  	// ClassPath indicates where class files are found.
   165  	ClassPath string `codec:"class_path"`
   166  
   167  	// JarPath indicates where a jar  file is found.
   168  	JarPath string `codec:"jar_path"`
   169  
   170  	// JvmOpts are arguments to pass to the JVM
   171  	JvmOpts []string `codec:"jvm_options"`
   172  
   173  	// Args are extra arguments to java executable
   174  	Args []string `codec:"args"`
   175  
   176  	// ModePID indicates whether PID namespace isolation is enabled for the task.
   177  	// Must be "private" or "host" if set.
   178  	ModePID string `codec:"pid_mode"`
   179  
   180  	// ModeIPC indicates whether IPC namespace isolation is enabled for the task.
   181  	// Must be "private" or "host" if set.
   182  	ModeIPC string `codec:"ipc_mode"`
   183  
   184  	// CapAdd is a set of linux capabilities to enable.
   185  	CapAdd []string `codec:"cap_add"`
   186  
   187  	// CapDrop is a set of linux capabilities to disable.
   188  	CapDrop []string `codec:"cap_drop"`
   189  }
   190  
   191  func (tc *TaskConfig) validate() error {
   192  	switch tc.ModePID {
   193  	case "", executor.IsolationModePrivate, executor.IsolationModeHost:
   194  	default:
   195  		return fmt.Errorf("pid_mode must be %q or %q, got %q", executor.IsolationModePrivate, executor.IsolationModeHost, tc.ModePID)
   196  
   197  	}
   198  
   199  	switch tc.ModeIPC {
   200  	case "", executor.IsolationModePrivate, executor.IsolationModeHost:
   201  	default:
   202  		return fmt.Errorf("ipc_mode must be %q or %q, got %q", executor.IsolationModePrivate, executor.IsolationModeHost, tc.ModeIPC)
   203  	}
   204  
   205  	supported := capabilities.Supported()
   206  	badAdds := supported.Difference(capabilities.New(tc.CapAdd))
   207  	if !badAdds.Empty() {
   208  		return fmt.Errorf("cap_add configured with capabilities not supported by system: %s", badAdds)
   209  	}
   210  	badDrops := supported.Difference(capabilities.New(tc.CapDrop))
   211  	if !badDrops.Empty() {
   212  		return fmt.Errorf("cap_drop configured with capabilities not supported by system: %s", badDrops)
   213  	}
   214  
   215  	return nil
   216  }
   217  
   218  // TaskState is the state which is encoded in the handle returned in
   219  // StartTask. This information is needed to rebuild the taskConfig state and handler
   220  // during recovery.
   221  type TaskState struct {
   222  	ReattachConfig *pstructs.ReattachConfig
   223  	TaskConfig     *drivers.TaskConfig
   224  	Pid            int
   225  	StartedAt      time.Time
   226  }
   227  
   228  // Driver is a driver for running images via Java
   229  type Driver struct {
   230  	// eventer is used to handle multiplexing of TaskEvents calls such that an
   231  	// event can be broadcast to all callers
   232  	eventer *eventer.Eventer
   233  
   234  	// config is the driver configuration set by the SetConfig RPC
   235  	config Config
   236  
   237  	// tasks is the in memory datastore mapping taskIDs to taskHandle
   238  	tasks *taskStore
   239  
   240  	// ctx is the context for the driver. It is passed to other subsystems to
   241  	// coordinate shutdown
   242  	ctx context.Context
   243  
   244  	// nomadConf is the client agent's configuration
   245  	nomadConfig *base.ClientDriverConfig
   246  
   247  	// logger will log to the Nomad agent
   248  	logger hclog.Logger
   249  }
   250  
   251  func NewDriver(ctx context.Context, logger hclog.Logger) drivers.DriverPlugin {
   252  	logger = logger.Named(pluginName)
   253  	return &Driver{
   254  		eventer: eventer.NewEventer(ctx, logger),
   255  		tasks:   newTaskStore(),
   256  		ctx:     ctx,
   257  		logger:  logger,
   258  	}
   259  }
   260  
   261  func (d *Driver) PluginInfo() (*base.PluginInfoResponse, error) {
   262  	return pluginInfo, nil
   263  }
   264  
   265  func (d *Driver) ConfigSchema() (*hclspec.Spec, error) {
   266  	return configSpec, nil
   267  }
   268  
   269  func (d *Driver) SetConfig(cfg *base.Config) error {
   270  	// unpack, validate, and set agent plugin config
   271  	var config Config
   272  	if len(cfg.PluginConfig) != 0 {
   273  		if err := base.MsgPackDecode(cfg.PluginConfig, &config); err != nil {
   274  			return err
   275  		}
   276  	}
   277  	if err := config.validate(); err != nil {
   278  		return err
   279  	}
   280  	d.config = config
   281  
   282  	if cfg != nil && cfg.AgentConfig != nil {
   283  		d.nomadConfig = cfg.AgentConfig.Driver
   284  	}
   285  	return nil
   286  }
   287  
   288  func (d *Driver) TaskConfigSchema() (*hclspec.Spec, error) {
   289  	return taskConfigSpec, nil
   290  }
   291  
   292  func (d *Driver) Capabilities() (*drivers.Capabilities, error) {
   293  	return driverCapabilities, nil
   294  }
   295  
   296  func (d *Driver) Fingerprint(ctx context.Context) (<-chan *drivers.Fingerprint, error) {
   297  	ch := make(chan *drivers.Fingerprint)
   298  	go d.handleFingerprint(ctx, ch)
   299  	return ch, nil
   300  }
   301  
   302  func (d *Driver) handleFingerprint(ctx context.Context, ch chan *drivers.Fingerprint) {
   303  	ticker := time.NewTimer(0)
   304  	for {
   305  		select {
   306  		case <-ctx.Done():
   307  			return
   308  		case <-d.ctx.Done():
   309  			return
   310  		case <-ticker.C:
   311  			ticker.Reset(fingerprintPeriod)
   312  			ch <- d.buildFingerprint()
   313  		}
   314  	}
   315  }
   316  
   317  func (d *Driver) buildFingerprint() *drivers.Fingerprint {
   318  	fp := &drivers.Fingerprint{
   319  		Attributes:        map[string]*pstructs.Attribute{},
   320  		Health:            drivers.HealthStateHealthy,
   321  		HealthDescription: drivers.DriverHealthy,
   322  	}
   323  
   324  	if runtime.GOOS == "linux" {
   325  		// Only enable if w are root and cgroups are mounted when running on linux system
   326  		if !utils.IsUnixRoot() {
   327  			fp.Health = drivers.HealthStateUndetected
   328  			fp.HealthDescription = drivers.DriverRequiresRootMessage
   329  			return fp
   330  		}
   331  
   332  		mount, err := cgutil.FindCgroupMountpointDir()
   333  		if err != nil {
   334  			fp.Health = drivers.HealthStateUnhealthy
   335  			fp.HealthDescription = drivers.NoCgroupMountMessage
   336  			d.logger.Warn(fp.HealthDescription, "error", err)
   337  			return fp
   338  		}
   339  
   340  		if mount == "" {
   341  			fp.Health = drivers.HealthStateUnhealthy
   342  			fp.HealthDescription = drivers.CgroupMountEmpty
   343  			return fp
   344  		}
   345  	}
   346  	if runtime.GOOS == "darwin" {
   347  		_, err := checkForMacJVM()
   348  		if err != nil {
   349  			// return no error, as it isn't an error to not find java, it just means we
   350  			// can't use it.
   351  
   352  			fp.Health = drivers.HealthStateUndetected
   353  			fp.HealthDescription = ""
   354  			d.logger.Trace("macOS jvm not found", "error", err)
   355  			return fp
   356  		}
   357  	}
   358  
   359  	version, jdkJRE, vm, err := javaVersionInfo()
   360  	if err != nil {
   361  		// return no error, as it isn't an error to not find java, it just means we
   362  		// can't use it.
   363  		fp.Health = drivers.HealthStateUndetected
   364  		fp.HealthDescription = ""
   365  		return fp
   366  	}
   367  
   368  	fp.Attributes[driverAttr] = pstructs.NewBoolAttribute(true)
   369  	fp.Attributes[driverVersionAttr] = pstructs.NewStringAttribute(version)
   370  	fp.Attributes["driver.java.runtime"] = pstructs.NewStringAttribute(jdkJRE)
   371  	fp.Attributes["driver.java.vm"] = pstructs.NewStringAttribute(vm)
   372  
   373  	return fp
   374  }
   375  
   376  func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error {
   377  	if handle == nil {
   378  		return fmt.Errorf("handle cannot be nil")
   379  	}
   380  
   381  	// If already attached to handle there's nothing to recover.
   382  	if _, ok := d.tasks.Get(handle.Config.ID); ok {
   383  		d.logger.Debug("nothing to recover; task already exists",
   384  			"task_id", handle.Config.ID,
   385  			"task_name", handle.Config.Name,
   386  		)
   387  		return nil
   388  	}
   389  
   390  	var taskState TaskState
   391  	if err := handle.GetDriverState(&taskState); err != nil {
   392  		d.logger.Error("failed to decode taskConfig state from handle", "error", err, "task_id", handle.Config.ID)
   393  		return fmt.Errorf("failed to decode taskConfig state from handle: %v", err)
   394  	}
   395  
   396  	plugRC, err := pstructs.ReattachConfigToGoPlugin(taskState.ReattachConfig)
   397  	if err != nil {
   398  		d.logger.Error("failed to build ReattachConfig from taskConfig state", "error", err, "task_id", handle.Config.ID)
   399  		return fmt.Errorf("failed to build ReattachConfig from taskConfig state: %v", err)
   400  	}
   401  
   402  	execImpl, pluginClient, err := executor.ReattachToExecutor(plugRC,
   403  		d.logger.With("task_name", handle.Config.Name, "alloc_id", handle.Config.AllocID))
   404  	if err != nil {
   405  		d.logger.Error("failed to reattach to executor", "error", err, "task_id", handle.Config.ID)
   406  		return fmt.Errorf("failed to reattach to executor: %v", err)
   407  	}
   408  
   409  	h := &taskHandle{
   410  		exec:         execImpl,
   411  		pid:          taskState.Pid,
   412  		pluginClient: pluginClient,
   413  		taskConfig:   taskState.TaskConfig,
   414  		procState:    drivers.TaskStateRunning,
   415  		startedAt:    taskState.StartedAt,
   416  		exitResult:   &drivers.ExitResult{},
   417  		logger:       d.logger,
   418  	}
   419  
   420  	d.tasks.Set(taskState.TaskConfig.ID, h)
   421  
   422  	go h.run()
   423  	return nil
   424  }
   425  
   426  func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) {
   427  	if _, ok := d.tasks.Get(cfg.ID); ok {
   428  		return nil, nil, fmt.Errorf("task with ID %q already started", cfg.ID)
   429  	}
   430  
   431  	var driverConfig TaskConfig
   432  	if err := cfg.DecodeDriverConfig(&driverConfig); err != nil {
   433  		return nil, nil, fmt.Errorf("failed to decode driver config: %v", err)
   434  	}
   435  
   436  	if err := driverConfig.validate(); err != nil {
   437  		return nil, nil, fmt.Errorf("failed driver config validation: %v", err)
   438  	}
   439  
   440  	if driverConfig.Class == "" && driverConfig.JarPath == "" {
   441  		return nil, nil, fmt.Errorf("jar_path or class must be specified")
   442  	}
   443  
   444  	absPath, err := GetAbsolutePath("java")
   445  	if err != nil {
   446  		return nil, nil, fmt.Errorf("failed to find java binary: %s", err)
   447  	}
   448  
   449  	args := javaCmdArgs(driverConfig)
   450  
   451  	d.logger.Info("starting java task", "driver_cfg", hclog.Fmt("%+v", driverConfig), "args", args)
   452  
   453  	handle := drivers.NewTaskHandle(taskHandleVersion)
   454  	handle.Config = cfg
   455  
   456  	pluginLogFile := filepath.Join(cfg.TaskDir().Dir, "executor.out")
   457  	executorConfig := &executor.ExecutorConfig{
   458  		LogFile:     pluginLogFile,
   459  		LogLevel:    "debug",
   460  		FSIsolation: driverCapabilities.FSIsolation == drivers.FSIsolationChroot,
   461  	}
   462  
   463  	exec, pluginClient, err := executor.CreateExecutor(
   464  		d.logger.With("task_name", handle.Config.Name, "alloc_id", handle.Config.AllocID),
   465  		d.nomadConfig, executorConfig)
   466  	if err != nil {
   467  		return nil, nil, fmt.Errorf("failed to create executor: %v", err)
   468  	}
   469  
   470  	user := cfg.User
   471  	if user == "" {
   472  		user = "nobody"
   473  	}
   474  
   475  	if cfg.DNS != nil {
   476  		dnsMount, err := resolvconf.GenerateDNSMount(cfg.TaskDir().Dir, cfg.DNS)
   477  		if err != nil {
   478  			return nil, nil, fmt.Errorf("failed to build mount for resolv.conf: %v", err)
   479  		}
   480  		cfg.Mounts = append(cfg.Mounts, dnsMount)
   481  	}
   482  
   483  	caps, err := capabilities.Calculate(
   484  		capabilities.NomadDefaults(), d.config.AllowCaps, driverConfig.CapAdd, driverConfig.CapDrop,
   485  	)
   486  	if err != nil {
   487  		return nil, nil, err
   488  	}
   489  	d.logger.Debug("task capabilities", "capabilities", caps)
   490  
   491  	execCmd := &executor.ExecCommand{
   492  		Cmd:              absPath,
   493  		Args:             args,
   494  		Env:              cfg.EnvList(),
   495  		User:             user,
   496  		ResourceLimits:   true,
   497  		Resources:        cfg.Resources,
   498  		TaskDir:          cfg.TaskDir().Dir,
   499  		StdoutPath:       cfg.StdoutPath,
   500  		StderrPath:       cfg.StderrPath,
   501  		Mounts:           cfg.Mounts,
   502  		Devices:          cfg.Devices,
   503  		NetworkIsolation: cfg.NetworkIsolation,
   504  		ModePID:          executor.IsolationMode(d.config.DefaultModePID, driverConfig.ModePID),
   505  		ModeIPC:          executor.IsolationMode(d.config.DefaultModeIPC, driverConfig.ModeIPC),
   506  		Capabilities:     caps,
   507  	}
   508  
   509  	ps, err := exec.Launch(execCmd)
   510  	if err != nil {
   511  		pluginClient.Kill()
   512  		return nil, nil, fmt.Errorf("failed to launch command with executor: %v", err)
   513  	}
   514  
   515  	h := &taskHandle{
   516  		exec:         exec,
   517  		pid:          ps.Pid,
   518  		pluginClient: pluginClient,
   519  		taskConfig:   cfg,
   520  		procState:    drivers.TaskStateRunning,
   521  		startedAt:    time.Now().Round(time.Millisecond),
   522  		logger:       d.logger,
   523  	}
   524  
   525  	driverState := TaskState{
   526  		ReattachConfig: pstructs.ReattachConfigFromGoPlugin(pluginClient.ReattachConfig()),
   527  		Pid:            ps.Pid,
   528  		TaskConfig:     cfg,
   529  		StartedAt:      h.startedAt,
   530  	}
   531  
   532  	if err := handle.SetDriverState(&driverState); err != nil {
   533  		d.logger.Error("failed to start task, error setting driver state", "error", err)
   534  		exec.Shutdown("", 0)
   535  		pluginClient.Kill()
   536  		return nil, nil, fmt.Errorf("failed to set driver state: %v", err)
   537  	}
   538  
   539  	d.tasks.Set(cfg.ID, h)
   540  	go h.run()
   541  	return handle, nil, nil
   542  }
   543  
   544  func javaCmdArgs(driverConfig TaskConfig) []string {
   545  	var args []string
   546  
   547  	// Look for jvm options
   548  	if len(driverConfig.JvmOpts) != 0 {
   549  		args = append(args, driverConfig.JvmOpts...)
   550  	}
   551  
   552  	// Add the classpath
   553  	if driverConfig.ClassPath != "" {
   554  		args = append(args, "-cp", driverConfig.ClassPath)
   555  	}
   556  
   557  	// Add the jar
   558  	if driverConfig.JarPath != "" {
   559  		args = append(args, "-jar", driverConfig.JarPath)
   560  	}
   561  
   562  	// Add the class
   563  	if driverConfig.Class != "" {
   564  		args = append(args, driverConfig.Class)
   565  	}
   566  
   567  	// Add any args
   568  	if len(driverConfig.Args) != 0 {
   569  		args = append(args, driverConfig.Args...)
   570  	}
   571  
   572  	return args
   573  }
   574  
   575  func (d *Driver) WaitTask(ctx context.Context, taskID string) (<-chan *drivers.ExitResult, error) {
   576  	handle, ok := d.tasks.Get(taskID)
   577  	if !ok {
   578  		return nil, drivers.ErrTaskNotFound
   579  	}
   580  
   581  	ch := make(chan *drivers.ExitResult)
   582  	go d.handleWait(ctx, handle, ch)
   583  
   584  	return ch, nil
   585  }
   586  
   587  func (d *Driver) handleWait(ctx context.Context, handle *taskHandle, ch chan *drivers.ExitResult) {
   588  	defer close(ch)
   589  	var result *drivers.ExitResult
   590  	ps, err := handle.exec.Wait(ctx)
   591  	if err != nil {
   592  		result = &drivers.ExitResult{
   593  			Err: fmt.Errorf("executor: error waiting on process: %v", err),
   594  		}
   595  	} else {
   596  		result = &drivers.ExitResult{
   597  			ExitCode: ps.ExitCode,
   598  			Signal:   ps.Signal,
   599  		}
   600  	}
   601  
   602  	select {
   603  	case <-ctx.Done():
   604  		return
   605  	case <-d.ctx.Done():
   606  		return
   607  	case ch <- result:
   608  	}
   609  }
   610  
   611  func (d *Driver) StopTask(taskID string, timeout time.Duration, signal string) error {
   612  	handle, ok := d.tasks.Get(taskID)
   613  	if !ok {
   614  		return drivers.ErrTaskNotFound
   615  	}
   616  
   617  	if err := handle.exec.Shutdown(signal, timeout); err != nil {
   618  		if handle.pluginClient.Exited() {
   619  			return nil
   620  		}
   621  		return fmt.Errorf("executor Shutdown failed: %v", err)
   622  	}
   623  
   624  	return nil
   625  }
   626  
   627  func (d *Driver) DestroyTask(taskID string, force bool) error {
   628  	handle, ok := d.tasks.Get(taskID)
   629  	if !ok {
   630  		return drivers.ErrTaskNotFound
   631  	}
   632  
   633  	if handle.IsRunning() && !force {
   634  		return fmt.Errorf("cannot destroy running task")
   635  	}
   636  
   637  	if !handle.pluginClient.Exited() {
   638  		if err := handle.exec.Shutdown("", 0); err != nil {
   639  			handle.logger.Error("destroying executor failed", "error", err)
   640  		}
   641  
   642  		handle.pluginClient.Kill()
   643  	}
   644  
   645  	d.tasks.Delete(taskID)
   646  	return nil
   647  }
   648  
   649  func (d *Driver) InspectTask(taskID string) (*drivers.TaskStatus, error) {
   650  	handle, ok := d.tasks.Get(taskID)
   651  	if !ok {
   652  		return nil, drivers.ErrTaskNotFound
   653  	}
   654  
   655  	return handle.TaskStatus(), nil
   656  }
   657  
   658  func (d *Driver) TaskStats(ctx context.Context, taskID string, interval time.Duration) (<-chan *drivers.TaskResourceUsage, error) {
   659  	handle, ok := d.tasks.Get(taskID)
   660  	if !ok {
   661  		return nil, drivers.ErrTaskNotFound
   662  	}
   663  
   664  	return handle.exec.Stats(ctx, interval)
   665  }
   666  
   667  func (d *Driver) TaskEvents(ctx context.Context) (<-chan *drivers.TaskEvent, error) {
   668  	return d.eventer.TaskEvents(ctx)
   669  }
   670  
   671  func (d *Driver) SignalTask(taskID string, signal string) error {
   672  	handle, ok := d.tasks.Get(taskID)
   673  	if !ok {
   674  		return drivers.ErrTaskNotFound
   675  	}
   676  
   677  	sig := os.Interrupt
   678  	if s, ok := signals.SignalLookup[signal]; ok {
   679  		sig = s
   680  	} else {
   681  		d.logger.Warn("unknown signal to send to task, using SIGINT instead", "signal", signal, "task_id", handle.taskConfig.ID)
   682  
   683  	}
   684  	return handle.exec.Signal(sig)
   685  }
   686  
   687  func (d *Driver) ExecTask(taskID string, cmd []string, timeout time.Duration) (*drivers.ExecTaskResult, error) {
   688  	if len(cmd) == 0 {
   689  		return nil, fmt.Errorf("error cmd must have at least one value")
   690  	}
   691  	handle, ok := d.tasks.Get(taskID)
   692  	if !ok {
   693  		return nil, drivers.ErrTaskNotFound
   694  	}
   695  
   696  	out, exitCode, err := handle.exec.Exec(time.Now().Add(timeout), cmd[0], cmd[1:])
   697  	if err != nil {
   698  		return nil, err
   699  	}
   700  
   701  	return &drivers.ExecTaskResult{
   702  		Stdout: out,
   703  		ExitResult: &drivers.ExitResult{
   704  			ExitCode: exitCode,
   705  		},
   706  	}, nil
   707  }
   708  
   709  var _ drivers.ExecTaskStreamingRawDriver = (*Driver)(nil)
   710  
   711  func (d *Driver) ExecTaskStreamingRaw(ctx context.Context,
   712  	taskID string,
   713  	command []string,
   714  	tty bool,
   715  	stream drivers.ExecTaskStream) error {
   716  
   717  	if len(command) == 0 {
   718  		return fmt.Errorf("error cmd must have at least one value")
   719  	}
   720  	handle, ok := d.tasks.Get(taskID)
   721  	if !ok {
   722  		return drivers.ErrTaskNotFound
   723  	}
   724  
   725  	return handle.exec.ExecStreaming(ctx, command, tty, stream)
   726  }
   727  
   728  // GetAbsolutePath returns the absolute path of the passed binary by resolving
   729  // it in the path and following symlinks.
   730  func GetAbsolutePath(bin string) (string, error) {
   731  	lp, err := exec.LookPath(bin)
   732  	if err != nil {
   733  		return "", fmt.Errorf("failed to resolve path to %q executable: %v", bin, err)
   734  	}
   735  
   736  	return filepath.EvalSymlinks(lp)
   737  }