A la découverte d'AWS Lambda (2/2)

Résumé des épisodes précédents

Pour ceux qui ont raté le début, RDV sur la première partie !

Objectifs

Le but du jeu de ce nouvel article est le suivant :

  • pousser une photo dans un bucket S3
  • récupérer dans un autre bucket S3 une miniature de cette photo

Pour ce faire, nous allons utiliser des tableaux croisés dynamiques dans Excel, avec des macros VisualBasic dans Access. ...

... Mais bien sûr... Non ! Nous allons utiliser Lambda ! (petite forme ce matin le rédacteur...)



Pas à pas



Environnement de travail

Comme nous l'avons vu dans la première partie, les fonctions Lambda sont des fonctions JavaScript exécutées par Node.js. (scoop : une version Java de Lambda est en préparation...).

Afin de développer notre fonction lambda, il nous faut donc Node.js.

Sous Ubuntu :

sudo apt-add-repository ppa:chris-lea/node.js
sudo apt-get install nodejs npm


Plaçons nous maintenant là où nous souhaitons écrire notre fonction, et installons deux modules Nodes.js :

mkdir ~/lambda
cd ~/lambda
npm install async gm
vim CreateThumbnail.js



Fonction Lambda

Nous allons donc maintenant écrire notre fonction Lambda :

// Chargement des modules Node.js
var async = require('async') ;
var AWS   = require('aws-sdk') ;
var util  = require('util');

// Chargement de ImageMagick, outil qui permet de traiter des images
var gm  = require('gm').subClass({ imageMagick: true }) ;

// Constantes
var MAX_WIDTH  = 100 ;
var MAX_HEIGHT = 100 ;


// Client S3
var s3 = new AWS.S3() ;

// Fonction Lambda
exports.handler = function(event, context)
{
    // Lecture des parametres passés à l'évenement
    console.log("Reading options from event:\n", util.inspect(event, {depth: 5})) ;

    // Bucket S3 source
    var srcBucket = event.Records[0].s3.bucket.name ;

    // Si l'ojbjet (fichiet) comporte des espaces ou des caracteres spéciaux
    var srcKey    = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " ")) ;

    // Bucket S3 de destination
    var dstBucket = srcBucket + "resized" ;

    // Objet de destination
    var dstKey    = "resized-" + srcKey ;


    // Sanity check: On vérifie que la source et la destination ne sont pas identiques
    if (srcBucket == dstBucket)
    {
        console.error("Destination bucket must not match source bucket.") ;
        return ;
    }


    // On vérifie qu'on a bien affaire a une image jpg ou png
    var typeMatch = srcKey.match(/\.([^.]*)$/) ;
    if (!typeMatch)
    {
        console.error('unable to infer image type for key ' + srcKey);
        return;
    }

    var imageType = typeMatch[1];
    if (imageType != "jpg" && imageType != "png")
    {
        console.log('skipping non-image ' + srcKey);
        return;
    }



    // Récupération de la photo originale, transformation et upload vers un autre bucket S3
    async.waterfall(
    [
        function download(next)
        {
            // Téléchargement de l'image depuis S3
            s3.getObject({ Bucket: srcBucket, Key: srcKey }, next) ;
        },

        function tranform(response, next)
        {
            gm(response.Body).size(function(err, size)
            {
                // Calcul du ratio de mise à l'échelle
                var scalingFactor = Math.min(MAX_WIDTH / size.width, MAX_HEIGHT / size.height);
                var width  = scalingFactor * size.width;
                var height = scalingFactor * size.height;

                // Transformation de l'image
                this.resize(width, height).toBuffer(imageType, function(err, buffer)
                {
                    if (err)
                    {
                        next(err);
                    }
                    else
                    {
                        next(null, response.ContentType, buffer);
                    }
                });
            });
        },

        function upload(contentType, data, next)
        {
            // Upload de l'image dans un autre bucket S3
            s3.putObject({ Bucket: dstBucket, Key: dstKey, Body: data, ContentType: contentType }, next);
        }
    ],
    function (err)
    {
        if (err)
        {
            console.error('Impossible de traiter '
                + srcBucket +'/'+ srcKey+' et ou de l\'envoyer vers '
                + dstBucket+ '/'+ dstKey+' a cause de l\'erreur : ' + err);
        }
        else
        {
            console.log('Image traitée ! ' + srcBucket + '/' + srcKey +' envoyée dans ' + dstBucket + '/' + dstKey);
        }

        context.done();
    });
};


Remarques importantes :

  • Le bucket source doit exister
  • Le bucket de destination doit exister. Si le source s'appelle toto, celui de destination doit s'appeler totoresized.



Envoi du code dans Lambda

On considère que le rôle IAM a bien été créé (cf. première partie). On peut donc envoyer notre fonction lambda.

Mais avant, il faut créer notre package sous forme de zip :

zip -r CreateThumbnail.zip ./*


Puis on l'envoie dans Lambda :

aws lambda create-function \
   --region eu-west-1 \
   --function-name CreateThumbnail \
   --zip-file fileb://CreateThumbnail.zip \
   --role role-arn arn:aws:iam::397960517128:role/lambda-exec \
   --handler CreateThumbnail.handler \
   --runtime nodejs \
   --profile adminuser \
   --timeout 10 \
   --memory-size 1024



Configuration de S3 pour interagir avec Lambda

On commence par autoriser le bucket S3 a déclencher la fonction Lambda

aws lambda add-permission \
   --function-name CreateThumbnail \
   --region eu-west-1 \
   --statement-id Id-x \
   --action "lambda:InvokeFunction" \
   --principal s3.amazonaws.com \
   --source-arn arn:aws:s3:::agh-osones \
   --source-account 397960517128 \
   --profile adminuser


Puis, on configure les notifications au niveau du bucket. Ceci se fait via la console. Cf. http://docs.aws.amazon.com/AmazonS3/latest/UG/SettingBucketNotifications.html



Test grandeur nature

Maintenant, si vous envoyez des photos dans le premier bucket, alors, quelques secondes après, vous aurez la version miniature dans le second bucket.

Magique non ?



Alexis GÜNST


Encore un peu de temps ? Lisez nos autres articles :

Vous avez des projets sur AWS ? N'hésitez pas à visiter notre site internet.

Vous avez des questions ? Rejoignez la discussion sur le groupe LinkedIn des Utilisateurs Francophones d'Amazon Web Services (AWS).

Banniere

La discussion continue !

Nous attendons vos questions, remarques & mots doux sur notre Twitter :