diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml
index 5ec81fb7f..e4c4c0536 100644
--- a/.github/workflows/cicd.yml
+++ b/.github/workflows/cicd.yml
@@ -118,8 +118,8 @@ jobs:
uses: docker/login-action@v1
with:
registry: ghcr.io
- username: hellt
- password: ${{ secrets.GHCR_READ_PKG_PAT }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull ceos image
run: docker pull ghcr.io/srl-labs/ceos:4.25.0F && docker tag ghcr.io/srl-labs/ceos:4.25.0F ceos:4.25.0F
- name: Run ceos tests
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 000000000..8ce82c8fc
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,116 @@
+linters-settings:
+ depguard:
+ list-type: blacklist
+ dupl:
+ threshold: 100
+ funlen:
+ lines: 100
+ statements: 50
+ gci:
+ local-prefixes: github.com/golangci/golangci-lint
+ goconst:
+ min-len: 2
+ min-occurrences: 2
+ gocritic:
+ enabled-tags:
+ - diagnostic
+ - experimental
+ - opinionated
+ - performance
+ - style
+ disabled-checks:
+ - dupImport # https://github.com/go-critic/go-critic/issues/845
+ - ifElseChain
+ - octalLiteral
+ - whyNoLint
+ - wrapperFunc
+ gocyclo:
+ min-complexity: 15
+ goimports:
+ local-prefixes: github.com/golangci/golangci-lint
+ gomnd:
+ settings:
+ mnd:
+ # don't include the "operation" and "assign"
+ checks: [argument, case, condition, return]
+ govet:
+ check-shadowing: true
+ settings:
+ printf:
+ funcs:
+ - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
+ - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
+ - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
+ - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
+ lll:
+ line-length: 150
+ misspell:
+ locale: US
+ nolintlint:
+ allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
+ allow-unused: false # report any unused nolint directives
+ require-explanation: false # don't require an explanation for nolint directives
+ require-specific: false # don't require nolint directives to be specific about which linter is being skipped
+linters:
+ # please, do not use `enable-all`: it's deprecated and will be removed soon.
+ # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
+ disable-all: true
+ enable:
+ - bodyclose
+ - deadcode
+ - depguard
+ - dogsled
+ - dupl
+ - errcheck
+ - exhaustive
+ - funlen
+ - gochecknoinits
+ - goconst
+ - gocritic
+ - gocyclo
+ - gofmt
+ - goimports
+ - gomnd
+ - goprintffuncname
+ - gosec
+ - gosimple
+ - govet
+ - ineffassign
+ - lll
+ - misspell
+ - nakedret
+ - noctx
+ - nolintlint
+ - revive
+ - rowserrcheck
+ - exportloopref
+ - staticcheck
+ - structcheck
+ - stylecheck
+ - typecheck
+ - unconvert
+ - unparam
+ - unused
+ - varcheck
+ - whitespace
+ - asciicheck
+ - gochecknoglobals
+ - gocognit
+ - godot
+ - godox
+ - goerr113
+ - nestif
+ - prealloc
+ - testpackage
+ - wsl
+
+issues:
+ # Excluding configuration per-path, per-linter, per-text and per-source
+ exclude-rules:
+ - path: _test\.go
+ linters:
+ - gomnd
+
+run:
+ skip-dirs:
+ - private
diff --git a/clab/config.go b/clab/config.go
index 64c9db4f0..6d1e52838 100644
--- a/clab/config.go
+++ b/clab/config.go
@@ -59,13 +59,6 @@ var kinds = []string{
"host",
}
-// DefaultCredentials holds default username and password per each kind
-var DefaultCredentials = map[string][]string{
- "vr-sros": {"admin", "admin"},
- "vr-vmx": {"admin", "admin@123"},
- "vr-xrv9k": {"clab", "clab@123"},
-}
-
// Config defines lab configuration as it is provided in the YAML file
type Config struct {
Name string `json:"name,omitempty"`
diff --git a/cmd/save.go b/cmd/save.go
index 82b772b1e..735e3967d 100644
--- a/cmd/save.go
+++ b/cmd/save.go
@@ -7,23 +7,14 @@ package cmd
import (
"context"
"fmt"
- "io/ioutil"
- "strings"
"sync"
- "github.com/Juniper/go-netconf/netconf"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/srl-labs/containerlab/clab"
- "github.com/srl-labs/containerlab/types"
+ "github.com/srl-labs/containerlab/nodes"
)
-var saveCommand = map[string][]string{
- "srl": {"sr_cli", "-d", "tools", "system", "configuration", "generate-checkpoint"},
- "ceos": {"Cli", "-p", "15", "-c", "copy running flash:conf-saved.conf"},
- "crpd": {"cli", "show", "conf"},
-}
-
// saveCmd represents the save command
var saveCmd = &cobra.Command{
Use: "save",
@@ -46,64 +37,21 @@ Refer to the https://containerlab.srlinux.dev/cmd/save/ documentation to see the
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- labels := []*types.GenericFilter{{FilterType: "label", Match: c.Config.Name, Field: "containerlab", Operator: "="}}
- containers, err := c.Runtime.ListContainers(ctx, labels)
- if err != nil {
- return fmt.Errorf("could not list containers: %v", err)
- }
- if len(containers) == 0 {
- return fmt.Errorf("no containers found")
+ if err := c.ParseTopology(); err != nil {
+ return err
}
var wg sync.WaitGroup
- wg.Add(len(containers))
- for _, cont := range containers {
- go func(cont types.GenericContainer) {
+ wg.Add(len(c.Nodes))
+ for _, node := range c.Nodes {
+ go func(node nodes.Node) {
defer wg.Done()
- kind := cont.Labels["clab-node-kind"]
-
- switch kind {
- case "vr-sros",
- "vr-vmx":
- netconfSave(cont)
- return
- }
- // skip saving if we have no command map
- if _, ok := saveCommand[kind]; !ok {
- return
- }
- stdout, stderr, err := c.Runtime.Exec(ctx, cont.ID, saveCommand[kind])
+ err := node.SaveConfig(ctx, c.Runtime)
if err != nil {
- log.Errorf("%s: failed to execute cmd: %v", cont.Names, err)
-
- }
- if len(stderr) > 0 {
- log.Infof("%s errors: %s", strings.TrimLeft(cont.Names[0], "/"), string(stderr))
- }
- switch {
- // for srl kinds print the full stdout
- case kind == "srl":
- if len(stdout) > 0 {
- confPath := cont.Labels["clab-node-dir"] + "/config/checkpoint/checkpoint-0.json"
- log.Infof("saved SR Linux configuration from %s node to %s\noutput:\n%s", strings.TrimLeft(cont.Names[0], "/"), confPath, string(stdout))
- }
-
- case kind == "crpd":
- // path by which to save a config
- confPath := cont.Labels["clab-node-dir"] + "/config/conf-saved.conf"
- err := ioutil.WriteFile(confPath, stdout, 0777)
- if err != nil {
- log.Errorf("failed to write config by %s path from %s container: %v", confPath, strings.TrimLeft(cont.Names[0], "/"), err)
- }
- log.Infof("saved cRPD configuration from %s node to %s", strings.TrimLeft(cont.Names[0], "/"), confPath)
-
- case kind == "ceos":
- // path by which a config was saved
- confPath := cont.Labels["clab-node-dir"] + "/flash/conf-saved.conf"
- log.Infof("saved cEOS configuration from %s node to %s", strings.TrimLeft(cont.Names[0], "/"), confPath)
+ log.Errorf("err: %v", err)
}
- }(cont)
+ }(node)
}
wg.Wait()
@@ -114,29 +62,3 @@ Refer to the https://containerlab.srlinux.dev/cmd/save/ documentation to see the
func init() {
rootCmd.AddCommand(saveCmd)
}
-
-func netconfSave(cont types.GenericContainer) {
- kind := cont.Labels["clab-node-kind"]
- config := netconf.SSHConfigPassword(clab.DefaultCredentials[kind][0],
- clab.DefaultCredentials[kind][1])
-
- host := strings.TrimLeft(cont.Names[0], "/")
- ncHost := host + ":830"
-
- s, err := netconf.DialSSH(ncHost, config)
- if err != nil {
- log.Errorf("%s: Could not connect SSH to %s %s", cont.Names[0], host, err)
- return
- }
- defer s.Close()
-
- save := ``
-
- _, err = s.Exec(netconf.RawMethod(save))
- if err != nil {
- log.Errorf("%s: Could not send Netconf save to %s %s", cont.Names[0], host, err)
- return
- }
-
- log.Infof("saved %s configuration from node %s\n", kind, host)
-}
diff --git a/go.mod b/go.mod
index 95a8c962a..12f842a58 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,6 @@ module github.com/srl-labs/containerlab
go 1.16
require (
- github.com/Juniper/go-netconf v0.1.1
github.com/awalterschulze/gographviz v2.0.1+incompatible
github.com/cloudflare/cfssl v1.4.1
github.com/containerd/containerd v1.5.2
@@ -23,10 +22,10 @@ require (
github.com/olekukonko/tablewriter v0.0.5-0.20201029120751-42e21c7531a3
github.com/opencontainers/runtime-spec v1.0.3-0.20210303205135-43e4633e40c1
github.com/pkg/errors v0.9.1
+ github.com/scrapli/scrapligo v0.0.0-20210627135102-7fd8d8e86545
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.0.0
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5
- github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b // indirect
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
gopkg.in/yaml.v2 v2.4.0
)
diff --git a/go.sum b/go.sum
index 393b8c70c..9d80bb156 100644
--- a/go.sum
+++ b/go.sum
@@ -40,8 +40,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
-github.com/Juniper/go-netconf v0.1.1 h1:5fx/T7L2Fwq51UnESPOP1CXgGCs7IYxR/pnyC5quu/k=
-github.com/Juniper/go-netconf v0.1.1/go.mod h1:2Fy6tQTWnL//D/Ll1hb0RYXN4jndcTyneRn6xj5E1VE=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
@@ -217,6 +215,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
+github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
@@ -592,8 +592,12 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/scrapli/scrapligo v0.0.0-20210627135102-7fd8d8e86545 h1:fhksj/v/SQEKV6QHHG9InCnDzr2QjjqoVQPeEhNB3So=
+github.com/scrapli/scrapligo v0.0.0-20210627135102-7fd8d8e86545/go.mod h1:+csimZHh80jQXjdDdHmAIKCwiXPZvXQ7ZgKEQWmFpK8=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirikothe/gotextfsm v1.0.0 h1:4kKwbUziG9G+31PfLY+vI3FzYK/kcByh4ndT3NyPMkc=
+github.com/sirikothe/gotextfsm v1.0.0/go.mod h1:CJYqpTg9u5VPCoD0VEl9E68prCIiWQD8m457k098DdQ=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -673,8 +677,6 @@ github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
-github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b h1:VfPXB/wCGGt590QhD1bOpv2J/AmC/RJNTg/Q59HKSB0=
-github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b/go.mod h1:IZpXDfkJ6tWD3PhBK5YzgQT+xJWh7OsdwiG8hA2MkO4=
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e h1:mvOa4+/DXStR4ZXOks/UsjeFdn5O5JpLUtzqk9U8xXw=
@@ -707,8 +709,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
diff --git a/nodes/bridge/bridge.go b/nodes/bridge/bridge.go
index 598ebb79f..653c3bc91 100644
--- a/nodes/bridge/bridge.go
+++ b/nodes/bridge/bridge.go
@@ -36,3 +36,6 @@ func (s *bridge) PostDeploy(ctx context.Context, r runtime.ContainerRuntime, ns
return nil
}
func (s *bridge) WithMgmtNet(*types.MgmtNet) {}
+func (s *bridge) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ return nil
+}
diff --git a/nodes/ceos/ceos.go b/nodes/ceos/ceos.go
index 8a56e9193..582872f7e 100644
--- a/nodes/ceos/ceos.go
+++ b/nodes/ceos/ceos.go
@@ -21,20 +21,24 @@ import (
"github.com/srl-labs/containerlab/utils"
)
-// defined env vars for the ceos
-var ceosEnv = map[string]string{
- "CEOS": "1",
- "EOS_PLATFORM": "ceoslab",
- "container": "docker",
- "ETBA": "4",
- "SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT": "1",
- "INTFTYPE": "eth",
- "MAPETH0": "1",
- "MGMT_INTF": "eth0",
-}
+var (
+ // defined env vars for the ceos
+ ceosEnv = map[string]string{
+ "CEOS": "1",
+ "EOS_PLATFORM": "ceoslab",
+ "container": "docker",
+ "ETBA": "4",
+ "SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT": "1",
+ "INTFTYPE": "eth",
+ "MAPETH0": "1",
+ "MGMT_INTF": "eth0",
+ }
-//go:embed ceos.cfg
-var cfgTemplate string
+ //go:embed ceos.cfg
+ cfgTemplate string
+
+ saveCmd = []string{"Cli", "-p", "15", "-c", "copy running flash:conf-saved.conf"}
+)
func init() {
nodes.Register(nodes.NodeKindCEOS, func() nodes.Node {
@@ -87,6 +91,22 @@ func (s *ceos) PostDeploy(ctx context.Context, r runtime.ContainerRuntime, ns ma
func (s *ceos) WithMgmtNet(*types.MgmtNet) {}
+func (s *ceos) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ _, stderr, err := r.Exec(ctx, s.cfg.LongName, saveCmd)
+ if err != nil {
+ return fmt.Errorf("%s: failed to execute cmd: %v", s.cfg.ShortName, err)
+ }
+
+ if len(stderr) > 0 {
+ return fmt.Errorf("%s errors: %s", s.cfg.ShortName, string(stderr))
+ }
+
+ confPath := s.cfg.LabDir + "/flash/conf-saved.conf"
+ log.Infof("saved cEOS configuration from %s node to %s\n", s.cfg.ShortName, confPath)
+
+ return nil
+}
+
//
func createCEOSFiles(node *types.NodeConfig) error {
diff --git a/nodes/crpd/crpd.go b/nodes/crpd/crpd.go
index 78aaabb34..0a067eab8 100644
--- a/nodes/crpd/crpd.go
+++ b/nodes/crpd/crpd.go
@@ -8,6 +8,7 @@ import (
"context"
_ "embed"
"fmt"
+ "io/ioutil"
"path"
log "github.com/sirupsen/logrus"
@@ -23,6 +24,8 @@ var (
//go:embed sshd_config
sshdCfg string
+
+ saveCmd = []string{"cli", "show", "conf"}
)
func init() {
@@ -81,6 +84,27 @@ func (s *crpd) PostDeploy(ctx context.Context, r runtime.ContainerRuntime, ns ma
func (s *crpd) WithMgmtNet(*types.MgmtNet) {}
+func (s *crpd) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ stdout, stderr, err := r.Exec(ctx, s.cfg.LongName, saveCmd)
+ if err != nil {
+ return fmt.Errorf("%s: failed to execute cmd: %v", s.cfg.ShortName, err)
+ }
+
+ if len(stderr) > 0 {
+ return fmt.Errorf("%s errors: %s", s.cfg.ShortName, string(stderr))
+ }
+
+ // path by which to save a config
+ confPath := s.cfg.LabDir + "/config/conf-saved.conf"
+ err = ioutil.WriteFile(confPath, stdout, 0777)
+ if err != nil {
+ return fmt.Errorf("failed to write config by %s path from %s container: %v", confPath, s.cfg.ShortName, err)
+ }
+ log.Infof("saved cRPD configuration from %s node to %s\n", s.cfg.ShortName, confPath)
+
+ return nil
+}
+
///
func createCRPDFiles(nodeCfg *types.NodeConfig) error {
diff --git a/nodes/host/host.go b/nodes/host/host.go
index b98828fe8..7a3124b1d 100644
--- a/nodes/host/host.go
+++ b/nodes/host/host.go
@@ -37,3 +37,6 @@ func (s *host) PostDeploy(ctx context.Context, r runtime.ContainerRuntime, ns ma
}
func (s *host) WithMgmtNet(*types.MgmtNet) {}
+func (s *host) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ return nil
+}
diff --git a/nodes/linux/linux.go b/nodes/linux/linux.go
index aa70cd40b..54f76abd8 100644
--- a/nodes/linux/linux.go
+++ b/nodes/linux/linux.go
@@ -43,3 +43,7 @@ func (l *linux) PostDeploy(ctx context.Context, r runtime.ContainerRuntime, ns m
}
func (l *linux) WithMgmtNet(*types.MgmtNet) {}
+
+func (s *linux) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ return nil
+}
diff --git a/nodes/mysocketio/mysocketio.go b/nodes/mysocketio/mysocketio.go
index 4a1db7fcd..ef6e0c0cb 100644
--- a/nodes/mysocketio/mysocketio.go
+++ b/nodes/mysocketio/mysocketio.go
@@ -49,4 +49,8 @@ func (s *mySocketIO) PostDeploy(ctx context.Context, r runtime.ContainerRuntime,
func (s *mySocketIO) WithMgmtNet(*types.MgmtNet) {}
+func (s *mySocketIO) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ return nil
+}
+
///
diff --git a/nodes/node.go b/nodes/node.go
index 1b2f699e2..d95cf8681 100644
--- a/nodes/node.go
+++ b/nodes/node.go
@@ -44,6 +44,7 @@ type Node interface {
Deploy(context.Context, runtime.ContainerRuntime) error
PostDeploy(context.Context, runtime.ContainerRuntime, map[string]Node) error
WithMgmtNet(*types.MgmtNet)
+ SaveConfig(context.Context, runtime.ContainerRuntime) error
}
var Nodes = map[string]Initializer{}
@@ -70,3 +71,10 @@ var DefaultConfigTemplates = map[string]string{
"crpd": "/etc/containerlab/templates/crpd/juniper.conf",
"vr-sros": "",
}
+
+// DefaultCredentials holds default username and password per each kind
+var DefaultCredentials = map[string][]string{
+ "vr-sros": {"admin", "admin"},
+ "vr-vmx": {"admin", "admin@123"},
+ "vr-xrv9k": {"clab", "clab@123"},
+}
diff --git a/nodes/ovs/ovs.go b/nodes/ovs/ovs.go
index 2c919dfaf..593f47eb3 100644
--- a/nodes/ovs/ovs.go
+++ b/nodes/ovs/ovs.go
@@ -41,3 +41,7 @@ func (l *ovs) PostDeploy(ctx context.Context, r runtime.ContainerRuntime, ns map
}
func (l *ovs) WithMgmtNet(*types.MgmtNet) {}
+
+func (s *ovs) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ return nil
+}
diff --git a/nodes/sonic/sonic.go b/nodes/sonic/sonic.go
index a9f33e6ca..d0e2bb29d 100644
--- a/nodes/sonic/sonic.go
+++ b/nodes/sonic/sonic.go
@@ -64,3 +64,7 @@ func (s *sonic) PostDeploy(ctx context.Context, r runtime.ContainerRuntime, ns m
}
func (s *sonic) WithMgmtNet(*types.MgmtNet) {}
+
+func (s *sonic) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ return nil
+}
diff --git a/nodes/srl/srl.go b/nodes/srl/srl.go
index b94c1aece..998f505b5 100644
--- a/nodes/srl/srl.go
+++ b/nodes/srl/srl.go
@@ -54,6 +54,8 @@ var (
//go:embed topology/*
topologies embed.FS
+
+ saveCmd []string = []string{"sr_cli", "-d", "tools", "system", "configuration", "generate-checkpoint"}
)
func init() {
@@ -172,6 +174,22 @@ func (s *srl) Destroy(ctx context.Context, r runtime.ContainerRuntime) error {
func (s *srl) WithMgmtNet(*types.MgmtNet) {}
+func (s *srl) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ stdout, stderr, err := r.Exec(ctx, s.cfg.LongName, saveCmd)
+ if err != nil {
+ return fmt.Errorf("%s: failed to execute cmd: %v", s.cfg.ShortName, err)
+ }
+
+ if len(stderr) > 0 {
+ return fmt.Errorf("%s errors: %s", s.cfg.ShortName, string(stderr))
+ }
+
+ confPath := s.cfg.LabDir + "/config/checkpoint/checkpoint-0.json"
+ log.Infof("saved SR Linux configuration from %s node to %s\noutput:\n%s", s.cfg.ShortName, confPath, string(stdout))
+
+ return nil
+}
+
//
func createSRLFiles(nodeCfg *types.NodeConfig) error {
diff --git a/nodes/vr_csr/vr-csr.go b/nodes/vr_csr/vr-csr.go
index f7609ccc4..dd4061989 100644
--- a/nodes/vr_csr/vr-csr.go
+++ b/nodes/vr_csr/vr-csr.go
@@ -8,6 +8,7 @@ import (
"context"
"fmt"
+ log "github.com/sirupsen/logrus"
"github.com/srl-labs/containerlab/nodes"
"github.com/srl-labs/containerlab/runtime"
"github.com/srl-labs/containerlab/types"
@@ -64,3 +65,17 @@ func (s *vrCsr) Destroy(ctx context.Context, r runtime.ContainerRuntime) error {
func (s *vrCsr) WithMgmtNet(mgmt *types.MgmtNet) {
s.mgmt = mgmt
}
+
+func (s *vrCsr) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ err := utils.SaveCfgViaNetconf(s.cfg.LongName,
+ nodes.DefaultCredentials[s.cfg.Kind][0],
+ nodes.DefaultCredentials[s.cfg.Kind][0],
+ )
+
+ if err != nil {
+ return err
+ }
+
+ log.Infof("saved %s running configuration to startup configuration file\n", s.cfg.ShortName)
+ return nil
+}
diff --git a/nodes/vr_ros/vr-ros.go b/nodes/vr_ros/vr-ros.go
index fc2fb3ffc..4c82a2a0f 100644
--- a/nodes/vr_ros/vr-ros.go
+++ b/nodes/vr_ros/vr-ros.go
@@ -65,3 +65,7 @@ func (s *vrRos) PostDeploy(ctx context.Context, r runtime.ContainerRuntime, ns m
}
func (s *vrRos) WithMgmtNet(mgmt *types.MgmtNet) { s.mgmt = mgmt }
+
+func (s *vrRos) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ return nil
+}
diff --git a/nodes/vr_sros/vr-sros.go b/nodes/vr_sros/vr-sros.go
index 3a931025e..5ab733a4f 100644
--- a/nodes/vr_sros/vr-sros.go
+++ b/nodes/vr_sros/vr-sros.go
@@ -84,6 +84,20 @@ func (s *vrSROS) WithMgmtNet(mgmt *types.MgmtNet) {
s.mgmt = mgmt
}
+func (s *vrSROS) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ err := utils.SaveCfgViaNetconf(s.cfg.LongName,
+ nodes.DefaultCredentials[s.cfg.Kind][0],
+ nodes.DefaultCredentials[s.cfg.Kind][0],
+ )
+
+ if err != nil {
+ return err
+ }
+
+ log.Infof("saved %s running configuration to startup configuration file\n", s.cfg.ShortName)
+ return nil
+}
+
//
func createVrSROSFiles(node *types.NodeConfig) error {
diff --git a/nodes/vr_veos/vr-veos.go b/nodes/vr_veos/vr-veos.go
index 46e73181d..dcae84acc 100644
--- a/nodes/vr_veos/vr-veos.go
+++ b/nodes/vr_veos/vr-veos.go
@@ -8,6 +8,7 @@ import (
"context"
"fmt"
+ log "github.com/sirupsen/logrus"
"github.com/srl-labs/containerlab/nodes"
"github.com/srl-labs/containerlab/runtime"
"github.com/srl-labs/containerlab/types"
@@ -63,3 +64,17 @@ func (s *vrVEOS) PostDeploy(ctx context.Context, r runtime.ContainerRuntime, ns
}
func (s *vrVEOS) WithMgmtNet(mgmt *types.MgmtNet) { s.mgmt = mgmt }
+
+func (s *vrVEOS) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ err := utils.SaveCfgViaNetconf(s.cfg.LongName,
+ nodes.DefaultCredentials[s.cfg.Kind][0],
+ nodes.DefaultCredentials[s.cfg.Kind][0],
+ )
+
+ if err != nil {
+ return err
+ }
+
+ log.Infof("saved %s running configuration to startup configuration file\n", s.cfg.ShortName)
+ return nil
+}
diff --git a/nodes/vr_vmx/vr-vmx.go b/nodes/vr_vmx/vr-vmx.go
index b008d1c5d..32a44f4d6 100644
--- a/nodes/vr_vmx/vr-vmx.go
+++ b/nodes/vr_vmx/vr-vmx.go
@@ -8,6 +8,7 @@ import (
"context"
"fmt"
+ log "github.com/sirupsen/logrus"
"github.com/srl-labs/containerlab/nodes"
"github.com/srl-labs/containerlab/runtime"
"github.com/srl-labs/containerlab/types"
@@ -67,3 +68,17 @@ func (s *vrVMX) PostDeploy(ctx context.Context, r runtime.ContainerRuntime, ns m
}
func (s *vrVMX) WithMgmtNet(mgmt *types.MgmtNet) { s.mgmt = mgmt }
+
+func (s *vrVMX) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ err := utils.SaveCfgViaNetconf(s.cfg.LongName,
+ nodes.DefaultCredentials[s.cfg.Kind][0],
+ nodes.DefaultCredentials[s.cfg.Kind][0],
+ )
+
+ if err != nil {
+ return err
+ }
+
+ log.Infof("saved %s running configuration to startup configuration file\n", s.cfg.ShortName)
+ return nil
+}
diff --git a/nodes/vr_xrv/vr-xrv.go b/nodes/vr_xrv/vr-xrv.go
index 7a5fdb56b..4a3f28e28 100644
--- a/nodes/vr_xrv/vr-xrv.go
+++ b/nodes/vr_xrv/vr-xrv.go
@@ -8,6 +8,7 @@ import (
"context"
"fmt"
+ log "github.com/sirupsen/logrus"
"github.com/srl-labs/containerlab/nodes"
"github.com/srl-labs/containerlab/runtime"
"github.com/srl-labs/containerlab/types"
@@ -66,3 +67,17 @@ func (s *vrXRV) PostDeploy(ctx context.Context, r runtime.ContainerRuntime, ns m
}
func (s *vrXRV) WithMgmtNet(mgmt *types.MgmtNet) { s.mgmt = mgmt }
+
+func (s *vrXRV) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ err := utils.SaveCfgViaNetconf(s.cfg.LongName,
+ nodes.DefaultCredentials[s.cfg.Kind][0],
+ nodes.DefaultCredentials[s.cfg.Kind][0],
+ )
+
+ if err != nil {
+ return err
+ }
+
+ log.Infof("saved %s running configuration to startup configuration file\n", s.cfg.ShortName)
+ return nil
+}
diff --git a/nodes/vr_xrv9k/vr-xrv9k.go b/nodes/vr_xrv9k/vr-xrv9k.go
index 315ecded0..bb2056f27 100644
--- a/nodes/vr_xrv9k/vr-xrv9k.go
+++ b/nodes/vr_xrv9k/vr-xrv9k.go
@@ -8,6 +8,7 @@ import (
"context"
"fmt"
+ log "github.com/sirupsen/logrus"
"github.com/srl-labs/containerlab/nodes"
"github.com/srl-labs/containerlab/runtime"
"github.com/srl-labs/containerlab/types"
@@ -69,3 +70,17 @@ func (s *vrXRV9K) PostDeploy(ctx context.Context, r runtime.ContainerRuntime, ns
}
func (s *vrXRV9K) WithMgmtNet(mgmt *types.MgmtNet) { s.mgmt = mgmt }
+
+func (s *vrXRV9K) SaveConfig(ctx context.Context, r runtime.ContainerRuntime) error {
+ err := utils.SaveCfgViaNetconf(s.cfg.LongName,
+ nodes.DefaultCredentials[s.cfg.Kind][0],
+ nodes.DefaultCredentials[s.cfg.Kind][0],
+ )
+
+ if err != nil {
+ return err
+ }
+
+ log.Infof("saved %s running configuration to startup configuration file\n", s.cfg.ShortName)
+ return nil
+}
diff --git a/runtime/docker/docker.go b/runtime/docker/docker.go
index 054ee6a6b..5412f62ee 100644
--- a/runtime/docker/docker.go
+++ b/runtime/docker/docker.go
@@ -303,6 +303,7 @@ func (c *DockerRuntime) CreateContainer(ctx context.Context, node *types.NodeCon
if err != nil {
return err
}
+
return utils.LinkContainerNS(node.NSPath, node.LongName)
}
diff --git a/utils/netconf.go b/utils/netconf.go
new file mode 100644
index 000000000..def09c5ec
--- /dev/null
+++ b/utils/netconf.go
@@ -0,0 +1,42 @@
+// Copyright 2020 Nokia
+// Licensed under the BSD 3-Clause License.
+// SPDX-License-Identifier: BSD-3-Clause
+
+package utils
+
+import (
+ "fmt"
+
+ "github.com/scrapli/scrapligo/driver/base"
+ "github.com/scrapli/scrapligo/netconf"
+ "github.com/scrapli/scrapligo/transport"
+)
+
+// SaveCfgViaNetconf saves the running config to the startup by means
+// of invoking a netconf rpc
+// this method is used on the network elements that can't perform a save of config via other means
+func SaveCfgViaNetconf(addr, username, password string) error {
+ d, err := netconf.NewNetconfDriver(
+ addr,
+ base.WithAuthStrictKey(false),
+ base.WithAuthUsername(username),
+ base.WithAuthPassword(password),
+ base.WithTransportType(transport.StandardTransportName),
+ )
+ if err != nil {
+ return fmt.Errorf("could not create netconf driver for %s: %+v", addr, err)
+ }
+
+ err = d.Open()
+ if err != nil {
+ return fmt.Errorf("failed to open netconf driver for %s: %+v", addr, err)
+ }
+ defer d.Close()
+
+ _, err = d.CopyConfig("running", "startup")
+ if err != nil {
+ return fmt.Errorf("%s: Could not send save config via Netconf: %+v", addr, err)
+ }
+
+ return nil
+}