Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8e938e2
first set of cmds for c9s runtime
FloSch62 Jun 5, 2026
dd7b06d
complete most relevant commands
FloSch62 Jun 5, 2026
2c2454b
add lab owner
FloSch62 Jun 5, 2026
20eab8a
c9s runtime docs
FloSch62 Jun 5, 2026
8f912cc
tests for c9s runtime
FloSch62 Jun 5, 2026
3d91414
fix: preserve root escalation for container runtimes
FloSch62 Jun 5, 2026
7cabfef
fix clabernetes runtime edge cases
FloSch62 Jun 5, 2026
5381d29
align labruntime imports with style
FloSch62 Jun 5, 2026
1c00243
Prevent clabernetes deploy from updating existing labs
FloSch62 Jun 5, 2026
0fe8db1
Split clabernetes runtime implementation
FloSch62 Jun 5, 2026
c2781df
Support Docker-style labs in clabernetes runtime
FloSch62 Jun 5, 2026
4cb6b7d
Harden clabernetes runtime edge cases and cleaning
FloSch62 Jun 10, 2026
9d4706e
Merge remote-tracking branch 'origin/main' into c9s
FloSch62 Jun 10, 2026
9136ccf
Merge remote-tracking branch 'origin/main' into c9s
FloSch62 Aug 12, 2026
2d4bf8d
Update c9s runtime for Node and Link APIs
FloSch62 Aug 12, 2026
21a064c
Complete c9s Node and Link runtime support
FloSch62 Aug 12, 2026
d0fc529
Add per-lab namespaces to c9s runtime
FloSch62 Aug 12, 2026
dfca912
Reconcile existing c9s labs on deploy
FloSch62 Aug 12, 2026
b79d4fe
Harden clabernetes runtime compatibility
FloSch62 Aug 12, 2026
a98a401
fix: warn for lossy c9s topology fields
FloSch62 Aug 13, 2026
8564f46
fix: remove inferred gNMIc metrics port
FloSch62 Aug 13, 2026
b8f0ad1
fix: resolve clabernetes component containers
FloSch62 Aug 13, 2026
74d4383
feat: improve clabernetes deploy progress
FloSch62 Aug 17, 2026
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
11 changes: 11 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,15 @@ jobs:
uv_ver: ${{ needs.process-gitref.outputs.uv_ver }}
runtime: ${{ needs.process-gitref.outputs.runtime }}

clabernetes-tests:
uses: ./.github/workflows/clabernetes-tests.yml
needs:
- file-changes
- build-containerlab
- process-gitref
with:
py_ver: ${{ needs.process-gitref.outputs.py_ver }}

# sros-tests:
# uses: ./.github/workflows/sros-tests.yml
# needs:
Expand Down Expand Up @@ -464,6 +473,7 @@ jobs:
- ixiac-one-basic-tests
- vxlan-tests
- kind-tests
- clabernetes-tests
- srsim-tests
- fortigate-tests
- cisco_iol-tests
Expand Down Expand Up @@ -562,6 +572,7 @@ jobs:
- ext-container-tests
- vxlan-tests
- kind-tests
- clabernetes-tests
- srsim-tests
- cisco_iol-tests
- fortigate-tests
Expand Down
99 changes: 99 additions & 0 deletions .github/workflows/clabernetes-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: clabernetes-tests

"on":
workflow_call:
inputs:
py_ver:
required: true
type: string

jobs:
clabernetes-tests:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- uses: actions/download-artifact@v7
with:
name: containerlab

- name: Move containerlab to usr/bin
run: sudo mv ./containerlab /usr/bin/containerlab && sudo chown root:root /usr/bin/containerlab && sudo chmod 4755 /usr/bin/containerlab

- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "0.5.28"
enable-cache: true

- uses: actions/setup-python@v6
with:
python-version-file: "pyproject.toml"

- name: Install the project
run: uv sync --all-extras --dev

- name: Install kubectl
run: |
curl -L -o ./kubectl "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin/kubectl

- name: Install kind
run: |
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.32.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind

- name: Create kind cluster
run: |
cat > /tmp/c9s-kind-config.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".containerd]
discard_unpacked_layers = false
EOF

kind create cluster --name c9s --config /tmp/c9s-kind-config.yaml
kubectl wait --for=condition=Ready nodes --all --timeout=120s

