No primeiro artigo desta série discutimos a diferença entre o modelo Declarativo e Imperativo. Concordamos que a escolha não é simples e que no mundo de telecomunicações dificilmente encontramos infraestrutura suficiente para adotarmos o modelo declarativo. No entanto, é também verdade que o modelo declarativo trás diversas vantagens. Por isso, vamos adotar um modelo híbrido no decorrer desta série, onde iremos demonstrar a construção de cada ferramenta que fará parte deste nosso modelo.

Quando pensamos em código, programação, um conceito básico que temos é utilizar um editor de texto que vai marcar certas palavras chaves daquela linguagem de programação, o que facilita o desenvolvimento e resolução de problemas do código. Como vamos tratar nossas configurações como código, precisaremos primeiro, implementar um marcador de sintaxe para a nossa configuração.

A Linguagem

Para efeitos práticos, iremos utilizar um arquivo de configuração de um equipamento da NOKIA que utiliza um sistema chamado SR-OS ou TiMOS. Este equipamento faz parte de uma série que a Nokia identifica como 7x50 cujo produto de maior destaque é sem dúvida o 7750 SR, plataforma que também é utilizada como base para outro produto, chamado de Cloud Mobile Gateway que desempenha as funções de Gateway de uma rede de Packet Core.

Claro que um arquivo de configuração possui diversos contextos e seria impraticável implementar marcador de sintaxe para cada contexto de configuração, porém existem palavras chaves e tipos de informação que se repetem e são muito importantes. Neste artigo iremos analisar como implementar um marcador para essas palavras chaves, mas também deixarei um exemplo de como realizar marcador de sintaxe para um contexto de configuração específico.

Configuração de Exemplo:

exit all
configure
#--------------------------------------------------
echo "System Configuration"
#--------------------------------------------------
    system
        name "SROS-Example"
        location "RiodeJaneiro-Brazil"
        load-balancing
            l4-load-balancing
            system-ip-load-balancing
        exit
        rollback
            rollback-location "cf3:\rollback\rollback"
        exit
        snmp
            streaming
                no shutdown
            exit
            packet-size 9216
        exit
        time
            ntp
                no shutdown
            exit
            sntp
                shutdown
            exit
            zone BRT-03
        exit
    exit
#--------------------------------------------------
echo "System Security Configuration"
#--------------------------------------------------
    system
        security
            ftp-server
            profile "purge"
                default-action deny-all
                entry 10
                    match "back"
                    action permit
                exit
                entry 20
                    match "exit"
                    action permit
                exit
                entry 30
                    match "help"
                    action permit
                exit
                entry 40
                    match "history"
                    action permit
                exit
#--------------------------------------------------
echo "Log Configuration"
#--------------------------------------------------
    log
        file-id 12
            description "KPI-KCI Collection For SR-OS"
            location cf1:
            rollover 15 retention 24
        exit
        file-id 15
            description "NOC-KPIs-KCI"
            location cf1:
            rollover 15 retention 24
        exit
        accounting-policy 1
            description "KPI-KCI Collection For SR-OS"
            record complete-kpi-kci
            collection-interval 15
            to file 12
            no shutdown
        exit
        accounting-policy 10
            description "NOC-KPI-KCI"
            record complete-kpi-kci
            collection-interval 15
            to file 15
            no shutdown
        exit
        syslog 3
            description "LOCAL-INFO"
            address 10.1.189.38
            level notice
            log-prefix "NOKIASROS"
        exit
#--------------------------------------------------
echo "Card Configuration"
#--------------------------------------------------
    card 1
        card-type iom-v
        mda 1
            mda-type m20-v
            no shutdown
        exit
        mda 2
            mda-type isa-ip-reas-v
            no shutdown
        exit
        no shutdown
    exit
    card 2
        card-type iom-v
        mda 1
            mda-type m20-v
            no shutdown
        exit
        mda 2
            mda-type isa-ip-reas-v
            no shutdown
        exit
        shutdown

Visual Studio Code

O VS Code é a IDE queridinha dos desenvolvedores, e não é por acaso. Realmente, o VS Code por ser de código aberto e facilitar a criação de extensões - como essa que vamos criar aqui - traz inúmeros benefícios em relação à outras IDEs. Escolhi o VS Code para essa série por alguns desses benefícios, principalmente a facilidade em criar uma extensão para ele, como vamos analisar em seguida.

Se você ainda não usa o VS Code, eu indico que comece a entender como utilizar a ferramenta, é simples e rápido além disso, o que não falta são tutoriais sobre ela na internet.

A extensão que vamos criar é do tipo Syntax Highlighter e existe uma documentação oficial sobre como prosseguir com a criação dela.

