JSONApiDoc

JSON Api Doc une petite bibliothèque Open Source que j’ai publiée.

En manipulant des API en JSON API une chose m’a beaucoup gêné. L’utilisation des included pour éviter de dupliquer les données rend la lecture très difficile par un humain.

Cette bibliothèque utilisable dans un programme Python ou en cli permet tout simplement de résoudre les included et de renvoyer un objet plus simple à lire et à manipuler par un humain.

Par exemple:

{
  "data": [{
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "JSON API paints my bikeshed!",
      "body": "The shortest article. Ever.",
      "created": "2015-05-22T14:56:29.000Z",
      "updated": "2015-05-22T14:56:28.000Z"
    },
    "relationships": {
      "author": {
        "data": {"id": "42", "type": "people"}
      }
    }
  }],
  "included": [
    {
      "type": "people",
      "id": "42",
      "attributes": {
        "name": "John",
        "age": 80,
        "gender": "male"
      }
    }
  ]
}

Donnera:

[
    {
        "type": "articles",
        "id": "1",
        "title": "JSON API paints my bikeshed!",
        "body": "The shortest article. Ever.",
        "created": "2015-05-22T14:56:29.000Z",
        "updated": "2015-05-22T14:56:28.000Z",
        "author": {
            "type": "people",
            "id": "42",
            "name": "John",
            "age": 80,
            "gender": "male"
        }
    }
]

Le code est disponible sur GitHub

Detect Python code duplicate

You can detect Python code duplicate with Pylint

pylint --disable=all --enable=duplicate-code src/

No config file found, using default configuration
************* Module gns3server.compute.dynamips.nodes.ethernet_switch
R:  1, 0: Similar lines in 2 files
==gns3server.compute.dynamips.nodes.ethernet_hub:101
==gns3server.compute.dynamips.nodes.ethernet_switch:136
    @property
    def mappings(self):
        """
        Returns port mappings

        :returns: mappings list
        """

        return self._mappings

    @asyncio.coroutine
    def delete(self):
        return (yield from self.close())

    @asyncio.coroutine
    def close(self):
        """
        Deletes this hub.
        """
 (duplicate-code)
R:  1, 0: Similar lines in 2 files
==gns3server.compute.dynamips.nodes.ethernet_hub:66
==gns3server.compute.dynamips.nodes.ethernet_switch:72
    @property
    def ports_mapping(self):
        """
        Ports on this hub

        :returns: ports info
        """

        return self._ports

    @ports_mapping.setter
    def ports_mapping(self, ports):
        """
        Set the ports on this hub

        :param ports: ports info
        """
        if ports != self._ports: (duplicate-code)

Report
======
231 statements analysed.

Statistics by type
------------------

+---------+-------+-----------+-----------+------------+---------+
|type     |number |old number |difference |%documented |%badname |
+=========+=======+===========+===========+============+=========+
|module   |2      |NC         |NC         |NC          |NC       |
+---------+-------+-----------+-----------+------------+---------+
|class    |2      |NC         |NC         |NC          |NC       |
+---------+-------+-----------+-----------+------------+---------+
|method   |0      |NC         |NC         |0           |0        |
+---------+-------+-----------+-----------+------------+---------+
|function |0      |NC         |NC         |0           |0        |
+---------+-------+-----------+-----------+------------+---------+



Raw metrics
-----------

+----------+-------+------+---------+-----------+
|type      |number |%     |previous |difference |
+==========+=======+======+=========+===========+
|code      |304    |48.95 |NC       |NC         |
+----------+-------+------+---------+-----------+
|docstring |167    |26.89 |NC       |NC         |
+----------+-------+------+---------+-----------+
|comment   |34     |5.48  |NC       |NC         |
+----------+-------+------+---------+-----------+
|empty     |116    |18.68 |NC       |NC         |
+----------+-------+------+---------+-----------+



Duplication
-----------

