Créez un driver FUSE pour Google Drive

Magazine
Marque
GNU/Linux Magazine
HS n°
Numéro
90
Mois de parution
mai 2017
Spécialité(s)


Résumé

Google aime beaucoup le Python et le fait savoir. Avant même de vouloir compter dans ses rangs le créateur du langage, il mettait à la disponibilité des programmeurs Python une grande partie de leurs API. Et Google Drive n'échappe pas à la règle.


Body

Derrière l'appellation cloud se cache souvent beaucoup de concepts, qui à mon sens n’ont rien à y faire. À l'inverse, les services proposant une gestion de vos données multimédias disponibles partout où une connexion internet est disponible, assurant par là même une sauvegarde et redondance de vos données, constituent un réel service de cloud. Google Drive est le service de stockage de données « offert » par Google dans la limite d'un stockage de 15 Gio. Pour plus d'espace, il faudra, comme la plupart des concurrents, utiliser la carte bancaire. Mais comment faire pour intégrer un partage Google Drive à Linux ? Python bien sûr. Nous aborderons les dessous de l'API Python, offerte par Google, avant d'en explorer le fonctionnement. Ensuite, nous verrons comment l'intégrer de façon transparente dans Linux via l'API FUSE et son binding Python.

1. REST

L'API Google Drive est une API de type REST (REpresentational State Transfer), elle respecte donc une convention permettant l'interrogation du service au travers du protocole HTTP et, dans notre cas, HTTP2. De nombreuses applications Google reposent sur cette convention.

Mais un problème subsiste quand on implémente une API respectant une telle architecture, que l'on définit souvent sous le terme de RESTFull : c'est l'aspect « sans état ». Ceci implique que chaque requête est complètement décorrélée, et donc exclut automatiquement tout moyen d'authentification et donc d'identification des ressources auxquelles un tiers peut avoir droit. Le protocole OAuth2 répond à cette problématique.

2. OAuth2

L'API permettant de communiquer avec Google Drive repose, comme l'intégralité des API Google, sur le protocole OAuth. OAuth et son successeur OAuth2 ne sont pas à proprement parler, des protocoles d'authentification, mais il faut plutôt les voir comme des protocoles de délégation d'identité.

Une application possède de nombreuses ressources qu'il peut être intéressant de rendre disponibles, du moins en partie, à d'autres applications. C'est bien cette problématique que tente de résoudre le protocole OAuth2 (car OAuth 1 n'est plus recommandé). Il fut initié par Blaine Cook, alors ingénieur chez Twitter. Il était responsable de l'implémentation de OpenID, un service de mutualisation d'authentication afin d'accéder avec un compte unique à plusieurs applications web. Durant cette implémentation, il s'est rendu compte qu'il n'existait aucun standard permettent de déléguer une partie des ressources d'une application à une autre. C'est pour cela qu'il a eu l'idée d'initier le protocole OAuth. Ce projet fût très vite soutenu par Google qui y voyait un moyen de développer la communauté des développeurs autour de leurs applications et ainsi inciter les utilisateurs à utiliser indirectement les ressources mises à disposition par Google.

OAuth est donc basé sur de la délégation d'autorisations. On voit donc deux notions émerger :

  • une application de type fournisseur offrant un service via une API ;
  • une application de type consommatrice utilisant le service de la précédente.

Concrètement, l'utilisateur de l'application de type fournisseur va définir un périmètre fonctionnel qu'il va ensuite déléguer à l'application de type consommatrice au travers d'un token. Ce dernier sera donc présenté à chaque appel de l'API afin de l'identifier.

3. API Python

L'API Google repose donc sur un protocole ouvert, OAuth2, et le protocole HTTP2. Cette dernière est très bien documentée et nous allons décrire les étapes afin de créer une application permettant de s'interfacer avec le compte Google Drive de n'importe quel client.

3.1 Génération du token OAuth2