Vou ensinar a vocês como criar sua própria extensão de uma maneira mais simples. Existem muitas informações na documentação oficial e me parecem fora de ordem. Portanto, meu objetivo é guiá-los, passo a passo.

Preparando o Ambiente

Para criar o esqueleto de nossa extensão vamos utilizar um helper chamado Yeoman, com ele fica mais fácil iniciar um projeto de extensão do VS Code.

Antes de ser possível instalar o Yeoman, é preciso instalar o NodeJS que já vem com o gerenciador de pacotes chamado npm que vamos utilizar para instalar as extensões que precisamos.

Entre no site do NodeJs e faça o download de sua última versão estável.

Uma vez instalado o NodeJS, abra um terminal e digite o seguinte comando:

npm install -g yo generator-code

Uma vez instalado, se você estiver utilizando Windows, será preciso permitir a execução de scripts utilizando o PowerShell.

Abra o PowerShell e digite o seguinte comando:

set-executionpolicy remotesigned

Agora já podemos executar o Yeoman e montar o esqueleto da nossa extensão.

Criando o esqueleto da extensão

Uma vez que tenhamos o ambiente preparado, podemos executar o Yeoman, basta digitar yo code no seu terminal de preferência e selecionar uma das opções que a ferramenta disponibiliza.

Em nosso caso, vamos selecionar a opção New Language Support

Yeoman New Language Support
Fonte: https://code.visualstudio.com/api/get-started/your-first-extension

Após selecionar a opção correta, o Yeoman irá fazer uma série de perguntas, conforme figura abaixo.

Yeoman New Language Questions
Fonte: https://code.visualstudio.com/api/get-started/your-first-extension

Em nosso caso, iremos responder as perguntas da seguinte maneira:

? URL or file to import, or none for new: (Deixaremos em branco)
? What's the name of your extension? Nokia SR-OS Syntax
? What's the identifier of your extension? sros
? What's the description of your extension? Syntax highlighter for the Nokia SR-OS configuration
? Language id: sros
Language name: Nokia SR-OS
? File extensions: .timos, .sros, .cmg
? Scope names: source.nokia.sros

Basicamente, estamos dizendo que o VS Code irá interpretar as extensões .timos, .sros e .cmg automaticamente como sendo a linguagem Nokia SR-OS que estamos desenvolvendo.

Após responder todas as perguntas o Yeoman irá criar a estrutura de projeto que precisamos para desenvolver nossa extensão.

Extension Project Structure

Para desenvolver nossa extensão, estaremos interessados em três arquivos:

  1. language-configuration.json
  2. syntaxes/sros.tmLanguage.json
  3. package.json

Editando o arquivo de configuração da linguagem

No arquivo de configuração da linguagem - language-configuration.json - é onde definimos algumas estruturas básicas que o VS Code utiliza para nos ajudar quando estivermos usando essa linguagem no editor.

{
    "comments": {
        // symbol used for single line comment. Remove this entry if your language does not support line comments
        "lineComment": "#",
        // symbols used for start and end a block comment. Remove this entry if your language does not support block comments
        "blockComment": [
            "#--------------------------------------------------",
            "#--------------------------------------------------"]
    },
    // symbols used as brackets
    "brackets": [
        ["{", "}"],
        ["[", "]"],
        ["(", ")"]
    ],
    // symbols that are auto closed when typing
    "autoClosingPairs": [
        ["{", "}"],
        ["[", "]"],
        ["(", ")"],
        ["\"", "\""],
        ["'", "'"]
    ],
    // symbols that can be used to surround a selection
    "surroundingPairs": [
        ["{", "}"],
        ["[", "]"],
        ["(", ")"],
        ["\"", "\""],
        ["'", "'"]
    ]
}

Para a linguagem Nokia SR-OS, vamos apenas modificar o parâmetro blockComment para adequá-lo à sintaxe da nossa linguagem, todos os outros parâmetros podem ser mantidos inalterados.

Editando o arquivo tmLanguage

O arquivo tmLanguage é aquele que define os tokens da linguagem. Pense nos tokens como identificadores que serão atribuídos às palavras chaves para as quais vamos criar regras de correspondência. Por exemplo, na linguagem de programação python, a palavra-chave for precisa ser marcada com um identificador, desta maneira um tema de cores poderá colorir esta palavra-chave, como no exemplo abaixo:

for i in range(5):
    print(i)

O VS Code baseia sua sintaxe para tokenizar uma linguagem numa gramática chamada TextMate. Os tokens que vamos criar, são baseados em padrões que o TextMate define, os chamados Naming Conventions. Isto é necessário para que nossa linguagem possa ser colorida por qualquer tema que queiramos instalar no VS Code.