+-------------------------+-------+---------+-----------+
|                         |now    |previous |difference |
+=========================+=======+=========+===========+
|nb duplicated lines      |87     |NC       |NC         |
+-------------------------+-------+---------+-----------+
|percent duplicated lines |14.100 |NC       |NC         |
+-------------------------+-------+---------+-----------+



Messages by category
--------------------

+-----------+-------+---------+-----------+
|type       |number |previous |difference |
+===========+=======+=========+===========+
|convention |0      |NC       |NC         |
+-----------+-------+---------+-----------+
|refactor   |7      |NC       |NC         |
+-----------+-------+---------+-----------+
|warning    |0      |NC       |NC         |
+-----------+-------+---------+-----------+
|error      |0      |NC       |NC         |
+-----------+-------+---------+-----------+



% errors / warnings by module
-----------------------------

+--------------------------------------------------+------+--------+---------+-----------+
|module                                            |error |warning |refactor |convention |
+==================================================+======+========+=========+===========+
|gns3server.compute.dynamips.nodes.ethernet_switch |0.00  |0.00    |100.00   |0.00       |
+--------------------------------------------------+------+--------+---------+-----------+



Messages
--------

+---------------+------------+
|message id     |occurrences |
+===============+============+
|duplicate-code |7           |
+---------------+------------+



Global evaluation
-----------------
Your code has been rated at 9.70/10

Un système d'exploitation souverain mais pourquoi?

La discussion sur le système d’exploitation souverain est devenue une blague sur internet tellement cette proposition parait absurde. Mais ce qui n’était au début qu’un amendement a était adopté par les députés. La blague pourrait au final coûter au citoyen. Même si elle part probablement d’un bon sentiment.

C’est quoi un système d’exploitation?

Le système d’exploitation c’est grosso modo la couche logicielle entre le matériel et vos applications. C’est donc indispensable et incontournable. Les systèmes les plus connus sont Windows, MacOSX, Linux pour les PC de bureau et IOS et Android pour les téléphones portables.

On veut changer quoi?

L’amendement adopté au parlement:

Le Gouvernement remet au Parlement,dans les trois mois suivant la promulgation de la présente loi, un rapport sur la possibilité de créer un Commissariat à la souveraineté numérique rattaché aux services du Premier ministre dont les missions concourront à l’exercice, dans le cyberespace, de la souveraineté nationale et des droits et libertés individuels et collectifs que la République protège. Ce rapport précise les conditions de mise en place, sous l’égide de ce Commissariat, d’un système d’exploitation souverain et de protocoles de chiffrement des données, ainsi que les moyens et l’organisation nécessaires au fonctionnement de cet établissement public. ».

Mais le début de l’exposé sommaire nous renseigne plus:

La guerre contre le terrorisme, mais aussi l’urgente nécessité de protéger dans le cyberespace les droits et libertés des citoyens alors qu’une récente décision de la Cour de Justice de l’Union Européenne a prouvé que leurs données à caractère personnel étaient exploitées en toute illégalité, appellent de la part de la représentation nationale une prise de conscience nouvelle sur les enjeux liés à l’exercice de la souveraineté de la France dans le domaine du numérique.

On a ici deux choses:

  • La guerre contre le terrorisme
  • Les données à caractère personnel

Le détail complet est ici: http://www.assemblee-nationale.fr/14/amendements/3318/CION_LOIS/CL129.asp

La guerre contre le terrorisme

C’est le premier point de l’exposé sommaire. Puisqu’à priori Google, Apple ou Microsoft ne sont pas des entreprises terroristes quel risque court-on? Et bien il se trouve que ces sociétés aient tendance à trop bien protéger nos données avec des systèmes ou seul l’utilisateur à accès. Et que même si un ingénieur de Apple voulait lire le contenu chiffré de votre téléphone il ne pourrait pas. On pourrait donc voir cette tentative de système souverain comme une volonté d’accéder à nos données par l’État.

