Skip to content

Commit

Permalink
graph drawio: Pull strategy and Interactive mode support (#2291)
Browse files Browse the repository at this point in the history
* Pull strategy and Interactive mode support

* update go.mod and go.sum

* add smoke test for drawio graph generation

* added smoke test for graph drawio

* make non-interactive env (ci/cd) happy

* using 03-linux-nodes-to-bridge-and-host as lab
  • Loading branch information
FloSch62 authored Nov 12, 2024
1 parent aab8d2d commit 14bcb1d
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 7 deletions.
1 change: 1 addition & 0 deletions .github/workflows/smoke-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
- "17*.robot"
- "18*.robot"
- "19*.robot"
- "20*.robot"
# allow podman job to fail, since it started to fail on github actions
continue-on-error: ${{ matrix.runtime == 'podman' }}
steps:
Expand Down
82 changes: 76 additions & 6 deletions clab/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ import (
"embed"
"fmt"
"html/template"
"io"
"io/fs"
"net/http"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"

"github.com/awalterschulze/gographviz"
"github.com/creack/pty"
log "github.com/sirupsen/logrus"
e "github.com/srl-labs/containerlab/errors"
"github.com/srl-labs/containerlab/internal/mermaid"
Expand All @@ -23,6 +27,7 @@ import (
"github.com/srl-labs/containerlab/runtime"
"github.com/srl-labs/containerlab/types"
"github.com/srl-labs/containerlab/utils"
"golang.org/x/term"
)

type GraphTopo struct {
Expand Down Expand Up @@ -298,10 +303,34 @@ func (c *CLab) ServeTopoGraph(tmpl, staticDir, srv string, topoD TopoData) error
func (c *CLab) GenerateDrawioDiagram(version string, additionalFlags []string) error {
topoFile := c.TopoPaths.TopologyFilenameBase()

imageName := fmt.Sprintf("ghcr.io/srl-labs/clab-io-draw:%s", version)

// If version is "latest", check for newer image and pull if necessary
if version == "latest" {
log.Info("Checking for updates to the latest Docker image...")
pullCmd := exec.Command("docker", "pull", imageName)
pullOut, pullErr := pullCmd.CombinedOutput()
pullOutput := string(pullOut)
if pullErr != nil {
log.Errorf("Failed to pull the latest image: %v", pullErr)
log.Errorf("Pull command output: %s", pullOutput)
return fmt.Errorf("failed to pull the latest image: %w\nOutput: %s", pullErr, pullOutput)
}

// Check if the image was updated or is up-to-date
if strings.Contains(pullOutput, "Downloaded newer image") {
log.Infof("Docker image updated to the latest version.")
} else if strings.Contains(pullOutput, "Image is up to date") {
log.Infof("Docker image is already the latest version.")
} else {
log.Warnf("Unexpected output from docker pull command: %s", pullOutput)
}
}

cmdArgs := []string{
"docker", "run",
"docker", "run", "-it",
"-v", fmt.Sprintf("%s:/data", c.TopoPaths.TopologyFileDir()),
fmt.Sprintf("ghcr.io/srl-labs/clab-io-draw:%s", version),
imageName,
"-i", topoFile,
}

Expand All @@ -313,16 +342,57 @@ func (c *CLab) GenerateDrawioDiagram(version string, additionalFlags []string) e
cmdArgs = append(cmdArgs, parts...)
}

// Create the command
cmd := exec.Command("sudo", cmdArgs...)

out, err := cmd.CombinedOutput()
// Start the command with a pseudo-terminal (PTY)
ptmx, err := pty.Start(cmd)
if err != nil {
log.Errorf("Failed to start command with PTY: %v", err)
return fmt.Errorf("failed to start command with PTY: %w", err)
}
defer func() { _ = ptmx.Close() }() // Best effort to close the PTY

// Check if os.Stdin is a terminal
if term.IsTerminal(int(os.Stdin.Fd())) {
// Handle PTY size changes
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
for range ch {
if err := pty.InheritSize(os.Stdin, ptmx); err != nil {
log.Errorf("Error resizing PTY: %v", err)
}
}
}()
ch <- syscall.SIGWINCH // Initial resize

// Set the terminal to raw mode
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
log.Errorf("Failed to set terminal to raw mode: %v", err)
return fmt.Errorf("failed to set terminal to raw mode: %w", err)
}
defer func() {
_ = term.Restore(int(os.Stdin.Fd()), oldState) // Best effort to restore
}()

// Copy stdin to the PTY and the PTY to stdout
go func() { _, _ = io.Copy(ptmx, os.Stdin) }()
}

// Always copy the PTY output to our program's stdout
// This ensures we capture the output regardless of TTY status
_, _ = io.Copy(os.Stdout, ptmx)

// Wait for the command to finish
err = cmd.Wait()
if err != nil {
log.Errorf("Command execution failed: %v", err)
log.Errorf("Command output: %s", string(out))
return fmt.Errorf("failed to generate diagram: %w\nOutput: %s", err, string(out))
return fmt.Errorf("failed to generate diagram: %w", err)
}

log.Infof("Diagram created successfully. Output: %s", string(out))
log.Infof("Diagram created successfully.")

return nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ require (
github.com/containers/storage v1.55.1 // indirect
github.com/coreos/go-iptables v0.7.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09 // indirect
github.com/creack/pty v1.1.21 // indirect
github.com/creack/pty v1.1.24 // indirect
github.com/cyphar/filepath-securejoin v0.3.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/disiqueira/gotree/v3 v3.0.2 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f h1:eHnXnuK47UlSTOQexbzxAZfekVz6i+LKRdj1CU5DPaM=
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
Expand Down
45 changes: 45 additions & 0 deletions tests/01-smoke/20-graph-generation.robot
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
*** Settings ***
Documentation This test ensures that the `clab graph` command generates a diagram successfully,
... and that the code handling the Docker image updates works as expected.
Library OperatingSystem
Library Process
Resource ../common.robot

Suite Setup Setup
Suite Teardown Teardown

*** Variables ***
${lab-file} 03-linux-nodes-to-bridge-and-host.clab.yml
${lab-name} graph-test
${runtime} docker
${diagram-file} 03-linux-nodes-to-bridge-and-host.clab.drawio

*** Test Cases ***
Generate Diagram for ${lab-name} Lab
[Documentation] This test runs `clab graph` to generate a diagram and verifies success.
# Run the 'clab graph' command to generate the diagram
${output}= Run Process sudo -E ${CLAB_BIN} graph -t ${CURDIR}/${lab-file} --drawio --drawio-args\=--theme nokia_modern_dark
... shell=True stdout=PIPE stderr=PIPE

Log ${output.stdout}
Log ${output.stderr}

# Ensure the command completed successfully
Should Be Equal As Integers ${output.rc} 0

# Check for expected output messages
Should Contain ${output.stdout} Diagram created successfully.

# Check that the diagram file was created
File Should Exist ${CURDIR}/${diagram-file}

*** Keywords ***
Setup
# Skip this test suite for podman for now
Skip If '${runtime}' == 'podman'

Teardown
# Clean up by destroying the lab and removing the diagram file
Remove File ${CURDIR}/${diagram-file}

0 comments on commit 14bcb1d

Please sign in to comment.