Pipeline CI/CD

DevOps est une nouvelle façon d’approcher la manière dont on conçoit, on exploite et on délivre les services IT. J’ai eu souvent l’occasion de le dire et de l’écrire, mais réaliser des programmes ne fait pas du programmeur un DevOps et encore moins de la société qui l’emploie une société DevOps.

Entrer dans le monde DevOps implique de focaliser sur les objectifs, les personnes, les processus et les outils à mettre en oeuvre. Cet article se voulant plutôt orienté technique, il focalisera sur des éléments appartenant aux domaines des processus et des outils. Dans les processus DevOps, l’approche CI/CD (Continuous Integration / Continuous Delivery ou Deployment) est fondamentale. Le schéma proposé ci-dessus illustre le pipeline du « Continuous Integration » et met en évidence la différence subtile entre « Continuous Delivery » et « Continuous Deployment » et les deux premières étapes vont être expliquées.

Généralement associé à un gestionnaire de versioning, on utilise un outil d’intégration continue comme Jenkins, CircleCI ou l’intégration continue disponible dans GitLab pour mettre en place le pipeline proposé dans l’illustration de cet article.

Les tests syntaxique (lint) sont les plus simple à mettre en place. Pour le langage Python, il en existe plusieurs et Pylint en est un exemple que j’utilise souvent. Prenons l’exemple du code suivant et appliquons Pylint configuré par défaut dessus. Nous pouvons voir un certains nombre d’erreurs syntaxiques, qui même si elles ne semblent que cosmétiques, méritent d’être corrigées.

#!/usr/bin/env python
import nornir
import nornir.plugins.tasks.networking as nptn
import nornir.plugins.tasks.text as nptt
import nornir.plugins.functions.text as npft
import nornir.core.filter as ncf

def arista_configuration(task):
    template = task.run(
        task=nptt.template_file,
        template='configuration.j2',
        path='templates/'
    )
    task.host['config'] = template.result
    results = task.run(
        task=nptn.napalm_configure,
        configuration=task.host['config']
    )


def arista_config_template():
    nr = nornir.InitNornir(config_file='config.yaml')
    veos = nr.filter(ncf.F(groups__contains='veos'))
    npft.print_title('Playbook to configure virtual switch with template')
    results = veos.run(task=arista_configuration)
    npft.print_result(results)


if __name__ == '__main__':
    arista_config_template()
Pylint avec erreurs
Pylint avec des erreurs

Une fois le code corrigé en fonction des recommandations, voici les améliorations apportées.

#!/usr/bin/env python
"""
Configure Arista vEOS-lab with a template
"""
import nornir
import nornir.plugins.tasks.networking as nptn
import nornir.plugins.tasks.text as nptt
import nornir.plugins.functions.text as npft
import nornir.core.filter as ncf

def arista_configuration(task):
    """
    Build and apply template
    """
    template = task.run(
        task=nptt.template_file,
        template='configuration.j2',
        path='templates/'
    )
    task.host['config'] = template.result
    task.run(
        task=nptn.napalm_configure,
        configuration=task.host['config']
    )


def arista_config_template():
    """
    Run nornir task
    """
    nornir_init = nornir.InitNornir(config_file='config.yaml')
    veos = nornir_init.filter(ncf.F(groups__contains='veos'))
    npft.print_title('Playbook to configure virtual switch with template')
    results = veos.run(task=arista_configuration)
    npft.print_result(results)


if __name__ == '__main__':
    arista_config_template()
Pylint sans erreur
Pylint sans erreur

Le TDD (Test Driven Development) est une technique de développement mêlant intimement l’écriture des tests unitaires, la programmation et l’amélioration continue du code (encore appelée refactorisation). Idéalement, chaque fonction unitaire de l’application possède son propre test unitaire, écrit avant le code de la fonction. Le test est écrit dans un premier temps pour échouer, et le développeur s’assure ainsi en écrivant le test des conditions de réussite, mais aussi d’échec, de la fonction. Pour le langage Python, le framework Pytest est mon choix de prédilection.

[pytest]
addopts = -p no:warnings
log_cli = true
#!/usr/bin/env python
"""
Check Arista vEOS-lab configuration
"""
import nornir
import nornir.plugins.tasks.networking as nptn
import nornir.core.filter as ncf


NORNIR_INIT = nornir.InitNornir(config_file='config.yaml')
VEOS = NORNIR_INIT.filter(ncf.F(groups__contains='veos'))
RESULTS = VEOS.run(
    task=nptn.napalm_get,
    getters=['interfaces', 'lldp_neighbors', 'environment', 'facts'])


def test_interfaces_status():
    """
    Check interfaces status
    """
    for res in RESULTS.values():
        assert res.result['interfaces']['Port-Channel1']['is_up']
        assert res.result['interfaces']['Port-Channel1']['is_enabled']


def test_lldp_neighbors():
    """
    Check number of lldp neighbors
    """
    for res in RESULTS.values():
        assert len(res.result['lldp_neighbors']) == 2


def test_cpu_threshold():
    """
    Check that CPU threshold is under 20%
    """
    for res in RESULTS.values():
        assert res.result['environment']['cpu'][0]['%usage'] < 20


def test_switch_version():
    """
    Check that release is 4.22 train
    """
    for res in RESULTS.values():
        assert res.result['facts']['os_version'][0:4] == '4.22'
Pytest
Résultat de la commande pytest dans le répertoire contenant le fichier test_config_template.py