Skip to content

Commit

Permalink
Merge pull request #1481 from steiler/execOnHost
Browse files Browse the repository at this point in the history
allow exec on host
  • Loading branch information
hellt committed Jul 26, 2023
2 parents 80a6554 + 4ece4f8 commit a21b0b6
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 13 deletions.
21 changes: 21 additions & 0 deletions docs/manual/kinds/host.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
search:
boost: 4
---

# Host

A node of kind `host` represents the containerlab host the labs are running on. It is a special node that is implicitly used when nodes have links connected to the host - see [host links](../network.md#host-links).

But there is a use case when users might want to define the node of kind `host` explicitly in the topology. For example, when some commands need to be executed on the host for the lab to function.

In such case, the following topology definition can be used:

```yaml
h1:
kind: host
exec:
- ip link set dev enp0s3 up
```
In the above example, the node `h1` is defined as a node of kind `host` and the `exec` option is used to run the command `ip link set dev enp0s3 up` in the containerlab host. Of course, the command can be any other command that is required for the lab to function.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ nav:
- RARE/freeRtr: manual/kinds/rare-freertr.md
- Openvswitch bridge: manual/kinds/ovs-bridge.md
- External container: manual/kinds/ext-container.md
- Host: manual/kinds/host.md
- Configuration artifacts: manual/conf-artifacts.md
- Network wiring concepts: manual/network.md
- Packet capture & Wireshark: manual/wireshark.md
Expand Down
83 changes: 70 additions & 13 deletions nodes/host/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@
package host

import (
"bytes"
"context"
"os"
"path/filepath"
"regexp"

osexec "os/exec"

log "github.com/sirupsen/logrus"
cExec "github.com/srl-labs/containerlab/clab/exec"
"github.com/srl-labs/containerlab/labels"
"github.com/srl-labs/containerlab/nodes"
"github.com/srl-labs/containerlab/runtime"
"github.com/srl-labs/containerlab/types"
Expand Down Expand Up @@ -47,31 +53,82 @@ func (*host) WithMgmtNet(*types.MgmtNet) {}
// UpdateConfigWithRuntimeInfo is a noop for hosts.
func (*host) UpdateConfigWithRuntimeInfo(_ context.Context) error { return nil }

// getOSRelease returns the OS release of the host by inspecting /etc/*-release.
func getOSRelease() string {
image := "N/A"

matches, err := filepath.Glob("/etc/*-release")
if err != nil {
return image
}
dat, err := os.ReadFile(matches[0])
if err != nil {
return image
}
// DISTRIB_DESCRIPTION exists in lsb-release, but not os-release.
// the lsb-release is coming first in the glob, so it works.
re := regexp.MustCompile(`DISTRIB_DESCRIPTION="(.*)"`)

regexres := re.FindSubmatch(dat)

return string(regexres[1])
}

// GetContainers returns a basic skeleton of a container to enable graphing of hosts kinds.
func (*host) GetContainers(_ context.Context) ([]runtime.GenericContainer, error) {

image := getOSRelease()

return []runtime.GenericContainer{
{
Names: []string{"Host"},
State: "running",
ID: "N/A",
ShortID: "N/A",
Image: "-",
Status: "running",
Image: image,
Labels: map[string]string{
labels.NodeKind: kindnames[0],
},
Status: "running",
NetworkSettings: runtime.GenericMgmtIPs{
IPv4addr: "N/A",
IPv4pLen: 0,
IPv4Gw: "N/A",
IPv6addr: "N/A",
IPv6pLen: 0,
IPv6Gw: "N/A",
IPv4addr: "",
//IPv4pLen: 0,
IPv4Gw: "",
IPv6addr: "",
//IPv6pLen: 0,
IPv6Gw: "",
},
},
}, nil
}

// RunExec is a noop for host kind.
func (n *host) RunExec(_ context.Context, _ *cExec.ExecCmd) (*cExec.ExecResult, error) {
log.Warnf("Exec operation is not implemented for kind %q", n.Config().Kind)
// RunExec runs commands on the container host
func (*host) RunExec(ctx context.Context, e *cExec.ExecCmd) (*cExec.ExecResult, error) {
// retireve the command with its arguments
command := e.GetCmd()

// execute the command along with the context
cmd := osexec.CommandContext(ctx, command[0], command[1:]...)

// create buffers for the output (stdout/stderr)
var outBuf, errBuf bytes.Buffer

// connect stdout and stderr to the buffers
cmd.Stdout = &outBuf
cmd.Stderr = &errBuf

// execute the command synchronously
err := cmd.Run()
if err != nil {
return nil, err
}

// create result struct
execResult := cExec.NewExecResult(e)
// set the result fields in the exec struct
execResult.SetReturnCode(cmd.ProcessState.ExitCode())
execResult.SetStdOut(outBuf.Bytes())
execResult.SetStdErr(errBuf.Bytes())

return nil, cExec.ErrRunExecNotSupported
return execResult, nil
}

0 comments on commit a21b0b6

Please sign in to comment.