From a4ae983840fe31fbc93836856cc1cdad07d52e37 Mon Sep 17 00:00:00 2001 From: steiler Date: Mon, 21 Jun 2021 17:33:23 +0000 Subject: [PATCH 01/14] -j/--json flag added to exec command --- cmd/exec.go | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/cmd/exec.go b/cmd/exec.go index 4e97617d2..4b3bf773b 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -6,6 +6,7 @@ package cmd import ( "context" + "encoding/json" "fmt" "strings" @@ -15,7 +16,10 @@ import ( "github.com/srl-labs/containerlab/types" ) -var labels []string +var ( + labels []string + jsonOutput bool +) // execCmd represents the exec command var execCmd = &cobra.Command{ @@ -59,7 +63,10 @@ var execCmd = &cobra.Command{ for _, a := range args { cmds = append(cmds, strings.Split(a, " ")...) } + jsonResult := make(map[string]map[string]interface{}) + for _, cont := range containers { + var doc interface{} if cont.State != "running" { continue } @@ -68,12 +75,30 @@ var execCmd = &cobra.Command{ log.Errorf("%s: failed to execute cmd: %v", cont.Names, err) continue } - if len(stdout) > 0 { - log.Infof("%s: stdout:\n%s", cont.Names, string(stdout)) + if jsonOutput { + jsonResult[cont.Names[0]] = make(map[string]interface{}) + err := json.Unmarshal([]byte(stdout), &doc) + if err == nil { + jsonResult[cont.Names[0]]["stdout"] = doc + } else { + jsonResult[cont.Names[0]]["stdout"] = string(stdout) + } + jsonResult[cont.Names[0]]["stderr"] = string(stderr) + } else { + if len(stdout) > 0 { + log.Infof("%s: stdout:\n%s", cont.Names, string(stdout)) + } + if len(stderr) > 0 { + log.Infof("%s: stderr:\n%s", cont.Names, string(stderr)) + } } - if len(stderr) > 0 { - log.Infof("%s: stderr:\n%s", cont.Names, string(stderr)) + } + if jsonOutput { + result, err := json.Marshal(jsonResult) + if err != nil { + log.Debug("Issue converting to json %v", err) } + fmt.Println(string(result)) } }, } @@ -81,4 +106,5 @@ var execCmd = &cobra.Command{ func init() { rootCmd.AddCommand(execCmd) execCmd.Flags().StringSliceVarP(&labels, "label", "", []string{}, "labels to filter container subset") + execCmd.Flags().BoolVarP(&jsonOutput, "json", "j", false, "get output in json format") } From 90bfe90bb55804bc9ddcdc56ea99737846508a90 Mon Sep 17 00:00:00 2001 From: steiler Date: Mon, 21 Jun 2021 17:49:42 +0000 Subject: [PATCH 02/14] fixed logging --- cmd/exec.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/exec.go b/cmd/exec.go index 4b3bf773b..6dba2518b 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -96,7 +96,7 @@ var execCmd = &cobra.Command{ if jsonOutput { result, err := json.Marshal(jsonResult) if err != nil { - log.Debug("Issue converting to json %v", err) + log.Errorf("Issue converting to json %v", err) } fmt.Println(string(result)) } From 21ba7c01b5d2f357de29ed15f34caaeedbd1f6d3 Mon Sep 17 00:00:00 2001 From: steiler Date: Tue, 22 Jun 2021 07:40:35 +0000 Subject: [PATCH 03/14] changed -j to -f / --format json / plain --- cmd/exec.go | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/cmd/exec.go b/cmd/exec.go index 6dba2518b..b3f4c9410 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -17,8 +17,7 @@ import ( ) var ( - labels []string - jsonOutput bool + labels []string ) // execCmd represents the exec command @@ -36,6 +35,13 @@ var execCmd = &cobra.Command{ fmt.Println("provide command to execute") return } + switch format { + case "json", + "plain": + // expected values, go on + default: + log.Error("format is expected to be either json or plain") + } opts := []clab.ClabOption{ clab.WithDebug(debug), clab.WithTimeout(timeout), @@ -75,25 +81,28 @@ var execCmd = &cobra.Command{ log.Errorf("%s: failed to execute cmd: %v", cont.Names, err) continue } - if jsonOutput { - jsonResult[cont.Names[0]] = make(map[string]interface{}) + contName := strings.TrimLeft(cont.Names[0], "/") + switch format { + case "json": + jsonResult[contName] = make(map[string]interface{}) err := json.Unmarshal([]byte(stdout), &doc) if err == nil { - jsonResult[cont.Names[0]]["stdout"] = doc + jsonResult[contName]["stdout"] = doc } else { - jsonResult[cont.Names[0]]["stdout"] = string(stdout) + jsonResult[contName]["stdout"] = string(stdout) } - jsonResult[cont.Names[0]]["stderr"] = string(stderr) - } else { + jsonResult[contName]["stderr"] = string(stderr) + case "plain": if len(stdout) > 0 { - log.Infof("%s: stdout:\n%s", cont.Names, string(stdout)) + log.Infof("%s: stdout:\n%s", contName, string(stdout)) } if len(stderr) > 0 { - log.Infof("%s: stderr:\n%s", cont.Names, string(stderr)) + log.Infof("%s: stderr:\n%s", contName, string(stderr)) } + } } - if jsonOutput { + if format == "json" { result, err := json.Marshal(jsonResult) if err != nil { log.Errorf("Issue converting to json %v", err) @@ -106,5 +115,5 @@ var execCmd = &cobra.Command{ func init() { rootCmd.AddCommand(execCmd) execCmd.Flags().StringSliceVarP(&labels, "label", "", []string{}, "labels to filter container subset") - execCmd.Flags().BoolVarP(&jsonOutput, "json", "j", false, "get output in json format") + execCmd.Flags().StringVarP(&format, "format", "f", "plain", "output format. One of [json, plain]") } From 87515217b303b0af47915133b47995f6358a24c1 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Thu, 24 Jun 2021 09:56:16 +0200 Subject: [PATCH 04/14] use exec scoped format var --- cmd/exec.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/exec.go b/cmd/exec.go index b3f4c9410..8d1fb0d62 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -17,7 +17,8 @@ import ( ) var ( - labels []string + labels []string + execFormat string ) // execCmd represents the exec command @@ -35,7 +36,7 @@ var execCmd = &cobra.Command{ fmt.Println("provide command to execute") return } - switch format { + switch execFormat { case "json", "plain": // expected values, go on @@ -82,7 +83,7 @@ var execCmd = &cobra.Command{ continue } contName := strings.TrimLeft(cont.Names[0], "/") - switch format { + switch execFormat { case "json": jsonResult[contName] = make(map[string]interface{}) err := json.Unmarshal([]byte(stdout), &doc) @@ -102,7 +103,7 @@ var execCmd = &cobra.Command{ } } - if format == "json" { + if execFormat == "json" { result, err := json.Marshal(jsonResult) if err != nil { log.Errorf("Issue converting to json %v", err) @@ -115,5 +116,5 @@ var execCmd = &cobra.Command{ func init() { rootCmd.AddCommand(execCmd) execCmd.Flags().StringSliceVarP(&labels, "label", "", []string{}, "labels to filter container subset") - execCmd.Flags().StringVarP(&format, "format", "f", "plain", "output format. One of [json, plain]") + execCmd.Flags().StringVarP(&execFormat, "format", "f", "plain", "output format. One of [json, plain]") } From b3417946e3a06bd90e30768db27b14d24f486f81 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Thu, 24 Jun 2021 13:19:57 +0200 Subject: [PATCH 05/14] testing cmd as a flag --- cmd/exec.go | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/cmd/exec.go b/cmd/exec.go index 8d1fb0d62..7b853bc95 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -7,9 +7,11 @@ package cmd import ( "context" "encoding/json" + "errors" "fmt" "strings" + "github.com/google/shlex" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/srl-labs/containerlab/clab" @@ -17,8 +19,9 @@ import ( ) var ( - labels []string - execFormat string + labels []string + execFormat string + execCommand string ) // execCmd represents the exec command @@ -26,16 +29,16 @@ var execCmd = &cobra.Command{ Use: "exec", Short: "execute a command on one or multiple containers", PreRunE: sudoCheck, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { if name == "" && topo == "" { - fmt.Println("provide either lab name (--name) or topology file path (--topo)") - return + return errors.New("provide either lab name (--name) or topology file path (--topo)") + } - log.Debugf("raw command: %v", args) - if len(args) == 0 { - fmt.Println("provide command to execute") - return + + if execCommand == "" { + return errors.New("provide command to execute") } + switch execFormat { case "json", "plain": @@ -63,13 +66,16 @@ var execCmd = &cobra.Command{ log.Fatalf("could not list containers: %v", err) } if len(containers) == 0 { - log.Println("no containers found") - return + return errors.New("no containers found") } - cmds := make([]string, 0, len(args)) - for _, a := range args { - cmds = append(cmds, strings.Split(a, " ")...) + + fmt.Println(execCommand) + cmds, err := shlex.Split(execCommand) + if err != nil { + return err } + + fmt.Println(cmds) jsonResult := make(map[string]map[string]interface{}) for _, cont := range containers { @@ -110,11 +116,13 @@ var execCmd = &cobra.Command{ } fmt.Println(string(result)) } + return err }, } func init() { rootCmd.AddCommand(execCmd) + execCmd.Flags().StringVarP(&execCommand, "command", "c", "", "command to execute") execCmd.Flags().StringSliceVarP(&labels, "label", "", []string{}, "labels to filter container subset") execCmd.Flags().StringVarP(&execFormat, "format", "f", "plain", "output format. One of [json, plain]") } From 041bfafa57e95f0dbd01a505868dfb3bead32cc2 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Thu, 24 Jun 2021 13:52:53 +0200 Subject: [PATCH 06/14] added cmd flag for exec --- cmd/exec.go | 4 +- docs/cmd/exec.md | 118 +++++++++++++++++++++++ mkdocs.yml | 1 + tests/01-smoke/03-bridges-and-host.robot | 2 +- 4 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 docs/cmd/exec.md diff --git a/cmd/exec.go b/cmd/exec.go index 7b853bc95..3a0992e0a 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -69,13 +69,11 @@ var execCmd = &cobra.Command{ return errors.New("no containers found") } - fmt.Println(execCommand) cmds, err := shlex.Split(execCommand) if err != nil { return err } - fmt.Println(cmds) jsonResult := make(map[string]map[string]interface{}) for _, cont := range containers { @@ -122,7 +120,7 @@ var execCmd = &cobra.Command{ func init() { rootCmd.AddCommand(execCmd) - execCmd.Flags().StringVarP(&execCommand, "command", "c", "", "command to execute") + execCmd.Flags().StringVarP(&execCommand, "cmd", "", "", "command to execute") execCmd.Flags().StringSliceVarP(&labels, "label", "", []string{}, "labels to filter container subset") execCmd.Flags().StringVarP(&execFormat, "format", "f", "plain", "output format. One of [json, plain]") } diff --git a/docs/cmd/exec.md b/docs/cmd/exec.md new file mode 100644 index 000000000..ae1de0e3e --- /dev/null +++ b/docs/cmd/exec.md @@ -0,0 +1,118 @@ +# exec command + +### Description + +The `exec` command allows to run a command inside the nodes that part of a certain lab. + +This command does exactly the same thing as `docker exec` does, but it allows to run the same command across all the nodes of a lab. + +### Usage + +`containerlab [global-flags] exec [local-flags]` + +### Flags + +#### topology + +With the global `--topo | -t` flag a user specifies from which lab to take the containers and perform the exec command. + +#### cmd +The command to be executed on the nodes is provided with `--cmd` flag. The command is provided as a string, thus it needs to be quoted to accommodate for spaces or special characters. + +#### format +The `--format | -f` flag allows to select between plain text format output or a json variant. Consult with the examples below to see the differences between these two formatting options. + +Defaults to `plain` output format. + +#### label +By default `exec` command will attempt to execute the command across all the nodes of a lab. To limit the scope of the execution, the users can leverage the `--label` flag to filter out the nodes of interest. + +### Examples + +```bash +# show ipv4 information from all the nodes of the lab +# with a plain text output +❯ containerlab exec -t srl02.yml --cmd 'ip -4 a show dummy-mgmt0' +INFO[0000] clab-srl02-srl1: stdout: +6: dummy-mgmt0: mtu 1500 qdisc noop state DOWN group default qlen 1000 + inet 172.20.20.3/24 brd 172.20.20.255 scope global dummy-mgmt0 + valid_lft forever preferred_lft forever +INFO[0000] clab-srl02-srl2: stdout: +6: dummy-mgmt0: mtu 1500 qdisc noop state DOWN group default qlen 1000 + inet 172.20.20.2/24 brd 172.20.20.255 scope global dummy-mgmt0 + valid_lft forever preferred_lft forever + + +# execute a CLI command with a plain text output +❯ containerlab exec -t srl02.yml --cmd 'sr_cli "show version"' +INFO[0001] clab-srl02-srl1: stdout: +---------------------------------------------------- +Hostname : srl1 +Chassis Type : 7250 IXR-6 +Part Number : Sim Part No. +Serial Number : Sim Serial No. +System MAC Address: 02:00:6B:FF:00:00 +Software Version : v20.6.3 +Build Number : 145-g93496a3f8c +Architecture : x86_64 +Last Booted : 2021-06-24T10:25:26.722Z +Total Memory : 24052875 kB +Free Memory : 21911906 kB +---------------------------------------------------- +INFO[0003] clab-srl02-srl2: stdout: +---------------------------------------------------- +Hostname : srl2 +Chassis Type : 7250 IXR-6 +Part Number : Sim Part No. +Serial Number : Sim Serial No. +System MAC Address: 02:D8:A9:FF:00:00 +Software Version : v20.6.3 +Build Number : 145-g93496a3f8c +Architecture : x86_64 +Last Booted : 2021-06-24T10:25:26.904Z +Total Memory : 24052875 kB +Free Memory : 21911914 kB +---------------------------------------------------- + + +# execute a CLI command with a json output +❯ containerlab exec -t srl02.yml --cmd 'sr_cli "show version | as json"' -f json | jq +{ + "clab-srl02-srl1": { + "stderr": "", + "stdout": { + "basic system info": { + "Architecture": "x86_64", + "Build Number": "145-g93496a3f8c", + "Chassis Type": "7250 IXR-6", + "Free Memory": "21911367 kB", + "Hostname": "srl1", + "Last Booted": "2021-06-24T10:25:26.722Z", + "Part Number": "Sim Part No.", + "Serial Number": "Sim Serial No.", + "Software Version": "v20.6.3", + "System MAC Address": "02:00:6B:FF:00:00", + "Total Memory": "24052875 kB" + } + } + }, + "clab-srl02-srl2": { + "stderr": "", + "stdout": { + "basic system info": { + "Architecture": "x86_64", + "Build Number": "145-g93496a3f8c", + "Chassis Type": "7250 IXR-6", + "Free Memory": "21911367 kB", + "Hostname": "srl2", + "Last Booted": "2021-06-24T10:25:26.904Z", + "Part Number": "Sim Part No.", + "Serial Number": "Sim Serial No.", + "Software Version": "v20.6.3", + "System MAC Address": "02:D8:A9:FF:00:00", + "Total Memory": "24052875 kB" + } + } + } +} +``` \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 184cd0f4c..76b4487fc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -35,6 +35,7 @@ nav: - destroy: cmd/destroy.md - inspect: cmd/inspect.md - save: cmd/save.md + - exec: cmd/exec.md - generate: cmd/generate.md - graph: cmd/graph.md - tools: diff --git a/tests/01-smoke/03-bridges-and-host.robot b/tests/01-smoke/03-bridges-and-host.robot index d595f8b31..c79e2a4d4 100644 --- a/tests/01-smoke/03-bridges-and-host.robot +++ b/tests/01-smoke/03-bridges-and-host.robot @@ -55,7 +55,7 @@ Verify management network is using user-specified bridge # show management interface info and cut the information about the ifindex of the remote veth # note that exec returns the info in the stderr stream, thus we use stderr to parse the ifindex ${rc} ${iface} = OperatingSystem.Run And Return Rc And Output - ... sudo containerlab --runtime ${runtime} exec -t ${CURDIR}/${lab-file} --label clab-node-name\=l1 ip l show eth0 2>&1 | cut -d ' ' -f5 | cut -d '@' -f2 | cut -c3- + ... sudo containerlab --runtime ${runtime} exec -t ${CURDIR}/${lab-file} --label clab-node-name\=l1 --cmd "ip l show eth0" 2>&1 | cut -d ' ' -f5 | cut -d '@' -f2 | cut -c3- Log ${iface} Should Be Equal As Integers ${rc} 0 ${rc} ${res} = OperatingSystem.Run And Return Rc And Output From 8adc2eac91a92937b9595be7ccf8b8a09b1ca7eb Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Sat, 26 Jun 2021 13:36:26 +0200 Subject: [PATCH 07/14] fixed crpd exec --- nodes/crpd/crpd.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nodes/crpd/crpd.go b/nodes/crpd/crpd.go index 06bbb7b6a..b1694463f 100644 --- a/nodes/crpd/crpd.go +++ b/nodes/crpd/crpd.go @@ -58,13 +58,15 @@ func (s *crpd) Deploy(ctx context.Context, r runtime.ContainerRuntime) error { func (s *crpd) PostDeploy(ctx context.Context, r runtime.ContainerRuntime, ns map[string]nodes.Node) error { log.Debugf("Running postdeploy actions for CRPD %q node", s.cfg.ShortName) - _, stderr, err := r.Exec(ctx, s.cfg.ContainerID, []string{"service ssh restart"}) + _, stderr, err := r.Exec(ctx, s.cfg.ContainerID, []string{"service", "ssh", "restart"}) if err != nil { return err } + if len(stderr) > 0 { return fmt.Errorf("crpd post-deploy failed: %s", string(stderr)) } + return err } From 25c5df276ce0ee34115d97f7c375e56631286b64 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Sat, 26 Jun 2021 13:51:02 +0200 Subject: [PATCH 08/14] fixed network bridge detection issue --- runtime/docker/docker.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/runtime/docker/docker.go b/runtime/docker/docker.go index 5ee366d2d..054ee6a6b 100644 --- a/runtime/docker/docker.go +++ b/runtime/docker/docker.go @@ -111,7 +111,15 @@ func (c *DockerRuntime) CreateNet(ctx context.Context) (err error) { Config: ipamConfig, } - networkOptions := dockerTypes.NetworkCreate{ + netwOpts := map[string]string{ + "com.docker.network.driver.mtu": c.Mgmt.MTU, + } + + if bridgeName != "" { + netwOpts["com.docker.network.bridge.name"] = bridgeName + } + + opts := dockerTypes.NetworkCreate{ CheckDuplicate: true, Driver: "bridge", EnableIPv6: enableIPv6, @@ -121,13 +129,10 @@ func (c *DockerRuntime) CreateNet(ctx context.Context) (err error) { Labels: map[string]string{ "containerlab": "", }, - Options: map[string]string{ - "com.docker.network.driver.mtu": c.Mgmt.MTU, - "com.docker.network.bridge.name": bridgeName, - }, + Options: netwOpts, } - netCreateResponse, err := c.Client.NetworkCreate(nctx, c.Mgmt.Network, networkOptions) + netCreateResponse, err := c.Client.NetworkCreate(nctx, c.Mgmt.Network, opts) if err != nil { return err } @@ -150,7 +155,7 @@ func (c *DockerRuntime) CreateNet(ctx context.Context) (err error) { case "bridge": bridgeName = "docker0" default: - if _, ok := netResource.Options["com.docker.network.bridge.name"]; ok { + if netResource.Options["com.docker.network.bridge.name"] != "" { bridgeName = netResource.Options["com.docker.network.bridge.name"] } else { bridgeName = "br-" + netResource.ID[:12] From 5f12e0c5a953bf01280e03ad09b8c21ab131d45a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=A9nol=C3=A9=20Delano=C3=AB?= Date: Mon, 28 Jun 2021 11:04:45 +0200 Subject: [PATCH 09/14] Edit documentation to clarify issue #464 --- docs/install.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/install.md b/docs/install.md index ddc007e7c..ec83d718c 100644 --- a/docs/install.md +++ b/docs/install.md @@ -99,6 +99,11 @@ Once installed, issue `sudo service docker start` to start the docker service in It appears to be that next versions of WSL2 kernels will support KVM. +### Mac OS +Containerlab doesn't run on mac OS because Docker isn't natively supported. + +The workaround for mac OS users is to start a Linux VM (Virtual Machine) on mac and run Containerlab inside the VM. + ### Upgrade To upgrade `containerlab` to the latest available version issue the following command[^1]: @@ -124,4 +129,4 @@ To build containerlab from source: [^1]: only available if installed from packages [^2]: Most containerized NOS will require >1 vCPU. RAM size depends on the lab size. Architecture: AMD64. -[^3]: No need to uninstall Docker Desktop, just make sure that it is not integrated with WSL2 machine that you intend to use with containerlab. Moreover, you can make it even work with Docker Desktop with a [few additional steps](https://twitter.com/networkop1/status/1380976461641834500/photo/1), but installing docker-ce into the WSL maybe more intuitive. \ No newline at end of file +[^3]: No need to uninstall Docker Desktop, just make sure that it is not integrated with WSL2 machine that you intend to use with containerlab. Moreover, you can make it even work with Docker Desktop with a [few additional steps](https://twitter.com/networkop1/status/1380976461641834500/photo/1), but installing docker-ce into the WSL maybe more intuitive. From bb2cbc33ffa76cede32302e273e744c4955b62d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=A9nol=C3=A9=20Delano=C3=AB?= Date: Mon, 28 Jun 2021 12:00:29 +0200 Subject: [PATCH 10/14] Add more details to macos install documentation --- docs/install.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/install.md b/docs/install.md index ec83d718c..551b53a37 100644 --- a/docs/install.md +++ b/docs/install.md @@ -100,9 +100,9 @@ Once installed, issue `sudo service docker start` to start the docker service in It appears to be that next versions of WSL2 kernels will support KVM. ### Mac OS -Containerlab doesn't run on mac OS because Docker isn't natively supported. +Containerlab doesn't run on mac OS because Docker Desktop for Mac doesn't provide the networking features containerlab relies on. -The workaround for mac OS users is to start a Linux VM (Virtual Machine) on mac and run Containerlab inside the VM. +The workaround for mac OS users is to start a Linux VM (Virtual Machine) on mac and run Containerlab inside the VM. For example, free softwares such as Vagrant or Virtualbox can be used to deploy a Linux VM on a Mac OS. ### Upgrade To upgrade `containerlab` to the latest available version issue the following command[^1]: From 4a15bc7d72aebc7599bd86153b32637fb45036d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=A9nol=C3=A9=20Delano=C3=AB?= Date: Mon, 28 Jun 2021 12:06:13 +0200 Subject: [PATCH 11/14] Correct typo --- docs/install.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/install.md b/docs/install.md index 551b53a37..0489bfeb7 100644 --- a/docs/install.md +++ b/docs/install.md @@ -100,9 +100,9 @@ Once installed, issue `sudo service docker start` to start the docker service in It appears to be that next versions of WSL2 kernels will support KVM. ### Mac OS -Containerlab doesn't run on mac OS because Docker Desktop for Mac doesn't provide the networking features containerlab relies on. +Containerlab doesn't run on Mac OS because Docker Desktop for Mac doesn't provide the networking features containerlab relies on. -The workaround for mac OS users is to start a Linux VM (Virtual Machine) on mac and run Containerlab inside the VM. For example, free softwares such as Vagrant or Virtualbox can be used to deploy a Linux VM on a Mac OS. +The workaround for Mac OS users is to start a Linux VM (Virtual Machine) on mac and run Containerlab inside the VM. For example, free softwares such as Vagrant or Virtualbox can be used to deploy a Linux VM on a Mac OS. ### Upgrade To upgrade `containerlab` to the latest available version issue the following command[^1]: From 95e957d85c70c2dd7a008d6f9fd580c123404134 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Mon, 28 Jun 2021 13:36:14 +0200 Subject: [PATCH 12/14] Update install.md --- docs/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install.md b/docs/install.md index 0489bfeb7..551a754c3 100644 --- a/docs/install.md +++ b/docs/install.md @@ -102,7 +102,7 @@ Once installed, issue `sudo service docker start` to start the docker service in ### Mac OS Containerlab doesn't run on Mac OS because Docker Desktop for Mac doesn't provide the networking features containerlab relies on. -The workaround for Mac OS users is to start a Linux VM (Virtual Machine) on mac and run Containerlab inside the VM. For example, free softwares such as Vagrant or Virtualbox can be used to deploy a Linux VM on a Mac OS. +The workaround for Mac OS users is to start a Linux VM (Virtual Machine) on mac and run Containerlab inside the VM. For example, free software such as Vagrant or Virtualbox can be used to deploy a Linux VM on a Mac OS. ### Upgrade To upgrade `containerlab` to the latest available version issue the following command[^1]: From 1a6fc8b5388f7634f4d19deed0cfde3e7962bfc9 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Mon, 28 Jun 2021 20:57:03 +0200 Subject: [PATCH 13/14] embed static files with `go:embed` and incorporate tests on `ceos` and `srl` (#479) * -j/--json flag added to exec command * fixed logging * changed -j to -f / --format json / plain * use exec scoped format var * testing cmd as a flag * added cmd flag for exec * fixed network bridge detection issue * fixed crpd exec a stab at gh actions ceos tests login action make it quick image fix? trying... go with cli login try PAT * fix ceos config method * switched ceos to embedded base template * remove template file as it is now embedded * embedded srl config * switch ceos cfg to goembed * use go:embed for srl topo files * rearrange ci deps * used proper go ver * removed fake dirs * combine unit tests with staticcheck * used go:embed with crpd * removed unused files * go embed for graph * remove templates from release build * increase wait timer for ceos test Co-authored-by: steiler --- .github/workflows/cicd.yml | 83 ++++++++++----- .goreleaser.yml | 3 - clab/config.go | 3 + .../index.html => cmd/graph-template.html | 0 cmd/graph.go | 28 +++-- .../ceos.cfg.tpl => nodes/ceos/ceos.cfg | 6 +- nodes/ceos/ceos.go | 11 +- .../crpd/juniper.conf => nodes/crpd/crpd.cfg | 1 - nodes/crpd/crpd.go | 22 ++-- {templates => nodes}/crpd/sshd_config | 0 nodes/node.go | 2 - .../srl/srlconfig.tpl => nodes/srl/srl.cfg | 0 nodes/srl/srl.go | 73 ++++++------- .../srl/topology/7220IXRD1.yml | 0 .../srl/topology/7220IXRD2.yml | 0 .../srl/topology/7220IXRD3.yml | 0 .../srl/topology/7250IXR10.yml | 0 .../srl/topology/7250IXR6.yml | 0 templates/ca/csr-root-ca.json | 16 --- templates/ca/csr.json | 19 ---- templates/srl/srl_env.conf | 100 ------------------ tests/03-basic-ceos/01-two-ceos.robot | 4 +- types/types.go | 25 +++-- utils/file.go | 9 +- 24 files changed, 159 insertions(+), 246 deletions(-) rename templates/graph/index.html => cmd/graph-template.html (100%) rename templates/arista/ceos.cfg.tpl => nodes/ceos/ceos.cfg (80%) rename templates/crpd/juniper.conf => nodes/crpd/crpd.cfg (84%) rename {templates => nodes}/crpd/sshd_config (100%) rename templates/srl/srlconfig.tpl => nodes/srl/srl.cfg (100%) rename templates/srl/topology-7220IXRD1.yml => nodes/srl/topology/7220IXRD1.yml (100%) rename templates/srl/topology-7220IXRD2.yml => nodes/srl/topology/7220IXRD2.yml (100%) rename templates/srl/topology-7220IXRD3.yml => nodes/srl/topology/7220IXRD3.yml (100%) rename templates/srl/topology-7250IXR10.yml => nodes/srl/topology/7250IXR10.yml (100%) rename templates/srl/topology-7250IXR6.yml => nodes/srl/topology/7250IXR6.yml (100%) delete mode 100644 templates/ca/csr-root-ca.json delete mode 100644 templates/ca/csr.json delete mode 100644 templates/srl/srl_env.conf diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 3e4ab1107..5ec81fb7f 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -48,43 +48,18 @@ jobs: - uses: actions/setup-go@v2 with: go-version: ${{ env.GOVER }} - # create fake default config of srl for tests to validate path - - run: sudo mkdir -p /etc/containerlab/templates/srl && sudo touch /etc/containerlab/templates/srl/srlconfig.tpl - run: go test -cover ./... - - staticcheck: - runs-on: ubuntu-20.04 - needs: file-changes - if: needs.file-changes.outputs.code == 'true' || startsWith(github.ref, 'refs/tags/v') - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-go@v2 - with: - go-version: ${{ env.GOVER }} - name: Staticcheck run: | go install honnef.co/go/tools/cmd/staticcheck@latest staticcheck ./... - docs-test: - runs-on: ubuntu-20.04 - needs: file-changes - if: needs.file-changes.outputs.docs == 'true' || startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/docs-publish' - steps: - - uses: actions/checkout@v2 - - run: docker run -v $(pwd):/docs --entrypoint mkdocs squidfunk/mkdocs-material:$MKDOCS_MATERIAL_VER build --clean --strict - - name: htmltest - run: | - curl https://htmltest.wjdp.uk | bash - ./bin/htmltest -c docs/htmltest.yml - smoke-tests: runs-on: ubuntu-20.04 strategy: matrix: runtime: ["docker", "containerd"] needs: - - staticcheck - unit-test steps: - name: Checkout @@ -96,7 +71,7 @@ jobs: with: go-version: ${{ env.GOVER }} - name: Build containerlab - run: sudo go build -o /usr/bin/containerlab + run: go build && sudo mv ./containerlab /usr/bin/containerlab - uses: actions/setup-python@v2 with: python-version: "3.8" @@ -114,12 +89,68 @@ jobs: name: 01-smoke-log path: ./tests/out/*.html + ceos-basic-tests: + runs-on: ubuntu-20.04 + strategy: + matrix: + runtime: ["docker", "containerd"] + needs: + - unit-test + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: ${{ env.GOVER }} + - name: Build containerlab + run: go build && sudo mv ./containerlab /usr/bin/containerlab + - uses: actions/setup-python@v2 + with: + python-version: "3.8" + - name: Install robotframework + run: | + python -m pip install --upgrade pip + pip install -r tests/requirements.txt + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: hellt + password: ${{ secrets.GHCR_READ_PKG_PAT }} + - 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 + run: | + bash ./tests/rf-run.sh ${{ matrix.runtime }} ./tests/03-basic-ceos + # upload test reports as a zip file + - uses: actions/upload-artifact@v2 + if: always() + with: + name: 03-basic-ceos-log + path: ./tests/out/*.html + + docs-test: + runs-on: ubuntu-20.04 + needs: file-changes + if: needs.file-changes.outputs.docs == 'true' || startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/docs-publish' + steps: + - uses: actions/checkout@v2 + - run: docker run -v $(pwd):/docs --entrypoint mkdocs squidfunk/mkdocs-material:$MKDOCS_MATERIAL_VER build --clean --strict + - name: htmltest + run: | + curl https://htmltest.wjdp.uk | bash + ./bin/htmltest -c docs/htmltest.yml + build-and-release: runs-on: ubuntu-20.04 if: startsWith(github.ref, 'refs/tags/v') needs: - docs-test - smoke-tests + - ceos-basic-tests steps: - name: Checkout uses: actions/checkout@v2 diff --git a/.goreleaser.yml b/.goreleaser.yml index e40e2a326..a5a1db470 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -16,7 +16,6 @@ archives: - replacements: linux: Linux files: - - templates/**/* - lab-examples/**/* checksum: name_template: checksums.txt @@ -54,8 +53,6 @@ nfpms: contents: - src: ./lab-examples dst: /etc/containerlab/lab-examples - - src: ./templates - dst: /etc/containerlab/templates # - src: ./tools # dst: /etc/containerlab/tools - src: /usr/bin/containerlab diff --git a/clab/config.go b/clab/config.go index 4e00e925d..64c9db4f0 100644 --- a/clab/config.go +++ b/clab/config.go @@ -119,6 +119,7 @@ func (c *CLab) NewNode(nodeName string, nodeDef *types.NodeDefinition, idx int) if err != nil { return err } + // Init nodeInitializer, ok := nodes.Nodes[nodeCfg.Kind] if !ok { @@ -131,6 +132,7 @@ func (c *CLab) NewNode(nodeName string, nodeDef *types.NodeDefinition, idx int) log.Errorf("failed to initialize node %q: %v", nodeCfg.ShortName, err) return fmt.Errorf("failed to initialize node %q: %v", nodeCfg.ShortName, err) } + n.Config().Labels = utils.MergeStringMaps(n.Config().Labels, map[string]string{ "containerlab": c.Config.Name, "clab-node-name": n.Config().ShortName, @@ -141,6 +143,7 @@ func (c *CLab) NewNode(nodeName string, nodeDef *types.NodeDefinition, idx int) "clab-topo-file": c.TopoFile.path, }) c.Nodes[nodeName] = n + return nil } diff --git a/templates/graph/index.html b/cmd/graph-template.html similarity index 100% rename from templates/graph/index.html rename to cmd/graph-template.html diff --git a/cmd/graph.go b/cmd/graph.go index 2996f920a..2b965c16a 100644 --- a/cmd/graph.go +++ b/cmd/graph.go @@ -6,6 +6,7 @@ package cmd import ( "context" + _ "embed" "encoding/json" "fmt" "html/template" @@ -19,14 +20,15 @@ import ( "github.com/srl-labs/containerlab/types" ) -const ( - defaultTemplatePath = "/etc/containerlab/templates/graph/index.html" -) +var ( + srv string + tmpl string + offline bool + dot bool -var srv string -var tmpl string -var offline bool -var dot bool + //go:embed graph-template.html + graphTemplate string +) type graphTopo struct { Nodes []containerDetails `json:"nodes,omitempty"` @@ -117,9 +119,15 @@ var graphCmd = &cobra.Command{ Name: c.Config.Name, Data: template.JS(string(b)), } - tmpl := template.Must(template.ParseFiles(tmpl)) + var t *template.Template + if tmpl != "" { + t = template.Must(template.ParseFiles(tmpl)) + } else { + t = template.Must(template.New("graph").Parse(graphTemplate)) + } + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - _ = tmpl.Execute(w, topoD) + _ = t.Execute(w, topoD) }) log.Infof("Listening on %s...", srv) @@ -173,5 +181,5 @@ func init() { graphCmd.Flags().StringVarP(&srv, "srv", "s", ":50080", "HTTP server address to view, customize and export your topology") graphCmd.Flags().BoolVarP(&offline, "offline", "o", false, "use only information from topo file when building graph") graphCmd.Flags().BoolVarP(&dot, "dot", "", false, "generate dot file instead of launching the web server") - graphCmd.Flags().StringVarP(&tmpl, "template", "", defaultTemplatePath, "Go html template used to generate the graph") + graphCmd.Flags().StringVarP(&tmpl, "template", "", "", "Go html template used to generate the graph") } diff --git a/templates/arista/ceos.cfg.tpl b/nodes/ceos/ceos.cfg similarity index 80% rename from templates/arista/ceos.cfg.tpl rename to nodes/ceos/ceos.cfg index 4e2d6b225..b4e9f9c87 100644 --- a/templates/arista/ceos.cfg.tpl +++ b/nodes/ceos/ceos.cfg @@ -1,3 +1,7 @@ +// Copyright 2020 Nokia +// Licensed under the BSD 3-Clause License. +// SPDX-License-Identifier: BSD-3-Clause + hostname {{ .ShortName }} username admin privilege 15 secret admin ! @@ -16,4 +20,4 @@ management api netconf management api http-commands no shutdown ! -end +end \ No newline at end of file diff --git a/nodes/ceos/ceos.go b/nodes/ceos/ceos.go index 3dd295f27..8a56e9193 100644 --- a/nodes/ceos/ceos.go +++ b/nodes/ceos/ceos.go @@ -6,6 +6,7 @@ package ceos import ( "context" + _ "embed" "fmt" "net" "path" @@ -32,6 +33,9 @@ var ceosEnv = map[string]string{ "MGMT_INTF": "eth0", } +//go:embed ceos.cfg +var cfgTemplate string + func init() { nodes.Register(nodes.NodeKindCEOS, func() nodes.Node { return new(ceos) @@ -47,9 +51,6 @@ func (s *ceos) Init(cfg *types.NodeConfig, opts ...nodes.NodeOption) error { for _, o := range opts { o(s) } - if s.cfg.Config == "" { - s.cfg.Config = nodes.DefaultConfigTemplates[s.cfg.Kind] - } s.cfg.Env = utils.MergeStringMaps(ceosEnv, s.cfg.Env) @@ -68,7 +69,7 @@ func (s *ceos) Init(cfg *types.NodeConfig, opts ...nodes.NodeOption) error { return nil } -func (s *ceos) Config() *types.NodeConfig { return nil } +func (s *ceos) Config() *types.NodeConfig { return s.cfg } func (s *ceos) PreDeploy(configName, labCADir, labCARoot string) error { utils.CreateDirectory(s.cfg.LabDir, 0777) @@ -106,7 +107,7 @@ func createCEOSFiles(node *types.NodeConfig) error { func ceosPostDeploy(ctx context.Context, r runtime.ContainerRuntime, nodeCfg *types.NodeConfig) error { // regenerate ceos config since it is now known which IP address docker assigned to this container - err := nodeCfg.GenerateConfig(nodeCfg.ResConfig, nodes.DefaultConfigTemplates[nodeCfg.Kind]) + err := nodeCfg.GenerateConfig(nodeCfg.ResConfig, cfgTemplate) if err != nil { return err } diff --git a/templates/crpd/juniper.conf b/nodes/crpd/crpd.cfg similarity index 84% rename from templates/crpd/juniper.conf rename to nodes/crpd/crpd.cfg index a83b318e7..48b807fbe 100644 --- a/templates/crpd/juniper.conf +++ b/nodes/crpd/crpd.cfg @@ -1,4 +1,3 @@ -## Last changed: 2021-01-11 09:07:59 UTC version 20200609.165031.6_builder.r1115480; system { root-authentication { diff --git a/nodes/crpd/crpd.go b/nodes/crpd/crpd.go index b1694463f..78aaabb34 100644 --- a/nodes/crpd/crpd.go +++ b/nodes/crpd/crpd.go @@ -6,6 +6,7 @@ package crpd import ( "context" + _ "embed" "fmt" "path" @@ -16,6 +17,14 @@ import ( "github.com/srl-labs/containerlab/utils" ) +var ( + //go:embed crpd.cfg + cfgTemplate string + + //go:embed sshd_config + sshdCfg string +) + func init() { nodes.Register(nodes.NodeKindCRPD, func() nodes.Node { return new(crpd) @@ -82,23 +91,22 @@ func createCRPDFiles(nodeCfg *types.NodeConfig) error { // copy crpd config from default template or user-provided conf file cfg := path.Join(nodeCfg.LabDir, "/config/juniper.conf") - err := nodeCfg.GenerateConfig(cfg, nodes.DefaultConfigTemplates[nodeCfg.Kind]) + err := nodeCfg.GenerateConfig(cfg, cfgTemplate) if err != nil { log.Errorf("node=%s, failed to generate config: %v", nodeCfg.ShortName, err) } - // copy crpd sshd conf file to crpd node dir - src := "/etc/containerlab/templates/crpd/sshd_config" + // write crpd sshd conf file to crpd node dir dst := path.Join(nodeCfg.LabDir, "/config/sshd_config") - err = utils.CopyFile(src, dst) + err = utils.CreateFile(dst, sshdCfg) if err != nil { - return fmt.Errorf("file copy [src %s -> dst %s] failed %v", src, dst, err) + return fmt.Errorf("failed to write sshd_config file %v", err) } - log.Debugf("CopyFile src %s -> dst %s succeeded\n", src, dst) + log.Debug("Writing sshd_config succeeded") if nodeCfg.License != "" { // copy license file to node specific lab directory - src = nodeCfg.License + src := nodeCfg.License dst = path.Join(nodeCfg.LabDir, "/config/license.conf") if err = utils.CopyFile(src, dst); err != nil { return fmt.Errorf("file copy [src %s -> dst %s] failed %v", src, dst, err) diff --git a/templates/crpd/sshd_config b/nodes/crpd/sshd_config similarity index 100% rename from templates/crpd/sshd_config rename to nodes/crpd/sshd_config diff --git a/nodes/node.go b/nodes/node.go index ffacf065d..1b2f699e2 100644 --- a/nodes/node.go +++ b/nodes/node.go @@ -67,8 +67,6 @@ func WithMgmtNet(mgmt *types.MgmtNet) NodeOption { } var DefaultConfigTemplates = map[string]string{ - "srl": "/etc/containerlab/templates/srl/srlconfig.tpl", - "ceos": "/etc/containerlab/templates/arista/ceos.cfg.tpl", "crpd": "/etc/containerlab/templates/crpd/juniper.conf", "vr-sros": "", } diff --git a/templates/srl/srlconfig.tpl b/nodes/srl/srl.cfg similarity index 100% rename from templates/srl/srlconfig.tpl rename to nodes/srl/srl.cfg diff --git a/nodes/srl/srl.go b/nodes/srl/srl.go index b82be2d4d..b94c1aece 100644 --- a/nodes/srl/srl.go +++ b/nodes/srl/srl.go @@ -7,6 +7,7 @@ package srl import ( "context" "crypto/rand" + "embed" "fmt" "os" "path" @@ -14,6 +15,7 @@ import ( "strings" "text/template" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/srl-labs/containerlab/cert" @@ -25,27 +27,34 @@ import ( const ( srlDefaultType = "ixr6" - baseConfigDir = "/etc/containerlab/templates/srl/" ) -var srlTypes = map[string]string{ - "ixr6": "topology-7250IXR6.yml", - "ixr10": "topology-7250IXR10.yml", - "ixrd1": "topology-7220IXRD1.yml", - "ixrd2": "topology-7220IXRD2.yml", - "ixrd3": "topology-7220IXRD3.yml", -} +var ( + srlSysctl = map[string]string{ + "net.ipv4.ip_forward": "0", + "net.ipv6.conf.all.disable_ipv6": "0", + "net.ipv6.conf.all.accept_dad": "0", + "net.ipv6.conf.default.accept_dad": "0", + "net.ipv6.conf.all.autoconf": "0", + "net.ipv6.conf.default.autoconf": "0", + } -var srlEnv = map[string]string{"SRLINUX": "1"} + srlTypes = map[string]string{ + "ixr6": "7250IXR6.yml", + "ixr10": "7250IXR10.yml", + "ixrd1": "7220IXRD1.yml", + "ixrd2": "7220IXRD2.yml", + "ixrd3": "7220IXRD3.yml", + } -var srlSysctl = map[string]string{ - "net.ipv4.ip_forward": "0", - "net.ipv6.conf.all.disable_ipv6": "0", - "net.ipv6.conf.all.accept_dad": "0", - "net.ipv6.conf.default.accept_dad": "0", - "net.ipv6.conf.all.autoconf": "0", - "net.ipv6.conf.default.autoconf": "0", -} + srlEnv = map[string]string{"SRLINUX": "1"} + + //go:embed srl.cfg + cfgTemplate string + + //go:embed topology/* + topologies embed.FS +) func init() { nodes.Register(nodes.NodeKindSRL, func() nodes.Node { @@ -71,9 +80,7 @@ func (s *srl) Init(cfg *types.NodeConfig, opts ...nodes.NodeOption) error { if s.cfg.NodeType == "" { s.cfg.NodeType = srlDefaultType } - if filename, found := srlTypes[s.cfg.NodeType]; found { - s.cfg.Topology = path.Join(baseConfigDir, filename) - } else { + if _, found := srlTypes[s.cfg.NodeType]; !found { keys := make([]string, 0, len(srlTypes)) for key := range srlTypes { keys = append(keys, key) @@ -147,9 +154,7 @@ func (s *srl) PreDeploy(configName, labCADir, labCARoot string) error { } s.cfg.TLSCert = string(nodeCerts.Cert) s.cfg.TLSKey = string(nodeCerts.Key) - // generate certificates if needed - // create directory structure and copy necessary files - // utils.CreateDirectory(s.cfg.LabDir, 0777) + return createSRLFiles(s.cfg) } @@ -183,30 +188,21 @@ func createSRLFiles(nodeCfg *types.NodeConfig) error { log.Debugf("CopyFile src %s -> dst %s succeeded", src, dst) // generate SRL topology file - err := generateSRLTopologyFile(nodeCfg.Topology, nodeCfg.LabDir, nodeCfg.Index) + err := generateSRLTopologyFile(nodeCfg.NodeType, nodeCfg.LabDir, nodeCfg.Index) if err != nil { return err } - // generate a config file if the destination does not exist + // generate a config file // if the node has a `config:` statement, the file specified in that section // will be used as a template in nodeGenerateConfig() utils.CreateDirectory(path.Join(nodeCfg.LabDir, "config"), 0777) dst = path.Join(nodeCfg.LabDir, "config", "config.json") - err = nodeCfg.GenerateConfig(dst, nodes.DefaultConfigTemplates[nodeCfg.Kind]) + err = nodeCfg.GenerateConfig(dst, cfgTemplate) if err != nil { log.Errorf("node=%s, failed to generate config: %v", nodeCfg.ShortName, err) } - // copy env config to node specific directory in lab - src = "/etc/containerlab/templates/srl/srl_env.conf" - dst = nodeCfg.LabDir + "/" + "srlinux.conf" - err = utils.CopyFile(src, dst) - if err != nil { - return fmt.Errorf("CopyFile src %s -> dst %s failed %v", src, dst, err) - } - log.Debugf("CopyFile src %s -> dst %s succeeded\n", src, dst) - return err } @@ -214,11 +210,12 @@ type mac struct { MAC string } -func generateSRLTopologyFile(src, labDir string, index int) error { +func generateSRLTopologyFile(nodeType, labDir string, index int) error { dst := path.Join(labDir, "topology.yml") - tpl, err := template.ParseFiles(src) + + tpl, err := template.ParseFS(topologies, "topology/"+srlTypes[nodeType]) if err != nil { - return err + return errors.Wrap(err, "failed to get srl topology file") } // generate random bytes to use in the 2-3rd bytes of a base mac diff --git a/templates/srl/topology-7220IXRD1.yml b/nodes/srl/topology/7220IXRD1.yml similarity index 100% rename from templates/srl/topology-7220IXRD1.yml rename to nodes/srl/topology/7220IXRD1.yml diff --git a/templates/srl/topology-7220IXRD2.yml b/nodes/srl/topology/7220IXRD2.yml similarity index 100% rename from templates/srl/topology-7220IXRD2.yml rename to nodes/srl/topology/7220IXRD2.yml diff --git a/templates/srl/topology-7220IXRD3.yml b/nodes/srl/topology/7220IXRD3.yml similarity index 100% rename from templates/srl/topology-7220IXRD3.yml rename to nodes/srl/topology/7220IXRD3.yml diff --git a/templates/srl/topology-7250IXR10.yml b/nodes/srl/topology/7250IXR10.yml similarity index 100% rename from templates/srl/topology-7250IXR10.yml rename to nodes/srl/topology/7250IXR10.yml diff --git a/templates/srl/topology-7250IXR6.yml b/nodes/srl/topology/7250IXR6.yml similarity index 100% rename from templates/srl/topology-7250IXR6.yml rename to nodes/srl/topology/7250IXR6.yml diff --git a/templates/ca/csr-root-ca.json b/templates/ca/csr-root-ca.json deleted file mode 100644 index e716a3691..000000000 --- a/templates/ca/csr-root-ca.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "CN": "{{.Prefix}} Root CA", - "key": { - "algo": "rsa", - "size": 2048 - }, - "names": [{ - "C": "BE", - "L": "Antwerp", - "O": "Nokia", - "OU": "Container lab" - }], - "ca": { - "expiry": "262800h" - } -} \ No newline at end of file diff --git a/templates/ca/csr.json b/templates/ca/csr.json deleted file mode 100644 index a2f6ca3b2..000000000 --- a/templates/ca/csr.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "CN": "{{.Name}}.{{.Prefix}}.io", - "key": { - "algo": "rsa", - "size": 2048 - }, - "names": [{ - "C": "BE", - "L": "Antwerp", - "O": "Nokia", - "OU": "Container lab" - }], - "hosts": [ - "{{.Name}}", - "{{.LongName}}", - "{{.Fqdn}}" - ] -} - diff --git a/templates/srl/srl_env.conf b/templates/srl/srl_env.conf deleted file mode 100644 index e4410039b..000000000 --- a/templates/srl/srl_env.conf +++ /dev/null @@ -1,100 +0,0 @@ -# /etc/opt/srlinux/srlinux.rc -# System wide global cli environment configuration file: -# It is loaded first after launching the cli (before loading ~/.srlinuxrc) -# -# The used file format is TOML - -[alias] -# Aliases can be entered at the start of the input line, and must represent a complete command. -# -# The alias name can contain the following: -# - One or more fixed keywords, for example 'display stuff' and 'my-alias' are valid names. -# - One or more required arguments, entered between '{' and '}'. -# These arguments will then be substituted in the alias value before executing. -# -# For example, if you create this alias: -# environment alias "display intf {name}" "info / interface {name} | as table" -# -# Then entering 'display interface ethernet-1/1' will execute 'info / interface ethernet-1/1 | as table'. -# -# The alias value can contain the following keywords: -# - '{}' will add an optional unnamed parameter. -# It defaults to '*' if it is not entered. -# - '{}' will add an optional named parameter with name ''. -# It defaults to '*' if it is not entered. -# -# For example, if you create this alias: -# environment alias "display interface" "info / interface {} subinterface {subinterface} | as table" -# -# Then entering the following are all equivalent: -# display interface -# display interface * -# display interface * subinterface * -# But you can of course also use other values -# display interface my_interface subinterface 1 -# -# E.g.: -# "display intf {name}" = "info / interface {name} | as table" -# "display interface" = "info / interface {} subinterface {subinterface} | as table" - -[prompt] -# Change the prompt displayed before every input line. -# -# The following list of keywords will be replaced automatically: -# aaa_session_id The session id of the current AAA session. -# aaa_user The user name from the current AAA session. -# configuration_session_type -# The current configuration session type (e.g. shared, -# exclusive or private). -# Will output empty string when no configuration session has -# been established. -# host The host name. -# mode The current CLI mode (e.g. candidate, running, show, state -# or tools). -# mode_and_session_type -# The current CLI mode and configuration session type (e.g. -# candidate shared, candidate exclusive, running, show, state -# or tools). -# modified Shows * if there are any changes in the candidate -# configuration to commit or discard. -# modified_with_change_count -# Shows * and the count of changed objects if there are any -# changes in the candidate configuration. -# pwc The present working context, formatted as a CLI command. -# Example: interface abc subinterface 1 -# short_pwc The nodes in the present working context, separated by '>'. -# Note this does not include any key values. -# Example: interface>subinterface -# time Current local time. -# user The user name. -# xpath_pwc The present working context, formatted as a xpath path. -# Example: /interface[name=abc]/subinterface[index=1] -# -# Default value: -# value = "--{{ {modified}{mode_and_session_type} }}--[ {pwc} ]--\n{host}# " - -[bottom-toolbar] -# Change the prompt displayed before every input line. -# -# For possible list of keyword see the prompt section. -# In addition | can be used to separate left and right aligned part. -# Note: The bottom toolbar is not shown in the dumb terminal mode. -# -# Default value: -# value = "Current mode: {mode_and_session_type}| {aaa_user} ({aaa_session_id}) {time}" - -[output-display-format] -# The default CLI output display format, can be json or text. -# It is used when the user does not specify the output modifier in the command itself. -# If the value is set to "json", then `info` will behave the same as `info | as json`. -# If the value is set to "text", then `info` will behave the same as `info | as text`. -# -# Default value: -# value = "text" - -[space-completion] -# Trigger auto-completion whenever you type a space (or on enter with incomplete command) -# Note: The space completion part does not work in the dumb terminal mode. -# -# Default value: -# enabled = false \ No newline at end of file diff --git a/tests/03-basic-ceos/01-two-ceos.robot b/tests/03-basic-ceos/01-two-ceos.robot index bd36eb059..c0e1d864a 100644 --- a/tests/03-basic-ceos/01-two-ceos.robot +++ b/tests/03-basic-ceos/01-two-ceos.robot @@ -46,14 +46,14 @@ Ensure n1 is reachable over ssh ... address=${n1-mgmt-ip} ... username=admin ... password=admin - ... try_for=60 + ... try_for=120 Ensure n2 is reachable over ssh Common.Login via SSH with username and password ... address=${n2-mgmt-ip} ... username=admin ... password=admin - ... try_for=60 + ... try_for=120 *** Keywords *** Cleanup diff --git a/types/types.go b/types/types.go index a9f1e3782..2ec303507 100644 --- a/types/types.go +++ b/types/types.go @@ -46,21 +46,19 @@ type MgmtNet struct { // NodeConfig is a struct that contains the information of a container element type NodeConfig struct { - ShortName string - LongName string - Fqdn string - LabDir string // LabDir is a directory related to the node, it contains config items and/or other persistent state - Index int - Group string - Kind string - // path to config template file that is used for config generation - Config string + ShortName string + LongName string + Fqdn string + LabDir string // LabDir is a directory related to the node, it contains config items and/or other persistent state + Index int + Group string + Kind string + Config string // path to config template file that is used for config generation ResConfig string // path to config file that is actually mounted to the container and is a result of templation NodeType string Position string License string Image string - Topology string Sysctls map[string]string User string Entrypoint string @@ -90,13 +88,14 @@ type NodeConfig struct { } // GenerateConfig generates configuration for the nodes -func (node *NodeConfig) GenerateConfig(dst, defaultTemplatePath string) error { - if utils.FileExists(dst) && (node.Config == defaultTemplatePath) { +// out of the templ based on the node configuration +func (node *NodeConfig) GenerateConfig(dst, templ string) error { + if utils.FileExists(dst) && (node.Config != "") { log.Debugf("config file '%s' for node '%s' already exists and will not be generated", dst, node.ShortName) return nil } log.Debugf("generating config for node %s from file %s", node.ShortName, node.Config) - tpl, err := template.New(filepath.Base(node.Config)).ParseFiles(node.Config) + tpl, err := template.New(filepath.Base(node.Config)).Parse(templ) if err != nil { return err } diff --git a/utils/file.go b/utils/file.go index 90e694c20..4c94a115e 100644 --- a/utils/file.go +++ b/utils/file.go @@ -74,17 +74,20 @@ func CopyFileContents(src, dst string) (err error) { return } -func CreateFile(file, content string) { +// CreateFile writes content to a file by path `file` +func CreateFile(file, content string) error { var f *os.File f, err := os.Create(file) if err != nil { - panic(err) + return err } defer f.Close() if _, err := f.WriteString(content + "\n"); err != nil { - panic(err) + return err } + + return nil } // CreateDirectory creates a directory by a path with a mode/permission specified by perm. From 930d9ad3d179c8ae7dde9816b6a6ae8a149bc73f Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Mon, 28 Jun 2021 21:10:22 +0200 Subject: [PATCH 14/14] fixed links to new config locations --- docs/manual/kinds/ceos.md | 2 +- docs/manual/kinds/crpd.md | 2 +- docs/manual/kinds/srl.md | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/manual/kinds/ceos.md b/docs/manual/kinds/ceos.md index 21950c45a..55047180b 100644 --- a/docs/manual/kinds/ceos.md +++ b/docs/manual/kinds/ceos.md @@ -109,7 +109,7 @@ cEOS nodes have a dedicated [`config`](../conf-artifacts.md#identifying-a-lab-di used as a startup config instead. #### Default node configuration -When a node is defined without `config` statement present, containerlab will generate an empty config from [this template](https://github.com/srl-labs/containerlab/blob/master/templates/arista/ceos.cfg.tpl) and copy it to the config directory of the node. +When a node is defined without `config` statement present, containerlab will generate an empty config from [this template](https://github.com/srl-labs/containerlab/blob/master/nodes/crpd/crpd.cfg) and copy it to the config directory of the node. ```yaml # example of a topo file that does not define a custom config diff --git a/docs/manual/kinds/crpd.md b/docs/manual/kinds/crpd.md index 3fc6f529f..5f649fcc6 100644 --- a/docs/manual/kinds/crpd.md +++ b/docs/manual/kinds/crpd.md @@ -96,7 +96,7 @@ When containerlab launches cRPD node, it will assign IPv4/6 address to the `eth0 cRPD nodes have a dedicated [`config`](../conf-artifacts.md#identifying-a-lab-directory) directory that is used to persist the configuration of the node. It is possible to launch nodes of `crpd` kind with a basic "empty" config or to provide a custom config file that will be used as a startup config instead. #### Default node configuration -When a node is defined without `config` statement present, containerlab will generate an empty config from [this template](https://github.com/srl-labs/containerlab/blob/master/templates/crpd/juniper.conf) and copy it to the config directory of the node. +When a node is defined without `config` statement present, containerlab will generate an empty config from [this template](https://github.com/srl-labs/containerlab/blob/master/nodes/crpd/crpd.cfg) and copy it to the config directory of the node. ```yaml # example of a topo file that does not define a custom config diff --git a/docs/manual/kinds/srl.md b/docs/manual/kinds/srl.md index be62ce20f..a22c03107 100644 --- a/docs/manual/kinds/srl.md +++ b/docs/manual/kinds/srl.md @@ -40,7 +40,7 @@ Based on the provided type, containerlab will generate the [topology file](#topo ### Node configuration SR Linux nodes have a dedicated [`config`](#config-directory) directory that is used to persist the configuration of the node. It is possible to launch nodes of `srl` kind with a basic "empty" config or to provide a custom config file that will be used as a startup config instead. #### Default node configuration -When a node is defined without `config` statement present, containerlab will generate an empty config from [this template](https://github.com/srl-labs/containerlab/blob/master/templates/srl/srlconfig.tpl) and put it in that directory. +When a node is defined without `config` statement present, containerlab will generate an empty config from [this template](https://github.com/srl-labs/containerlab/blob/master/nodes/srl/srl.cfg) and put it in that directory. ```yaml # example of a topo file that does not define a custom config @@ -149,8 +149,5 @@ The `config` directory is mounted to container's `/etc/opt/srlinux/` in `rw` mod banner cli config.json devices tls ztp ``` -#### CLI env config -Another file that SR Linux expects to have is the `srlinux.conf` file that contains CLI environment config. Containerlab uses a [template of this file](https://github.com/srl-labs/containerlab/blob/master/templates/srl/srl_env.conf) and mounts it to `/home/admin/.srlinux.conf` in `rw` mode. - #### Topology file The topology file that defines the emulated hardware type is driven by the value of the kinds `type` parameter. Depending on a specified `type` the appropriate content will be populated into the `topology.yml` file that will get mounted to `/tmp/topology.yml` directory inside the container in `ro` mode. \ No newline at end of file