Pour être efficace, il faudrait donc que les terroristes acceptent de remplacer leurs ordinateurs par des versions françaises. Autant dire que cela n’arrivera pas. D’autant plus qu’ils disposent déjà de logiciels leur permettant de chiffrer les communications sans faire confiance au chiffrement mis en place par Apple, Google ou Microsoft.

Et quant à l’imposer, cela reviendrait à un désastre économique qui isolerait la France du reste du monde numérique. Imaginez vous être obligé d’acheter un téléphone français fait uniquement pour le marché français. Et le cauchemar pour les entreprises obligées d’utiliser un système différent en fonction du pays.

Les données à caractère personnel

Un certain nombre de ces systèmes sont fermés et pourrait contenir des portes dérobées pour le gouvernement américain. Par ailleurs, les entreprises qui les exploitent peuvent utiliser ces données sans l’accord des personnes.

Mais ce n’est pas le cas de Linux ou d’Android si l’on prend un dérivé. En effet le logiciel libre nous permet d’auditer le code source. On a donc déjà des solutions que l’on pourrait promouvoir.

Et les données personnelles doivent être protégées par la loi, quelle que soit la couche concernée. Il serait absurde que l’on empêche Google d’accéder aux données de votre téléphone via le système, mais que de toute façon il lise vos emails via Gmail. Cela passe donc par des lois et non des solutions techniques. D’ailleurs la loi discutée à l’Assemblée renforce très fortement les pouvoirs de sanction de la CNIL.

Qui fait cela?

Peu de pays ont essayé. L’exemple le plus connu est celui de la Corée du Nord avec Red Star OS: https://fr.wikipedia.org/wiki/Red_Star_OS

Pourquoi il ne faut pas essayer?

Après tout quel mal il y aurait a essayer de faire un système pour les gens qui veulent et nos administrations?

C’est un gouffre financier. La précédente tentative de cloud souverain s’est soldée par 150 millions d’euros d’argent public gaspillé. Et c’est sans compter le tort causé aux sociétés françaises qui était déjà sur ce marché.

Tout ce qui reste c’est cette publicité:

Le numérique en plus n’a pas besoin de grand projet pour créer de l’emploi, financer un projet ne participe pas à la relance de l’économie.

http://www.lesechos.fr/idees-debats/editos-analyses/0204173981400-cloud-souverain-un-gachis-a-la-francaise-1096130.php

Si le projet démarre de zéro Numerama à estimé le coût à 1 milliard d’euros. Ce qui me parait sous-estimé compte tenu de la complexité de l’opération.

http://www.numerama.com/business/139709-developper-un-os-souverain-combien-cela-coute.html

Même si à priori la volonté serait de se baser sur un Linux. Mais quel intérêt face à l’existant ?

Certains diront que pour le moment on engage peu de dépense vu que ce n’est qu’un rapport. Mais cela aura déjà un coût pour la société et le rapport pourrait déboucher sur un nouveau fiasco.

Mais alors on peut faire quoi?

A ma modeste échelle j’ai trois propositions.

Tout d’abord, on priorise le logiciel libre qui est une solution déjà existante et peu couteuse économiquement. Et l’on a la capacité de les auditer pour vérifier qu’il n’existe pas de portes dérobées au profit des USA.

Cela passe d’abord par l’administration, en effet comment parler de souveraineté quand l’éducation nationale signe des accords avec Microsoft pour utiliser ses services de clouds. http://www.education.gouv.fr/cid96030/numerique-a-l-ecole-partenariat-entre-le-ministere-de-l-education-nationale-et-microsoft.html

Ou l’assemblée nationale qui utilise Microsoft Exchange pour ses emails: http://www2.assemblee-nationale.fr/decouvrir-l-assemblee/role-et-pouvoirs-de-l-assemblee-nationale/l-administration-de-l-assemblee-nationale/l-informatique-a-l-assemblee-nationale

