-
Notifications
You must be signed in to change notification settings - Fork 1
File Output
This page explains how dot2net generates and controls output files.
File output in dot2net is controlled by two key concepts:
| Concept | Role |
|---|---|
| FileDefinition | Defines file properties (name, path, output location) |
| ConfigTemplate.file | Associates a template with a file for output |
FileDefinitions declare output files with their properties:
file:
- name: frr.conf
path: /etc/frr/frr.conf # where it goes in the container, and where it is written
- name: check
output: root # Output to lab root directory
name_suffix: .sh # Results in "r1.sh", "r2.sh"A platform's own files are declared by its module, not by your topology.
lab.conf,topo.yaml,spec.yamland Kathara's<device>.startupare output files of the containerlab, TiNET and Kathara modules. You write the content as a template — a node config entry namedstartup, for instance — and each module puts it where its platform expects it: containerlab inexec:, TiNET incmds:, Kathara in<device>.startup. Declaring such a file yourself makes two definitions write one file, which dot2net rejects. Thefile:section is for files that are yours: a check script, an inventory, a report.
The fields answer three questions: what the file is called, where it ends up, and how it reaches the container.
| Field | What it decides | Default |
|---|---|---|
name |
The name a config entry uses to say file: <name>. It is the output filename too, unless name_prefix/name_suffix build one |
Required |
path |
Where the file belongs inside the container — and, from that, where it is written in the output: /etc/frr/frr.conf becomes r1/etc/frr/frr.conf. A file with no path is generated and left in the output; it never reaches a container |
"" |
provide |
How it gets there. mount shows the container the generated file itself, before the container starts; copy gives the container a copy of its own, once it is up. Which to use, and why it matters
|
mount |
scope |
What one file is generated for: the whole lab (network), each group (group), or each node (node). What each is for
|
node |
output |
Where it is placed, when that is not where its scope would put it. root puts a per-node file at the lab root instead of under the node's own directory — how Kathara's r1.startup, which has to sit beside lab.conf, gets there |
Inferred from scope |
name_prefix |
Build the filename from the object's own name instead of from name: name_suffix: .startup writes r1.startup, r2.startup
|
"" |
name_suffix |
See name_prefix
|
"" |
executable |
Write the file with the executable bit set. For a script that is meant to be run | false |
Scope decides how many of this file there are, and what each one can see. A device's own configuration is one per node; a platform's deployment file is one for the whole lab — unless the lab spans machines, in which case it is one per machine, holding that machine's nodes and nothing else. The aggregations follow from the scope: an object can only gather what it owns.
| Scope | One file per | Aggregations it can use |
|---|---|---|
network |
the whole lab |
{{ .nodes_<name> }}, {{ .groups_<name> }}
|
group |
each group of a class | the group's own children — a connection with one end outside the group is not one of them |
node (default) |
each node | {{ .interfaces_<name> }} |
A group-scoped file is what makes a lab larger than one machine work: each machine's file holds its own nodes and the links it can wire itself. See Placing nodes on machines.
The output field controls where files are placed:
output_directory/
├── topo.yaml # scope: network (always in root)
├── r1.sh # scope: node, output: root
├── r2.sh # scope: node, output: root
├── r1/
│ ├── etc/frr/frr.conf # a file with path: /etc/frr/frr.conf
│ └── staging/etc/motd # a file with provide: copy
└── r2/
└── etc/frr/frr.conf
Examples:
file:
# Traditional: node config in subdirectory
- name: frr.conf
path: /etc/frr/frr.conf
# output: node (default for node scope)
# Per-node file in the root directory, named after the node
- name: check
output: root
name_suffix: .sh
# Results: r1.sh, r2.sh in root
# Network-scope file (always in root)
- name: topo.yaml
scope: networkWhen name_prefix or name_suffix is set, the filename is constructed as:
{name_prefix}{object_name}{name_suffix}
| Configuration | Object | Result |
|---|---|---|
name_suffix: .sh |
r1 | r1.sh |
name_prefix: config_ |
r1 | config_r1 |
name_prefix: init_, name_suffix: .sh |
r1 | init_r1.sh |
If neither prefix nor suffix is set, the name field is used as the filename.
A ConfigTemplate generates output to a file when it has a file attribute:
nodeclass:
- name: router
config:
- name: frr_config
file: frr.conf # Output to this file
template:
- "hostname {{ .name }}"
- "!"A file is generated for an object only if:
- The object has a ConfigTemplate with matching
fileattribute - The ConfigTemplate conditions are satisfied (required_params, class conditions, etc.)
This means different nodes can generate different sets of files based on their class configurations.
path says where the file belongs inside the container. Two things follow from
it: where the file is written in the output, and how it gets from there into the
container.
file:
- name: frr.conf
path: /etc/frr/frr.conf # written to r1/etc/frr/frr.confChanged in 0.8.0. A node's files are laid out by the path they take inside the container.
path: /etc/frr/frr.confused to be written tor1/frr.confand is now written tor1/etc/frr/frr.conf. Anything reading generated files by path has to follow.
Neither is the better one, and which to use follows from what the file is for.
mount (the default) |
copy |
|
|---|---|---|
| What the container gets | the generated file itself | its own copy |
| When | before the container's first process runs | once the container is up |
| What the container writes | reaches the generated file | stays in the container |
| Can serve a file read while booting | yes | no |
file:
- name: frr.conf
path: /etc/frr/frr.conf # provide: mount, and must be: FRR reads it while booting
- name: motd
path: /etc/motd
provide: copy # the container may rewrite it; the generated file stays as generatedA mounted file is the generated file, shown to the container. That cuts both ways: what the container writes reaches it, and a container's own startup can take ownership of it.
A copied file waits in a staging directory — r1/staging/etc/motd, mounted
read only at /staging — and is copied to its own path by a command the module
puts ahead of the topology's own startup commands. Copy cannot serve a file read
while booting on any platform: containerlab's exec:, TiNET's cmds: and
Kathara's <device>.startup all run after the container has started.
Nothing falls back silently. A combination a platform cannot honour is reported — see Module Kathara, whose mounts are directories rather than files.
A node class can name files to copy out of the container before the lab is destroyed:
nodeclass:
- name: router
collect: ["/var/log/frr.log"]Each entry is a template, so a path that follows a value stays right when the
value is changed. Collected files land in collected/<node>/<the path inside the container>, beside the generated tree rather than in it. A module can declare
what it needs back — frrLogFile collects its own log — so a topology that
never named the file does not have to name it to get it back.
The entry script does the copying, which is why a topology that collects
anything needs one (module_config.<module>.generate_scripts: true). See
Command Reference.
- YAML Configuration - Files Configuration
- Module System - How modules use file definitions
- Template System - Template syntax for file content