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 }