lindev.fr

Aller au contenu | Aller au menu | Aller à la recherche

07 fév. 2016

Vagrant, environnement de développement unifié

Vagrant.png Je souhaite proposer aux différents développeurs d'un projet, un moyen simple d'avoir un environnement le plus proche possible du serveur de production, que ce soit au niveau de la distribution, mais également des versions des outils installés ( Php, Mysql MariaDb, MongoDb, python, uwsgi, etc ... ). Le but étant de mettre en place l'environnement en un minimum de temps possible, mais aussi de permettre aux développeurs de rester sur leur système d'exploitation préféré. (Linux Mac ou même Windows) pour développer.

Première approche

Peut être (certainement) dû à l'effet de mode, j'ai commencé mes tests avec Docker. Ce dernier permet ce genre de chose, mais je trouve la façon de faire un peu complexe ( Liaisons entre les conteneurs, maintenabilité de l’ensemble etc ) , juste pour avoir un environnement de dev ! Il est vrai que cette techno est vraiment intéressante, le partage de conteneurs avec les autres utilisateurs, le versionning grâce à l'union FS et tout ça sans exploser la conso d'espace disque.

Plus simple, vagrant

Puis un ami m'a parlé de Vagrant, une techno que je n'avais pas encore étudié plus que ça. Pour résumer, il n'y a rien de magique, c'est "juste" un écosystème de gestion de VMs , basé sur virtualbox, qui permet de créer/manager des VMs à partir de systèmes de bases disponibles sur le net ( catalogue Vagrant Boxes ) et surtout de provisionner ces VMs.

C'est là que ça devient intéressant, on a donc un système de gestion de VMs compatible Linux,Windows,Mac, permettant une gestion de VMs très efficace, start, stop, freeze, resume, reload etc ... mais surtout, vous permet de monter une VMs configurée selon vos souhaits et ce de plusieurs façons.

Testons avec un simple environnement LAMP

Je vais volontairement aller vite sur les options disponibles, la doc en ligne de Vagrant étant extrêmement clair, je ne vais pas la copier/coller ici

Commencez par installer Vagrant ( pas besoin d'explication pour cette étape triviale )

Système de base

Nous allons travailler sur une Debian Jessie . Commençons par créer un répertoire pour notre projet ( je le nomme lamp )

mkdir lamp && cd lamp
vagrant init debian/jessie64

Dans notre répertoire lamp, nous trouvons maintenant un fichier de configuration de notre environnement nommé Vagrantfile. Nous allons bientôt mettre les mains dedans .

Capture_d_e_cran_2016-02-13_a__14.53.05.png

Le téléchargement est terminé ? oui ? très bien lançons notre "machine" et allons faire un tour dedans .

Lancer la VM et tour du propriétaire

vagrant up

Une fois la machine lancée, prenons la main en ssh

vagrant ssh

Vous voilà connecté à notre machine. Le système est nu. Avez-vous remarqué au lancement de la vm, ces deux lignes :

==> default: Installing rsync to the VM...
==> default: Rsyncing folder: /Users/moi/lamp/ => /vagrant

Par défaut, vagrant synchronise le répertoire local ( dans notre cas /Users/moi/lamp/ ) avec le répertoire /vagrant présent à la racine du système de fichiers de la VM.
Nous pouvons bien entendu ajouter d'autres répertoires ( voir le fichier de configuration Vagrantfile ligne: config.vm.synced_folder "../data", "/vagrant_data" ).

Retournons sur notre machine hôte ( Cmd + D )

LAMP

Linux ( Ok ça c'est fait ), Apache ( à installer ), Mysql/MariaDB ( à installer ), Php ( à installer ).
Bon on sait ce qu'il nous reste à faire. Mettons les quelques lignes nécessaires à l'installation de ces derniers dans un script bash. Éditons donc dans le répertoire lamp, un fichier que nous nommerons bootstrap.sh

Voici le contenu:

#!/usr/bin/env bash

#Login par defaut : root et pwd : rootpass ( à modifier bien évidemment )
debconf-set-selections <<< 'mysql-server-5.5 mysql-server/root_password password rootpass'
debconf-set-selections <<< 'mysql-server-5.5 mysql-server/root_password_again password rootpass'

apt-get update
apt-get install -y apache2
apt-get install -y mysql-server
apt-get install -y php5 php5-mysql php-pear


if ! [ -L /var/www ]; then
  rm -rf /var/www
  ln -fs /vagrant /var/www
fi

Éditons ensuite le fichier Vagrantfile, et en dessous de la ligne config.vm.box = "debian/jessie64" ajouter :

config.vm.provision :shell, path: "bootstrap.sh"

Ne reste plus qu'à provisionner la VM

vagrant reload --provision

Capture_d_e_cran_2016-02-13_a__14.55.18.png

Vous voilà avec un lamp de base, mais comment communiquer facilement avec ?

Le réseau

Il y a plusieurs solutions selon votre besoin.

  • Natter un port de votre machine hôte vers la VM
  • Créer un bridge avec une des vos interfaces pour que la VM soit présente comme n'importe quel autre poste sur votre réseau local
  • Créer un réseau privé permettant à la machine hôte d’accéder à la VM , tout en restant inaccessible depuis l'extérieur pour les autres postes ( solution retenu dans mon cas ).

Éditons le fichier de configuration pour activer ce mode. dé-commenter/compléter cette ligne :

config.vm.network "private_network", ip: "192.168.33.10"

Relançons notre vm

vagrant reload

Testons notre serveur Apache, ouvrez un navigateur, et entrez l'ip de votre vm : http://192.168.33.10

Et là ... vous avez un magnifique "404 not found" !!!.
Normal, le vhost par défaut d'apache cherche ses sources dans le répertoire /var/www/html . et si vous avez bien observé, dans le fichier bootstrap.sh, nous avons ces lignes :

if ! [ -L /var/www ]; then
  rm -rf /var/www
  ln -fs /vagrant /var/www
fi

Qui a pour but de mettre le répertoire "vagrant" ( partagé entre l'hôte et la vm ) accessible ( via un lien symbolique ) vers /var/www . Il nous suffit donc de créer un répertoire nommé html dans notre répertoire de projet, lamp ( sur la machine hôte ).

Donc, sur notre machine hôte, dans notre répertoire lamp :

mkdir html && cd html
echo '<?php echo phpinfo();' > index.php

Relançons notre vm

vagrant reload

Maintenant allons rafraichir notre navigateur . Vous devriez avoir la belle page de phpinfo qui s'affiche . Capture_d_e_cran_2016-02-13_a__13.54.36.png

NFS

C'est magnifique, mais si vous ajoutez des fichiers depuis la machine hôte dans le répertoire html, ces derniers ne seront pas accessibles ( synchronisés ) sur la VM. Il faudra pour celà lancer la commande

vagrant rsync-auto

Pas très pratique. Je me suis donc tourné vers le protocole NFS qui est pris en charge en natif avec vagrant ( attention.. ne fonctionne pas avec Windows ).
Éditons notre fichier Vagrantfile et ajoutons ces quelques lignes avant de relancer notre vm

  config.vm.synced_folder ".", "/vagrant",
    :nfs => true

Relançons la VM

vagrant reload

Vagrant va vous demander le mot de passe sudo, afin de modifier pour le fichier /etc/exports.
Afin que le mot de passe ne soit pas demandé à chaque démarrage, vous pouvez soit:

  • Ajouter des règles dans le fichier de config sudo ou ...
  • Renseigner une bonne fois pour toute votre fichier exports et indiquer à vagrant qu'il n'a rien à faire . ( voir les options NFS ).

Conclusion

Voilà pour le premier tour d'horizon de Vagrant, nous avons dans nos main un système de gestion de VM bien ficelé, doté de nombreuses autres options, ( snapshots/restauration, share, ... ) je ne peux que vous conseiller d'aller faire un tour sur le site officiel pour découvrir les différentes options qui s'offrent à vous. Enfin, pour gérer vos environnements, je vous conseil de versionner votre projet ( dossier lamp dans notre exemple ) avec Git par exemple, car c'est grâce aux fichiers de ce répertoire que vous serez en mesure de remonter/partager une VM configurée à l'identique sans aucun effort et en un minimum de temps.

26 janv. 2016

Php7, installation/compilation ... et rollback

Les articles sur Php7 ne cessent de culpabiliser les développeurs sous php5.x .. , il est plus .. performant, rapide, des nouvelles fonctionnalités qui se rapprochent de python ( ok ... ça c'est un troll ), il est ... bref .. il faut le tester pour se faire une idée, et surtout vérifier si les projets tournent dessus dans devoir faire face à une horde de logs de type Notice , Deprecated, ou encore pire .. Fatal Error

On fait quoi

Vous avez de beaux projets qui tournent sous une version de Php stable 5.X et .. malgré l'adage .. on ne change pas quelque chose qui marche, vous trépigniez d'impatience de voir ce qu'ils donnent avec la dernière monture de la société Zend .

Ok .. mais sans tout casser s'il vous plait.

On va donc voir comment installer ( par compilation ) php7 afin de pouvoir l'utiliser (ou pas) projet par projet. Ou plutôt vhost par vhost .

Installer PHP7

J'ai pris comme base, un serveur tout neuf .. à poil .. Debian Jessie sur la plateforme Google.

Commençons par installer quelques paquets nécessaires pour le bon déroulement de cet article ( et de la compilation de php ).

sudo apt install vim bzip2 build-essential libxml2-dev

Puis classiquement nous téléchargeons les sources de php

cd /usr/local/src
wget http://us2.php.net/distributions/php-7.0.2.tar.bz2
tar xjf php-7.0.2.tar.bz2
cd php-7.0.2

Nous allons créer un petit fichier de config pour notre version de php ( activer ou pas certains modules, définir l'emplacement de php... )

vim myConfig

Voici le contenu ( nous allons l'installer dans /usr/local/php7 )

#!/bin/sh

mkdir -p /usr/local/php7

export OPTIM=-02
./configure --prefix=/usr/local/php7 \
        --enable-fpm
        --with-xsl \
        --enable-soap \
        --with-gettext \
        --enable-mbstring --with-mbstring=all \
        --disable-debug \
        --enable-memory-limit \
        --enable-ftp \
        --with-mcrypt \
        --enable-zip \
        --enable-calendar \
        --enable-exif \
        --enable-pdo \
        --with-pdo-mysql \
        --with-mysql \
        --with-pdo-sqlite \
        --with-sqlite \
        --with-zlib \
        --with-jpeg-dir \
        --with-gd \
        --with-freetype-dir=DIR \
        --with-imap-ssl \
        --with-kerberos \
        --with-imap \
        --with-curl \
        --enable-bcmath \

Rendons ce script exécutable et ... c'est parti

chmod +x myConfig
./myConfig

Si tout ce passe bien, nous pouvons lancer les deux dernières commandes pour installer php7

make 
sudo make install

Configuration

Et bien oui .. vous ne passez pas par les paquets de la distrib .. vous avez donc un poil plus de boulot . Commençons par récupérer le configuration par défaut ( vous 'adapterez au besoin plus tard, ce point sort du cadre de cet article )

cd /usr/local/php7/etc/
cp php-fpm.conf.default php-fpm.conf

Malgré tout, vérifiez bien à dé-commenter la ligne suivante ( vous verrez plus tard pourquoi )

pid = run/php7-fpm.pid
Pools

Enfin il nous faut également créer le pool par défaut de php-fpm

cd php-fpm.d
cp www.conf.default www.conf

Idem il nous faut dé-commencter/éditer/modifier deux trois lignes ( vous pouvez reprendre les valeurs ci-dessous, si vous avez une installation de base . Notamment pour l'utilisateur utilisé pour interpréter vos scripts php )

user = www-data
group = www-data
listen = /var/run/php7-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
php.ini

Aller .. encore un fichier de config à gérer.
On va prendre la version "prod" disponible dans les sources que nous avons téléchargées .

cp /usr/local/src/php-7.0.2/php.ini-production /usr/local/php7/lib/php.ini

Ajoutons le module opcache dans la foulé

vim /usr/local/lib/php.ini

Ajouter en fin de fichier

zend_extension=opcache.so

Ready ! ? ... non pas encore

Lancer php-fpm au démarrage

Commençons par créer notre fichier d'init ( modèle dispo dans les sources également. Je précise ce point car certains articles semblent sortir ces scripts de leur chapeau, sans explication ! )
Les modèles se trouvent dans : /usr/local/src/php-7.0.2/sapi/fpm

vim /etc/init.d/php7-fpm

Le contenu :

#! /bin/sh
### BEGIN INIT INFO
# Provides:          php7-fpm
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts php7-fpm
# Description:       starts the PHP FastCGI Process Manager daemon
### END INIT INFO
php_fpm_BIN=/usr/local/php7/sbin/php-fpm
php_fpm_CONF=/usr/local/php7/etc/php-fpm.conf
php_fpm_PID=/usr/local/php7/var/run/php7-fpm.pid
php_opts="--fpm-config $php_fpm_CONF"
wait_for_pid () {
        try=0
        while test $try -lt 35 ; do
                case "$1" in
                        'created')
                        if [ -f "$2" ] ; then
                                try=''
                                break
                        fi
                        ;;
                        'removed')
                        if [ ! -f "$2" ] ; then
                                try=''
                                break
                        fi
                        ;;
                esac
                echo -n .
                try=`expr $try + 1`
                sleep 1
        done
}
case "$1" in
        start)
                echo -n "Starting php-fpm "
                $php_fpm_BIN $php_opts
                if [ "$?" != 0 ] ; then
                        echo " failed"
                        exit 1
                fi
                wait_for_pid created $php_fpm_PID
                if [ -n "$try" ] ; then
                        echo " failed"
                        exit 1
                else
                        echo " done"
                fi
        ;;
        stop)
                echo -n "Gracefully shutting down php-fpm "
                if [ ! -r $php_fpm_PID ] ; then
                        echo "warning, no pid file found - php-fpm is not running ?"
                        exit 1
                fi
                kill -QUIT `cat $php_fpm_PID`
                wait_for_pid removed $php_fpm_PID
                if [ -n "$try" ] ; then
                        echo " failed. Use force-exit"
                        exit 1
                else
                        echo " done"
                       echo " done"
                fi
        ;;
        force-quit)
                echo -n "Terminating php-fpm "
                if [ ! -r $php_fpm_PID ] ; then
                        echo "warning, no pid file found - php-fpm is not running ?"
                        exit 1
                fi
                kill -TERM `cat $php_fpm_PID`
                wait_for_pid removed $php_fpm_PID
                if [ -n "$try" ] ; then
                        echo " failed"
                        exit 1
                else
                        echo " done"
                fi
        ;;
        restart)
                $0 stop
                $0 start
        ;;
        reload)
                echo -n "Reload service php-fpm "
                if [ ! -r $php_fpm_PID ] ; then
                        echo "warning, no pid file found - php-fpm is not running ?"
                        exit 1
                fi
                kill -USR2 `cat $php_fpm_PID`
                echo " done"
        ;;
        *)
                echo "Usage: $0 {start|stop|force-quit|restart|reload}"
                exit 1
        ;;