Podemos criar nossos próprios tokens, porém precisaríamos criar também nosso próprio tema que iria ser utilizado para colorir nossa linguagem especificamente. Portanto, vamos utilizar os padrões e extende-los com alguns marcadores, para que seja possível colorir especificamente nossa linguagem, caso alguém queria desenvolver um tema para ela.

{
 "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
 "name": "Nokia SR-OS",
 "patterns": [
  {
   "include": "#strings"
  },
  {
   "include": "#logconfig"
  },
  {
   "include": "#addresses"
  },
  {
   "include": "#cardmdatypes"
  },
  {
   "include": "#keywords"
  },
  {
   "include": "#comments"
  }
 ],
 "repository": {
  "keywords": {
   "patterns": [{
    "name": "keyword.control.sros",
    "match": "\\b(no shutdown|allow|forward|permit)\\b"
   },
  {
   "name": "invalid.illegal.sros",
   "match": "\\b(deny-all|drop|deny|shutdown)\\b"
  },
  {
   "name": "constant.language.sros.loopback",
   "match": "\\b(loopback)\\b"
  },
  {
   "name": "constant.numeric.sros",
   "match": "\\b(\\d+)\\b"
  }
  ]
  },
  "strings": {
   "name": "string.quoted.double.sros",
   "begin": "\"",
   "end": "\"",
   "patterns": [
    {
     "name": "constant.numeric.sros.ipv4",
     "match": "\\d+\\.\\d+\\.\\d+\\.\\d+(\/\\d{1,2})?"
    },
    {
     "name": "constant.numeric.sros.ipv6",
     "match": "[0-9a-fA-F]{0,4}:([0-9a-fA-F]{0,4}:)+[0-9a-fA-F]{0,4}(\/\\d{1,3})?"
    }
   ]
  },
  "logconfig": {
   "name": "",
   "begin": "log-id|accounting-policy|syslog|file-id",
   "end": "exit",
   "patterns": [
    {
     "name": "support.type.property-name.sros",
     "match": "\\b(from|to|description|address|level|log-prefix|collection-interval|record|location|rollover|retention)\\b"
    },
    {
     "name": "constant.numeric.sros",
     "match": "\\b(\\d+)\\b"
    },
    {
     "name": "invalid.illegal.sros.shutdown",
     "match": "\\b(shutdown)\\b"
    },
    {
     "name": "string.quoted.double.sros",
     "match": "\"(.+)\""
    },
    {
     "name": "keyword.control.sros.noshut",
     "match": "no shutdown"
    }
   ]
  },
  "addresses": {
   "patterns": [
    {
     "name": "constant.numeric.sros.ipv4",
     "match": "\\d+\\.\\d+\\.\\d+\\.\\d+(\/\\d{1,2})?"
    },
    {
     "name": "constant.numeric.sros.ipv6",
     "match": "[0-9a-fA-F]{0,4}:([0-9a-fA-F]{0,4}:)+[0-9a-fA-F]{0,4}(\/\\d{1,3})?"

    }
  ]
  },
  "cardmdatypes": {
   "patterns": [
    {
     "name": "string.quoted.single.sros.cardtype",
     "match": "\\b(iom-v-mg|iom-v)\\b"
    },
    {
     "name": "string.quoted.single.sros.mdatype",
     "match": "\\b(m20-v|isa-mg-v|isa-aa-v|isa-bb-v|isa-ip-reas-v)\\b"
    }
   ]
  },
  "comments": {
   "name": "comment.block.documentation",
   "begin": "#--------------------------------------------------|#",
   "end": "#--------------------------------------------------|\n"
  }
 },
 "scopeName": "source.nokia.sros"
}

Como podemos ver, estamos utilizando a funcionalidade do #include no arquivo tmLanguage, essa funcionalidade nos permite organizar nossos tokens de uma forma mais inteligente. Definimos alguns macros, onde iremos definir os padrões aos quais iremos atribuir os tokens que queremos.

  • strings
  • logconfig
  • addresses
  • cardmdatypes
  • keywords
  • comments

Como prometido, iremos realizar uma marcação de sintaxe geral e outra de um contexto específico, o contexto selecionado para tal é o de log-id.

Para definirmos os tokens é bastante simples, precisamos criar uma lista de padrões (patterns) com dois parâmetros: name e match.

  • O parâmetro name é o token que vamos especificar para aquele padrão.
  • O parâmetro match é a expressão de REGEX que vamos utilizar para identificar o padrão que queremos.

