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 +}