Dans un premier temps, il faut créer notre token nous permettant de nous authentifier auprès du service. Pour ce faire, il nous faut un compte Google et ensuite nous connecter au service de développeur à l'adresse suivante https://console.developers.google.com (voir figure 1). Ensuite, il faut créer une nouvelle application que nous allons nommer Test Linux Magazine.

googledrive_figure_01

 

Fig. 1 : Console de développeur Google. Liste de toutes les APIs Google disponibles.

Il faut donc activer Google Drive. Ensuite, il faut créer notre token OAuth2 en sélectionnant l'onglet identifiant puis ID client Oauth(voir figure 2).

googledrive_figure_02

 

Fig. 2 : Liste des identifiants disponibles pour une application.

Ensuite nous allons télécharger ce token au format JSON pour la suite des opérations et que nous allons sauvegarder sous oauth_token.json.

3.2 Python

Notre code Python va donc se découper en une partie d'authentification basée sur la bibliothèque OAuth2 de Python, et enfin une partie plus spécifique utilisant un wrapper REST pour le Google Drive API.

Nous allons donc commencer par installer les dépendances nécessaires pour notre petite application.

# pip install httplib2 google-api-python-client

3.2.1 Authentification

L'authentification de l'application repose entièrement sur le protocole OAuth2 :

from oauth2client import client

from oauth2client import tools

from oauth2client.file import Storage

 

def get_credentials():

    home_dir = os.path.expanduser('~')

    credential_dir = os.path.join(home_dir, '.credentials')

    if not os.path.exists(credential_dir):

        os.makedirs(credential_dir)

    credential_path = os.path.join(credential_dir,

                                   'test_linux_magazine.json')

 

    store = Storage(credential_path)

    credentials = store.get()

    if not credentials or credentials.invalid:

        flow = client.flow_from_clientsecrets('oauth_token.json', 'https://www.googleapis.com/auth/drive')

        flow.user_agent = 'Test Linux Magazine'

        if flags:

            credentials = tools.run_flow(flow, store, flags)

        else:

            credentials = tools.run(flow, store)

    return credentials

Rien de bien extraordinaire dans ce code où toute la complexité est cachée par l'API. Ici, nous optimisons simplement les appels et autorisations que nous sauvegardons dans le répertoire caché, sous le répertoire root de l'utilisateur, .credentials.

Si nous exécutons cette fonction, nous verrons que notre navigateur sera automatiquement lancé sur une page d'identification Google. Après avoir rentré vos identifiants, une nouvelle page d'autorisation sera affichée, permettant donc de déléguer une partie de Google Drive à votre nouvelle application (voir figure 3).

googledrive_figure_03

 

Fig. 3 : Fenêtre d'autorisation OAuth2 pour Google Drive.

3.2.2 REST

Nous allons donc explorer les fonctionnalités que nous offre l'API REST de Google Drive. Pour cela, Google nous propose un wrapper autour des requêtes et surtout des mécanismes nous permettant de manipuler simplement les résultats en Python.

Par exemple, il est simple de lister l'ensemble des fichiers disponibles pour un utilisateur :

from apiclient import discovery

 

credentials = get_credentials()

http = credentials.authorize(httplib2.Http())

service = discovery.build('drive', 'v3', http=http)

 

results = service.files().list(fields="nextPageToken, files(id, name)").execute()

items = results.get('files', [])

L'objet items contient l'ensemble des informations de vos fichiers présents dans votre Drive.

Il est bien sûr possible de télécharger vos fichiers :

from apiclient import discovery, http

 

file_id = '0BwwA4oUTeiV1UVNwOHItT0xfa2M'

request = service.files().get_media(fileId=file_id)

fh = io.BytesIO()

downloader = http.MediaIoBaseDownload(fh, request)

done = False

while done is False:

    status, done = downloader.next_chunk()

    print("Download %d%%." % int(status.progress() * 100))

Il est aussi possible de créer un répertoire via l'API :

file_metadata = {

  'name' : 'Images',

  'mimeType' : 'application/vnd.google-apps.folder'

}

file = service.files().create(body=file_metadata,

                                    fields='id').execute()

print('Folder ID: %s' % file.get('id'))