esac

On le rend exécutable et crée les liens de démarrage

sudo chmod 755 /etc/init.d/php7-fpm
insserv php7-fpm

Puis on crée le fichier pour initd

vim /lib/systemd/system/php7-fpm.service

Voici le contenu

[Unit]
Description=The PHP 7 FastCGI Process Manager
After=network.target

[Service]
Type=simple
PIDFile=/usr/local/php7/var/run/php7-fpm.pid
ExecStart=/usr/local/php7/sbin/php-fpm --nodaemonize --fpm-config /usr/local/php7/etc/php-fpm.conf
ExecReload=/bin/kill -USR2 $MAINPID

[Install]
WantedBy=multi-user.target

On active le service

systemctl enable php7-fpm.service
systemctl daemon-reload
systemctl enable php7-fpm

On lance ENFIN php7-fpm

service php7-fpm start

Petit check

ps -xa | grep php-fpm

qui doit donner un truc du genre:

  861 ?        Ss     0:00 php-fpm: master process (/usr/local/php5/etc/php-fpm.conf)                
  862 ?        S      0:00 php-fpm: pool www                                                         
  863 ?        S      0:00 php-fpm: pool www

Et voilà, vous avez la toute dernière monture de php disponible .
Ok mais ... comment l'utiliser dans le vhost.

Nginx

Installons rapidement Nginx

sudo apt-get install nginx

éditons le vhost par défaut pour prendre en compte notre installation de php

vim /etc/nginx/sites-enabled/default

Et on dé-commente pour avoir ceci

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
        #
        #       # With php5-cgi alone:
        #       fastcgi_pass 127.0.0.1:9000;
        #       # With php5-fpm:
                fastcgi_pass unix:/var/run/php7-fpm.sock;
        }

On demande à Nginx de passer les requêtes pointant les fichiers php au socket : /var/run/php7-fpm.sock

Test

Un rapide script de test ...LE phpinfo() :)

vim /var/www/html/test.php

Puis on colle le code suivant

<?php

echo phpinfo();

Rendez-vous sur l'url de votre serveur http://x.x.x.x/test.php

Et là ... victoire ..

Capture_d_e_cran_2016-01-26_a__22.00.30.png

Conclusion

Vous avez compris comment ajouter une version de php à votre serveur ( en plus de la version des dépôts ). Vous pouvez comme celà installer n'importe quelle version de php et utiliser l'une ou l'autre au besoin, en spécifiant le bon socket dans votre vhost .

N'hésitez pas à laisser un commentaire si vous avez des questions .

Bonne nuit.

23 janv. 2016

Python pip et son cache local

Afin d'éviter de re-télécharger les paquets pour chaque projet nous allons voir ce petit truc extra simple qui est d'activer le cache local de pip.
Le principe est simple, la première fois que vous installez un paquet, ce dernier est téléchargé, puis mis en cache.
La seconde fois ( s'il s’agit de la même version ), pip va simplement l'installer depuis le cache.

Ok, ça ne va pas vous changer la vie, mais moi je trouve ça sympa à utiliser, surtout lorsque vous devez installer des gros paquets avec une connexion d’hôtel faiblarde, ça peut vous faire gagner de précieuses minutes .

Configuration de PIP

Commençons par éditer le fichier de configuration de pip

vim ~/.pip/pip.conf

Ajouter dans la section "global":

[global]
download-cache=/usr/local/pip/cache

enfin on donne les droits d'écriture

mkdir -p /usr/local/pip/cache
chmod -R 755 /usr/local/pip/cache

Deuxième méthode un peu plus courte

Simplement ajouter dans votre fichier de profil : ~/.bash_profile

export PIP_DOWNLOAD_CACHE=$HOME/.pip_download_cache

Voilà pour ce micro billet.

Bon weekend .

06 déc. 2015

Réference commande dans prestashop [1.6.x]

Prestashop et ses bizarreries

presta.png Je travaille quotidiennement sur Prestashop, car à mon grand regret, il n’existe pas aujourd'hui, beaucoup d'alternatives qui proposent un produit :

  • libre
  • fonctionnel et évolutif
  • supporté par une communauté active
  • avec une documentation complète ( pour l'utilisateur et le développeur )
  • développé en python sous Django par exemple .... humm ce dernier point sent le troll ;)

Bref dans ma grande frustration personnel, je dois tout de même admettre que Pretashop offre une solution complète performante et fonctionnelle pour l'utilisateur final, mais dés qu'il s'agit de mettre les mains dans le code, la documentation et plus que maigre !! Et il faut investiguer, poser des questions qui bien souvent dés que ces dernières sont un peu techniques, restent sans réponse sur le forum officiel :( !!
Nous ( développeurs ) sommes doc seuls devant le code et devons faire preuve de patience pour apprivoiser le code sans documentation approfondie. Ce manque d'information est selon moi voulu pour pousser le système économique qui tourne autour de Prestashop ( modules payants pour ajouter des fonctionnalités qui parfois devraient être disponibles par défaut dans la solution ).

D'autant que certains choix fait par Prestashop semblent parfois peu efficaces, voir illogiques.
Prenons le cas de la référence commande, qui est sous la forme d'une chaine de caractères aléatoire et unique .
Pour une boutique qui n'a que quelques commandes / jour cela ne pose aucun problème (quoique), mais pour un flux plus important, cette référence qui sera utilisée par vous et vos clients n'est absolument pas pratique.

Prenons un exemple. Si je vous donne la référence : QRFRUMBMF vous êtes bien avancé, ce n'est pas mnémotechnique et ne vous apprend rien sur la commande.

Alors que ( par exemple ) une référence comme : 20151206-225 qui est la concaténation de :

  • 2015 : L'année de la commande
  • 12 : le mois de la commande
  • 06 : le jour de la commande
  • 225 : id unique de la commande

Aurait était un choix beaucoup plus pertinent pour tout le monde ( client et marchand ).

Posez la questions sur le forum et vous aurez avec un peu de chance, une âme charitable qui va vous guider pour effectuer ce changement. Dans le cas contraire, votre demande tombera dans les abîmes avec les autres messages du genre restés sans réponse .

Aller au boulot, voyons comment faire ce changement dans le code.

La surcharge

Je ne vais pas détailler comment surcharger les différents éléments de Prestashop mais utiliser cette technique pour deux fichiers.

Comme expliqué pus haut, prestashop est bien fini et permet de tout "surcharger" pour vous permettre de modeler/modifier les fonctionnalités de base de l'outil à volonté.

Nous allons donc surcharger dans un premier temps les fichiers suivants :

  1. classes/order/OrderHistory.php
  2. classes/PaymentModule.php

OrderHistory.php

Cette classe gère l'historique des commandes et de ce fait, gère également l’expédition des emails liés à ces états. Mails dans lesquels est rappelé la référence de la commande.

Nous allons donc créer le fichier de surcharge override/classes/order/OrderHistory.php , copier la méthode d'origine addWithemail et enfin le modeler à notre sauce.

ce qui donne :

<?php
class OrderHistory extends OrderHistoryCore{
    
    /**
     * @param bool $autodate Optional
     * @param array $template_vars Optional
     * @param Context $context Optional
     * @return bool
     */

	public function addWithemail($autodate = true, $template_vars = false, Context $context = null){
            if (!$context)
                $context = Context::getContext();

            $order = new Order( (int)$this->id_order );
            $date = new DateTime($order->date_add);

            $data = array(
                '{id_order}' => $date->format('Ymd').'-'.$this->id_order
            );

            if ($template_vars){
                $data = array_merge($data, $template_vars);
            }

            return parent::addWithemail($autodate, $data, $context);
    }
}

Ne vous reste plus qu'à remplacer dans vos mails la balise {order_name} par {id_order}

PaymentModule.php

Cette classe est utilisée à la confirmation de commande et s'occupe également de l'expédition du mail de confirmation de commande ( pourquoi avoir séparé ce mails des autres ? ). Bref, nous allons donc également surcharger cette classe et plus précisément la méthode validateOrder.

Nous allons donc créer le fichier de surcharge override/classes/PaymentModule.php , copier la méthode d'origine validateOrder() et enfin le modeler à notre sauce.

class PaymentModule extends PaymentModuleCore{
    
        /**
     * Validate an order in database
     * Function called from a payment module
     *
     * @param int $id_cart
     * @param int $id_order_state
     * @param float   $amount_paid    Amount really paid by customer (in the default currency)
     * @param string  $payment_method Payment method (eg. 'Credit card')
     * @param null    $message        Message to attach to order
     * @param array   $extra_vars
     * @param null    $currency_special
     * @param bool    $dont_touch_amount
     * @param bool    $secure_key
     * @param Shop    $shop
     *
     * @return bool
     * @throws PrestaShopException
     */
    public function validateOrder($id_cart, $id_order_state, $amount_paid, $payment_method = 'Unknown',
        $message = null, $extra_vars = array(), $currency_special = null, $dont_touch_amount = false,
        $secure_key = false, Shop $shop = null)
    {

        ...
        ...
        ...
        //contenu de la méthode d'origine ( copier - coller de la methode validateOrder dans le fichier classes/PaymentModule.php )
        ...
        ...
        ...
        // Avant la liste des paramétres, o va formater la date de la commande
        $dateOrder = new DateTime($order->date_add);
        ....
        //Vers la ligne 622 dans la liste des paramètres balancés au template du mail, ajouter la ligne 
        ...
        '{id_order}' => $dateOrder->format('Ymd').'-'.$order->id
        ...
        ...
        ...
    }
}

Voilà , ne vous reste plus qu'à mettre à jour le mail orderConf pour remplacer {order_name} par {idorder}

vider le cache

Afin que les surcharges soient bien prises en compte, vous devez supprimer le fichier de cache suivant :

cache/class_index.php

Conclusion

Voilà comment changer une partie de prestashop sans toucher au code d'origine.
Si ce que vous avez fait ne fonctionne pas ou casse quelque chose, il vous suffit alors de supprimer ces fichiers ( dans le répertoire Override ) et remettre les balises dans les mails.
Pas d'inquiétude donc lancez-vous.

01 oct. 2015

EC2, ajouter un compte utilisateur avec certificat .pem

A la création d'une instance EC2, vous avez un compte par défaut ( ec2-user ou admin selon la distrib ) .
Ce compte vous permet d’accéder et d'administrer votre instance via une connexion SSH , avec une authentification par certificat ( que vous avez préalablement téléchargé à la création de l'instance ).

