Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/smoke-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
- "20*.robot"
- "21*.robot"
- "22*.robot"
- "23*.robot"

# allow podman job to fail, since it started to fail on github actions
continue-on-error: ${{ matrix.runtime == 'podman' }}
Expand Down
71 changes: 71 additions & 0 deletions clab/clab.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
depMgr "github.com/srl-labs/containerlab/clab/dependency_manager"
"github.com/srl-labs/containerlab/clab/exec"
errs "github.com/srl-labs/containerlab/errors"
clabels "github.com/srl-labs/containerlab/labels"
"github.com/srl-labs/containerlab/links"
"github.com/srl-labs/containerlab/nodes"
"github.com/srl-labs/containerlab/runtime"
Expand Down Expand Up @@ -221,6 +222,46 @@ func WithTopoPath(path, varsFile string) ClabOption {
}
}

// WithTopoFromLab loads the topology file path based on a running lab name.
// The lab name is used to look up the container labels of a running lab and
// derive the topology file location. It falls back to WithTopoPath once the
// topology path is discovered.
func WithTopoFromLab(labName string) ClabOption {
return func(c *CLab) error {
if labName == "" {
return fmt.Errorf("lab name is required to derive topology path")
}

ctx := context.Background()
filter := []*types.GenericFilter{
{
FilterType: "label",
Field: clabels.Containerlab,
Operator: "=",
Match: labName,
},
}

containers, err := c.globalRuntime().ListContainers(ctx, filter)
if err != nil {
return err
}

if len(containers) == 0 {
return fmt.Errorf("lab '%s' not found - no running containers", labName)
}

topoFile := containers[0].Labels[clabels.TopoFile]
if topoFile == "" {
return fmt.Errorf("could not determine topology file from container labels")
}

log.Debugf("found topology file for lab %s: %s", labName, topoFile)

return WithTopoPath(topoFile, "")(c)
}
}

// ProcessTopoPath takes a topology path, which might be the path to a directory or a file
// or stdin or a URL and returns the topology file name if found.
func (c *CLab) ProcessTopoPath(path string) (string, error) {
Expand Down Expand Up @@ -324,6 +365,36 @@ func downloadTopoFile(url, tempDir string) (string, error) {
return tmpFile.Name(), err
}

// GetTopology creates a containerlab instance using either a topology file path
// or a running lab name. It returns the initialized CLab structure with the
// topology loaded.
func GetTopology(ctx context.Context, topoPath, labName, varsFile, runtimeName string, debug bool, timeout time.Duration, graceful bool) (*CLab, error) {
if topoPath == "" && labName == "" {
cwd, err := os.Getwd()
if err == nil {
topoPath = cwd
}
}

opts := []ClabOption{
WithTimeout(timeout),
WithRuntime(runtimeName, &runtime.RuntimeConfig{
Debug: debug,
Timeout: timeout,
GracefulShutdown: graceful,
}),
WithDebug(debug),
}

if topoPath != "" {
opts = append(opts, WithTopoPath(topoPath, varsFile))
} else {
opts = append(opts, WithTopoFromLab(labName))
}

return NewContainerLab(opts...)
}

// WithNodeFilter option sets a filter for nodes to be deployed.
// A filter is a list of node names to be deployed,
// names are provided exactly as they are listed in the topology file.
Expand Down
44 changes: 44 additions & 0 deletions cmd/common/tools.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package common

import (
"path/filepath"
"strings"

clabels "github.com/srl-labs/containerlab/labels"
)

// createLabels creates container labels
func CreateLabels(labName, containerName, owner, toolType string) map[string]string {
shortName := strings.Replace(containerName, "clab-"+labName+"-", "", 1)

labels := map[string]string{
"containerlab": labName,
"clab-node-name": shortName,
"clab-node-longname": containerName,
"clab-node-kind": "linux",
"clab-node-group": "",
"clab-node-type": "tool",
"tool-type": toolType,
}

// Add topology file path
if Topo != "" {
absPath, err := filepath.Abs(Topo)
if err == nil {
labels["clab-topo-file"] = absPath
} else {
labels["clab-topo-file"] = Topo
}

// Set node lab directory
baseDir := filepath.Dir(Topo)
labels["clab-node-lab-dir"] = filepath.Join(baseDir, "clab-"+labName, shortName)
}

// Add owner label if available
if owner != "" {
labels[clabels.Owner] = owner
}

return labels
}
80 changes: 45 additions & 35 deletions cmd/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,11 @@ func destroyFn(_ *cobra.Command, _ []string) error {
}
}