Ici nous nous sommes attardés sur des aspects de consultation, mais il est tout aussi possible d'uploader des fichiers. Mais bien sûr ceci requiert un scope différent pour l'application.

Cette API m'a donc donné une idée. Et si nous pouvions interagir avec nos données Google Drive au travers d'un explorateur de fichiers ? Nous allons donc implémenter un driver FUSE simple pour Google Drive écrit entièrement en Python.

4. FUSE

FUSE signifie Filesystem in UserSpacE. Concrètement, FUSE est une technique qui va nous permettre d'émuler un système de fichiers depuis l'espace utilisateur, et donc sans droits root. FUSE se décompose en une partie driver relayant les appels à une bibliothèque, libfuse, en espace utilisateur.

Il existe des bindings pour presque tous les langages et bien sûr Python n'échappe pas à la règle. Il existe de nombreux bindingset ici nous avons fait le choix de fusepy.

# pip install fusepy

Nous allons ensuite reprendre le code source que nous avons testé précédemment pour initier notre « driver » en userspace.

Pour cela, nous allons implémenter une classe simple qui va permettre de lister nos fichiers depuis le compte Google Drive de notre client, et ensuite télécharger à la volée les fichiers demandés.

Nous allons donc initier une classe GoogleDriveFS qui va implémenter la méthode init() suivante :

class GoogleDriveFS(Operations):

    def __init__(self):

        self.credentials = get_credentials()

        self.http = self.credentials.authorize(httplib2.Http())

        self.service = discovery.build('drive', 'v3', http=self.http)

        self.items = {}

        self.fh = {}

        self.next_fh = 0

Les credentials sont issus de la fonction précédemment décrite ; de plus, nous rajoutons un contexte permettant de gérer nos fichiers :

  • items pour le résultat des requêtes REST ;
  • fh qui sera tous les fichiers ouverts que nous stockerons bêtement en mémoire ;
  • next_fh est un compteur de descripteur de fichiers.

Nous allons ensuite implémenter l'interface readdir appelée pour lister le contenu d'un répertoire. Dans cet exemple, nous ne gérons pas les répertoires Google Drive ; tout est contenu dans la racine.

    def readdir(self, path, fh):

        results = self.service.files().list(fields="nextPageToken, files(id, name, size)").execute()

        self.items = dict([(item['name'], (item['id'], int(item.get('size') or '0'))) for item in results.get('files', [])])

        return ['.', '..'] + list(self.items.keys())

Nous retrouvons ici notre requête précédente, plus une information de taille via l'attribut size. Nous remarquons qu'il est nécessaire d'ajouter les pseudo-répertoires . et ...

Ensuite, la méthode getattr est appelée à chaque fois que des méta-informations sont demandées. Ici pour nous tout est un fichier, nous rajoutons l'information de taille.

    def getattr(self, path, fh=None):

        if path == '/':

            return dict(st_mode=(S_IFDIR | 0o755), st_nlink=2)

        if path[1:] not in self.items:

            raise FuseOSError(EROFS)

Enfin, nous allons implémenter les méthodes de lecture de fichiers. La fonction open est appelée pour l'ouverture d'un fichier puis la fonction read de façon répétée pour lire l'intégralité d'un fichier.

    def open(self, path, flags):

        if path[1:] not in self.items:

            raise FuseOSError(EROFS)

 

        request = self.service.files().get_media(fileId=self.items[path[1:]][0])

        fh = io.BytesIO()

        downloader = http.MediaIoBaseDownload(fh, request)

        done = False

        while done is False:

            status, done = downloader.next_chunk()

 

        fh_id = self.next_fh

        self.next_fh += 1

        self.fh[fh_id] = fh

 

        return fh_id

 

    def read(self, path, size, offset, fh):

        if fh not in self.fh:

            raise FuseOSError(EROFS)

        self.fh[fh].seek(offset)

returnself.fh[fh].read(size)

Ici nous manipulons des objets au sein de la mémoire au travers d'un io.Bytes.