Mais cela l’assemblée l’a refusé malgré le fait que c’était la proposition la plus soutenue lors de la consultation publique. https://www.april.org/les-debats-ne-font-que-commencer-autour-de-la-priorite-au-logiciel-libre-dans-la-loi

Deuxièment la CNIL doit sanctionner ceux qui viole la vie privée et là-dessus la loi république numérique va dans le bon sens en augmentant le pouvoir de sanction de la CNIL.

Troisièment, on mène des actions d’abus de position dominante au niveau européen pour que les géants ne puissent plus imposer arbitrairement leurs règles. Le système des stores pose notamment des problèmes de libre concurrence.

Mais pourquoi perdre du temps à écrire là dessus?

Lors de la création du cloud souverain, on a rigolé, mais au final c’est passé et 150 millions d’euros se sont évaporés et les copains des dirigeants qui se sont enrichis. On doit être méfiant pour éviter un nouveau Cloud Watt.

Pour continuer

L’excellent article de monsieur Bortzmeyer http://www.bortzmeyer.org/os-souverain.html

Why SourceForge should not die today

With the recent downtime of SourceForge we can read comment like:

  • Sourceforge should die
  • Someone still use sourceforge?
  • Sourceforge is a place with only malware
  • All active projects are already on Github
  • Nobody will notice if Sourceforge disapear

It’s wrong. If sourceforge disapear the transition will be annoying.

Sourceforge exist since a long time and even if we don’t know a lot of project still use it especially for the download mirror service. Some are actively maintain and can move, but some are abandoned and will be lost forever even if they still have users.

You disagree with me?

Take a look on where Homebrew formula download their source code or patches.

  • We have 3157 formula
  • 527 formulas use downloads.sourceforge.net (grep ".*url.*downloads.sourceforge.net" * | cut -d":" -f1|sort|uniq|wc -l)
  • 888 use Github (grep ".*url.*https://github.com/" * | cut -d":" -f1|sort|uniq|wc -l)
  • 113 for google code (grep ".*url.*googlecode.com" * | cut -d":" -f1|sort|uniq|wc -l)

Even if it’s not the majority we can’t say we can just ignore it.

But it’s only dead and unused project.

If we have a quick look on the list of this minor/dead project. We can found some pretty common software:

  • avidemux
  • boost
  • clamav
  • gnuplot
  • lame
  • netcat
  • pngcrush
  • proguard
  • privoxy
  • pwgen
  • swig
  • w3m
  • wxpython
  • zsh

And homebrew contain only project working on OSX and where someone has taken the time to package it.

We can survive to a lost of sourceforge website for the downloads, thanks to large number a sourceforge mirror. But for source repository, mailling list archive all of this can be lost. If SourceForge die for financial issues a risk exists that the investors will try to sell this archives instead of transferring for free to a foundation like Apache or Mozilla.

Also if tomorrow sourceforge die, how can you be sure that the github repository of a sourceforge project is made by the same team?

We need to prepare a smooth transition from Sourceforge. Why the parent compagnie of Sourceforge should continue to put money on a dying website? They are a for profit organization. In my mind the best solution is transfering a read only version to a non profit organisation. And because the same issue could happen with GitHub we need to work to decentralize free software hosting. It’s critical for the Free Software world, but also for all compagnies using this technologies.

Photo: Mark Lobo Licence CC BY-NC-ND 2.0

Reverse on OSX

This articles is a simple collection of programm I use for understanding how a third party programm interact with the system.

List symbols of a binary

nm -g /bin/ls
                 U __DefaultRuneLocale
                 U ___assert_rtn
                 U ___bzero
                 U ___error
                 U ___maskrune
                 U ___snprintf_chk
                 U ___stack_chk_fail
                 U ___stack_chk_guard

# Show shared libraries used by a programm

otool -L /bin/ls
/bin/ls:
	/usr/lib/libutil.dylib (compatibility version 1.0.0, current version 1.0.0)
	/usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)

Get asm code

otool -vt /bin/ls

