YAML e Configurações de Automação de Rede

pDamascenopDamasceno
4 min read

Quando trabalhamos com arquivos YAML para armazenar configurações de dispositivos de rede, é comum nos depararmos com um problema: strings multilinha que representam comandos são carregadas como uma única string sem quebras de linha. Isso pode gerar dificuldades ao aplicar essas configurações em dispositivos usando ferramentas como o Netmiko, que esperam comandos separados por quebras de linha. Neste artigo, vamos explorar esse problema e apresentar uma solução simples usando o estilo literal do YAML.

O Problema

Vamos analisar o seguinte trecho de um arquivo YAML, que define a configuração de um dispositivo de rede:

R1:
  conn:
    host: clab-ospf_foundations__ospf_broadcast-r1
    device_type: cisco_ios
    username: admin
    password: autonetops
    port: 22
  config:
    interface Ethernet0/1
      ip address 100.100.100.1 255.255.255.0
      !
    interface Tunnel1234
      ip address 10.1.1.1 255.255.255.0
      no ip redirects
      ip nhrp map 10.1.1.2 100.100.100.2
      ip nhrp map 10.1.1.3 100.100.100.3
      ip nhrp map 10.1.1.4 100.100.100.4
      ip nhrp network-id 111
      ip ospf network broadcast
      ip ospf priority 100
      tunnel source Ethernet0/1
      tunnel mode gre multipoint
      !
    interface Loopback0
      ip address 1.1.1.1 255.255.255.255
      ip ospf network point-to-point
      !
    router ospf 1
      network 1.1.1.1 0.0.0.0 area 0
      network 10.1.1.0 0.0.0.255 area 0

Ao carregar esse YAML em Python usando yaml.safe_load, a seção config é interpretada como uma única string, com as quebras de linha substituídas por espaços:

'interface Ethernet0/1 ip address 100.100.100.1 255.255.255.0 ! interface Tunnel1234 ...'

Isso se torna um problema quando tentamos mandar as configurações para um roteador com netmiko por exemplo:

from netmiko import ConnectHandler

### LOAD YAML CODE INTO A VARIABLE
device = yaml.safe_load(file_path)

conn = ConnectHandler(**device['R1']['conn'])
conn.send_config_set(device['R1']['config'])

## Esse código irá retornar um erro, pois não há quebra de linhas
## E nós não conseguimos quebrar em vários comandos, pois não há quebra de linhas

Essa string "compactada" não é adequada para ferramentas de automação de rede, pois elas não conseguem identificar os comandos individuais sem as quebras de linha.

💡
Você pode acompanhar esse exemplo no link a seguir

A Solução

Para preservar as quebras de linha na string de configuração, podemos usar o estilo literal do YAML, adicionando o símbolo de pipe (|) após a chave config:. Isso instrui o parser YAML a manter o texto exatamente como foi escrito, incluindo quebras de linha e indentação. Veja o YAML corrigido:

R1:
  conn:
    host: clab-ospf_foundations__ospf_broadcast-r1
    device_type: cisco_ios
    username: admin
    password: autonetops
    port: 22
  config: |
    interface Ethernet0/1
      ip address 100.100.100.1 255.255.255.0
      !
    interface Tunnel1234
      ip address 10.1.1.1 255.255.255.0
      no ip redirects
      ip nhrp map 10.1.1.2 100.100.100.2
      ip nhrp map 10.1.1.3 100.100.100.3
      ip nhrp map 10.1.1.4 100.100.100.4
      ip nhrp network-id 111
      ip ospf network broadcast
      ip ospf priority 100
      tunnel source Ethernet0/1
      tunnel mode gre multipoint
      !
    interface Loopback0
      ip address 1.1.1.1 255.255.255.255
      ip ospf network point-to-point
      !
    router ospf 1
      network 1.1.1.1 0.0.0.0 area 0
      network 10.1.1.0 0.0.0.255 area 0

Agora, ao carregar com yaml.safe_load, a seção config mantém suas quebras de linha:

"interface Ethernet0/1\n ip address 100.100.100.1 255.255.255.0\n !\ninterface Tunnel1234\n ip address 10.1.1.1 255.255.255.0\n no ip redirects\n..."

Essa string multilinha pode ser facilmente dividida em comandos individuais, tornando-a compatível com ferramentas como o Netmiko.

from netmiko import ConnectHandler

### LOAD YAML CODE INTO A VARIABLE
device = yaml.safe_load(file_path)

def convert_yaml_to_commands(config):
    """Convert YAML config to list of commands"""
    commands = []
    for line in config.split('\n'):
        line = line.strip()
        commands.append(line.lstrip())
    return commands

conn = ConnectHandler(**device['R1']['conn'])
commands = convert_yaml_to_commands(config)
conn.send_config_set(commands)


## Esse código irá retornar um erro, pois não há quebra de linhas
## E nós não conseguimos quebrar em vários comandos, pois não há quebra de linhas

Por Que Isso Funciona?

  • Comportamento Padrão: Sem o |, o YAML utiliza o estilo "folded" (compactado), substituindo quebras de linha por espaços.

  • Estilo Literal: O | garante que as quebras de linha e a indentação sejam preservadas, mantendo a estrutura da configuração.

Uma Abordagem Alternativa

Outra opção é estruturar a seção config como uma lista de comandos, usando hífens (-):

R1:
  conn:
    # ... detalhes de conexão ...
  config:
    - interface Ethernet0/1
    - ip address 100.100.100.1 255.255.255.0
    - !
    - interface Tunnel1234
    # ... e assim por diante

Isso será carregado como uma lista em Python, o que pode ser útil para algumas ferramentas de automação. No entanto, essa abordagem perde a hierarquia de indentação, o que pode dificultar a leitura ou impactar a funcionalidade em certos cenários.

Conclusão

Para configurações de rede em YAML, adicionar o símbolo | para usar o estilo literal é uma solução simples e eficaz para preservar quebras de linha e indentação. Isso garante que os comandos sejam carregados corretamente e estejam prontos para automação, sendo a abordagem mais indicada para configurações hierárquicas como a apresentada. Boa automação!

0
Subscribe to my newsletter

Read articles from pDamasceno directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

pDamasceno
pDamasceno