Ce qui est appréciable avec les Fonctions as a Service, c’est la simplicité de déploiement. Vous avez un bout de code que vous souhaitez expérimenter ? Vous le codez, demandez le déploiement et… Et rien d’autre ! C’est disponible après quelques secondes !

Un des cas d’usages les plus courants est la glue entre services. Le code est généralement simple et permet de lier plusieurs services entre eux. Lorsque l’on développe du code d’intégration, il n’est pas rare de s’y reprendre à plusieurs fois. Il y a toujours des petits ajustements à réaliser en phase de développement. Or, si on doit déployer à chaque fois sa fonction pour la tester, la boucle de retour va être longue et les développeurs vont perdre en productivité.

Nous avions vu comment tester une fonction AWS Lambda depuis sa machine de développement1. Nous allons voir aujourd’hui comment faire de même avec les Google Cloud Functions grâce au Cloud Functions Local Emulator.

Installation

Il faut Node.js au moins en version v6.11.1 pour installer l’émulateur de cette façon :

$ npm install -g @google-cloud/functions-emulator

Démarrage de l’émulateur

Dans un premier temps, il faut démarrer l’émulateur via la commande functions :

# démarrer l'émulateur
$ functions start
ERROR: (gcloud.info) Name expected [*HERE* 'value(config.project)'].
Enter a projectId to get started: projectId:  my-project
Starting Google Cloud Functions Emulator...
Google Cloud Functions Emulator STARTED
No functions deployed ¯\_(ツ)_/¯. Run functions deploy --help for how to deploy a function.

# arrêter l'émulateur
$ functions stop
Stopping Google Cloud Functions Emulator...
Google Cloud Functions Emulator STOPPED

# statut de l'émulateur
$ functions status
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Google Cloud Functions Emulator                                                                                               │
├──────────────────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Status           │ RUNNING                                                                                                    │
├──────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Uptime           │ 27 seconds                                                                                                 │
├──────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Process ID       │ 10184                                                                                                      │
├──────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ REST Service     │ http://localhost:8008/                                                                                     │
├──────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ gRPC Service     │ http://localhost:8009/                                                                                     │
├──────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ HTTP Triggers    │ http://localhost:8010/my-project/us-central1/:function                                                     │
├──────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Log file         │ C:\Programmes\node-v6.11.5\node_modules\@google-cloud\functions-emulator\logs\cloud-functions-emulator.log │
├──────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Emulator Version │ 1.0.0-alpha.25                                                                                             │
└──────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

If the Emulator becomes unresponsive, kill it will functions kill and then ensure that no other Emulator Node.js processes are running before restarting the Emulator.

# tuer l'émulateur
$ functions kill
Google Cloud Functions Emulator KILLED

Evénement HTTP

Prenons le cas simple d’une fonction déclenchée sur un événement HTTP. Voici le code de la fonction :

'use strict';

exports.hello = (request, response) => {
  response.status(200).send(JSON.stringify({ "message": "Hello " + request.body.name }));
};

Ensuite, il suffit de déployer cette fonction pour la tester avec l’émulateur :

$ functions deploy myFunctions --trigger-http
Copying file://C:\Users\PNOM\AppData\Local\Temp\us-central1-hello-292laY3oifKRwrn.zip...
Waiting for operation to finish...done.
Deploying function.........done.
Function hello deployed.
┌────────────┬───────────────────────────────────────────────────────────────────────────────────┐
│ Property   │ Value                                                                             │
├────────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ Name       │ hello                                                                             │
├────────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ Trigger    │ HTTP                                                                              │
├────────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ Resource   │ http://localhost:8010/my-project/us-central1/hello                                │
├────────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ Timeout    │ 60 seconds                                                                        │
├────────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ Local path │ C:\Users\pnom\myproject\functions                                                 │
├────────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ Archive    │ file://C:\Users\PNOM\AppData\Local\Temp\us-central1-hello-292laY3oifKRwrn.zip     │
└────────────┴───────────────────────────────────────────────────────────────────────────────────┘

Pour exécuter localement la fonction, c’est tout aussi simple :

$ functions call hello --data='{"name":"you"}'
ExecutionId: 66408ab9-a280-4be6-8f24-5f327cbaa16b
Result: { message: 'Hello you' }

Evénement de stockage

Il est possible de déclencher l’exécution d’une fonction à partir d’un événement provenant de Google Cloud Storage. Ceci est très pratique pour mettre en place des traitements de fichiers avec forte capacité de montée en charge. Voici le code de la fonction permettant de simplement tracer l’événement :

'use strict';

exports.helloStorage = function (event, callback) {
  const file = event.data;
  const filePath = file.name;

  if (file.resourceState === 'not_exists') {
    console.log(`File ${filePath} deleted.`);
  } else if (file.metageneration === '1') {
    console.log(`File ${filePath} uploaded.`);
  } else {
    console.log(`File ${filePath} metadata updated.`);
  }

  callback();
};

Les événements émits par GCS proposent un peu plus d’informations. Voici le fichier data.json qui contient les données de test :

{
    "name": "myfolder/JarwWgSpjca4sUSSYFjP2jmKidH3/myfile.txt",
    "bucket": "gs://my-project-bucket.appspot.com"
}

Les commandes pour déployer et exécuter cette fonction sont très semblables aux précédentes :

$ functions deploy helloStorage --trigger-bucket=my-project-bucket.appspot.com --local-path=./functions
Copying file://C:\Users\PNOM\AppData\Local\Temp\us-central1-helloStorage-5384ujGp4yOYOl4L.zip...
Waiting for operation to finish...done.
Deploying function.........done.
Function helloStorage deployed.
┌────────────┬───────────────────────────────────────────────────────────────────────────────────────────┐
│ Property   │ Value                                                                                     │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ Name       │ helloStorage                                                                              │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ Trigger    │ providers/cloud.storage/eventTypes/object.change                                          │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ Resource   │ my-project-bucket.appspot.com                                                             │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ Timeout    │ 60 seconds                                                                                │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ Local path │ C:\Users\pnom\myproject\functions                                                         │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ Archive    │ file://C:\Users\PNOM\AppData\Local\Temp\us-central1-helloStorage-5384ujGp4yOYOl4L.zip     │
└────────────┴───────────────────────────────────────────────────────────────────────────────────────────┘

$ functions call helloStorage -f ../data.json
ExecutionId: a413399d-5fe9-4302-bf4c-90a1d0bd72c0

Cette fonction ne fait que tracer l’événement. Elle ne retourne aucun résultat. Il est possible de consulter les logs avec la commande ci-dessous :

$ functions logs read
2017-10-31T23:38:16.678Z - info: User function triggered, starting execution
2017-10-31T23:38:16.678Z - info: File myfolder/JarwWgSpjca4sUSSYFjP2jmKidH3/myfile.txt metadata updated.
2017-10-31T23:38:16.709Z - info: Execution took 46 ms, user function completed successfully

Conclusion

Rien ne pourra vous fournir une boucle de retour plus rapidement que les tests unitaires. Cependant, les erreurs rencontrées lors de développements d’intégration ne sont pas toutes détectées par ces tests. C’est pourquoi il est intéressant de pouvoir tester rapidement nos fonctions localement.

A noter qu’il est également possible de débugger vos fonctions avec functions debug et functions inspect.

Enfin, si vous utilisez les APIs de Firebase (via les Firebase Functions), il faut (à priori) passer par une autre méthode2.

A vos claviers… Codez, déployez, testez !

  1. En fait, non. Il faut faire un serverless invoke local -f myFunction
  2. Via le Cloud Funtions Shell