List open file and network connections

lsof
COMMAND     PID            USER   FD      TYPE             DEVICE   SIZE/OFF     NODE NAME
launchd       1            root  cwd       DIR                1,2       1224        2 /
launchd       1            root  txt       REG                1,2     304240 21153870 /sbin/launchd

Based on Dtrace

dtrace is a powerfull tools for reverse engineer

iosnoop

iosnoop is a live trace of disk/io

sudo iosnoop
  UID   PID D    BLOCK   SIZE       COMM     PATHNAME
  501 88892 R 440836400   4096 Google Chrome ??/Cache/data_2
  501 88892 R 420909232   4096 Google Chrome ??/Cache/data_0
  501 88892 R 439174816   4096 Google Chrome ??/Cache/data_2

## opensnoop

Live trace file opening

sudo opensnoop -ve
STRTIME                UID    PID COMM          FD ERR PATH
2015 May 11 13:51:00     0     99 DisplayLinkMana   6   0 /Library/Application Support/DisplayLink/.dl.xml
2015 May 11 13:51:00     0     99 DisplayLinkMana   6   0 /Library/Application Support/DisplayLink/.dl.xml

## execsnoop

Live trace program execution

sudo execsnoop
  UID    PID   PPID ARGS
  501  52098  48099 sudo

## druss

dtruss allow you to show all system calls. You can filter by binary with the -n options.

sudo dtruss -n ls
	PID/THRD  SYSCALL(args) 		 = return
52383/0x1e98b4:  thread_selfid(0x7FFF5B281460, 0x7F93C18013F0, 0x7F93C060A7D0)		 = 2005172 0
52383/0x1e98b4:  csops(0x0, 0x0, 0x7FFF5BF4FA08)		 = 0 0
52383/0x1e98b4:  issetugid(0x0, 0x0, 0x7FFF5BF4FA08)		 = 0 0

errinfo

Show system calls errors

sudo errinfo
            EXEC          SYSCALL  ERR  DESC
           iTerm             read   35  Resource temporarily unavailable
           iTerm             read   35  Resource temporarily unavailable
           iTerm             read   35  Resource temporarily unavailable

Migration from PyQT4 to PyQT5 at GNS3

The current GNS3 GUI interface use PyQT4. In order to support the retina display we choose to move to PyQT5.

The move was not too complicated with the help of a small script.

You can see the complete list of PyQT4 / PyQT5 differences here: http://pyqt.sourceforge.net/Docs/PyQt5/pyqt4_differences.html

Actually most of recent distributions support PyQT5:

  • Debian Jessie
  • Ubuntu 14.04 and later
  • Arch linux
  • Gentoo
  • Fedora 21 and later

In all our code we already not directly include the PyQT4 module but our own qt module responsible of all the imports this save us the need to patch every files for changing the module.

An annoying point was the move from QtGui to QtWidgets of a lot of element. That’s why we wrote a script finding all the call to QtGui and checking if the module as not move to QtWidgets. The clean syntax of Python allow us to write a naive script.

It’s very easy with this apply to each line of the file:

if "QtGui." in line:
    for name, obj in inspect.getmembers(QtWidgets):
        line = line.replace("QtGui." + name, "QtWidgets." + name)

After that you need to import also QtWidgets. All our imports was like:

from .qt import QtGui

It’s easy to patch it by code:

if line.startswith("from ") and "import" in self._line and "QtGui" in line and not "QtWidgets" in line:
    line = line.strip() + ", QtWidgets\n"

Thanks to the previous move to Python 3 fixing the call to parent classe was easy because we can replace:

QtGui.QWidget.__init__(self)

By:

super().__init__()

Our script detect the name of parent classes and replace it by super.

Thanks to the script most of our code was working after this. We just need to check the script behavior by using git diff.

Also when using the app we have detected some issue. You need to know that method deprecated in QT5 are totaly removed in PyQT5. For us it was only a few calls.

