phone

    • chevron_right

      genma : Lifehacking - Création de tickets dans le Kanban Gitlab via l'API

      genma • news.movim.eu / PlanetLibre • 6 January, 2021 • 13 minutes

    Introduction

    Il y a quelques années, je me suis initié à l'usage du Kanban ( Lifehacking - Kanban ) et j'ai utilisé pour de nombreux projets et suivi d'actions des Kanbans au sein de Gitlab ( Lifehacking - Gitlab, outil idéal ? ). Je continue d'utiliser le Kanban Gitlab : j'ai mes habitudes, je forme les personnes avec lesquelles je travaille sur les procédures et bonnes pratiques que j'ai mises en place sur les rédactions des tickets et leurs vie (suivi /traçabilité...).

    Je me suis demandé comment il serait possible d'automatiser certaines choses dans l'usage du Kanban de Gitlab. Si vous utilisez Gitlab au quotidien, vous serez peut-être intéressés par son API. Elle vous permet, au travers de scripts dans le langage de votre choix, d'exécuter certaines tâches en lignes de commandes.

    J'ai demandé à Brume, qui est en stage avec moi, de rédiger le présent billet de tutoriel de création de tickets dans le Kanban Gitlab via l'API de Gitlab. Voici donc le tutoriel.

    Les Kanbans dans Gitlab ?

    A chaque projet dans Gitlab est associé un Kanban permettant la gestion du projet. L'objectif n'est pas de présenter l'usage de ce Kanban (qui est assez intuitif mais aussi assez riche en fonctionnalités et complet).

    Tutoriel de l'API de GitLab

    L'API de Gitlab est une API REST. Elle fournit des fichiers JSON accessibles par liens, ainsi que la possibilité de réaliser des actions en donnant des paramètres GET, POST... : après le lien, vous pouvez rajouter ?argument=value.

    Dans le navigateur, il n'y en a pas besoin, mais avec Curl, les requêtes doivent être suivies par ?access_token= ou précédées par —header "PRIVATE-TOKEN : ".

    Accéder aux fichiers JSON issu d'un Kanban

    Les requêtes suivantes sont uniquement des requêtes GET, il est donc possible de soit les ouvrir dans un navigateur soit avec Curl.

    Pages d'un projet : https://gitlab-exemple.com/api/v4/projects/nom_du_projet

    Chaque / entre le nom du mainteneur et le nom du projet doivent être remplacés par %2F. Remarquez également que /projects/ a été rajouté comparé au lien d'origine.

    Boards (le Kanban) : https://gitlab-exemple.com/api/v4/projects/9/boards/2

    Le nom du projet est remplacé par son ID, ici 9. 2 correspond à l'ID du board que nous voulons regarder. Les boards sont seulement des filtres de tickets, proposant un rendu plus visuel et qui permet de trouver les informations facilement. Accéder à un board via l'API ne permet que de voir la liste des filtres par label.

    Tickets https://gitlab-exemple.com/api/v4/projects/9/issues

    Il existe beaucoup d'arguments GET pour filter les tickets. Par exemple, pour filtrer les tickets par label, utilisez le l'argument labels=value (vous pouvrez ajouter plusieurs labels en les séparant par des virgules).

    Actions via des requêtes sur les tickets

    Les requêtes suivantes utilisent d'autre paramètres que GET. Le plus avisé est donc d'utiliser Curl.

    Créer un ticket

    curl --request POST --header "PRIVATE-TOKEN: " "https://gitlab-exemple.com/api/v4/projects/13/issues?&title=A%20Title&labels=To%20Do&description=test"

    Vous pouvez constater ici qu'il y a trois arguments : title, labels et description. Seul le titre est obligatoire. Il existe de nombreuses possibilités. Remarquez que les espaces doivent être remplacés par %20, sinon vous optiendrez une erreur 400.

    Supprimer un ticket

    curl --request DELETE --header "PRIVATE-TOKEN: " "https://gitlab-exemple.com/api/v4/projects/13/issues/2"

    2 est l'IID (internal ID) du ticket que nous voulons supprimer. Vous pouvez l'obtenir dans la liste des tickets (ils sont triés par ordre de création).

    Éditer un ticket

    curl --request PUT --header "PRIVATE-TOKEN: " "https://gitlab-exemple.com/api/v4/projects/13/issues/6?title=Title%20Changed&add_labels=AAA,BBB,changed"

    Il existe beaucoup d'arguments pour éditer des tickets, comme :
    - add_label, remove_labels et labels pour ajouter, supprimer ou réécrire entièrement la liste des labels ;
    - title, description pour ajouter un titre ou une description ;
    - state_event=closed ou reopen pour fermer ou réouvrir un ticket.

    Un script d'exemple, en Python

    Nous allons proposer et expliquer un script d'exemple, en Python, d'utilisation de cette API. Ce script permettra, pour un projet donné, de publier, éditer ou supprimer des issues.

    Prérequis

    Pour suivre cet exemple, vous aurez besoin de :
    - avoir Python 3 installé sur votre machine ;
    - avoir un compte sur une instance GitLab ;
    - créer un projet sur votre compte GitLab ;
    - créér un token d'accès (décrit ci-dessous).

    Se créer un token d'accès
    Sur Gitlab, allez dans les paramètres de votre compte utilisateur. Allez dans l'onglet "Access Token". Entrez un nom, cochez au moins la case "api", et validez. Vous recevrez votre token. Pensez à le stocker, car il ne sera plus accessible par la suite et vous serez obligés de vous en re-créer un.

    Bibliothèques utilisées

    Requests est une bibliothèque qui permet de faire des requêtes HTML en Python, à la manière de Curl en Bash.

    Utilisation :

    import requests
    headers = {'PRIVATE-TOKEN': "YOUR-TOKEN"}
    r = requests.get(link, headers=headers)

    link est le lien de la page que l'on souhaite récupérer, `YOUR-TOKEN` votre token secret, et `get` le type de la requête. Il est possible d'utiliser d'autre paramètres comme `delete`, `post`, `put`...

    r est un objet, contenant notamment :
    - le code de retour de la requête, `r.status_code` ;
    - le texte (du json dans notre cas) récupéré, `r.text`.

    Json permet tout simplement de parser et de récupérer des informations dans des JSON.

    Utilisation :

    import json
    file = json.loads(file)
    print(json[0]["username"])

    file est le fichier ou texte que l'on veut utiliser, dans notre cas `r.text`. Il est possible d'extraire les éléments du json par clés ou index, comme dans l'exemple.

    ConfigParser permet de lire un fichier de configuration dans un fichier `.ini`, et d'ajouter les valeurs à notre code.

    Utilisation :

    from configparser import ConfigParser
    cfg = ConfigParser()
    cfg.read('config.ini')
    variable = cfg.get("section", "variable")

    Attention à bien vérifier que le fichier existe et que toutes les valeurs sont présentes à l'intérieur, pour éviter tout dysfonctionnement.

    En premier, un objet `ConfigParser` est initialisé. Ensuite, on lui donne le fichier de configuration à lire, ici `config.ini`. Ce fichier utilise la syntaxe suivante :

    [section]
    variable=valeur

    Il est possible de rajouter autant de variables et de sections que l'on veut (avec des noms différents).
    Pour récupérer la valeur d'une variable, on utilise donc `cfg.get`, en précisant la section et la variable.

    Dans notre cas, utiliser un fichier de configuration est utile pour que l'utilisateur puisse renseigner une fois mais pas à chaque fois le lien de l'instance Gitlab, l'id de son projet et son token d'accès.

    Urllib

    `urllib` permet de remplacer tous les caractères spéciaux d'une chaîne de caractères, pour n'utiliser que des caractères autorisés dans les liens. Par exemple, les espaces ne sont pas autorisés, donc ils seront remplacés par `%20`. Certains caractères ont également une signification dans le lien (comme `&`, qui lie deux paramètres. Les remplacer permet d'éviter toute mauvaise interprétation de l'entrée de l'utilisateur.

    Utilisation :

    import urllib.parse
    variable = urllib.parse.quote("Titre de mon issue"), safe='')

    Ici, `Titre de mon issue` deviendra `Titre%20de%20mon%issue`. Par défaut, `urllib.parse.quote` remplace tous les caractères spéciaux sauf le `/`. Nous souhaitons que ce caractère soit aussi remplacé, pour le fonctionnement de notre requête, alors nous rajoutons `safe=''` pour indiquer que même le `/` n'est pas un caractère sécurisé.

    En plus de ces 4 bibliothèques, `datetime` et `os.path` on été utilisées, mais ne sont pas fondamentalement nécessaires dans la création d'un script autour de l'API de Gitlab.

    Utilisation de l'API de Gitlab

    Notre script Python va tout d'abord proposer un choix multiple, où l'utilisateur entrera un chiffre pour accéder aux fonctionnalités suivantes :
    - créer une issue,
    - modifier une issue,
    - supprimer une issue.

    À chacune de ces actions est liée une fonction utilisant l'API de Gitlab.

    Pour accéder à cette API, il faut rajouter `/api/v4/` à la fin de l'url de votre instance Gitlab. Dans les requêtes suivantes, nous utiliserons ` https://gitlab-exemple.com` en exemple. Cela donne donc :

    https://gitlab-exemple.com/api/v4/

    Pour envoyer vos requêtes, vous pouvez utiliser la bibliothèque `requests` présentée au-dessus. Pensez bien à utiliser la bibliothèque `urllib` pour sécuriser et formater les entrées des utilisateurs que vous allez rajouter à vos liens.

    Créer une issue

    Pour créer une nouvelle issue, il faut utiliser la requête suivante :

    POST https://gitlab-exemple.com/api/v4/projects/ID/issues/?title=TITRE&labels=LABEL1,LABEL2&description=DESCRIPTION

    Ici, `ID` est à remplacer par l'ID du projet. Nous utilisons les arguments `title`, `labels` et `description`. Il en existe beaucoup d'autres, et seul `title` est obligatoire. `TITRE`, `LABEL1,LABEL2`, `DESCRIPTION` sont donc à remplacer par vos propres valeurs.

    La liste des arguments est disponible [ici]( https://docs.gitlab.com/ee/api/issues.html#new-issue ).

    Modifier une issue

    Pour modifier une issue, il faut utiliser la requête suivante :

    PUT https://gitlab-exemple.com/api/v4/projects/ID/issues/IID/?title=TITRE&labels=LABEL1,LABEL2&description=DESCRIPTION

    Ici, `IID` est à remplacer par l'ID interne de l'issue. Le reste de la requête est semblable à la première.

    Encore une fois, il existe de [nombreux arguments]( https://docs.gitlab.com/ee/api/issues.html#edit-issue ), pour modifier plein de détails comme le statut de l'issue, sa date d'échéance...

    Supprimer une issue

    Pour supprimer une issue, il faut utiliser la requête suivante :

    DELETE https://gitlab-exemple.com/api/v4/projects/ID/issues/IID/

    Récupérer des informations de l'API

    L'API ne sert pas qu'à faire des actions, mais aussi à récupérer des informations. Voici quelques exemples :

    https://gitlab-exemple.com/api/v4/projects/NOM-DU-PROJET

    https://gitlab-exemple.com/api/v4/projects/ID-DU-PROJET

    Récupérer la liste des issues d'un projet :

    https://gitlab-exemple.com/api/v4/projects/ID-DU-PROJET

    À chaque fois, vous récupérerez un JSON à parser avec la librairie JSON présentée plus haut.

    Script en Python utilisant l'API de Gitlab permettant de créer, modifier et supprimer des issues en ligne de commande.

    Pour lancer le script, remplissez le fichier `config.ini` avec l'url de votre instance Gitlab, l'id de votre projet et votre token (générable depuis les paramètres utilisateurs), puis lancez :

    ./gitlab-cli.py

    Contenu du fichier config.ini

    [configuration]
    baselink=
    project=
    access_token=

    Code source du script ./gitlab-cli.py

    #!/usr/bin/env python3

    ### BIBLIOTHÈQUES ###

    import requests
    import json
    from configparser import ConfigParser
    from datetime import datetime
    import os.path
    import urllib.parse

    ### COULEURS DE TEXTE ###

    NC = "\\033[0m"
    LB = "\\033[38;5;33m"
    RED = "\\033[1;38;5;196m"
    CY="\\033[38;5;81m"

    ### FONCTIONS ###

    # Ajouter des labels
    def lab_add():
    labels = urllib.parse.quote(input("Please enter the labels you want to add: "), safe='')
    if labels:
    labels = "&add_labels={}".format(labels)
    return labels

    # Effacer des labels
    def lab_delete():
    labels = urllib.parse.quote(input("Please enter the labels you want to delete: "), safe='')
    if labels:
    labels = "&remove_labels={}".format(labels)
    return labels

    # Réécrire la liste des labels
    def lab_rewrite():
    labels = urllib.parse.quote(input("Please enter the new labels: "), safe='')
    if labels:
    labels = "&labels={}".format(labels)
    return labels

    def lab_keep():
    return ""

    # Fonction permettant de réaliser des choix multiples
    def multiple_choice(choices):
    value = 0
    while value len(choices) :
    try:
    value = int(input("Enter a number: "))
    except ValueError:
    default()
    continue
    result = choices.get(value,default)()
    return result

    # Imprime les détails d'un ticket
    def print_issue(link):
    r = requests.get(link, headers=headers)
    if r.status_code >= 400:
    return 0
    json_r = r.text
    json_tk = json.loads(json_r)
    date = datetime.strptime(json_tk["created_at"], "%Y-%m-%dT%H:%M:%S.%fZ")
    date = datetime.strftime(date, "%Hh%M the %d %B %Y")
    print("\\n{}Issue #{}{}{:>30} {}, at {}".format(LB, json_tk["iid"], NC, "Created by", json_tk["author"]["username"], date))
    print("{}Title{}: {}".format(CY, NC, json_tk["title"]))
    print("{}Description{}: {}".format(CY, NC, json_tk["description"]))
    print("{}Labels{}: {}\\n".format(CY, NC, json_tk["labels"]))
    return 1

    # Imprime la liste des tickets
    def print_list(link):
    print("\\nList of issues:")
    r = requests.get(link, headers=headers)
    if r.status_code >= 400:
    return 0
    json_r = r.text
    json_list = json.loads(json_r)
    for issue in json_list:
    print("#{}\\t{}".format(issue["iid"], issue["title"]))

    # confirmer le choix du ticket. Fonction utilisée pour éditer et supprimer.
    # Paramêtres : le lien du ticket et l'action à faire (dialogues)
    def confirm_issue(link, action):
    print_list(link)
    iid = 0
    code = 0
    while iid boolean = "a"
    try:
    iid = int(input("\\nPlease enter the id of the issue you want to {}: ".format(action)))
    except ValueError:
    default()
    continue
    link_id = "{}{}".format(link, iid)
    code = print_issue(link_id)
    if not code :
    print("{}Error:{} This issue doesn't exists. Please try again.".format(RED, NC))
    continue
    while boolean != "y" and boolean != "n":
    boolean = input("Is it this ticket that you want to {}? (y/n) ".format(action))
    return link_id



    # Erreur avec l'API
    def error_api(verb):
    print("\\n{}Error:{} The issue has not been {}. Please verify that informations in 'config.ini' are valid.".format(RED, NC, verb))
    exit(0)

    # créer un ticket
    def gl_create():
    title = ""
    while not title:
    title = urllib.parse.quote(input("Please enter the title of your new issue (must not be empty) : ").strip(), safe='')
    if not title:
    print("{}Error:{} Title must not be empty. Please try again.".format(RED, NC))
    desc = urllib.parse.quote(input("Please enter the description: "), safe='')
    labels = urllib.parse.quote(input("Please enter labels, separated by commas: "), safe='')
    link = "{}/issues?&title={}&labels={}&description={}".format(baselink, title, labels, desc)
    try:
    r = requests.post(link, headers=headers)
    if r.status_code >= 400:
    error_api("created")
    print("Issue created!")
    except:
    error_api("created")

    # éditer un ticket
    def gl_edit():
    link = "{}/issues/".format(baselink)
    link = confirm_issue(link, "edit")
    title = urllib.parse.quote(input("Please enter the new title (leave empty to change nothing): "), safe='')
    desc = urllib.parse.quote(input("Please enter the new description (leave empty to change nothing): "), safe='')
    if title:
    title = "&title={}".format(title)
    if desc:
    desc = "&description={}".format(desc)
    # menu des labels
    print("\\nDo you want to:\\n{}1){} Add some labels\\t\\t{}2){} Delete some labels\\n{}3){} Rewrite all labels\\t\\t{}4){} Keep labels unchanged".format(LB, NC, LB, NC, LB, NC, LB, NC))

    choices = {
    1 : lab_add,
    2 : lab_delete,
    3 : lab_rewrite,
    4 : lab_keep,
    }
    labels = multiple_choice(choices)
    link = "{}/?{}{}{}".format(link, title, desc, labels)
    try:
    r = requests.put(link, headers=headers)
    if r.status_code >= 400:
    error_api("updated")
    print("The issue had been updated, thank you!")
    except:
    error_api("updated")

    # effacer un ticket
    def gl_delete():
    link = "{}/issues/".format(baselink)
    link = confirm_issue(link, "delete")
    try:
    r = requests.delete(link, headers=headers)
    if r.status_code >= 400:
    error_api("deleted")
    print("The issue had been deleted, thank you!")
    except:
    error_api("deleted")

    def default():
    print("{}Error:{} Wrong answer. Please try again.".format(RED, NC))


    ### MAIN ###

    # effacer l'écran
    print('\\033[2J')

    if not os.path.isfile('config.ini'):
    print("{}Error:{} config.ini file not found.".format(RED, NC))
    exit(0)

    # utilisation de config.ini
    cfg = ConfigParser()
    cfg.read('config.ini')
    try:
    baselink = "{}/api/v4/projects/{}".format(cfg.get("configuration", "baselink"), cfg.get("configuration", "project"))
    headers = {'PRIVATE-TOKEN': cfg.get("configuration", "access_token")}
    except:
    print("{}Error:{} config.ini is incomplete. Please check that 'baselink', 'project' and 'access_token' fields exists.".format(RED, NC))
    exit(0)

    # premier menu
    print("Hello! What do you want to do?\\n\\n{}1){} Create an issue\\t\\t{}2){} Edit an issue\\n{}3){} Delete an issue".format(LB, NC, LB, NC, LB, NC))

    # dictionnaire des choix possibles
    choices = {
    1 : gl_create,
    2 : gl_edit,
    3 : gl_delete,
    }
    # interception du ^C
    try:
    multiple_choice(choices)
    except (KeyboardInterrupt, SystemExit):
    print("\\nGoodbye!")
    exit(0)

    Captures d'écrans de l'exécution du script

    Conclusion

    Voilà, avec tous ces éléments, vous devriez pouvoir vous débrouiller pour coder un script similaire. Gardez en tête que je n'ai présenté que quelques-unes des fonctionnalités de l'API de Gitlab, et qu'il en existe bien d'autres !

    Enfin, voici un tutoriel https://git.42l.fr/brume/Script_API_Gitlab/src/branch/master/tutoriel-api-gitlab-bash.md , en français et en anglais, de l'utilisation de cette API en Bash.

    Gravatar de genma
    Original post of genma .Votez pour ce billet sur Planet Libre .

    • chevron_right

      citizenz7 : GIT / GITHUB : aide-mémoire pour "les nuls"

      citizenz7 • news.movim.eu / PlanetLibre • 6 January, 2021

    git-github-aide-memoire-pour-les-nuls

    Gravatar de citizenz7
    Original post of citizenz7 .Votez pour ce billet sur Planet Libre .

    • wifi_tethering open_in_new

      This post is public

      www.citizenz.info /articles/git-github-aide-memoire-pour-les-nuls

    • chevron_right

      citizenz7 : Symfony 5 : bien débuter

      citizenz7 • news.movim.eu / PlanetLibre • 1 January, 2021

    symfony-5-bien-debuter

    Gravatar de citizenz7
    Original post of citizenz7 .Votez pour ce billet sur Planet Libre .

    • wifi_tethering open_in_new

      This post is public

      www.citizenz.info /articles/symfony-5-bien-debuter

    • chevron_right

      Journal du hacker : Liens intéressants Journal du hacker semaine #52

      Journal du hacker • news.movim.eu / PlanetLibre • 27 December, 2020 • 1 minute

    Pour la 52ème semaine de l'année 2020, voici 12 liens intéressants que vous avez peut-être ratés, relayés par le Journal du hacker , votre source d’informations pour le Logiciel Libre francophone !

    logo-journal-du-hacker.png

    Pour ne plus rater aucun article de la communauté francophone, voici :

    De plus le site web du Journal du hacker est « adaptatif (responsive) ». N’hésitez pas à le consulter depuis votre smartphone ou votre tablette !

    Le Journal du hacker fonctionne de manière collaborative, grâce à la participation de ses membres. Rejoignez-nous pour proposer vos contenus à partager avec la communauté du Logiciel Libre francophone et faire connaître vos projets !

    Et vous ? Qu’avez-vous pensé de ces articles ? N’hésitez pas à réagir directement dans les commentaires de l’article sur le Journal du hacker :)

    Gravatar de Journal du hacker
    Original post of Journal du hacker .Votez pour ce billet sur Planet Libre .

    Articles similaires

    • chevron_right

      Francois Aichelbaum : JSON, MongoDB and rsyslog are on a boat

      Francois Aichelbaum • news.movim.eu / PlanetLibre • 23 December, 2020 • 8 minutes

    JSON, MongoDB and rsyslog are on a boat

    When dealing with log management, obvious solutions emerge earlier, often before you even discussed the purpose of the log management. We can discuss about ELK , Graylog , Splunk , … Those are great tools but they may not fit will all your needs. Lately, I had to work for one of my customer on enforcing log management for billing purposed. Rsyslog was already set to collect and centralize all the logs (and manage their backups). MongoDB seemed as a perfect tips for storing JSON extract of the logs to generate the proper stats.

    Invoice Management - DronaHQ
    Invoice Management - DronaHQ

    JSON is standard

    From json.org :

    JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate. It is based on a subset of the JavaScript Programming Language Standard ECMA-262 3rd Edition - December 1999. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others. These properties make JSON an ideal data-interchange language.

    On top of that format, we can used a more structured syntax, adapted to log management: CEE Log Syntax . This defined format is well used by various tools you might use, in the present case: nginx, rsyslog, and mongodb. Up to a certain level…

    Structured JSON

    Nginx knows about @cee

    Sort of. Nginx has a native syslog export for both error_log and access_log. Out of the box, it will simply push all logs from the default log formats to syslog. This won’t be very convenient for proper management afterwards. But nginx is easy to use, and you can define your own custom format. You’ll find plenty of blogposts, tutorials, and others as Any IT here? Help me! describing the proper @cee JSON log format you need:

    log_formatmainJSONescape=json'@cee:{''"vhost":"$server_name",''"remote_addr":"$remote_addr",''"time_iso8601":"$time_iso8601",''"request_uri":"$request_uri",''"request_length":"$request_length",''"request_method":"$request_method",''"request_time":"$request_time",''"server_port":"$server_port",''"server_protocol":"$server_protocol",''"ssl_protocol":"$ssl_protocol",''"status":"$status",''"bytes_sent":"$bytes_sent",''"http_referer":"$http_referer",''"http_user_agent":"$http_user_agent",''"upstream_response_time":"$upstream_response_time",''"upstream_addr":"$upstream_addr",''"upstream_connect_time":"$upstream_connect_time",''"upstream_cache_status":"$upstream_cache_status",''"tcpinfo_rtt":"$tcpinfo_rtt",''"tcpinfo_rttvar":"$tcpinfo_rttvar"''}';

    They all give you the very same structure. Not sure who started it, but that’s not the point: they’re all wrong about it!

    I am this close - Terminator 2
    I am this close - Terminator 2

    They are close, indeed, but some details have to be fixed first. Why are they wrong? Because JSON, and especially the @cee version of it, has datatypes. Numbers (integers and floats) should not be encapsulated between quotes: quotes are for string only.

    log_formatmainJSONescape=json'@cee:{''"vhost":"$server_name",''"remote_addr":"$remote_addr",''"time_iso8601":"$time_iso8601",''"request_uri":"$request_uri",''"request_length":$request_length,''"request_method":"$request_method",''"request_time":$request_time,''"server_port":$server_port,''"server_protocol":"$server_protocol",''"ssl_protocol":"$ssl_protocol",''"status":$status,''"bytes_sent":$bytes_sent,''"http_referer":"$http_referer",''"http_user_agent":"$http_user_agent",''"upstream_response_time":$upstream_response_time,''"upstream_addr":"$upstream_addr",''"upstream_connect_time":$upstream_connect_time,''"upstream_cache_status":"$upstream_cache_status",''"tcpinfo_rtt":$tcpinfo_rtt,''"tcpinfo_rttvar":$tcpinfo_rttvar''}';

    Now we are good to go. Then, as all documentations state it, we can use this log format to push logs to syslog:

    access_logsyslog:server=10.10.10.2:514,facility=local7,tag=nginx,severity=infomainJSON;

    For sure, you can adapt the faciliy, the tag and the severity of it, along with the log format name.

    Rsyslog is great. Its documentation is not.

    That’s a fact, technical documentation, made by technical individuals, for technical persons, are not the best. Rsyslog might be one of the best example about this statement. Rainer Gerhards did a great work with his tool, one of the best syslog manager in my humble opinion. Thought, the documentation is a hell to read. Add to this fact, that it not as accurate as it should be.

    In the use case I was working on, the infrastructure is benefiting from rsyslog v8-sable . And as stated, we want to use the mongodb exported, aka ommongodb . Seems easy and straight forward? Almost. But remember this IT mantra:

    If all goes well, we forget something.

    For reasons out of my mind while writing down this post, I can tell you that it just do not work. Several aspects are to be considering before dealing with those logs.

    1. You need to ensure about the encodingi: BSON (the JSON variant mongodb uses internally) only supports valid UTF-8 character. For that, you need to properly fix the encoding before sending them. There is a module for that.
    2. The structured @cee sent using the default configuration to mongodb will just push data “as-is”: first issue and main issue you’ll hit is about the date which will be pushed as a regular string.

    But why? - Ryan Reynolds
    But why? - Ryan Reynolds

    Why? Simply because rsyslog only understand numbers and strings as datatypes on one end, and mongodb doesn’t auto detect date and timestamps on the other end. Is it an issue at the end? If you want to benefit from mongodb filtering features on dates, yes it is. For that purpose, you need to use the ISODate() functions that mongodb only knows about.

    After a tremendous number of attempts, trying to deal with the documentation to find the proper format, I decided to read the ommongodb module source code . Pretty easy as it’s a well written C code:

    /* Convert text to ISODATE when needed */if(strncmp(name,"date",5)==0||strncmp(name,"time",5)==0){

    You’ve just read it well: the code does not expect the fieldname to start with a date or time , but it expect the fieldname to be date or time .

    Two solutions there:

    1. either update the log format, in nginx configuration, from time_iso8601: to date:
    2. update your rsyslog configuration

    I’m not found of the string exporter used in the default documentation of rsyslog for mongodb: we’re using JSON as an input, we expect JSON at the output, why should we use strings in between?

    For that reason, I moved to a JSON manipulation, thanks to the JSON parse module, and list type for the template:

    module(load="mmutf8fix")module(load="mmjsonparse")module(load="ommongodb")template(name="json-syslog" type="list" option.jsonf="on") {        property(outname="vhost" name="$!vhost" format="jsonf" datatype="string" onEmpty="null")        property(outname="remote_addr" name="$!remote_addr" format="jsonf" datatype="string" onEmpty="null")        property(outname="time_iso8601" name="$!time_iso8601" format="jsonf")        property(outname="date" name="$!time_iso8601")        property(outname="request_uri" name="$!request_uri" format="jsonf" datatype="string" onEmpty="null")        property(outname="request_length" name="$!request_length" format="jsonf" datatype="number")        property(outname="request_method" name="$!request_method" format="jsonf" datatype="string" onEmpty="null")        property(outname="request_time" name="$!request_time" format="jsonf" datatype="number")        property(outname="server_port" name="$!server_port" format="jsonf" datatype="number")        property(outname="server_protocol" name="$!server_protocol" format="jsonf" datatype="string" onEmpty="null")        property(outname="ssl_protocol" name="$!ssl_protocol" format="jsonf" datatype="string" onEmpty="null")        property(outname="status" name="$!status" format="jsonf" datatype="number")        property(outname="bytes_sent" name="$!bytes_sent" format="jsonf" datatype="number")        property(outname="http_referer" name="$!http_referer" format="jsonf" datatype="string" onEmpty="null")        property(outname="http_user_agent" name="$!http_user_agent" format="jsonf" datatype="string" onEmpty="null")        property(outname="upstream_response_time" name="$!upstream_response_time" format="jsonf" datatype="string" onEmpty="null")        property(outname="upstream_addr" name="$!upstream_addr" format="jsonf" datatype="string" onEmpty="null")        property(outname="upstream_connect_time" name="$!upstream_connect_time" format="jsonf" datatype="string" onEmpty="null")        property(outname="upstream_cache_status" name="$!upstream_cache_status" format="jsonf" datatype="string" onEmpty="null")        property(outname="tcpinfo_rtt" name="$!tcpinfo_rtt" format="jsonf" datatype="number")        property(outname="tcpinfo_rttvar" name="$!tcpinfo_rttvar" format="jsonf" datatype="number")}

    Now we can talk. - Kyle Maclachlan - Twin Peaks
    Now we can talk. - Kyle Maclachlan - Twin Peaks

    That’s not all folks!

    Following that changes, nginx logs are pushed to mongodb, allowing easy statistics aggregation for billing purpose. As we just want to push these lines to mongodb, best is to proceed with something like:

    if ($syslogfacility-text == 'local7' and $syslogseverity-text == 'info') then {        action(type="mmutf8fix")        action(type="mmjsonparse")        action(type="ommongodb" uristr="mongodb://--REDACTED--:--REDACTED--@--REDATED--:27017/?authSource=logs&authMechanism=SCRAM-SHA-1" db="logs" collection="nginx" template="json-syslog")        & stop}

    The uristr is a bit different from the the documentation because, once again, the document is not really explicit enough about it. For most real-life scenarios, even if you use a mongodb cluster, you want to rely on dedicated database and dedicated user, with the proper set of permissions. To benefit from it, you need to add some details within the uristr as the user:password but also some query parameters:

    • authSource : the db to rely on for authentication, as you user has only permissons on it
    • authMechanism : you’re pushing a password via the dsn

    At the end

    We went through. We’ve set up nginx to push JSON @cee-compliant logs to syslog, then we prepare the logs to be properly pushed to mongodb, and we publish them.

    Now our folks can run their micro-batching to generate live billing and usage statistics for the customers.

    How would you have tackled this kind of need? Did you suffer from technical documentations not adequate or not uptodate?

    Gravatar de Francois Aichelbaum
    Original post of Francois Aichelbaum .Votez pour ce billet sur Planet Libre .

    • wifi_tethering open_in_new

      This post is public

      francois.aichelbaum.com /2020/12/23/json-mongodb-rsyslog-are-on-a-boat

    • chevron_right

      antistress : Mon bilan informatique de l'année 2020

      antistress • news.movim.eu / PlanetLibre • 23 December, 2020 • 3 minutes

    Père Noël à cheval en short devant la mer par beau temps

    Ha, « informatique », un mot qui fleure bon le siècle dernier. J'ai hésité avec numérique, avant de décider d'assumer pleinement le premier. En tout cas, on ne parlera pas de Covid dans ce billet (mince, trop tard).

    Mon blogue

    Il y a eu pas mal de nouveautés concernant mon blogue !

    Tout d'abord je l'ai déménagé de Toile Libre (qui semble bloqué sur PHP 5.5) vers TuxFamily . Grâce à la simplicité et à la flexibilité du moteur de blogue PluXml , il m'a suffit de déplacer les fichiers composant mon blogue d'un espace à l'autre (à condition de faire attention à bien employer des URL relatives pour les éventuels liens internes au blogue).

    Ensuite j'ai changé le sous-titre du blogue. Pour rappel, le sous-titre du blogue a longtemps été : bloc-notes d'un affranchi , en référence à l'époque où je n'utilisais pas encore de logiciels libres.
    Je l'ai changé :
    – une première fois, le 4 février 2014, pour Passer à GNU/Linux n'est pas une fin : c'est un commencement en m'inspirant de la formule d'un lecteur (otyugh) ,
    – puis, le 1er novembre dernier, en logiciels libres, formats ouverts, libertés numériques & éducation pour être cohérent avec le fait que ce dernier thème fait désormais partie du blogue.

    Car, et c'est sans doute le plus gros changement, cette année j'ai élargi la thématique du blogue pour publier également en lien avec mon métier de CPE . Notez que, pour ne rien vous imposer, j'ai tiré des flux RSS distincts .

    Enfin, ceci expliquant cela, cela faisait longtemps que je n'avais pas publié autant ici. J'en suis à 18 billets cette année et il faut remonter à 2014 pour retrouver pareil nombre (j'ai bien conscience que ce nombre peut paraître faible : tout est relatif). Mieux, 13 billets ont été publiés au cours des deux derniers mois. C'est, en ce qui me concerne, une fréquence rarement (jamais ?) atteinte. Les nouvelles thématiques semble m'inspirer puisque, sur les 13 derniers billets, seulement 3 évoquent l'informatique.

    L'incroyable sprint final :
    Diagramme du nombre de publications par mois

    Mon informatique

    J'ai quasiment fini la « flatpakisation » et la « waylandisation » de mon système. L'essentiel de mes applications sont des flatpak (hors celles au cœur de GNOME dont je laisse la gestion à ma distrib', et à l'exception notable de Firefox : à cause de deux blogues ) et tournent nativement sous Wayland, ce qui m'a poussé à activer la fonctionnalité expérimentale de GNOME XWayland On-Demand .

    Question hardware , mon PC fixe me convient tel qu'il est depuis un moment. Son cerveau a été changé il y a huit ans tout comme l'écran. Ne jouant pas, j'avais misé sur la sobriété pour tous les éléments, sauf la mémoire vive que j'avais prévue large par expérience (j'ai toujours assemblé mes PC depuis mon premier : un Pentium MMX 166MHz). Je sais que je devrais changer la pâte thermique de mon CPU mais la motivation me manque encore…

    Côté smartphone , un sujet que je n'ai encore jamais abordé sur le blogue (j'utilise surtout les fonctions SMS, appareil photo et voix – dans cet ordre), j'en suis à mon quatrième. Après un ZTE OpenC sous Firefox OS, les suivants ont tous tourné sous LineageOS : Sony Xperia Z5 Compact, Samsung Galaxy A3 2017 et le petit dernier que je viens de recevoir, un Samsung Galaxy S5 (oui, oui, un modèle de 2014). Mes critères sont en général la qualité de l'appareil photo, l'autonomie, une taille raisonnable, et bien sûr la prise en charge de LineageOS. C'est une chute de trop (malgré leur étui de protection) qui m'a poussé à remplacer successivement les Xperia Z5 Compact (plus de mise au point de l'appareil photo) et Galaxy A3 2017 (hémorragie de cristaux liquides inondant l'écran). Pour des objets du quotidien, ces appareils sont très peu solides.

    Mes projets

    Je travaille sur un projet ambitieux, libre et gratuit, que j'aimerais bien voir se finaliser en début d'année prochaine. Des obstacles techniques et humains restent à franchir, nous verrons bien. J'en parlerai bien évidemment prioritairement ici si le projet aboutit.

    Hommage à bloguelinux.ca

    Je termine avec une pensée pour mes podcasts informatiques préférés qui s'arrêtent les uns après les autres : après Parole de Tux en 2014 , c'est au tour de bloguelinux de tirer sa révérence. Deux podcasts qui avaient pour points communs Linux, l'humour et la bienveillance.

    À Eric, Patrick et Sandrine :
    Merci écrit sur le sol

    Gravatar de antistress
    Original post of antistress .Votez pour ce billet sur Planet Libre .

    Articles similaires

    • chevron_right

      Simon Vieille : Bilan tech de l'année 2020

      Simon Vieille • news.movim.eu / PlanetLibre • 22 December, 2020 • 1 minute

    Cette année très particulière m'a quand même permis de consacrer plus de temps pour le logiciel libre que l'an passé.

    Mes efforts se sont naturellement dirigés vers les outils que j'utilise et c'est Nextcloud et son écosystème sur lesquels je me suis concentré.

    Mon plus gros projet de 2020 est Custom menu , une application pour Nextcloud qui permet de modifier le menu par défaut en proposant d'autres affichages. Je suis très content du résultat et beaucoup d'administrateurs ont décidé de l'installer sur leur instance. En effet, toutes versions confondues, Custom menu a été téléchargé plus de 102 mille fois et les dernières versions sont installées sur environ 9000 instances.

    J'ai réalisé du code pour faire évoluer l'application Forms on y ajoutant de nouveaux types de champ. Cela n'a pas du tout été évident d'échanger avec l'équipe de développement mais j'espère voir arriver ces évolutions dans une prochaine version.

    Ensuite, j'ai traduis Analytics en français même si je ne suis pas satisfait. Je vais faire de nouvelles traductions l'année prochaine ! Cette applicationpermet de mettre en graphique des données stockées sur Nextcloud ou accessiblesdepuis une API. C'est encore relativement basique mais c'est tout de même très pratique.

    Même si le développement n'est pas très actif, j'utilise l'application Printer et j'ai proposé du code pour gérer les permissions et réduire les risques d'injection de code.

    Dans un contexte très différent de Nextcloud, j'ai apporté un correctif au projet PDNS Manager , une interface web qui permet de gérer les enregistrements d'un serveur Powerdns .

    J'ai récemment publié Mail RSS , une application pour transformer des emails en flux RSS. Enfin, un peu plus tôt cette année, j'ai mis en ligne les sources du site web qui permet de monitorer le terrarium de la maison.

    Je crois avoir fait le tour de mon activité en espérant poursuivre en 2021 !

    Gravatar de Simon Vieille
    Original post of Simon Vieille .Votez pour ce billet sur Planet Libre .

    Articles similaires

    • wifi_tethering open_in_new

      This post is public

      www.deblan.io /post/594/bilan-tech-de-l-annee-2020

    • chevron_right

      Journal du hacker : Liens intéressants Journal du hacker semaine #51

      Journal du hacker • news.movim.eu / PlanetLibre • 20 December, 2020 • 1 minute

    Pour la 51ème semaine de l'année 2020, voici 10 liens intéressants que vous avez peut-être ratés, relayés par le Journal du hacker , votre source d’informations pour le Logiciel Libre francophone !

    logo-journal-du-hacker.png

    Pour ne plus rater aucun article de la communauté francophone, voici :

    De plus le site web du Journal du hacker est « adaptatif (responsive) ». N’hésitez pas à le consulter depuis votre smartphone ou votre tablette !

    Le Journal du hacker fonctionne de manière collaborative, grâce à la participation de ses membres. Rejoignez-nous pour proposer vos contenus à partager avec la communauté du Logiciel Libre francophone et faire connaître vos projets !

    Et vous ? Qu’avez-vous pensé de ces articles ? N’hésitez pas à réagir directement dans les commentaires de l’article sur le Journal du hacker :)

    Gravatar de Journal du hacker
    Original post of Journal du hacker .Votez pour ce billet sur Planet Libre .

    Articles similaires

    • chevron_right

      miamondo : Labortablo, une interface de bureau codée en Python sur une base Archlinux/Openbox (2ème partie)

      miamondo • news.movim.eu / PlanetLibre • 20 December, 2020 • 7 minutes

    La barre de tâches

    Sommaire

    1. La boucle

    2. Récupérer et convertir les icônes avec xprop

    3. Iconifier et déiconifier les fenêtres


    Voici le deuxième article sur mon interface de bureau moulé à la louche. Il traite de la barre des tâches. Le processus fonctionne de manière tout à fait satisfaisante mais il m’a vraiment donné du fil à retordre. Cette zone qui se trouve en bas de l’écran, affiche:

    • Le bouton d’ouverture ou de fermeture du menu principal,
    • quatre boutons qui permettent d’éteindre, de redémarrer, de mettre en veille ou de se déconnecter,
    • et bien sûr, autant de boutons qu’il y a de fenêtres ouvertes.

    Le rôle de ces derniers est triple: iconifier, déiconifier ou bien fermer les fenêtres qui leur sont associées. Les boutons sont des valeurs stockées dans un dictionnaire. Les clés de ce dernier sont les identifiants obtenus avec la commande wmctrl -l .

    Dans la capture d’écran ci-dessous, on peut constater que trois applications sont ouvertes, mon agenda, chromium et Libre-Office Calc.

    1. La boucle

    Toute la difficulté de cette barre de tâches est qu’il faut créer une boucle qui récupère les informations sur chaque fenêtre d’application ouverte et surtout qui affiche l’icône qui lui est associée.

    Voici donc le déroulé des opérations:

    La boucle est incluse dans le script taskbar.py . Elle exécute pour la première fois la commande wmctrl -l (voir capture d’écran ci-dessus) et stocke les informations obtenues dans un dictionnaire que nous allons baptiser running_applications . Ce dictionnaire doit être hors de la boucl e. À chaque tour, la boucle exécute wmctrl -l et compare les informations obtenues avec celles stockées dans le dictionnaire running_applications . Les lignes contenant N/A ) sont exclues du processus. Le programme sait désormais ce qu’il doit faire. Il splitte chaque ligne, c’est-à dire qu’il les transforme en liste, pour en extraire le numéro d’identification (par exemple 0x008000007 ), ainsi que le titre de la fenêtre (par exemple Agenda ). Le titre de la fenêtre va s’afficher sur le bouton qui lui correspond.

    Si une nouvelle fenêtre a été ouverte, la boucle se charge elle-même de créer le bouton correspondant. Et si une fenêtre a été fermée, elle se charge de détruire le bouton associé. Bien sûr, le script taskbar.py ne s’amuse pas à détruire et à recréer une nouvelle barre à chaque changement. Il ne rajoute ou ne détruit que les boutons impactés par une modification. Puis, en fin de boucle, après avoir effectué les modifications et actualisé la barre des tâches, le script met à jour le contenu du dictionnaire running_applications , qui est hors de la boucle.

    Je vous invite à aller voir le code sur mon dépôt git. Je pense qu’il est assez concis.

    2. Récupérer les icônes avec xprop, puis les convertir au format *.png pour les afficher à la volée

    Il s’agit sans aucun doute de la partie la plus délicate. Je dois vous avouer que le titre de l’article n’est pas correct. Je prétends que l’interface de bureau est codée tout en Python, mais il y a tout de même un fichier *.sh . J’ai déniché ce script à cette adresse .

     #!/bin/bashpath=$1id=$2xprop -id "$id" -notype 32c _NET_WM_ICON |    perl -0777 -pe '@_=/\\d+/g;    printf "P7\\nWIDTH %d\\nHEIGHT %d\\nDEPTH 4\\nMAXVAL 255\\nTUPLTYPE RGB_ALPHA\\nENDHDR\\n", splice@_,0,2;    $_=pack "N*", @_;    s/(.)(...)/$2$1/gs' > "$path"/"$id".pamconvert "$path"/"$id".pam "$path"/"$id".png

    Il est manifestement l’oeuvre d’un dénommé Stéphane Chazelas. Bien sûr, je l’ai adapté à ma sauce. Il prend deux paramètres en arguments (le répertoire courant et le numéro d’identification). La mission de ce script est d’exécuter xprop et de générer des fichiers portant l’extension .pam . Ceux-ci sont immédiatement convertis en icônes .png pour être affichés à la volée sur les boutons respectifs. Si l’identifiant de la fenêtre est 0x00800007 , alors l’icône qui lui est associé, s’appelle 0x00800007.png.

    Ça marche presque à tous les coups. Seules quelques applications résistent et refusent de convertir le fichier *.pam . C’est le cas de Shotwell par exemple. Lorsque cela se produit, elles sont capturées dans une exception.

    Le processus xprop prend un peu de temps, si bien qu’il est possible que le bouton affiche seulement le texte et pas l’icône. Afin de remédier à ce problème et être ceinture-bretelles , je vais modifier le code pour que le processus soit lancé une deuxième fois en début de boucle. Si vraiment, la conversion au format png s’avère impossible, je peux encore utiliser une liste d’icônes de secours. Après tout, cela ne concerne que quelques applications. En tout cas, le processus actuel est lisse pour l’utilisateur. Il n’y a aucun retard. C’est instantané. Le texte de l’icône s’actualise dès qu’on change de page ou qu’on ouvre un nouvel onglet dans le navigateur. Il n’y a aucun sursaut.

    Les fichiers générés par xprop et portant l’extension *.pam , ainsi que les icônes au format *.png sont conservés vingt secondes dans le répertoire courant, avant d’être détruits. Si l’ordinateur est éteint pendant ce laps de temps, le programme se charge de détruire les éventuels survivants juste avant le processus d’extinction, d’une part pour ne pas encombrer le répertoire, et d’autre part pour être certain qu’à la prochaine session, ils ne seront pas réattribués à une mauvaise application. Le cas s’est déjà produit. Le bouton de Thunar affichait l’icône de Chromium!

    3. Iconifier et déiconifier les fenêtres en utilisant un simple clic de souris

    Cette opération est bien moins évidente qu’il n’y parait. Il ne s’agit pas seulement d’alterner la commande d’un bouton pour qu’il réduise sa fenêtre puis qu’il l’agrandisse. En se contentant de faire ça, une fenêtre ouverte mais totalement cachée va d’abord se réduire puis s’agrandir au-dessus des autres, ce qui va contraindre l’utilisateur à faire un double-clic un peu énervant.

    Il faut donc faire en sorte de créer une condition pour que le bouton qui iconifie une fenêtre puisse aussi être celui qui va la passer au premier plan si cette dernière n’a pas le focus. Pour ce qui est de la déiconification, c’est beaucoup plus simple puisqu’on demande juste au bouton d’agrandir et de passer sa fenêtre au premier plan.

    C’est la commande xdotool getwindowfocus getwindowname qui se charge de déterminer quelle fenêtre a le focus. Ensuite, selon le cas, c’est xdotool windowminimize qui va iconifier la fenêtre ou bien wmctrl -i -a qui va la placer au-dessus de toutes les autres, et par conséquent la rendre visible.

    def iconify_window(self, ID, tb):    """iconifies the window"""    self.ID = ID    self.taskbar_button = tb    process = subprocess.Popen(['xdotool', 'getwindowfocus', 'getwindowname'], text=True, stdout=subprocess.PIPE)    for line in process.stdout.readlines():        if line.strip()==self.taskbar_button['text'].strip():            subprocess.Popen(['xdotool', 'windowminimize', self.ID], text=True, stdout=subprocess.PIPE)            self.taskbar_button.bind('', lambda event, ID=self.ID,                                                  tb=self.taskbar_button: self.deiconify_window(ID, tb))        else:            subprocess.Popen(['wmctrl', '-i', '-a', self.ID], text=True, stdout=subprocess.PIPE)            self.taskbar_button.bind('', lambda event, ID=self.ID,                                                  tb=self.taskbar_button: self.iconify_window(ID, tb))

    Conclusion

    Voilà pour ce qui concerne la barre des tâches. Elle fonctionne très bien mais il reste tout de même quelques points d’amélioration.

    Ajouter de la transparence et opacifier la barre des tâches au survol de la souris. Malgré la méthode root.attributes(‘-alpha’, 0.5) , je n’y parviens pas.

    Je souhaiterais également que mes deux écrans affichent une barre de tâches qui corresponde aux fenêtres ouvertes sur chacun d’entre eux. Pour l’instant, les deux barres sont des clônes. Mais j’ai peut-être déjà la solution…

    Et pour celles et ceux que ça intéresse, le code est ici:

    https://gitlab.com/miamondo/labortablo

    Gravatar de miamondo
    Original post of miamondo .Votez pour ce billet sur Planet Libre .

    • wifi_tethering open_in_new

      This post is public

      miamondo.org /2020/12/20/labortablo-une-interface-de-bureau-codee-en-python-sur-une-base-archlinux-openbox-2eme-partie/