- name: Install Clabernetes
run: |
docker run --network host --rm \
-v "${HOME}/.kube:/root/.kube" \
-v "${HOME}/.helm:/root/.helm" \
-v "${HOME}/.config/helm:/root/.config/helm" \
-v "${HOME}/.cache/helm:/root/.cache/helm" \
alpine/helm:3.18.2 \
upgrade --install --create-namespace --namespace c9s \
clabernetes oci://ghcr.io/clabernetes/clabernetes/clabernetes

kubectl -n c9s rollout status deploy/clabernetes-manager --timeout=180s

- name: Run Clabernetes Robot tests
run: |
bash ./tests/rf-run.sh clabernetes ./tests/14-clabernetes

- name: Upload test report
uses: actions/upload-artifact@v6
if: always()
with:
name: 14-clabernetes-log
path: ./tests/out/*.html

- name: Upload coverage
uses: actions/upload-artifact@v6
if: always()
with:
name: coverage-clabernetes-tests
path: /tmp/clab-tests/coverage/*
retention-days: 7
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ endif
ifndef suite
override suite = .
endif
.PHONY: robot-test
robot-test: build-with-podman-debug
sudo chown root:root $(BINARY) && sudo chmod 4755 $(BINARY)
CLAB_BIN=$(BINARY) $$PWD/tests/rf-run.sh $(runtime) $$PWD/tests/$(suite)
Expand Down
73 changes: 59 additions & 14 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/spf13/cobra"
clabconstants "github.com/srl-labs/containerlab/constants"
clabcore "github.com/srl-labs/containerlab/core"
clablabruntime "github.com/srl-labs/containerlab/labruntime"
clabutils "github.com/srl-labs/containerlab/utils"
)

Expand All @@ -34,6 +35,10 @@ func deployCmd(o *Options) (*cobra.Command, error) { //nolint: funlen
Aliases: []string{"dep", "apply"},
SilenceUsage: true,
PreRunE: func(_ *cobra.Command, _ []string) error {
if commandSkipsRoot(o.Global.Runtime) {
return nil
}

return clabutils.CheckAndGetRootPrivs()
},
RunE: func(cobraCmd *cobra.Command, _ []string) error {
Expand Down Expand Up @@ -227,7 +232,7 @@ func deployFn(cobraCmd *cobra.Command, o *Options) error {
}

if o.Deploy.DryRun {
return printDryRunResult(result.Apply, o)
return printDryRunResult(result, o)
}

// keep stdout machine-readable for non-table formats: the reconciliation summary
Expand All @@ -236,27 +241,37 @@ func deployFn(cobraCmd *cobra.Command, o *Options) error {
printApplyResult(result.Apply)
}

// historically i think this was 5s, but we will already have had at least some time for
// the manager to have gone off and fetched the version, so 3s max to wrap that up and print
// seems reasonable
versionCheckContext, cancel := context.WithTimeout(
cobraCmd.Context(),
postDeployVersionCheckTimeout,
)
defer cancel()
if shouldDisplayPostDeployVersion(o.Global.Runtime) {
// The manager has fetched in the background during deploy, so allow at most three more
// seconds to finish and print the available containerlab release.
versionCheckContext, cancel := context.WithTimeout(
cobraCmd.Context(),
postDeployVersionCheckTimeout,
)
defer cancel()

m := getVersionManager()
m.DisplayNewVersionAvailable(versionCheckContext, false)
m := getVersionManager()
m.DisplayNewVersionAvailable(versionCheckContext, false)
}

// print table summary
return PrintContainerInspect(result.Containers, o)
}

func shouldDisplayPostDeployVersion(runtimeName string) bool {
return !clablabruntime.IsLabRuntimeName(runtimeName)
}

// printDryRunResult prints the planned changes of a dry run, as JSON when requested via
// the --format flag and as a table otherwise.
func printDryRunResult(result *clabcore.ApplyResult, o *Options) error {
func printDryRunResult(result *clabcore.DeployResult, o *Options) error {
if o.Inspect.Format == clabconstants.FormatJSON {
b, err := json.MarshalIndent(result, "", " ")
value := any(result.Apply)
if result.RuntimePlan != nil {
value = result.RuntimePlan
}

b, err := json.MarshalIndent(value, "", " ")
if err != nil {
return err
}
Expand All @@ -266,11 +281,41 @@ func printDryRunResult(result *clabcore.ApplyResult, o *Options) error {
return nil
}

printApplyResult(result)
if result.RuntimePlan != nil {
printLabRuntimePlan(result.RuntimePlan)

return nil
}

printApplyResult(result.Apply)

return nil
}

func printLabRuntimePlan(plan *clablabruntime.DeployPlan) {
log.Info("Lab runtime plan", "name", plan.LabName, "namespace", plan.Namespace)

table := tableWriter.NewWriter()
table.SetOutputMirror(os.Stdout)
table.SetStyle(tableWriter.StyleRounded)
table.Style().Format.Header = text.FormatTitle
table.Style().Format.HeaderAlign = text.AlignCenter
table.AppendHeader(tableWriter.Row{"Action", "Kind", "Resource"})

for _, change := range plan.Changes {
resourceName := change.Name
if change.Namespace != "" {
resourceName = change.Namespace + "/" + resourceName
}
table.AppendRow(tableWriter.Row{change.Action, change.Kind, resourceName})
}
if len(plan.Changes) == 0 {
table.AppendRow(tableWriter.Row{"no changes", "-", "-"})
}

table.Render()
}

func printApplyResult(result *clabcore.ApplyResult) {
title := "Apply summary"
if result.DryRun {
Expand Down
20 changes: 20 additions & 0 deletions cmd/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,26 @@ func TestApplyIsDeployAlias(t *testing.T) {
}
}

func TestPostDeployVersionDisplaySkipsLabRuntimes(t *testing.T) {
tests := []struct {
name string
runtime string
want bool
}{
{name: "default runtime", want: true},
{name: "docker runtime", runtime: "docker", want: true},
{name: "clabernetes runtime", runtime: "clabernetes", want: false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := shouldDisplayPostDeployVersion(tt.runtime); got != tt.want {
t.Fatalf("shouldDisplayPostDeployVersion(%q) = %t, want %t", tt.runtime, got, tt.want)
}
})
}
}

func TestPrintApplyResultUsesInfoAndItemRows(t *testing.T) {
output := captureApplyOutput(t, func() {
printApplyResult(&clabcore.ApplyResult{
Expand Down
4 changes: 4 additions & 0 deletions cmd/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ func destroyCmd(o *Options) (*cobra.Command, error) {
"reference: https://containerlab.dev/cmd/destroy/",
Aliases: []string{"des"},
PreRunE: func(_ *cobra.Command, _ []string) error {
if commandSkipsRoot(o.Global.Runtime) {
return nil
}

return clabutils.CheckAndGetRootPrivs()
},
RunE: func(cobraCmd *cobra.Command, _ []string) error {
Expand Down
4 changes: 4 additions & 0 deletions cmd/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ func eventsCmd(o *Options) (*cobra.Command, error) {
"reference: https://containerlab.dev/cmd/events/",
Aliases: []string{"ev"},
PreRunE: func(*cobra.Command, []string) error {
if commandSkipsRoot(o.Global.Runtime) {
return nil
}

return clabutils.CheckAndGetRootPrivs()
},
RunE: func(cmd *cobra.Command, _ []string) error {
Expand Down
24 changes: 11 additions & 13 deletions cmd/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,18 @@ func execFn(_ *cobra.Command, o *Options) error {
}

resultCollection, err := c.Exec(ctx, o.Exec.Commands, listOptions...)
if err != nil {
return err
}

switch outputFormat {
case clabconstants.FormatPlain:
resultCollection.Log()
case clabconstants.FormatJSON:
out, err := resultCollection.Dump(outputFormat)
if err != nil {
return fmt.Errorf("failed to print the results collection: %v", err)
if resultCollection != nil {
switch outputFormat {
case clabconstants.FormatPlain:
resultCollection.Log()
case clabconstants.FormatJSON:
out, dumpErr := resultCollection.Dump(outputFormat)
if dumpErr != nil {
return fmt.Errorf("failed to print the results collection: %v", dumpErr)
}

fmt.Println(out)
}

fmt.Println(out)
}

return err
Expand Down
7 changes: 7 additions & 0 deletions cmd/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ func listContainers(
c *clabcore.CLab,
o *Options,
) ([]clabruntime.GenericContainer, error) {
if c.LabRuntime != nil {
return c.ListLabRuntimeContainers(ctx, o.Destroy.All)
}

var containers []clabruntime.GenericContainer

var err error
Expand Down Expand Up @@ -248,6 +252,9 @@ func getShortestTopologyPath(p string) (string, error) {
if p == "" {
return "", nil
}
if strings.Contains(p, "://") {
return p, nil
}

// get topo file path relative of the cwd
cwd, err := os.Getwd()
Expand Down
Loading
Loading