On va voir ici comment ajouter un compte utilisateur avec un nouveau certificat pour qu'il puisse également se connecter sur cette instance EC2.

Première étape le certificat

Nous allons commencer par créer ce nouveau certificat via l'interface d'amazon ( la clef privée )

Clef privée ( .pem )

C'est très simple, suivez le guide...

Dirigez-vous vers le service EC2 Capture_d_e_cran_2015-10-01_a__09.22.02.png

Gestion des clef Capture_d_e_cran_2015-10-01_a__09.22.13.png

Créer un clef Capture_d_e_cran_2015-10-01_a__09.22.23.png

Vous devrez alors télécharger cette nouvelle clef ( ici elle s'appelle lindev.pem )

Clef public

Coté serveur, nous allons devoir spécifier la clef publique liée à cette clef privée pour le nouvel utilisateur.
Commençons donc par récupérer cette clef publique.

Changez les droits du certificat
chmod 400 lindev.pem
Générer les clef publique à partir de la clef privée
ssh-keygen -y

Cette commande va vous demander le chemin vers la clef privée. Suite à quoi il va vous sortir la clef public .
Par exemple :

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCISBb/pQYoSz1tm2mWrBBK8Yfs+8z2rdcbYhCIgz7oLE2YNrT/cs8D9WXduTezS1p/SjdfA4zhXSJNBvjNP0A0M2s1Mj3E+edpENhsYrLBxDc1F60CQ4be0hLICX2e2mDzPYi7sLgAzAdVI67Eo2Zmj/0QYwckS4JgFl7JXedV8Fz4D2gm8xjByxbtlsPhG84Y9wq7GGmtHuaHzi+H+RVS1hlDZIH8QKwX9U5eWJY3BaQjXpwnorNvn2OrlO9wmTE+22A7F0vF8zsRia+t5TwqPaVYmfX7DN5zqBMcd8tQC1LdYS0b+h6+fLAkUdaqQ0kXGm1FCxYH00hTYNS+JV/V

Gardez cette clef nous allons en avoir besoin bientôt coté serveur.

Coté serveur

Nous sommes maintenant sur notre instance EC2 avec l'utilisateur par defaut, nous allons donc créer notre second utilisateur et lui transmettre notre clef publique.

Création de l'utilisateur

root@ip-172-41-26-237:/home/admin# adduser lindev
Adding user `lindev' ...
Adding new group `lindev' (1001) ...
Adding new user `lindev' (1001) with group `lindev' ...
Creating home directory `/home/lindev' ...
Copying files from `/etc/skel' ...
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully
Changing the user information for lindev
Enter the new value, or press ENTER for the default
	Full Name []: Lindev
	Room Number []: 
	Work Phone []: 
	Home Phone []: 
	Other []: 
Is the information correct? [Y/n] Y

Voilà mon utilisateur créé, ne nous reste plus qu'à lui créer sa configuration ssh

ssh

Création du répertoire contenant la configuration ssh de l'utilisateur

sudo -s
su - lindev
mkdir /home/lindev/.ssh
chmod 700 /home/lindev/.ssh

Création du fichier contenant les clefs publiques

touch /home/lindev/.ssh/authorized_keys
chmod 600 /home/lindev/.ssh/authorized_keys

Editer le fichier authorized_keys et y coller votre clef public .

vim /home/lindev/.ssh/authorized_keys

je colle donc :

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCISBb/pQYoSz1tm2mWrBBK8Yfs+8z2rdcbYhCIgz7oLE2YNrT/cs8D9WXduTezS1p/SjdfA4zhXSJNBvjNP0A0M2s1Mj3E+edpENhsYrLBxDc1F60CQ4be0hLICX2e2mDzPYi7sLgAzAdVI67Eo2Zmj/0QYwckS4JgFl7JXedV8Fz4D2gm8xjByxbtlsPhG84Y9wq7GGmtHuaHzi+H+RVS1hlDZIH8QKwX9U5eWJY3BaQjXpwnorNvn2OrlO9wmTE+22A7F0vF8zsRia+t5TwqPaVYmfX7DN5zqBMcd8tQC1LdYS0b+h6+fLAkUdaqQ0kXGm1FCxYH00hTYNS+JV/V

On enregistre et on quitte.

Tests

Il ne vous reste plus qu'à tester a connexion avec votre nouvel utilisateur .

ssh -i "lindev.pem" lindev@52.29.115.207

Normalement vous devriez être connecté sans avoir à rentrer le moindre mot de passe, en utilisant le certificat Amazon.

14 juil. 2015

Monter un Bucket S3 sur un serveur Débian

S3, espace de stockage illimité chez Amazon avec des tarifs défiant toute concurrence. Qui en plus de son prix, offre des options plus qu’intéressantes...

  • chiffrement des données
  • versioning automatique
  • rotation ou mise en glacier des vielles versions ..


Etc ...
Je ne vais pas détailler ces options dans ce billet, mais juste expliquer comment monter un Bucket S3 ( répertoire S3 ) sur une machine / serveur linux afin d'y accéder comme un simple montage réseau . Nous verrons également comment le monter automatiquement via une simple ligne dans le fstab.

Prérequis

Nous allons utiliser fuse pour monter ce bucket, et l'authentification peut être chiffrée selon vos options, nous allons installer tout ce qu'il nous faut...

sudo apt-get install build-essential git libfuse-dev libcurl4-openssl-dev libxml2-dev mime-support automake libtool fuse-utils pkg-config libssl-dev

S3fs-fuse

Maintenant, S3 + fuse n'étant pas un "système de fichiers" conventionnel, nous allons utiliser l'outil s3fs-fuse disponible sur un dépôt Gît

cd /usr/local/src/
git clone https://github.com/s3fs-fuse/s3fs-fuse
cd s3fs-fuse/
./autogen.sh
./configure --prefix=/usr --with-openssl 
make
sudo make install

Authentification S3

Sauf si votre serveur se trouve sur la plateforme Amazon et que vous lui avez donné des droits spécifiques (S3) à sa création, il vous faudra obligatoirement préciser le couple "Access Key / Secret Key" récupéré sur la plateforme Amazon.
Personnellement, je donne à mes utilisateurs un minimum de droits. Dans mon exemple donc, mon utilisateur fictif "John Doe" aura uniquement accès à son bucket, qui sera appelé "jdbucket".
Nous allons donc éditer le fichier qui va lister les authentifications passwd-s3fs.

sudo vim /etc/passwd-s3fs

Le contenu devra respecter la forme suivante bucketName:accessKeyId:secretAccessKey :

jdbucket:AKIAIOSFODNN7EXAMPLE:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Nous allons maintenant retirer un maximum de droits au fichier passwd-s3fs

sudo chmod 600 /etc/passwd-s3fs

Montage

Ne nous reste plus qu'à monter notre bucket jdbucket . Disons sur le répertoire /var/mntJdBucket

Commençons par créer le répertoire de montage.

sudo mkdir /var/mntJdBucket; sudo chmod 777 /var/mntJdBucket

Testons le montage avant de la mettre dans le fstab

/usr/bin/s3fs jdbucket /var/mntJdBucket -ouse_cache=/tmp,passwd_file=/etc/passwd-s3fs

Note: Spécifier le fichier de mot de passe S3 n'est pas obligatoire.
Si tout va bien .. vous voilà connecté à votre S3 !
Ne vous faite pas berner par le débit ( important ), ici le débit apparent sera celui de votre disque dur, car l'option use_cache=/tmp utilise un répertoire local comme cache entre votre système et le S3.

Fstab

Ne nous reste plus qu'à configurer notre montage dans le fstab, afin que le montage se fasse à chaque démarrage.

sudo vim /etc/fstab


Voici la ligne qu'il faut ajouter

#Fuse
s3fs#jdbucket	/var/mntJdBucket	fuse	rw,_netdev,use_cache=/tmp,allow_other,passwd_file=/etc/passwd-s3fs	0	0

Au niveau des options, du classique, droits en lecture et écriture pour les utilisateurs, attente de la connexion réseau pour tenter de monter le bucket, configuration du répertoire de cache et du fichier de mot de passe ( toujours facultatif )

Conclusion

Nous avons maintenant fait le tour du montage S3 sur une machine debian, rien de bien méchant. Cependant, suite à mes tests, je conseille d'utiliser les commandes s3cmd pour envoyer de gros fichiers ( plusieurs Gb ) celle-ci étant plus adaptée.

N'hésitez pas si vous avez des questions.

Ch.

27 juin 2015

Prestashop - Surcharger le code d'un module

Prestashop permet dans sa version actuelle (1.6) de surcharger à peu prés .. tout !
Ce qui est vraiment pratique pour modeler votre site e-commerce comme vous l'entendez sans toucher au cœur du code. Ce qui vous permettra ( dans une certaine mesure ) de pouvoir mettre à jour prestashop sans devoir réécrire vos fonctionnalités spécifiques ou votre thème.

Mais il y a les modules

Une autre des force de cet outil e-commerce, ce son ses modules, très nombreux qui permettent d'ajouter des fonctionnalités de façon .. "plug and play", activer désactiver etc .. super !!

Seulement dans la documentation, impossible de trouver le moyen de surcharger un module existant.( je parle de la surcharge de la fonctionnalité apportée par ce module, les templates associés sont surchargeable de façon classique )
jusqu'à maintenant, si vous souhaitiez changer le fonctionnement d'un module, vous aviez le choix entre :

  1. Toucher directement au code du module ( prochaine mise à jour .. et hop ... vous devez recommencer )
  2. Copier le module pour le dupliquer à votre sauce ( Assez lourd )

La signature à utiliser

Et bien figurez vous que c'est possible .. si si ..

Note : uniquement à partir de la v1.6.0.11 ( merci à ChDUP pour cette précision )
Pour surcharger une classe, vous utilisez l'écriture suivante :

class [className sans Core] extends [className]Core

dans le répertoire override/ ou modules/monModule/override/

Et bien pour surcharger le code d'un module, il vous faut utiliser l'écriture suivante

class [classNameModule]Override extends ClassNameModule

Il suffit donc d'utiliser la chaine "Override" dans le nom de votre classe qui va surcharger le module souhaité .
Par contre, il faut placer votre code dans le répertoire

override/modules/moduleName/

L'exemple qui va bien

Prenons le cas du module BlockPaymentLogo disponible par défaut, qui permet d'afficher les logis de paiement sur une colonne de votre home.
Moi je souhaite les afficher .. dans le pied de page "Footer", ce module n'a pas prévu ce cas, est n'est donc pas enregistré au bon Hook ( displayFooter ) . Bref impossible de le faire en natif .

Surchargeons

Nous allons commencer par créer le répertoire du même nom que celui d'origine

override/modules/blockpaymentlogo/

Puis créer le code de surcharge que voici.

if (!defined('_PS_VERSION_'))
    exit;

class BlockPaymentLogoOverride extends BlockPaymentLogo
{


    public function install(){

        if( parent::install() ){
            return $this->registerHook('displayFooter');
        }else{
            return false;
        }

    }

    public function hookDisplayFooter($params)
    {
        return $this->hookLeftColumn($params);
    }

}

Petites explications,

  • on commence par déclarer notre classe comme il se doit ( en ajoutant Override, et en héritant de la classe du module d'origine )
  • Puis j'ajoute dans le constructeur l'association du module dans le hook displayFooter
  • Et enfin, je définis le code spécifique à ce hook ( ici le code sera le même que sur le hook leftcolumn, donc je ne réécris pas le code j'appelle celui du hook leftcolumn )

Ne reste plus qu'à réinitialiser le module .. et voilà les logos qui apparaissent dans mon footer .

Restez informé

inscrivez vous à la newsletter pour recevoir les nouveaux billets par mail. ( formulaire en haut à droite )

25 mai 2015

Gérer les Bounces avec Amazon

Ce billet va présenter deux outils Amazon autour d'un exemple concret, la gestion des Bounces en utilisant Amazon en relais pour vos mails.

Bounces

Lorsque vous envoyez une "newsletter" à de nombreuses adresses mails, il y a ce que l'on appel les "BOUNCES" , qui traduit donne "rebonds". Il s'agit en fait du retour des mails qui partent vers une adresse qui ne fonctionne pas. Il y a plusieurs raisons qui peuvent engendrer un tel rebond, certaines raisons sont temporaires, d'autres définitives. Par exemple :

  • Le destinataire n’existe pas ( définitif )
  • Boite pleine ( temporaire )
  • Erreur Serveur ( temporaire )
  • Plainte ( définitif )
  • Pas de réponse ( temporaire )
  • Pas de résolution DNS ( définitif )

Pourquoi traiter ces rebonds ?

Tout est une histoire de réputation, en effet ne pas traiter ces rebonds et les laisser s'augmenter à chaque newsletter, peut tout doucement vous faire passer pour un "spammeur", d'autant que nous utilisons le relais mails Amazon ( service SES ) qui va réduire votre capacité à envoyer des mails si le nombre de bounces augmente sans action de votre part.

Deuxième raison, financière, pourquoi continuer à envoyer et payer des mails à des adresses qui n’existent pas ! ( ok le cout du mail est ridicule avec Amazon, mais c'est une question de principe )

Comment les gérer ?

Souvent, vous définissez une adresse de retour via ue entête spécifique, dédiée au retour "système" ( Entête : return-path ), ainsi les bounces atterrissement dans cette boite mail dédiée et c'est à vous de les traiter. ( les outils des mailing intègrent souvent une fonction de récupération des bounces par une lecture de boite mail en pop ).

Cependant, Amazon permet d'aller un peu plus loin grâce à son service de notification SNS.
Le principe est simple, lorsque Amazon reçoit un "Bounce" il va transmettre un message de notification dans le topic associé . A ce "topic" nous allons lui associer une "inscription" ( qui est en fait une action à lancer lorsque qu'un "message" arrive sur ce topic ). Dans notre cas, ce sera une simple requête HTTP avec dans son corps les informations du Bounce en format JSON.

Création du Topic

Depuis l'interface Amazon SNS, vous créez un nouveau "topic"

Capture_d_e_cran_2015-05-24_a__11.04.03.png

On va le nommer Bounces

Capture_d_e_cran_2015-05-24_a__11.04.33.png

Puis nous allons créer une "subscription"

Capture_d_e_cran_2015-05-24_a__11.04.57.png

On va donc choisir le protocole HTTP et dans le champ "endpoint", nous mettons l'url qui pointe sur notre script décrit plus bas ( exemple : http://lindev.fr/sns.php )

Capture_d_e_cran_2015-05-24_a__11.05.20.png

Pour le moment, voici notre script sns.php :

<?php
ob_start();
var_dump( $_POST );
$content = ob_get_clean();
$fp = fopen('sns_validation.txt', a+);
fwrite($fp,$content);
fclose($fp);

Le but étant d'enregistrer dans un fichier texte, ce que nous envoi Amazon . NÉCESSAIRE AU DÉBUT POUR LA VALIDATION DE LE SOUSCRIPTION

Vous pouvez maintenant valider votre formulaire d'ajout de "subscription", vous allez alors voir apparaitre dans le fichier sns_validation.txt une url pour valider définitivement l'enregistrement.
Il vous suffit alors d'ouvrir cette url pour terminer l'enregistrement.

SES et SNS, le lien

Maintenant nous allons faire en sorte que tout les bounces liés à un domaine géré par le service SES d'amazon, utilisent le système de notification SNS et surtout le "topic" fraichement créé.

Pour cela, nous allons dans la section SES d'amazon, ( le domaine doit auparavant être validé pour pouvoir relayer des mails, pour cela il suffit d'ajouter des champs TXT dans la zone DNS. La documentation d'amazon est très bien faite à ce sujet ).

Nous allons donc dans la section Notifications, et l'on édite la configuration

Capture_d_e_cran_2015-05-24_a__11.06.36.png

Et là nous allons choisir les options adéquats

Capture_d_e_cran_2015-05-24_a__11.06.52.png

On sélectionne donc notre souscription dans les menus "Bounces" et "Complaints" uniquement, car ce sont ces deux cas que nous souhaitons traiter automatiquement.

Et voilà, après validation, dés qu'un "bounce" montre le bout de son nez, un message est transmit au service SNS et une requête HTTP avec toutes les informations nécessaires dans un format JSON est lancée en POST.

Tests

Pour tester le service, Amazon nous met à disposition des adresses spécifiques, qui simulent chaque cas.

  • bounce@simulator.amazonses.com
  • complaint@simulator.amazonses.com

Ne vous reste donc plus qu'à envoyer un mail à ces deux adresses, et regarder le contenu des données POST qui vont s'ajouter dans votre fichier sns_validation.txt

Cas simple

Imaginons, que nous souhaitons, simplement lister les mails des Bounces et Complaints dans un fichier TXT avec trois champs

  1. Date de réception du Bounce
  2. Adresse mail
  3. Type de Bounce ( temporaire ou définitif ou complaint ).

Nous allons donc extraire ces données du JSON, notre script sns.php devient :

<?php

// Fetch the raw POST body containing the message
$postBody = file_get_contents('php://input');

// JSON decode the body to an array of message data
$msg = json_decode($postBody, true);

if ($msg) {

    //Ouverture du fichier de log
    $fp = fopen('sns_bounces.txt', 'a+');

    $data = json_decode($msg['Message'],true);
    $typeMsg = $data['notificationType'];//A utiliser pour séparer plaintes et Bounces

    switch( $typeMsg ){
        case 'Bounce':
            $bounceType = $data['bounce']['bounceType'];
            foreach( $data['mail']['destination'] AS &$mail ){
                //Ecriture dans la liste txt
                fwrite($fp, date('Y-m-d H:i:s').";".$mail.";". $bounceType . "\n");
            }
        break;
        case 'Complaint':
            foreach( $data['mail']['destination'] AS &$mail ){
                //Ecriture dans la liste txt
                fwrite($fp, date('Y-m-d H:i:s').";".$mail.";complaint" . "\n");
            }
        break;
    }
    fclose($fp);

}

Vous allez alors vous retrouver avec une liste exploitable comme un CSV .

L'idée, c'est d'améliorer ce script de démo pour une désinscription automatique dans votre outil de mailing ( très simple avec phplist )

Restez informé

inscrivez vous à la newsletter pour recevoir les nouveaux billets par mail. ( formulaire en haut à droite )

18 mai 2015

Amazon Ec2 IO et Raid

J'ai eu quelques soucis récemment avec une instance Amazon qui héberge une base de données Mysql très sollicitée par période.
En effet les données de la base étaient sur la même partition que le système ( déjà c'est mauvais ), celle-ci étant fortement sollicitée en IO, la charge système se met à grimper, les temps d'attente d’accès au disque étant de plus en plus grand, jusqu'au crash complet .

La solution simple et rapide est de déplacer les données vers une partition dédiée, avec des performances en IO plus importantes (tant qu'à faire). C'est dans ce contexte que je vais présenter, dans ce billet, la mise en place d'une partition en Raid0 pour augmenter significativement les IO/s et surtout éviter de surcharger le système.

Ce que l'on va voir

  • Création de système de fichiers XFS
  • Création d'un Raid logiciel
  • Monitoring avec atop
  • Ajout d'un disque au Raid
  • Extension d'une partition XFS

Etat des lieux

Je vais utiliser une instance micro ( gratuite ) sous Debian Jessie en HVM ( virtualisation totale ). je vais y attacher 4 disques.

Capture_d_e_cran_2015-05-17_a__10.28.54.png

  • /dev/xvda pour le système
  • /dev/sdb pour tester les perfs. sur un seul disque
  • /dev/sdc premier disque du Raid
  • /dev/sdb deuxième disque du Raid

Installation des outils

Le système étant vierge nous allons installer les paquets nécessaires pour les manipulations ci-dessous.

sudo apt-get install xfsprogs atop mdadm

Premier test : Disque seul

Pour pouvoir comparer, nous allons commencer avec un test sur un disque "seul" possédant une partition XFS

Voici comment sont perçus les disques à ce moment

Capture_d_e_cran_2015-05-17_a__10.35.40.png

Commençons par créer la partition :

sudo cfdisk /dev/xvdb

Une fois la partition créée, voici comment sont perçus les disques

Capture_d_e_cran_2015-05-17_a__10.37.39.png

Nous allons maintenant créer le système de fichiers sur cette nouvelle partition

mkfs.xfs /dev/xvdb1

Enfin il ne nous reste plus qu'à monter cette partition sur un répertoire que nous allons également créer maintenant

mkdir /home/admin/diskLocal
mount -t xfs /dev/xvdb1 /home/admin/diskLocal/

Dirigeons nous dans ce répertoire et effectuons le premier test basique, grâce à l'outil dd

cd /home/admin/diskLocal
dd if=/dev/zero of=bench.dat bs=1M count=1024 oflag=direct

Pendant le test on voit bien grâce à la commande atop, l'utilisation du disque qui est à son maximum

Capture_d_e_cran_2015-05-17_a__10.45.21.png

Et voici le résultat

Capture_d_e_cran_2015-05-17_a__10.45.39.png

Deuxième test : 2 Disques en Raid0

Commençons par construire notre raid0 "software" ( via la commande mdadm )

mdadm --create --verbose /dev/md0 --level=stripe --raid-devices=2 /dev/xvdc /dev/xvdd

Ce qui nous donne :

Capture_d_e_cran_2015-05-17_a__10.48.43.png

Un petit check :

mdadm -D /dev/md0

Maintenant, créons notre système de fichiers sur md0

mkfs.xfs /dev/md0

Enfin il ne nous reste plus qu'à monter cette partition sur un répertoire que nous allons créer: diskRaid

mkdir /home/admin/diskRaid
mount -t xfs /dev/md0 /home/admin/diskRaid/

Dirigeons nous dans ce répertoire et effectuons le second test sur ce disque Raid0

cd /home/admin/diskRaid
dd if=/dev/zero of=bench.dat bs=1M count=1024 oflag=direct

Regardons ce qui se passe pendant le test

Capture_d_e_cran_2015-05-17_a__10.52.27.png

Les deux disques sont en pleine charge, et quelques secondes plus tard ...

Capture_d_e_cran_2015-05-17_a__10.52.37.png

On passe donc de 30 secondes à 18 sec. pour effectuer ce test et un débit d’écriture de 35.5MB/s à 51MB/s

Conclusion

Il est plus intéressant de mettre en place 2 disques en Raid0 qu'avoir un gros disque pour améliorer les IO/s.

Ajoutons un disque au Raid0

Si nous ajoutons un disque à notre Raid0, nous passerons ainsi à 3 disques

Voici le disque à ajouter

  • /dev/xvde

Capture_d_e_cran_2015-05-17_a__14.21.34.png

Ajoutons le à notre raid md0

sudo mdadm --grow /dev/md0 --raid-devices=3 --level=0 --add /dev/xvde
sudo xfs_growfs /dev/md0

Regardons si tout est ok

lsblk

Capture_d_e_cran_2015-05-17_a__14.59.08.png

Vérifions que la partition fasse bien 15G

df -h

Capture_d_e_cran_2015-05-17_a__15.00.30.png

Bien ça me semble parfait, lançons le troisième test

cd /home/admin/diskRaid
dd if=/dev/zero of=bench.dat bs=1M count=1024 oflag=direct

Capture_d_e_cran_2015-05-17_a__15.02.34.png

Résultat :

Capture_d_e_cran_2015-05-17_a__15.03.51.png

Les performances restent les mêmes, certainement limitées par la virtualisation ou simplement physiquement, cependant la charge étant répartie sur 4 disques, l'occupation ne dépasse pas les 65%, ce qui permet d'avoir d'autres processus en parallèles.

L'ajout de disques reste donc très intéressante pour pouvoir encaisser une forte charge d'IO/s .

Bon tests ,

Ch.

Restez informé

inscrivez vous à la newsletter pour recevoir les nouveaux billets par mail. ( formulaire en haut à droite )

16 mai 2015

Envoi de mail à la création de compte via le backend de prestashop

logo.png Un petit truc tout bête bien pratique, à tel point que je ne comprends pas pourquoi ce n'est pas en place dans les fonctionnalités de base de prestashop.

Le besoin

Prestashop 1.6

Lorsqu'un employé crée un compte utilisateur depuis le backoffice de prestashop, aucun mail n'est envoyé au client final ( le mail de bienvenue contenant ses identifiants pour se connecter ), de plus l'employé doit entrer lui-même un mot de passe, ce qui n'est pas des plus pratique, surtout lorsque l'employé est en manque d'imagination, on peut se retrouver alors dans une problématique de sécurité non négligeable.

La solution

Nous allons simplement permettre à l'employé de laisser le champs "passwd" vide, il sera alors automatiquement généré de façon aléatoire.
Enfin, nous allons ajouter un bouton "On/Off" dans le formulaire de création de compte qui va permettre à l'employé de décider si oui ou non les identifiants seront envoyés au client.

Le code

Bien commençons par créer la classe de surcharge

override/controllers/admin/AdminCustomersController.php

Nous allons donc surcharger les méthodes suivantes :

  1. processAdd()
  2. renderForm()

Et créer une fonction

  1. sendConfirmationMail()

Voici donc le code complet de notre fichier AdminCustomersController.php

<?php
/*
*  05-2015
*
*  @author Christophe De Saint Leger 
*  @Description Surcharge Formulaire création compte depuis le BackEnd
*/
class AdminCustomersController extends AdminCustomersControllerCore
{


        public function processAdd()
        {
            if (Tools::getValue('submitFormAjax'))
                $this->redirect_after = false;
            // Check that the new email is not already in use
            $customer_email = strval(Tools::getValue('email'));
            $customer = new Customer();
            if (Validate::isEmail($customer_email))
                $customer->getByEmail($customer_email);
            if ($customer->id)
            {
                $this->errors[] = Tools::displayError('An account already exists for this email address:').' '.$customer_email;
                $this->display = 'edit';
                return $customer;
            }
            elseif (trim(Tools::getValue('passwd')) == '')
            {
                $_POST['passwd'] = Tools::passwdGen();
            }
            if ($customer = parent::processAdd())
            {
                $this->context->smarty->assign('new_customer', $customer);
                if( Tools::getValue('sendWelcomeEmail') ){
                    $this->sendConfirmationMail($customer);
                }
                return $customer;
            }
            return false;
        }




        public function renderForm()
        {

            if (!($obj = $this->loadObject(true)))
                return;
            
            $genders = Gender::getGenders();
            $list_genders = array();
            foreach ($genders as $key => $gender)
            {
                $list_genders[$key]['id'] = 'gender_'.$gender->id;
                $list_genders[$key]['value'] = $gender->id;
                $list_genders[$key]['label'] = $gender->name;
            }

            $years = Tools::dateYears();
            $months = Tools::dateMonths();
            $days = Tools::dateDays();

            $groups = Group::getGroups($this->default_form_language, true);
            $this->fields_form = array(
                'legend' => array(
                    'title' => $this->l('Customer'),
                    'icon' => 'icon-user'
                ),
                'input' => array(
                    array(
                        'type' => 'radio',
                        'label' => $this->l('Social title'),
                        'name' => 'id_gender',
                        'required' => false,
                        'class' => 't',
                        'values' => $list_genders
                    ),
                    array(
                        'type' => 'text',
                        'label' => $this->l('First name'),
                        'name' => 'firstname',
                        'required' => true,
                        'col' => '4',
                        'hint' => $this->l('Invalid characters:').' 0-9!&lt;&gt;,;?=+()@#"°{}_$%:'
                    ),
                    array(
                        'type' => 'text',
                        'label' => $this->l('Last name'),
                        'name' => 'lastname',
                        'required' => true,
                        'col' => '4',
                        'hint' => $this->l('Invalid characters:').' 0-9!&lt;&gt;,;?=+()@#"°{}_$%:'
                    ),
                    array(
                        'type' => 'text',
                        'prefix' => '<i class="icon-envelope-o"></i>',
                        'label' => $this->l('Email address'),
                        'name' => 'email',
                        'col' => '4',
                        'required' => true,
                        'autocomplete' => false
                    ),
                    array(
                        'type' => 'password',
                        'label' => $this->l('Password'),
                        'name' => 'passwd',
                        'required' => ($obj->id ? false : true),
                        'col' => '4',
                        'hint' => ($obj->id ? $this->l('Leave this field blank if there\'s no change.') :
                            sprintf($this->l('Password should be at least %s characters long. or void for automatic generation'), Validate::PASSWORD_LENGTH))
                    ), 
                    array(
                        'type' => 'switch',
                        'label' => $this->l('Send Welcome Email'),
                        'name' => 'sendWelcomeEmail',
                        'required' => false,
                        'class' => 't',
                        'is_bool' => true,
                        'values' => array(
                            array(
                                'id' => 'sendWelcomeEmail_on',
                                'value' => 1,
                                'label' => $this->l('Enabled')
                            ),
                            array(
                                'id' => 'sendWelcomeEmail_off',
                                'value' => 0,
                                'label' => $this->l('Disabled')
                            )
                        ),
                        'hint' => $this->l('Send the credentials to the client')
                    ),
                    array(
                        'type' => 'birthday',
                        'label' => $this->l('Birthday'),
                        'name' => 'birthday',
                        'options' => array(
                            'days' => $days,
                            'months' => $months,
                            'years' => $years
                        )
                    ),
                    array(
                        'type' => 'switch',
                        'label' => $this->l('Enabled'),
                        'name' => 'active',
                        'required' => false,
                        'class' => 't',
                        'is_bool' => true,
                        'values' => array(
                            array(
                                'id' => 'active_on',
                                'value' => 1,
                                'label' => $this->l('Enabled')
                            ),
                            array(
                                'id' => 'active_off',
                                'value' => 0,
                                'label' => $this->l('Disabled')
                            )
                        ),
                        'hint' => $this->l('Enable or disable customer login.')
                    ),
                    array(
                        'type' => 'switch',
                        'label' => $this->l('Newsletter'),
                        'name' => 'newsletter',
                        'required' => false,
                        'class' => 't',
                        'is_bool' => true,
                        'values' => array(
                            array(
                                'id' => 'newsletter_on',
                                'value' => 1,
                                'label' => $this->l('Enabled')
                            ),
                            array(
                                'id' => 'newsletter_off',
                                'value' => 0,
                                'label' => $this->l('Disabled')
                            )
                        ),
                        'disabled' =>  (bool)!Configuration::get('PS_CUSTOMER_NWSL'),
                        'hint' => $this->l('This customer will receive your newsletter via email.')
                    ),
                    array(
                        'type' => 'switch',
                        'label' => $this->l('Opt-in'),
                        'name' => 'optin',
                        'required' => false,
                        'class' => 't',
                        'is_bool' => true,
                        'values' => array(
                            array(
                                'id' => 'optin_on',
                                'value' => 1,
                                'label' => $this->l('Enabled')
                            ),
                            array(
                                'id' => 'optin_off',
                                'value' => 0,
                                'label' => $this->l('Disabled')
                            )
                        ),
                        'disabled' =>  (bool)!Configuration::get('PS_CUSTOMER_OPTIN'),
                        'hint' => $this->l('This customer will receive your ads via email.')
                    ),
                )
            );
            
            // if we add a customer via fancybox (ajax), it's a customer and he doesn't need to be added to the visitor and guest groups
            if (Tools::isSubmit('addcustomer') && Tools::isSubmit('submitFormAjax'))
            {
                $visitor_group = Configuration::get('PS_UNIDENTIFIED_GROUP');
                $guest_group = Configuration::get('PS_GUEST_GROUP');
                foreach ($groups as $key => $g)
                    if (in_array($g['id_group'], array($visitor_group, $guest_group)))
                        unset($groups[$key]);
            }

            $this->fields_form['input'] = array_merge(
                $this->fields_form['input'],
                array(
                    array(
                        'type' => 'group',
                        'label' => $this->l('Group access'),
                        'name' => 'groupBox',
                        'values' => $groups,
                        'required' => true,
                        'col' => '6',
                        'hint' => $this->l('Select all the groups that you would like to apply to this customer.')
                    ),
                    array(
                        'type' => 'select',
                        'label' => $this->l('Default customer group'),
                        'name' => 'id_default_group',
                        'options' => array(
                            'query' => $groups,
                            'id' => 'id_group',
                            'name' => 'name'
                        ),
                        'col' => '4',
                        'hint' => array(
                            $this->l('This group will be the user\'s default group.'),
                            $this->l('Only the discount for the selected group will be applied to this customer.')
                        )
                    )
                )
            );

            // if customer is a guest customer, password hasn't to be there
            if ($obj->id && ($obj->is_guest && $obj->id_default_group == Configuration::get('PS_GUEST_GROUP')))
            {
                foreach ($this->fields_form['input'] as $k => $field)
                    if ($field['type'] == 'password')
                        array_splice($this->fields_form['input'], $k, 1);
            }

            if (Configuration::get('PS_B2B_ENABLE'))
            {
                $risks = Risk::getRisks();

                $list_risks = array();
                foreach ($risks as $key => $risk)
                {
                    $list_risks[$key]['id_risk'] = (int)$risk->id;
                    $list_risks[$key]['name'] = $risk->name;
                }

                $this->fields_form['input'][] = array(
                    'type' => 'text',
                    'label' => $this->l('Company'),
                    'name' => 'company'
                );
                $this->fields_form['input'][] = array(
                    'type' => 'text',
                    'label' => $this->l('SIRET'),
                    'name' => 'siret'
                );
                $this->fields_form['input'][] = array(
                    'type' => 'text',
                    'label' => $this->l('APE'),
                    'name' => 'ape'
                );
                $this->fields_form['input'][] = array(
                    'type' => 'text',
                    'label' => $this->l('Website'),
                    'name' => 'website'
                );
                $this->fields_form['input'][] = array(
                    'type' => 'text',
                    'label' => $this->l('Allowed outstanding amount'),
                    'name' => 'outstanding_allow_amount',
                    'hint' => $this->l('Valid characters:').' 0-9',
                    'suffix' => $this->context->currency->sign
                );
                $this->fields_form['input'][] = array(
                    'type' => 'text',
                    'label' => $this->l('Maximum number of payment days'),
                    'name' => 'max_payment_days',
                    'hint' => $this->l('Valid characters:').' 0-9'
                );
                $this->fields_form['input'][] = array(
                    'type' => 'select',
                    'label' => $this->l('Risk rating'),
                    'name' => 'id_risk',
                    'required' => false,
                    'class' => 't',
                    'options' => array(
                        'query' => $list_risks,
                        'id' => 'id_risk',
                        'name' => 'name'
                    ),
                );
            }

            $this->fields_form['submit'] = array(
                'title' => $this->l('Save'),
            );

            $birthday = explode('-', $this->getFieldValue($obj, 'birthday'));

            $this->fields_value = array(
                'years' => $this->getFieldValue($obj, 'birthday') ? $birthday[0] : 0,
                'months' => $this->getFieldValue($obj, 'birthday') ? $birthday[1] : 0,
                'days' => $this->getFieldValue($obj, 'birthday') ? $birthday[2] : 0,
            );

            // Added values of object Group
            if (!Validate::isUnsignedId($obj->id))
                $customer_groups = array();
            else
                $customer_groups = $obj->getGroups();
            $customer_groups_ids = array();
            if (is_array($customer_groups))
                foreach ($customer_groups as $customer_group)
                    $customer_groups_ids[] = $customer_group;

            // if empty $carrier_groups_ids : object creation : we set the default groups
            if (empty($customer_groups_ids))
            {
                $preselected = array(Configuration::get('PS_UNIDENTIFIED_GROUP'), Configuration::get('PS_GUEST_GROUP'), Configuration::get('PS_CUSTOMER_GROUP'));
                $customer_groups_ids = array_merge($customer_groups_ids, $preselected);
            }

            foreach ($groups as $group)
                $this->fields_value['groupBox_'.$group['id_group']] =
                    Tools::getValue('groupBox_'.$group['id_group'], in_array($group['id_group'], $customer_groups_ids));

            return AdminController::renderForm();
        }



        /**
         * sendConfirmationMail
         * @param Customer $customer
         * @return bool
         */
        protected function sendConfirmationMail(Customer $customer)
        {
            if (!Configuration::get('PS_CUSTOMER_CREATION_EMAIL'))
                return true;

            return Mail::Send(
                $this->context->language->id,
                'account',
                Mail::l('Welcome!'),
                array(
                    '{firstname}' => $customer->firstname,
                    '{lastname}' => $customer->lastname,
                    '{email}' => $customer->email,
                    '{passwd}' => Tools::getValue('passwd')),
                $customer->email,
                $customer->firstname.' '.$customer->lastname
            );
        }



}

Attention à ne pas oublier

Pour toute nouvelle surcharge, il vous faut supprimer le fichier de cache suivant

cache/class_index.php

Résultat

Et voilà le résultat au niveau du formulaire

Capture_d_e_cran_2015-05-16_a__14.27.06.png

Bonne journée,

Ch.

Restez informé

inscrivez vous à la newsletter pour recevoir les nouveaux billets par mail. ( formulaire en haut à droite )

13 mai 2015

Time zones nommées mysql

Un nouveau billet pense bête ...

A l'installation de Mysql selon la version ou la façon de l'installer, les Time Zone nommées ne sont pas toujours ajoutées dans la base de données mysql. Voilà la petite ligne de commande à exécuter (sous linux) pour y remédier ( les ajouter )

mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u <utilisateur admin> mysql

Et voilà .. vous pouvez utiliser les requête

SET time_zone = timezonename;

Restez informé

inscrivez vous à la newsletter pour recevoir les nouveaux billets par mail. ( formulaire en haut à droite )

26 avr. 2015

Jessie, la bonne nouvelle du jour

2015.04.25 8 Jessie

Ça y est, après de longs mois de développement des équipes Debian, voilà la nouvelle monture disponible "Debian Jessie".

Merci aux développeurs pour ce travail formidable .

22 mar. 2015

Un blog, une histoire

Voilà maintenant plus de 8 ans que ce blog existe ... déjà 8 ans !!
Je pense jamais avoir expliquer l'origine de cette volonté de tenir un tel site, ce sera chose faite à la fin de ce billet .

L'origine

Me voilà jeune diplômé sur le marché du travail, sans expérience j'arrive alors dans une société industrielle dédiée à l'impression numérique et sérigraphique grand format Creavi.
Rapidement, je mets en pratique tout ce que je connais dans mon domaine qu'est l'informatique ( plus précisément le développement et l'administration système linux ).
Grand "guru" de l'open source, "ce qui ma valu quelques pseudonymes au passage" je mets en place de plus en plus de solutions libres au sein de la société dans laquelle je me trouve .
Comme tout bon informaticien, je me tiens à jour, et tente de suivre les évolutions qui changent à un rythme effréné.
C'est alors que je me donne le temps pour partager mes découvertes, mes tests, mes galères, Lindev.fr est né !
C'est ce même genre de site qui m'a permis d'évoluer dans le domaine, je vois donc ça comme un juste retour des choses .
Évidemment, toujours sans pub, le but n'est certainement pas de tenter une quelconque rentabilité de ce blog, mais bien un partage pur et simple.
Le temps passe, les articles se succèdent à une fréquence plus ou moins rapide, mais reste toujours actif, d'ailleurs vous êtes de plus en plus nombreux à lire mes pages, ce qui au passage me motive encore un peu plus pour continuer à prendre du temps pour en écrire de nouveaux .

2015 le changement

Ce blog étant lié à mon activité professionnelle, il évoluera avec elle.
En 2015 je décide de changer de job !
Et oui, une envie de nouvelles aventures, une remise en question total, une nouvelle prise de risques, mais aussi des nouveaux chemins à parcourir/découvrir .
Me voilà maintenant dans une nouvelle structure totalement différente de la précédente: Speechi, spécialiste dans les solutions interactives pour l'enseignement que ce soit logiciel ou matériel ( vidéoprojecteur interactif, visualiseurs, tableau blanc interactif, ou encore et plus impressionnant, les écrans intéractifs )
Autant de nouvelles technologies au service de l'apprentissage avec lesquels je vais désormais travailler, et qui dit nouveau boulot, dit nouveaux besoins et indirectement nouvelles technos donc pour vous ... nouveaux articles .

Voilà pour cette petite explication sur l'origine de ce blog et son avenir, je trouve que ce changement est suffisamment important pour vous en faire part, c'est désormais chose faite .

A bientôt, Ch.

22 fév. 2015

Apache2 php5-fpm VirtualHost

Cela fait maintenant quelques mois que je n'ai pas édité de nouveau billet sur ce site, je vais donc couper cette période creuse en vous proposant un petit tutoriel sur la mise en place de php5-fpm avec apache2 et tout ça configurable au besoin dans des virtualhost .

Nous avons déjà vu comment faire avec Nginx ( qui est franchement plus clair qu'apache ) mais parfois on a pas le choix du serveur web sur lequel on travail .. alors voyons comment faire ça proprement avec apache2.

Etat des lieux

Le tutoriel se base sur la version stable de Débian du moment, Debian 7 "Wheezy" .
Nous allons également activer les dépôts contrib et non-free pour installer ce qui va suivre .

Pour cela commencez par éditer le fichier /etc/apt/sources.list

sudo vim /etc/apt/sources.list

Et ajoutez à la fin de chaque ligne contrib non-free
Ce qui donne quelque chose comme çà

deb http://cloudfront.debian.net/debian wheezy main contrib non-free
deb-src http://cloudfront.debian.net/debian wheezy main contrib non-free
deb http://security.debian.org/ wheezy/updates main contrib non-free
deb-src http://security.debian.org/ wheezy/updates main contrib non-free
deb http://cloudfront.debian.net/debian wheezy-updates main contrib non-free
deb-src http://cloudfront.debian.net/debian wheezy-updates main contrib non-free

PS: ne faite pas attention à l'url de mes dépôts ci-dessus "cloudfront.debian.net" , j'utilise une instance (VM) Amazon pour réaliser mes tests .

Installation des paquets nécessaires

Nous allons installer les paquets suivants :

sudo apt-get install php5-fpm libapache2-mod-fastcgi php5-cgi

Puis il nous faut activer le module fastcgi et actions via la commande a2enmod

sudo a2enmod actions fastcgi

Configuration du module fastcgi

Il nous faut lier le module fastcgi aux processus php5-fpm, pour celà nous allons éditer le fichier de configuration du module fastcgi

vim /etc/apache2/mods-available/fastcgi.conf

Pour arriver à ce résultat

<IfModule mod_fastcgi.c>
 AddType application/x-httpd-fastphp5 .php
 Action application/x-httpd-fastphp5 /php5-fcgi
 Alias /php5-fcgi /usr/bin/php5-fcgi
 FastCgiExternalServer /usr/bin/php5-fcgi -socket /var/run/php5-fpm.sock -pass-header Authorization
</IfModule>

NB : il vous faut vérifier le chemin du socket php5-fpm dans votre cas, normalement, /var/run/php5-fpm.sock et la valeur par défaut pour l’environnement Débian 7, pour vérifier exécuter cette commande

cat /etc/php5/fpm/pool.d/www.conf | grep "listen = "

Le test

Il ne vous reste plus qu'à tester cette nouvelle configuration, commençons par tester l'ensemble des paramètres d'apache via la commande très pratique

sudo apache2ctl configtest

Puis il ne reste plus qu'à relancer Apache si tout est ok

sudo service apache2 restart

Enfin, nous allons mettre un simple fichier php dans le DocumentRoot par défaut d'apache2

sudo echo "<?php echo phpinfo();" > /var/www/lindev.php && chown www-data:www-data /var/www/lindev.php

Il ne vous reste plus qu'à entrer l'url http://localhost/lindev.php pour voir le résultat .

Conf spécifique par virtualhost

Jusque là, les scripts php sont exécutés par l'utilisateur système www-data dans la plupart des cas, cela va être adéquat, mais si pour une raison quelconque vous devez utiliser un autre utilisateur pour exécuter un script/site en particulier, dans le virtualhost correspondant, il vous faudra surcharger la configuration du module fastcgi, ce qui donnera par exemple

<VirtualHost *:80>
    ServerAdmin webmaster@lindev.fr
    ServerName lindev.fr
    ServerAlias www.lindev.fr
    DocumentRoot /var/www/lindev/
    ErrorLog /var/www/lindev/error.log
    CustomLog /var/www/lindev/access.log combined

    <IfModule mod_fastcgi.c>
        AddType application/x-httpd-fastphp5 .php
        Action application/x-httpd-fastphp5 /php5-fcgi
        Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi_lindev
        FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi_lindev -socket /var/run/php5-fpm_lindev.sock -pass-header Authorization
    </IfModule>

</VirtualHost>

Ici , j'ai donc donné un autre socket à utiliser, que j'ai nommé php5-fpm_lindev.sock. Ce socket devra bien évidement être configuré coté php-fpm, en créant un nouveau pool ( dans un prochain article certainement ), en attendant, pour tester copiez juste la conf du pool par défaut, et changer le paramètre listen pour spécifier le socket à créer pour ce pool.
C'est également dans ce fichier de pool que vous pourrez spécifier l'utilisateur système à utiliser pour interpréter les fichiers php.

cp /etc/php5/fpm/pool.d/www.conf /etc/php5/fpm/pool.d/lindev.conf

Changer les paramètres listen, user et group , puis relancer php-fpm

sudo service php5-fpm reload

L'article touche maintenant à sa fin, vous savez maintenant installer apache, php5-fpm et configurer un pool spécifique de php pour un virtualhost.

Ch.

15 sept. 2014

Réparer son virtualenv aprés un update Systéme/Libs

Après une belle maj de votre système, vous avez, au moment de vous remettre au travail, un sympathique message du genre

ImportError: No module named datetime

detatime !! Cette lib fait pourtant partie du standard de python ! Le fait est que lorsque l'on met à jour python, les liens utilisés à la création du venv sont hs !

Ici c'est la lib datetime qui a été remonté, mais ça peu être n'importe laquelle ..

Solution

Il ne m'a pas fallu longtemps pour résoudre ce problème tout bête, ( beaucoup de cas similaires sur le net ).

Réinitialiser le virtualenv

Par exemple avec mon venv nommé foo

virtualenv /home/cdsl/.virtualenvs/foo
New python executable in /home/foo/.virtualenvs/creasoft3/bin/python
Installing setuptools............done.
Installing pip...............done.

Et voilà ..

27 août 2014

Django déploiement avec Nginx Gunicorn et Supervisord

Je vois souvent passer des questions sur le forums ou autres fils de discussion concernant la mise en production d'un projet Django.
Ici je vais présenter une façon de faire, utilisant Nginx Gunicorn et Supervisord .

Prérequis

Sur une Débian toute fraiche,

Nginx

sudo apt-get install nginx

Virtualenv

Car nous travaillons toujours avec un environnement virtuel, dans lequel nous allons y installer les dépendances de notre projet.
Ce qui permet de ne pas avoir de conflit de version avec d'autres éventuels projets déjà en prod.

pip install virtualenv virtualenvwrapper

Note : Si vous n'avez pas l'outil pip de disponible, installez-le comme ceci

sudo apt-get install python-setuptools
sudo easy_install pip

Puis dans votre fichier ~/.bashrc , ajoutez les lignes suivantes, pour l'autocompletion

export VIRTUALENVWRAPPER_VIRTUALENV_ARGS='--no-site-package'
export WORKON_HOME=$HOME/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh

Gunicorn

Pourquoi pas utiliser uWSGI ? humm ... car je préfère Gunicorn qui me donne toute satisfaction sur mes projets en prod. donc ... pourquoi pas !

sudo apt-get install libevent-dev
pip install gunicorn

Supervisor

Installation simple :

sudo pip install supervisor

Récupération du fichier de conf par défaut

echo_supervisord_conf > /etc/supervisord.conf

Dans le fichier de conf, afin de pouvoir manager votre projet via l'interface web, il faut dé-commenter et paramétrer les lignes suivantes

[inet_http_server]         ; inet (TCP) server disabled by default
port=*:9001        ; (ip_address:port specifier, *:port for all iface)
username=user              ; (default is no username (open server))
password=123               ; (default is no password (open server))

Note: Changer le username et password évidemment

L'interface web sera disponible à l'adresse : http://ipduserveur:9001

Pour le lancement automatique au démarrage du système, vous pouvez utiliser ces scripts d'init:

  1. debian init supervisor
  2. debian init supervisor

Installer votre projet

Notre environnement est prêt, nous allons commencer par installer le projet Django dans l’environnement virtuel.

Création du virtualenv

mkvirtualenv monprojet

Si vous utilisez git, cloner votre projet, sinon, copier le à l'endroit que vous souhaitez, ( pour l'exemple ce sera /var/www/monprojet )

Installation des dépendances du projet

si vous utilisez également les environnements virtuels pour développer ( ce que je conseille ) vous pouvez alors enregistrer la liste des libs python installées dans cette environnement, afin de pouvoir également les installer ( avec la même version ) sur un autre serveur.

Pour avoir la liste des libs :

pip freeze > requirements.txt

puis sur votre serveur, pour installer les libs depuis un export freeze :

pip install -r requirements.txt

Configuration de Django

Il vous faudra peut-être toucher un peu à votre fichier settings.py pour le passer du mode debug au mode production.
Personnellement j'utilise une autre subtilité qui me permet de ne pas avoir à toucher au fichier settings.py ( j'expliquerai celà dans un autre billet ) .

N'oubliez pas de vérifier le paramètre STATIC_ROOT

STATIC_ROOT = '/var/www/static_monprojet/'

C'est le répertoire ou seront copiés les fichiers statiques du projet, pour ensuite être servis par Nginx

Ce répertoire DOIT EXISTER

Une fois le répertoire créé, nous allons y "placer/lier" les fichiers statiques du projet

python manage.py collectstatic --link

Perso je préfère y mettre des liens symboliques .. ( les gouts et les couleurs ... )

Liaison du projet avec Gunicorn

Nous allons créer un fichier dans notre projet qui sera utilisé pour le lancement du/des process Gunicorn ( vous devrez adapter les valeurs dans ce script )

vim /var/www/monprojet/monprojet/gunicorn.sh

Et voici le contenu

#!/bin/bash
  set -e
  NUM_WORKERS=2
  USER=www-data
  GROUP=www-data
  ADDRESS=127.0.0.1:5002
  cd /var/www/monprojet
  source /home/monuser/.virtualenvs/monprojet/bin/activate
  exec gunicorn monprojet.wsgi:application -w $NUM_WORKERS --bind=$ADDRESS \
    --user=$USER --group=$GROUP --log-level=debug

Puis on le rend exécutable

chmod 777 /var/www/monprojet/monprojet/gunicorn.sh

Configuration Supervisord

Notre projet est prêt, afin de lancer Gunicorn automatiquement, nous allons utiliser supervisord, qui en plus de s'occuper de démarrer le projet automatiquement au démarrage du système, va aussi le relancer en cas de crash, gérer les logs et vous donner la main pour arrêter ré-demarrer les process, via l'interface web ou en mode console.

supervisord.png

Ajoutez à la fin du fichier de configuration /etc/supervisord.conf les lignes suivantes ( paramètres à adapter selon votre cas )

[program:guni_monprojet]
directory=/var/www/monprojet/monprojet
user = www-data
autostart=true
autorestart=true
stdout_logfile=/var/log/monprojet.log
redirect_stderr=true
stopsignal=QUIT
command = /var/www/monprojet/monprojet/monprojet.sh

Ne reste plus qu'à redémarrer supervisor pour prendre en compte la nouvelle config.

Pour le lancer manuellement

sudo supervisord -c /etc/supervisord.conf

Vhost Nginx

Dernier point, la création du vhost de Nginx, pour diriger les requêtes vers Gunicorn qui écoute sur le port 5002 ( il est aussi possible d'utiliser un fichier socket à la place )

vim /etc/nginx/sites-enabled/monprojet

Et voilà le contenu ( fonctionnel, mais vous pouvez l'adapter )

upstream us_monprojet {
        server 127.0.0.1:5002;
}


server {
        listen 80;

        root /var/www/monprojet;

        gzip             on;
        gzip_min_length  1000;
        gzip_proxied     expired no-cache no-store private auth;
        gzip_types       text/plain application/xml text/css text/javascript application/x-javascript application/x-shockwave-flash video/x-flv;
        gzip_disable     "MSIE [1-6]\.";


        server_name monprojet.com;
        charset utf-8;

        client_max_body_size 75M;


        location ~ /\.ht {
            deny  all;
        }

        location /favicon.ico {
                alias /var/www/monprojet/monprojet/static/favicon.ico;

                if (-f $request_filename) {
                        access_log off;
                        expires max;
                }

        }


        location /media {
                alias /var/www/monprojet/monprojet/media;

                if (-f $request_filename) {
                        access_log off;
                        expires max;
                }

        }

        location /static {
                alias /var/www/static_monprojet/;

                if (-f $request_filename) {
                        access_log off;
                        expires max;
                }

        }
location / {

                if (-f $request_filename) {
                        access_log off;
                        expires max;
                }       

                #gunicornParams
                if (!-f $request_filename) {
                        proxy_pass         http://us_monprojet;
                        break;
                }       
                proxy_redirect     off;
                proxy_set_header   Host             $host;
                proxy_set_header   X-Real-IP        $remote_addr;
                proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

        }       
}       

Ne reste plus qu'à activer le vhost, et relancer nginx

ln -s /etc/nginx/sites-available/monprojet /etc/nginx/sites-enabled/
service nginx reload

01 août 2014

Proxy Socks sous android NON Rooté

En cette période de vacances, nous sommes nombreux à nous connecter depuis nos terminaux ( smartphone, tablette .. ) pour surfer, parfois travailler , dépanner .
Il est vrai que ces appareils sont formidables sur ce point ( le coté nomade ). Mais le revers de la médaille, c'est la sécurité !
Lorsque vous vous connectez à une borne wifi ( gratuite ou non ), qui vous assure que votre navigation n'est pas épiée ?
Voilà pour le coté sécurité, mais il peut être aussi utile de se connecter à son réseau local depuis l'extérieur.
Nous allons pour cela nous connecter via un serveur proxy de type socks.
Maintenant peu importe ce qui motive l'utilisation d'une telle connexion, voyons comment faire pour nous y connecter depuis un client Android non chrooté

Les apps à installer

  1. juiceSSH
  2. Firefox

JuiceSSH est un client SSH qui fonctionne vraiment bien, gratuit pour les fonctionnalités de base, mais payant pour faire une redirection de port. Vous allez donc devoir l'acheter, mais l'investissement en vaut la peine.
D'autres ont utilisé ConnectBOT qui lui est à 100% gratuit, mais de mon coté il plantait régulièrement je n'ai donc pas insisté.

Configuration de juiceSSH

Nous allons commencer par créer une connexion SSH de base, le tout en image :

2014_08_01_12.06.59.png

2014_08_01_12.08.03.png

  • Nickname : nom de la connexion
  • Type : SSH
  • Adresse : Adresse du serveur SSH
  • Identity : Login pour se connecter au serveur SSH
  • Port : port à utiliser pour se connecter au serveur SSH

Configuration de la redirection de port

Allez dans l'onglet PORT FORWARD, et nous allons utiliser la connexion SSH précédemment crée, comme ceci.

2014_08_01_12.08.40.png

Il est ensuite possible de mettre une icône sur votre page principale, pour se connecter plus rapidement au serveur proxy SOCKS

2014_08_01_12.08.57.png

Configuration de Firefox

Pour utiliser le serveur proxy ( après s'y être connecté ), dans firefox, ouvrez un nouvel onglet et entrez dans la barre d'adresse :

about:config


Voici les 5 paramètres à configurer comme ceci

2014_08_01_12.05.51.png 2014_08_01_12.06.11.png

Tests

Voilà maintenant vous pouvez tester le bon fonctionnement en entrant l'url suivante : http://www.whatismyip.com/, qui devrait vous afficher l'ip du serveur SSH à partir duquel vos requêtes sont envoyés .

Bonnes vacances .

16 juil. 2014

Django, générer du xlsx

Les exports ... on est tous amené dans un projet à devoir se farcir des exports de données pour de l'analyse comptable, statistiques ou de l'analyse de production.

Bien souvent je ne cherchai pas trop loin pour sortir les données ( il faut dire que ce n'est pas ce qu'il y a de plus sexy comme travail ), j'optais pour un vulgaire fichier CSV!
Bien pratique, et rapide à sortir mais il faut avouer que ce n'est pas ce qu'il y a de plus présentable aux clients finaux, et il faut vendre le fait que la personne qui va exploiter ce genre d'export, va devoir commencer par structurer le fichier csv pour le rendre exploitable . ( et je ne parle même pas des encodages ! )

Bref ... ça fait porc !

xlsxwriter

Je me suis donc tourné vers une lib très bien documentée et qui fonctionne à merveille . xlsxwriter
Qui plus est très rapide à mettre en place et à prendre en main .

voyez vous même avec l'exemple N°1 de la documentation

import xlsxwriter

# Create a workbook and add a worksheet.
workbook = xlsxwriter.Workbook('Expenses01.xlsx')
worksheet = workbook.add_worksheet()

# Some data we want to write to the worksheet.
expenses = (
    ['Rent', 1000],
    ['Gas',   100],
    ['Food',  300],
    ['Gym',    50],
)

# Start from the first cell. Rows and columns are zero indexed.
row = 0
col = 0

# Iterate over the data and write it out row by row.
for item, cost in (expenses):
    worksheet.write(row, col,     item)
    worksheet.write(row, col + 1, cost)
    row += 1

# Write a total using a formula.
worksheet.write(row, 0, 'Total')
worksheet.write(row, 1, '=SUM(B1:B4)')

workbook.close()

Voilà pour la lib xlsxwriter, je ne vais pas vous faire un tutoriel de la doc qui est très bien faite .
Par contre, regardons comment l'intégrer à une vue Django .

Django

L'objectif, est de générer le fichier xlsx ci-dessus, depuis une vue Django, mais sans enregistrer le fichier sur le disque du serveur pour ensuite le servir, non, nous allons le générer en mémoire et l'envoyer avec les entêtes qui vont bien pour avoir une belle boite de dialogue comme ceci

xlsx.png

StringIO et cStringIO

Afin d'éviter de générer un fichier sur le disque à chaque exportation, nous allons travailler en mémoire exclusivement.
Pour y parvenir, nous allons utiliser la lib StringIO ou cStringIO si elle est dispo.
StringIO permet de travailler sur un object qui réagit comme un fichier text ( read, write, ... ) sauf qu'on est en mémoire et non sur le disque .

Voilà à quoi va ressembler la vue Django :

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO
buffer=StringIO()

    
import xlsxwriter

def export(self):
    """ Export Démo  """

    # Create a workbook and add a worksheet.
    workbook = xlsxwriter.Workbook(buffer, {'constant_memory': True})
    worksheet = workbook.add_worksheet()

    # Some data we want to write to the worksheet.
    expenses = (
        ['Rent', 1000],
        ['Gas',   100],
        ['Food',  300],
        ['Gym',    50],
    )

    # Start from the first cell. Rows and columns are zero indexed.
    row = 0
    col = 0

    # Iterate over the data and write it out row by row.
    for item, cost in (expenses):
        worksheet.write(row, col,     item)
        worksheet.write(row, col + 1, cost)
        row += 1

    # Write a total using a formula.
    worksheet.write(row, 0, 'Total')
    worksheet.write(row, 1, '=SUM(B1:B4)')

    workbook.close()


    #Reponse envoyée au navigateur
    response = HttpResponse(buffer.getvalue(), mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    response['Content-Disposition'] = "attachment; filename=test_export.xlsx"

    return response

Petite liste des types mime .. toujours pratique .

Et voilà le travail .. simple et efficace .
Maintenant ne vous reste plus qu'à mettre en forme vos exports afin qu'ils soient immédiatement exploitables par les principaux intéressés.

C'est pas grand chose, mais ça fait du bien de partager ;)
Ch.

30 juin 2014

Jquery.tableSorter, persistance du tri

Jquery TableSorter

Est un plugin jquery très efficace qui vous permet d'avoir les fonctions de tri sur un tableau en quelques lignes !
Sa mise en place est extrêmement simple, il reconnait pas mal de formats ( monétaire, date .. ) et pour couronner le tout, il est livré avec quelques de widgets intéressants pour aller encore plus loin .

Dans un récent développement, j'ai utilisé ce plugin pour pouvoir trier un tableau, mais j'avais besoin d'une persistance de ce tri dans le temps. J'ai été étonné qu'il n'y ai pas de widget en natif pour ce cas, mais comme ce plugin est bien fichu, il suffit d'étendre ses fonctionnalités avec un nouveau widget !
Je vous partage donc le code du widget qui me permet de sauvegarder le tri dans un cookie ( Dépend du plugin Jquery-cookie )

PS: si vous avez plusieurs tableaux, il y aura un enregistrement par tableau .

widget-sortPersist.js

;(function($){
"use strict";
var ts = $.tablesorter;

  ts.addWidget({
    // give the widget an id
    id: "sortPersist",
    // format is called when the on init and when a
    // sorting has finished
    format: function(table, thisWidget, c) {
 
      // Cookie info
      var cookieName = 'MY_SORT_COOKIE';
      var cookie = $.cookie(cookieName);
      var options = {path: '/'};
 
      var data = {};
      var sortList = table.config.sortList;
      var tableId = $(table).attr('id');
      var cookieExists = (typeof(cookie) != "undefined"
          && cookie != null);
 
      // If the existing sortList isn't empty, set it into the cookie
      // and get out
      if (sortList.length > 0) {
        if (cookieExists) {
          data = $.parseJSON(cookie);
        }
        data[tableId] = sortList;
        $.cookie(cookieName, JSON.stringify(data), options);
      }
 
      // Otherwise...
      else {
        if (cookieExists) {
 
          // Get the cookie data
          var data = $.parseJSON($.cookie(cookieName));
 
          // If it exists
          if (typeof(data[tableId]) != "undefined"
              && data[tableId] != null) {
 
            // Get the list
            sortList = data[tableId];
 
            // And finally, if the list is NOT empty, trigger
            // the sort with the new list
            if (sortList.length > 0) {
              $(table).trigger("sorton", [sortList]);
            }
          }
        }
      }
    }
  });
  

})(jQuery);

Pour l'utiliser, il vous suffit :

  1. d'inclure le widget dans votre page html
  2. d'activer le widget dans l'appel de tablesorter
$("#montableau").tablesorter({widgets: ['sortPersist']});

Attention

Attention ce code possède une dépendance : Jquery-cookie

Ch.

27 mar. 2014

OS X 10.9.2, smb, et les autres ...

Petite mise en situation, j'ai un parc doté de nombreuses machines Windows, Linux et Mac de versions différentes, et un des partages réseau est placé sur une machine Windows 7 bien intégrée au domaine ...

Jusque là tout baigne, mais voilà qu'un étrange phénomène fait son apparition...

En effet dés qu'un utilisateur sur Mac OS X 10.9.x dépose un fichier sur ce fameux partage ( sous Windows je le rappelle ) tous les autres utilisateurs connectés également à ce partage se retrouvent dehors, et dans l’incapacité de s'y reconnecter !

C'est quoi ce bor*** truc

C'est un peu ce que je me suis dit au départ ... après avoir testé le partage depuis d'autres postes, vérifié les règles FW du windows, l'anti-virus etc etc .. bref sur le moment je sèche ..

Soudain une explication apparaît

J'ai vu quelque part, une note indiquant que la version d'OS X 10.9.x utilisait la version 2 du protocole samba par défaut, ce qui ne pose aucun problème à Windows, il "switch" le protocole du partage smb2 et point barre !!
Oui mais le problème est justement là !

Il "switch" dans un seul sens !!! Donc mes autres postes en une version antérieure ou ne possédant simplement pas le protocole samba dans sa version 2 se retrouvent dehors, à poil !

Les solutions

  1. Changer la version du protocole samba par défaut au niveau du client ( Mac 10.9.x )
  2. Utiliser le protocole cifs en lieu et place de smb , toujours au niveau client

Changer la version sur le mac

Une manip simple, mais qui a l'inconvénient de forcer l'utilisation du protocole dans son ancienne version pour l’ensemble des partages.

Ouvrez un terminal et entrez cette commande :

echo "[default]" >> ~/Library/Preferences/nsmb.conf; echo "smb_neg=smb1_only" >> ~/Library/Preferences/nsmb.conf

Pour revenir à l'état d'origine .. il suffit de supprimer le fichier de config

rm ~/Library/Preferences/nsmb.conf

Utilisez cifs

cifs est en fait l'autre nom qu'avait donné Microsoft pour le protocole smb dans sa version 1, en y intégrant quelques améliorations ..
bref c'est presque un alias de smb v1 !

Le fait donc de se connecter en spécifiant cifs://<le partage> va forcer l'utilisation du protocole samba dans sa version antérieure !

cifs://user:password@x.x.x.x/sharePath

C'est cette dernière solution que j'ai retenu pour son coté pratique et sans modification du comportement du client par défaut.

Conclusion

Je n'aime toujours pas Windows ! ;)

Ch.

- page 1 de 27