Our patch script it’s specific for our code but can be a source of inspiration, if you want to use it you need to replace gns3 by your module. It’s also check if you haven’t a double init call:

import os
import sys
import re
import inspect
from PyQt5 import QtWidgets
 
class FilePatcher:
    def __init__(self, path):
        self._path = path
        self._line_number = 1
        self._previous_line = None
        self._line = None
        self._parent_classes = []
        self._qtwidgets_imported = False
 
    def error(self, msg):
        print("ERROR: {}:{} {}".format(self._path,self._line_number, msg))
 
    def info(self, msg , *args):
        msg = msg + " ".join(args)
        print("INFO: {}:{} {}".format(self._path,self._line_number, msg))
 
    def patch(self):
        out = ""
        with open(self._path) as f:
            previous_line = None
            for self._line in f.readlines():
                self.fix_object_inheritance()
                self.get_parent_class()
                if self._previous_line:
                    self.error_double_init()
                self.fix_qt_widgets()
                self.fix_init_with_self()
                self.fix_super_with_argument()
                self.fix_call_without_super()
                self.error_import_missing()
                self._line_number += 1
                self._previous_line = self._line
                out += self._line
 
        with open(self._path, 'w+') as f:
            f.write(out)
 
    def get_parent_class(self):
        """Extract parent class to self._parent_classes var"""
        if self._line.startswith("class "):
            l = re.match(r".*\((.*)\):", self._line)
            if l:
                self._parent_classes = []
                for cls in l.group(1).split(","):
                    self._parent_classes.append(cls.strip())
 
    def fix_object_inheritance(self):
        """In Python3 you no longer need to inherit from object"""
 
        if "(object):" in self._line:
            self._line = re.sub(r"\(object\)", "", self._line)
 
    def error_double_init(self):
        """
        Detect double call to init and raise an error
        """
 
        if ".__init__" in self._previous_line and ".__init__" in self._line:
            self.error("Double init is no longer allowed")
 
    def fix_super_with_argument(self):
        """
        Thanks to Python 3 now we can call super() instead of super(MyClassParent)
        """
        if re.match(r".*super\([^)].*", self._line):
            self._line = re.sub(r"super\([^)]+\)", "super()", self._line)
            self.info("Fix ", self._line.strip())
 
    def fix_init_with_self(self):
        """
        In some part of the code we have MyClassParent.__init__(self

        Now we can use super().__init__
        """
        if ".__init__(self" in self._line:
            self._line = re.sub(r"([A-Z\.a-z0-9_-]+)\.__init__\(self,? ?", "super().__init__(", self._line)
 
    def fix_call_without_super(self):
        """
        We need to call parent with super instead of class name in order to avoid issues
        """
 
        for cls in self._parent_classes:
            m = re.match(r"(.*)({})\.([a-zA-Z]+)\((self, ? ?)(.*)".format(cls), self._line)
            if m:
                fixed = m.group(1) + "super()." + m.group(3) + "(" + m.group(5) + "\n"
                self._line = fixed
 
    def fix_qt_widgets(self):
        """
        Replace QtGui by QtWidgets when require
        """
 
        if self._line.startswith("from ") and "import" in self._line and "QtGui" in self._line and not "QtWidgets" in self._line:
            self._line = self._line.strip() + ", QtWidgets\n"
 
        if "QtGui." in self._line:
            for name, obj in inspect.getmembers(QtWidgets):
                self._line = self._line.replace("QtGui." + name, "QtWidgets." + name)
 
    def error_import_missing(self):
        """
        Check if we use QtWidgets without QtGui
        """
        if "QtWidgets" in self._line:
            if " import " in self._line:
                self._qtwidgets_imported = True
            else:
                if self._qtwidgets_imported is False:
                    self.error("QtWidgets import missing")
 
for root, dirs, files in os.walk('gns3'):
  for file in files:
    if file.endswith('.py'):
        FilePatcher(os.path.join(root, file)).patch()