diff --git a/docs/manual/kinds/ceos.md b/docs/manual/kinds/ceos.md index 827dc3435a..fd94ac3dc2 100644 --- a/docs/manual/kinds/ceos.md +++ b/docs/manual/kinds/ceos.md @@ -280,7 +280,18 @@ topology: #### User defined config -It is possible to make ceos nodes to boot up with a user-defined config instead of a built-in one. With a [`startup-config`](../nodes.md#startup-config) property a user sets the path to the config file that will be mounted to a container and used as a startup config: +cEOS supports user defined startup configurations in two forms: + +- Full startup configuration. +- Partial startup configuration. + +Both types of startup configurations are only applied during initial lab deployment. When you save configuration in cEOS to the startup-config using `wr` or `copy running-config startup-config`, the saved configuration will override the startup configuration on subsequent reboots. + +##### Full startup configuration + +The full startup configuration is used to fully replace the default startup configuration that is applied. This means you must define the necessary configuration for management interface IP addressing and services in your configuration to access cEOS. + +You can use the template variables that are defined in the [default startup configuration](https://github.com/srl-labs/containerlab/blob/main/nodes/ceos/ceos.cfg). On lab deployment the template variables will be replaced/substituted. ```yaml name: ceos_lab @@ -321,6 +332,21 @@ It is possible to change the default config which every ceos node will start wit - endpoints: ["ceos1:eth1", "ceos2:eth1"] ``` +##### Partial startup configuration + +The partial startup configuration is appended to the default startup configuration. This is useful to preconfigure certain things like additional interfaces, while also taking advantage of the startup configuration that containerlab applies by default for management interface IP addressing and services. + +The partial startup configuration must contain `.partial` in the filename. For example: `config.partial.txt` or `config.partial` + +```yaml +name: ceos_partial_startup_cfg +topology: + nodes: + ceos: + kind: -{{ kind_code_name }}- + startup-config: configuration.txt.partial +``` + #### Saving configuration In addition to cli commands such as `write memory` user can take advantage of the [`containerlab save`](../../cmd/save.md) command. It saves running cEOS configuration into a startup config file effectively calling the `write` CLI command. diff --git a/nodes/ceos/ceos.cfg b/nodes/ceos/ceos.cfg index e8934d2db8..e1bc854a3b 100644 --- a/nodes/ceos/ceos.cfg +++ b/nodes/ceos/ceos.cfg @@ -34,5 +34,6 @@ management api http-commands vrf {{ .Env.CLAB_MGMT_VRF }} no shutdown {{end}} +{{ .PartialCfg }} ! end diff --git a/nodes/ceos/ceos.go b/nodes/ceos/ceos.go index a89a098666..c663f72c9b 100644 --- a/nodes/ceos/ceos.go +++ b/nodes/ceos/ceos.go @@ -5,6 +5,7 @@ package ceos import ( + "bytes" "context" _ "embed" "encoding/json" @@ -16,6 +17,7 @@ import ( "path/filepath" "regexp" "strings" + "text/template" "github.com/charmbracelet/log" clabconstants "github.com/srl-labs/containerlab/constants" @@ -81,6 +83,7 @@ func Register(r *clabnodes.NodeRegistry) { type ceos struct { clabnodes.DefaultNode + partialStartupCfg string } // intfMap represents interface mapping config file. @@ -217,7 +220,12 @@ func (n *ceos) createCEOSFiles(ctx context.Context) error { if err != nil { return err } - currentCfgTemplate = string(c) + + if clabutils.IsPartialConfigFile(nodeCfg.StartupConfig) { + n.partialStartupCfg = string(c) + } else { + currentCfgTemplate = string(c) + } } err = n.GenerateConfig(nodeCfg.ResStartupConfig, currentCfgTemplate) @@ -402,3 +410,28 @@ func (n *ceos) CheckInterfaceName() error { return nil } + +type ceosTemplateData struct { + *clabtypes.NodeConfig + PartialCfg string +} + +func (n *ceos) GenerateConfig(dst, t string) error { + data := ceosTemplateData{ + NodeConfig: n.Cfg, + PartialCfg: n.partialStartupCfg, + } + + ceosCfgTpl, err := template.New("ceos-config").Funcs(clabutils.CreateFuncs()).Parse(t) + if err != nil { + return fmt.Errorf("failed to parse ceos cfg template for node %q: %w", n.Cfg.ShortName, err) + } + + buf := new(bytes.Buffer) + err = ceosCfgTpl.Execute(buf, data) + if err != nil { + return fmt.Errorf("failed to execute ceos cfg template for node %q: %w", n.Cfg.ShortName, err) + } + + return clabutils.CreateFile(dst, buf.String()) +} diff --git a/tests/03-basic-ceos/02-partial-config.robot b/tests/03-basic-ceos/02-partial-config.robot new file mode 100644 index 0000000000..cfa0f5b816 --- /dev/null +++ b/tests/03-basic-ceos/02-partial-config.robot @@ -0,0 +1,56 @@ +*** Settings *** +Library OperatingSystem +Resource ../common.robot +Resource ../ssh.robot + +Suite Teardown Run Keyword Cleanup + + +*** Variables *** +${lab-name} ceos-partial-test +${lab-file-name} partial-clab.yml +${runtime} docker + + +*** Test Cases *** +Deploy ${lab-name} lab + Log ${CURDIR} + ${rc} ${output} = Run And Return Rc And Output + ... ${CLAB_BIN} deploy -t ${CURDIR}/${lab-file-name} + Log ${output} + Should Be Equal As Integers ${rc} 0 + +Verify default configuration on ceos1 + ${f} = OperatingSystem.Get File ${CURDIR}/clab-${lab-name}/ceos1/flash/startup-config + Log ${f} + Should Contain ${f} hostname ceos1 + Should Contain ${f} username admin + Should Contain ${f} management api gnmi + +Verify partial startup configuration on ceos2 + ${f} = OperatingSystem.Get File ${CURDIR}/clab-${lab-name}/ceos2/flash/startup-config + Log ${f} + Should Contain ${f} hostname ceos2 + Should Contain ${f} username admin + Should Contain ${f} management api gnmi + Should Contain ${f} description PARTIAL_CONFIG_TEST + Should Contain ${f} interface Ethernet1 + +Verify full startup configuration on ceos3 + ${f} = OperatingSystem.Get File ${CURDIR}/clab-${lab-name}/ceos3/flash/startup-config + Log ${f} + Should Contain ${f} hostname ceos3-full + Should Not Contain ${f} management api gnmi + +Destroy ${lab-name} lab + Log ${CURDIR} + ${rc} ${output} = Run And Return Rc And Output + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file-name} + Log ${output} + Should Be Equal As Integers ${rc} 0 + + +*** Keywords *** +Cleanup + Run ${CLAB_BIN} destroy -t ${CURDIR}/${lab-file-name} --cleanup + Run rm -rf ${CURDIR}/clab-${lab-name} diff --git a/tests/03-basic-ceos/full_config.cfg b/tests/03-basic-ceos/full_config.cfg new file mode 100644 index 0000000000..f7024e27c2 --- /dev/null +++ b/tests/03-basic-ceos/full_config.cfg @@ -0,0 +1,2 @@ +hostname {{ .ShortName }}-full +! diff --git a/tests/03-basic-ceos/interface.partial.cfg b/tests/03-basic-ceos/interface.partial.cfg new file mode 100644 index 0000000000..81f8ab1f46 --- /dev/null +++ b/tests/03-basic-ceos/interface.partial.cfg @@ -0,0 +1,4 @@ +interface Ethernet1 + description PARTIAL_CONFIG_TEST + no shutdown +! diff --git a/tests/03-basic-ceos/partial-clab.yml b/tests/03-basic-ceos/partial-clab.yml new file mode 100644 index 0000000000..5432aff760 --- /dev/null +++ b/tests/03-basic-ceos/partial-clab.yml @@ -0,0 +1,19 @@ +name: ceos-partial-test + +topology: + kinds: + arista_ceos: + image: ceos:4.32.0F + nodes: + ceos1: + kind: arista_ceos + ceos2: + kind: arista_ceos + startup-config: ./interface.partial.cfg + ceos3: + kind: arista_ceos + startup-config: ./full_config.cfg + + links: + - endpoints: ["ceos1:eth1", "ceos2:eth1"] + - endpoints: ["ceos2:eth1", "ceos3:eth1"]