Vamos pegar os padrões de addresses como exemplo:

  "addresses": {
   "patterns": [
    {
     "name": "constant.numeric.sros.ipv4",
     "match": "\\d+\\.\\d+\\.\\d+\\.\\d+(\/\\d{1,2})?"
    },
    {
     "name": "constant.numeric.sros.ipv6",
     "match": "[0-9a-fA-F]{0,4}:([0-9a-fA-F]{0,4}:)+[0-9a-fA-F]{0,4}(\/\\d{1,3})?"

    }
  ]
  }

Percebam que estamos utilizando um token padrão chamado constant.numeric para ambos os tipos de IP - IPv4 e IPv6 - no entanto estamos estendendo estes nomes com .sros.ipvX onde X é a versão do protocolo que estamos identificando. Este é um bom exemplo de como extender os nomes padrões para que seja possível desenvolver um tema que vai colorir especificamente os tokens da nossa linguagem.

A diferença de um padrão geral e um padrão por contexto específico está no uso dos parâmetros begin e end. Tomando o padrão logconfig como exemplo.

  "logconfig": {
   "name": "",
   "begin": "log-id|accounting-policy|syslog|file-id",
   "end": "exit",

Estamos dizendo que os padrões e tokens definidos no logconfig só serão válidos dentro de um contexto que se inicia com uma das palavras-chaves definidas em begin e que termine na palavra-chave exit.

Editando o Arquivo package

Agora que já definimos os padrões da sintaxe de nossa linguagem, precisamos apenas revisar o arquivo package.json antes de publicar nossa extensão.

{
    "name": "vscode-nokia-sros-syntax",
    "publisher": "thepacketwizards",
    "displayName": "Nokia SR-OS Syntax",
    "description": "Nokia SR-OS Syntax Highlighter",
    "version": "0.0.1",
    "engines": {
        "vscode": "^1.48.0"
    },
    "repository": {
        "type": "git",
        "url": "https://github.com/decastromonteiro/vscode-nokia-sros-syntax.git"
    },
    "categories": [
        "Programming Languages"
    ],
    "contributes": {
        "languages": [{
            "id": "sros",
            "aliases": ["Nokia SR-OS", "sros"],
            "extensions": [".timos",".sros",".cmg"],
            "configuration": "./language-configuration.json"
        }],
        "grammars": [{
            "language": "sros",
            "scopeName": "source.nokia.sros",
            "path": "./syntaxes/sros.tmLanguage.json"
        }]
    }
}

Precisamos incluir o caminho do repositório onde armazenaremos o código que criamos da nossa extensão, neste caso, estou utilizando o github para fazer o versionamento desta extensão. Além disso, incluímos também o parâmetro publisher com o nome que utilizaremos para publicar esta extensão.

Finalmente, podemos testar se nossa extensão está funcionando conforme planejado. Para isso, basta estar com a pasta, onde se encontra nosso código, aberta no VS Code e pressionar a tecla F5, com isso, o VS Code irá abrir uma nova janela com nossa extensão carregada, assim podemos verificar se a extensão está fazendo aquilo que esperamos que faça.

Uma vez que tenhamos certeza que tudo está funcionando corretamente, podemos finalmente publicar nossa extensão. Para isso, basta seguir o guia oficial.

Comparando o arquivo de configuração sem nossa extensão e com a nossa extensão

Agora que temos um marcador de sintaxe para o nosso arquivo de configuração, fica muito mais fácil identificar parâmetros chaves como palavras "shutdown" ou "no shutdown", "deny-all" ou "permit". Com isso, começamos a tratar nossa configuração como código.

Config Before Syntax Highlighter
Config After Syntax Highlighter

Concluindo

Neste artigo vimos como é fácil criar um marcador de sintaxe para o nosso arquivo de configuração. Se vamos tratar nossa configuração como código, esse é um passo imprescindível que nos permitirá entender muito mais rapidamente a configuração, nos permitindo inclusive identificar erros que poderiam passar desapercebidos caso não tivéssemos um marcador de sintaxe.

Nos próximos artigos dessa série, iremos entender algumas linguagens e ferramentas essenciais para trilharmos nosso caminho, entre elas estão JSON, YAML, TOML, Git, JINJA2 e Python. Vamos conversar sobre cada uma e que tipo de modelo de Configuração como Código queremos implementar.

Se gostou da extensão e quer contribuir, fique à vontade! A extensão é de código aberto e está no Github sob meu usuário. Você pode pesquisar por vscode-nokia-sros-syntax ou simplesmente clicar no link.

Se quiser instalar esta extensão no seu VS Code, basta buscar por Nokia SR-OS no marketplace do VS Code.