diff --git a/clab/config.go b/clab/config.go index a9c197ea0..e549bdde6 100644 --- a/clab/config.go +++ b/clab/config.go @@ -240,6 +240,8 @@ func (c *CLab) createNodeCfg(nodeName string, nodeDef *types.NodeDefinition, idx if err != nil { return nil, err } + nodeCfg.EnforceStartupConfig = c.Config.Topology.GetNodeEnforceStartupConfig(nodeCfg.ShortName) + // initialize license field nodeCfg.License, err = c.Config.Topology.GetNodeLicense(nodeCfg.ShortName) if err != nil { diff --git a/docs/manual/kinds/ceos.md b/docs/manual/kinds/ceos.md index fd8382877..d543ea933 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/nodes/ceos/ceos.cfg) and copy it to the config directory of the node. +When a node is defined without `startup-config` statement present, containerlab will generate an empty config from [this template](https://github.com/srl-labs/containerlab/blob/master/nodes/ceos/ceos.cfg) and copy it to the config directory of the node. ```yaml # example of a topo file that does not define a custom config @@ -138,7 +138,7 @@ topology: startup-config: myconfig.conf ``` -When a config file is passed via `startup-config` parameter, it will override any configuration that may have left upon lab destroy. +When a config file is passed via `startup-config` parameter it will be used during an initial lab deployment. However, a config file that might be in the lab directory of a node takes precedence over the startup-config[^3]. With such topology file containerlab is instructed to take a file `myconfig.conf` from the current working directory, copy it to the lab directory for that specific node under the `/flash/startup-config` name and mount that dir to the container. This will result in this config to act as a startup config for the node. @@ -247,3 +247,4 @@ Consult your distribution's documentation for details regarding configuring cgro [^1]: https://eos.arista.com/ceos-lab-topo/ [^2]: do not remove the template variables from the `Management0` interface, otherwise the nodes will not apply the IP address from docker IPAM service. +[^3]: if startup config needs to be enforced, either deploy a lab with `--reconfigure` flag, or use [`enforce-startup-config`](../nodes.md#enforce-startup-config) setting. \ No newline at end of file diff --git a/docs/manual/nodes.md b/docs/manual/nodes.md index 880f82cfd..c2d731c01 100644 --- a/docs/manual/nodes.md +++ b/docs/manual/nodes.md @@ -60,6 +60,11 @@ Some containerized NOSes require a license to operate or can leverage a license ### startup-config For some kinds it's possible to pass a path to a config file that a node will use on start instead of a bare config. Check documentation for a specific kind to see if `startup-config` element is supported. +Note, that if a config file exists in the lab directory for a given node, then it will take preference over the startup config passed with this setting. If it is desired to discard the previously saved config and use the startup config instead, use the `enforce-startup-config` setting or deploy a lab with the [`reconfigure`](../cmd/deploy.md#reconfigure) flag. + +### enforce-startup-config +By default, containerlab will use the config file that is available in the lab directory for a given node even if the `startup config` parameter points to another file. To make a node to boot with the config set with `startup-config` parameter no matter what, set the `enforce-startup-config` to `true`. + ### binds In order to expose host files to the containerized nodes a user can leverage the bind mount capability. diff --git a/nodes/srl/srl.go b/nodes/srl/srl.go index 54dd21d06..09b75038a 100644 --- a/nodes/srl/srl.go +++ b/nodes/srl/srl.go @@ -226,6 +226,7 @@ func createSRLFiles(nodeCfg *types.NodeConfig) error { utils.CreateDirectory(path.Join(nodeCfg.LabDir, "config"), 0777) dst = filepath.Join(nodeCfg.LabDir, "config", "config.json") if nodeCfg.StartupConfig != "" { + log.Debugf("GenerateConfig reading startup-config %s", nodeCfg.StartupConfig ) c, err := os.ReadFile(nodeCfg.StartupConfig) if err != nil { return err diff --git a/types/node_definition.go b/types/node_definition.go index 033950900..1b2d740d1 100644 --- a/types/node_definition.go +++ b/types/node_definition.go @@ -11,15 +11,16 @@ const ( // NodeDefinition represents a configuration a given node can have in the lab definition file type NodeDefinition struct { - Kind string `yaml:"kind,omitempty"` - Group string `yaml:"group,omitempty"` - Type string `yaml:"type,omitempty"` - StartupConfig string `yaml:"startup-config,omitempty"` - Config *ConfigDispatcher `yaml:"config,omitempty"` - Image string `yaml:"image,omitempty"` - License string `yaml:"license,omitempty"` - Position string `yaml:"position,omitempty"` - Cmd string `yaml:"cmd,omitempty"` + Kind string `yaml:"kind,omitempty"` + Group string `yaml:"group,omitempty"` + Type string `yaml:"type,omitempty"` + StartupConfig string `yaml:"startup-config,omitempty"` + EnforceStartupConfig bool `yaml:"enforce-startup-config,omitempty"` + Config *ConfigDispatcher `yaml:"config,omitempty"` + Image string `yaml:"image,omitempty"` + License string `yaml:"license,omitempty"` + Position string `yaml:"position,omitempty"` + Cmd string `yaml:"cmd,omitempty"` // list of bind mount compatible strings Binds []string `yaml:"binds,omitempty"` // list of port bindings @@ -77,6 +78,13 @@ func (n *NodeDefinition) GetStartupConfig() string { return n.StartupConfig } +func (n *NodeDefinition) GetEnforceStartupConfig() bool { + if n == nil { + return false + } + return n.EnforceStartupConfig +} + func (n *NodeDefinition) GetConfigDispatcher() *ConfigDispatcher { if n == nil { return nil diff --git a/types/topology.go b/types/topology.go index 9b3fbee7d..d8a0a1bd8 100644 --- a/types/topology.go +++ b/types/topology.go @@ -171,6 +171,19 @@ func (t *Topology) GetNodeStartupConfig(name string) (string, error) { return cfg, nil } +func (t *Topology) GetNodeEnforceStartupConfig(name string) bool { + if ndef, ok := t.Nodes[name]; ok { + if ndef.GetEnforceStartupConfig() { + return true + } + if t.GetKind(t.GetNodeKind(name)).GetEnforceStartupConfig() { + return true + } + return t.GetDefaults().GetEnforceStartupConfig() + } + return false +} + func (t *Topology) GetNodeLicense(name string) (string, error) { var license string if ndef, ok := t.Nodes[name]; ok { diff --git a/types/types.go b/types/types.go index 558ac317a..8b38928e2 100644 --- a/types/types.go +++ b/types/types.go @@ -53,29 +53,30 @@ type MgmtNet struct { // NodeConfig is a struct that contains the information of a container element type NodeConfig struct { - ShortName string // name of the Node inside topology YAML - LongName string // containerlab-prefixed unique container name - 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 - StartupConfig string // path to config template file that is used for startup config generation - ResStartupConfig string // path to config file that is actually mounted to the container and is a result of templation - Config *ConfigDispatcher - 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 - Sysctls map[string]string - User string - Entrypoint string - Cmd string - Env map[string]string - Binds []string // Bind mounts strings (src:dest:options) - PortBindings nat.PortMap // PortBindings define the bindings between the container ports and host ports - PortSet nat.PortSet // PortSet define the ports that should be exposed on a container + ShortName string // name of the Node inside topology YAML + LongName string // containerlab-prefixed unique container name + 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 + StartupConfig string // path to config template file that is used for startup config generation + EnforceStartupConfig bool // when set to true will enforce the use of startup-config, even when config is present in the lab directory + ResStartupConfig string // path to config file that is actually mounted to the container and is a result of templation + Config *ConfigDispatcher + 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 + Sysctls map[string]string + User string + Entrypoint string + Cmd string + Env map[string]string + Binds []string // Bind mounts strings (src:dest:options) + PortBindings nat.PortMap // PortBindings define the bindings between the container ports and host ports + PortSet nat.PortSet // PortSet define the ports that should be exposed on a container // container networking mode. if set to `host` the host networking will be used for this node, else bridged network NetworkMode string MgmtNet string // name of the docker network this node is connected to with its first interface @@ -105,12 +106,15 @@ type NodeConfig struct { // GenerateConfig generates configuration for the nodes // out of the templ based on the node configuration and saves the result to dst func (node *NodeConfig) GenerateConfig(dst, templ string) error { - // if startup config is not set, and the config file is already present in the node dir - // we do not regenerate the config, since we will take what was saved from the previous run - // in other words, the startup config set by a user takes preference and will trigger config generation - if utils.FileExists(dst) && (node.StartupConfig == "") { - log.Debugf("config file '%s' for node '%s' already exists and will not be generated", dst, node.ShortName) + + // If the config file is already present in the node dir + // we do not regenerate the config unless EnforceStartupConfig is explicitly set to true and startup-config points to a file + // this will persist the changes that users make to a running config when booted from some startup config + if utils.FileExists(dst) && (node.StartupConfig == "" || !node.EnforceStartupConfig) { + log.Infof("config file '%s' for node '%s' already exists and will not be generated/reset", dst, node.ShortName) return nil + } else if node.EnforceStartupConfig { + log.Infof("Startup config for '%s' node enforced: '%s'", node.ShortName, dst) } log.Debugf("generating config for node %s from file %s", node.ShortName, node.StartupConfig) tpl, err := template.New(filepath.Base(node.StartupConfig)).Parse(templ)