Skip to content

Commit

Permalink
feat: module shell
Browse files Browse the repository at this point in the history
The shell module executes shell commands on the dutagent.

Signed-off-by: Jens Drenhaus <jens.drenhaus@blindspot.software>
  • Loading branch information
jensdrenhaus committed Nov 28, 2024
1 parent dc2d224 commit 2e00469
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmds/dutagent/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ package main
import (
_ "github.com/BlindspotSoftware/dutctl/pkg/module/agent"
_ "github.com/BlindspotSoftware/dutctl/pkg/module/dummy"
_ "github.com/BlindspotSoftware/dutctl/pkg/module/shell"
_ "github.com/BlindspotSoftware/dutctl/pkg/module/time"
)
23 changes: 23 additions & 0 deletions pkg/module/shell/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
The _shell_ package provides a single module:

# Shell

This module executes a shell command on the DUT agent.

```
ARGUMENTS:
[command-string]
The shell is executed with the -c flag and the the first argument to the module is passed as the command-string.
So make sure to quote the command-string if it contains spaces or special characters. E.g.: "ls -l /tmp"
The shell module is non-interactive and does not support stdin.
```

See [shell-example-cfg.yml](./shell-example-cfg.yml) for examples.

## Configuration Options

| Option | Value | Description
|----------|--------|------------------------------------|
| path | string | Path is th path to the shell executable on the dutagent. Defaults to the system default shell |
| quiet | bool | Suppress forwarding stout, but not sterr |
32 changes: 32 additions & 0 deletions pkg/module/shell/shell-example-cfg.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
version: 0
devices:
self:
desc: 'This device represents no DUT but the dutagent itself to demonstrate the shell module.
However, the shell module can of corse be used to take actions on the DUT, depending on what the shell command is.'
cmds:
greet:
desc: 'Demonstrates the shell module by writing a greeting to a file, giving the user the chance to edit,
then reading it back and deleting the file. E.g try: dutctl self greet "echo 'Hello World' >> /tmp/shell-test.txt"'
modules:
- module: shell
options:
path: bash
quiet: true
args:
- "echo 'Greetings from the shell module!\n\n' > /tmp/shell-test.txt"
- module: shell
main: true
- module: shell
options:
path: sh
quiet: true
args:
- "ls /tmp/shell-test.txt"
- module: shell
options:
path: /bin/bash
args:
- "cat /tmp/shell-test.txt && rm -f /tmp/shell-test.txt"


112 changes: 112 additions & 0 deletions pkg/module/shell/shell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2024 Blindspot Software
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package shell provides a dutagent module that executes shell commands.
package shell

import (
"context"
"fmt"
"log"
"os/exec"
"strings"

"github.com/BlindspotSoftware/dutctl/pkg/module"
)

func init() {
module.Register(module.Record{
ID: "shell",
New: func() module.Module { return &Shell{} },
})
}

// DefaultShellPath is the default path to the shell executable on the dutagent.
const DefaultShellPath = "/bin/sh"

// Shell is a module that executes commands on the dutagent. It is non-interactive and does not support stdin.
// The shell command is executed with the -c flag and the command to execute as an argument.
type Shell struct {
Path string // Path is th path to the shell executable on the dutagent. If unset, [DefaultShellPath] is used.
Quiet bool // Quiet suppresses stdout from the shell command, stderr will be forwarded regardless.
}

// Ensure implementing the Module interface.
var _ module.Module = &Shell{}

const abstract = `Execute a shell command on the DUT agent
`
const usage = `
ARGUMENTS:
[command-string]
`
const description = `

Check failure on line 45 in pkg/module/shell/shell.go

View workflow job for this annotation

GitHub Actions / Lint

Duplicate words (the) found (dupword)
The shell is executed with the -c flag and the the first argument to the module is passed as the command-string.
So make sure to quote the command-string if it contains spaces or special characters. E.g.: "ls -l /tmp"
The shell module is non-interactive and does not support stdin.
`

func (s *Shell) Help() string {
log.Println("shell module: Help called")

help := strings.Builder{}
help.WriteString(abstract)
help.WriteString(usage)
help.WriteString(fmt.Sprintf("The used shell is %q.\n", s.Path))
help.WriteString(description)

if s.Quiet {
help.WriteString("NOTE: The module is configured to quiet mode. Only stderr is forwarded.\n")
}

return help.String()
}

func (s *Shell) Init() error {
log.Println("shell module: Init called")

if s.Path == "" {
s.Path = DefaultShellPath
}

_, err := exec.LookPath(s.Path)
if err != nil {
return fmt.Errorf("shell path %q: %w", s.Path, err)
}

return nil
}

func (s *Shell) Deinit() error {
log.Println("shell module: Deinit called")

return nil
}

func (s *Shell) Run(_ context.Context, sesh module.Session, args ...string) error {
log.Println("shell module: Run called")

if len(args) == 0 {
return fmt.Errorf("missing command-string")
}

if len(args) > 1 {
return fmt.Errorf("too many arguments - if the command-string contains spaces or special characters, quote it")
}

cmdStr := args[0]
binary := s.Path
_, stdout, stderr := sesh.Console()

shell := exec.Command(binary, "-c", cmdStr)

if !s.Quiet {
shell.Stdout = stdout
}

shell.Stderr = stderr

return shell.Run()
}

0 comments on commit 2e00469

Please sign in to comment.