if len(errs) != 0 {
return fmt.Errorf("error(s) occurred during the deletion. Check log messages")
}

// Additional step: Remove any SSHX containers associated with destroyed labs
// Remove any tool containers associated with destroyed labs
if !all && len(labs) > 0 {
// Get the lab name from the first lab
labName := labs[0].Config.Name
log.Debugf("Looking for SSHX containers with lab name: %s", labName)
log.Debugf("Looking for tool containers with lab name: %s", labName)

// Initialize runtime
_, rinit, err := clab.RuntimeInitializer(common.Runtime)
Expand All @@ -186,35 +182,9 @@ func destroyFn(_ *cobra.Command, _ []string) error {
}))

if err == nil {
// Look for SSHX containers with matching lab label
sshxFilter := []*types.GenericFilter{
{
FilterType: "label",
Field: "tool-type",
Operator: "=",
Match: "sshx",
},
{
FilterType: "label",
Field: labels.Containerlab,
Operator: "=",
Match: labName,
},
}

sshxContainers, err := rt.ListContainers(ctx, sshxFilter)
if err == nil && len(sshxContainers) > 0 {
log.Infof("Found %d SSHX containers associated with lab %s", len(sshxContainers), labName)
for _, container := range sshxContainers {
containerName := strings.TrimPrefix(container.Names[0], "/")
log.Infof("Removing SSHX container: %s", containerName)
if err := rt.DeleteContainer(ctx, containerName); err != nil {
log.Warnf("Failed to remove SSHX container %s: %v", containerName, err)
} else {
log.Infof("SSHX container %s removed successfully", containerName)
}
}
}
// Clean up any tool containers (SSHX, GoTTY, etc.)
removeToolContainers(ctx, rt, labName, "sshx")
removeToolContainers(ctx, rt, labName, "gotty")
}
}
}
Expand All @@ -226,6 +196,46 @@ func destroyFn(_ *cobra.Command, _ []string) error {
return nil
}

// removeToolContainers removes containers of a specific tool type associated with a lab
func removeToolContainers(ctx context.Context, rt runtime.ContainerRuntime, labName, toolType string) {
toolFilter := []*types.GenericFilter{
{
FilterType: "label",
Field: "tool-type",
Operator: "=",
Match: toolType,
},
{
FilterType: "label",
Field: labels.Containerlab,
Operator: "=",
Match: labName,
},
}

containers, err := rt.ListContainers(ctx, toolFilter)
if err != nil {
log.Warnf("Failed to list %s containers: %v", toolType, err)
return
}

if len(containers) == 0 {
log.Debugf("No %s containers found for lab %s", toolType, labName)
return
}

log.Infof("Found %d %s containers associated with lab %s", len(containers), toolType, labName)
for _, container := range containers {
containerName := strings.TrimPrefix(container.Names[0], "/")
log.Infof("Removing %s container: %s", toolType, containerName)
if err := rt.DeleteContainer(ctx, containerName); err != nil {
log.Warnf("Failed to remove %s container %s: %v", toolType, containerName, err)
} else {
log.Infof("%s container %s removed successfully", toolType, containerName)
}
}
}

func destroyLab(ctx context.Context, c *clab.CLab) (err error) {
return c.Destroy(ctx, maxWorkers, keepMgmtNet)
}
Expand Down
Loading
Loading