Il suffit de lancer notre driver et nous pouvons interagir, de façon encore un peu simpliste, avec nos informations dans le Google Drive (voir figures 4 et 5).

googledrive_figure_04

 

Fig. 4 : Intégration au sein de l'application Files sous Ubuntu.

googledrive_figure_05

 

Fig. 5 : Affichage d'une image présente depuis Google Drive.

Conclusion

100 lignes de code Python au total pour réaliser un « driver » pour Google Drive. Cela révèle la maturité de Python ainsi que son intégration dans l'écosystème Linux et réseau. Google offre une API simple pour son Drive ainsi qu'un binding Python adapté, associé aux contributeurs réalisant des bibliothèques telles que fusepy qui peuvent nous permettre d'imaginer des solutions toujours plus innovantes !



Article rédigé par

Par le(s) même(s) auteur(s)

Le VPN du pauvre...

Magazine
Marque
GNU/Linux Magazine
Numéro
228
Mois de parution
juillet 2019
Résumé

Le VPN est un trigramme utilisé à outrance dans le petit milieu de l'informatique pour résoudre des problématiques liées à la confidentialité. Mais il n'est nul besoin d'acheter à prix d'or une machine nord-américaine pour assurer sa confidentialité. En utilisant intelligemment les outils que nous offre notre système préféré, nous allons pouvoir nous créer un VPN à très bas coût...

Les derniers articles Premiums

Les derniers articles Premium

Game & Watch : utilisons judicieusement la mémoire

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Au terme de l'article précédent [1] concernant la transformation de la console Nintendo Game & Watch en plateforme de développement, nous nous sommes heurtés à un problème : les 128 Ko de flash intégrés au microcontrôleur STM32 sont une ressource précieuse, car en quantité réduite. Mais heureusement pour nous, le STM32H7B0 dispose d'une mémoire vive de taille conséquente (~ 1,2 Mo) et se trouve être connecté à une flash externe QSPI offrant autant d'espace. Pour pouvoir développer des codes plus étoffés, nous devons apprendre à utiliser ces deux ressources.

Raspberry Pi Pico : PIO, DMA et mémoire flash

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Le microcontrôleur RP2040 équipant la Pico est une petite merveille et malgré l'absence de connectivité wifi ou Bluetooth, l'étendue des fonctionnalités intégrées reste très impressionnante. Nous avons abordé le sujet du sous-système PIO dans un précédent article [1], mais celui-ci n'était qu'une découverte de la fonctionnalité. Il est temps à présent de pousser plus loin nos expérimentations en mêlant plusieurs ressources à notre disposition : PIO, DMA et accès à la flash QSPI.

Programmation des PIO de la Raspberry Pi Pico

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

La carte Pico de Raspberry Pi est appréciable à bien des égards. Ses ressources, son prix, ses deux cœurs ARM... Mais ce morceau de silicium qu'est le RP2040 renferme une fonctionnalité unique : des blocs PIO permettant de créer librement des périphériques supplémentaires qu'il s'agisse d'éléments standardisés comme SPI, UART ou i2c, ou des choses totalement exotiques et très spécifiques à un projet ou un environnement donné. Voyons ensemble comment prendre en main cette ressource et explorer le monde fantastique des huit machines à états de la Pico !

Les listes de lecture

9 article(s) - ajoutée le 01/07/2020
Vous désirez apprendre le langage Python, mais ne savez pas trop par où commencer ? Cette liste de lecture vous permettra de faire vos premiers pas en découvrant l'écosystème de Python et en écrivant de petits scripts.
11 article(s) - ajoutée le 01/07/2020
La base de tout programme effectuant une tâche un tant soit peu complexe est un algorithme, une méthode permettant de manipuler des données pour obtenir un résultat attendu. Dans cette liste, vous pourrez découvrir quelques spécimens d'algorithmes.
10 article(s) - ajoutée le 01/07/2020
À quoi bon se targuer de posséder des pétaoctets de données si l'on est incapable d'analyser ces dernières ? Cette liste vous aidera à "faire parler" vos données.
Voir les 102 listes de lecture

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous