Skip to content

File Output

sat edited this page Aug 12, 2026 · 8 revisions

File Output

This page explains how dot2net generates and controls output files.

Overview

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

File Definition

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 scenario. lab.conf, topo.yaml, spec.yaml and Kathara's <device>.startup are output files of the containerlab, TiNET and Kathara modules. You write the content as a template — a node config entry named startup, for instance — and each module puts it where its platform expects it: containerlab in exec:, TiNET in cmds:, Kathara in <device>.startup. Declaring such a file yourself makes two definitions write one file, which dot2net rejects. The file: section is for files that are yours: a check script, an inventory, a report.

FileDefinition Fields

Field Description Default
name File identifier (referenced by ConfigTemplate.file) Required
path Where the file goes inside the container. Also decides where it is written in the output ""
scope network, group or node - what the file is generated for node
output root or node - output directory location Inferred from scope
name_prefix Prefix for output filename ""
name_suffix Suffix for output filename ""
provide How the file reaches the container: mount or copy mount
executable Write the file with the executable bit set false

Scope: what a file is generated for

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.

Output Location Control

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: network

Filename Generation

When 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.

ConfigTemplate and File Output

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 }}"
          - "!"

File Generation Rules

A file is generated for an object only if:

  1. The object has a ConfigTemplate with matching file attribute
  2. 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.

Reaching the container (path and provide)

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.conf

Changed in 0.8.0. A node's files are laid out by the path they take inside the container. path: /etc/frr/frr.conf used to be written to r1/frr.conf and is now written to r1/etc/frr/frr.conf. Anything reading generated files by path has to follow.

provide: mount or copy

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 generated

A 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 scenario'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.

Collecting files back

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 scenario 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 scenario that collects anything needs one (module_config.<module>.generate_scripts: true). See Command Reference.

See Also

Clone this wiki locally