Artisan Développeur

Qualité, amélioration continue, agilité.

Jenkins et symfony2

L’intégration continue est une des bonnes bases à mettre en place au début d’un projet.

(Ce post me sert principalement de mémo, n’hésitez pas à commenter ou me contacter si besoin)
(Note : j’ai installé les outils en phar dans le répertoire de jenkins, il serait bien de voir si il y a moyen de tous les gérer avec composer)

Je choisis Jenkins que je vais installer sur mon serveur de dev, de cette manière, lorsque je ferais un push sur github, Jenkins lancera un build, puis déploiera mon application en préproduction.

Le développement et l’avancement du projet seront complètement transparents, ce qui est une bonne chose, pour (la future) équipe, et les associés.

Pour l’installation basique sous debian :

Tuto install site jenkins

Pour la suite, il faut bien penser à installer git et curl :

[pastacode lang= »bash » message= » » highlight= » » provider= »manual »]

apt-get install curl git

[/pastacode]

1-admin

Sécurité Globale, pour définir un mode d’authentification :

2-admin-securite-globale

Je choisis de gérer les users Jenkins à part, il y en aura peu au début.
Ensuite, je choisis de donner le contrôle de Jenkins aux users, c’est surtout pour protéger le serveur des accès extérieurs non autorisés.

 

Après avoir valider je vais créer mon user :

0-admin-menu

Dans la « Gestion des utilisateurs » -> « Créer un utilisateur »

4-admin-user-add

Une fois inscrit, je préfère supprimer la possibilité de s’inscrire, retour dans le sécurité globale, pour décocher la case « 

Prochaine étape installer et configurer les plugins nécessaires et les configurer.

Pour faire mon setup j’ai utilisé les infos de plusieurs sources :

Pour avoir un mot de passe pour l’utilisateur Jenkins et pouvoir faire certaines manips :

http://stackoverflow.com/questions/6234016/jenkins-user-on-apt-get-install-installation

Sous debian 8, je suis logué en root, j’ai donc fait :

[pastacode lang= »bash » message= » » highlight= » » provider= »manual »]

passwd jenkins

[/pastacode]

Maintenant que je peux faire un su jenkins, et me loguer.

J’ai choisis d’utiliser Github pour mon projet,

j’ai configuré jenkins pour lancer un build quand je push.

Reste à faire :

  • code check, duplicator, php mess detector
  • configurer mage.php pour déployer ma preprod à la fin si le build est ok

Pour cela il y a ce tuto : http://blog.lazycloud.net/utiliser-jenkins-pour-un-projet-symfony2/

Cependant je vais installer les outils en .phar dans le répertoire de jenkins (puisque j’ai commencé comme ça) :

Pdepend : (site officiel)

[pastacode lang= »bash » message= » » highlight= » » provider= »manual »]

wget http://static.pdepend.org/php/latest/pdepend.phar
chmod +x pdepend.phar

[/pastacode]

Dans Jenkins : « Administrer jenkins » -> « gestion des plugins » -> onglet « Disponibles » -> Jdepend

5-plugins-jdepend

J’ajoute dans le script shell du build :

[pastacode lang= »bash » message= » » highlight= » » provider= »manual »]

#pdepend - Calculate software metrics using PHP_Depend
php ~/pdepend.phar --jdepend-xml=build/logs/jdepend.xml --jdepend-chart=build/pdepend/dependencies.svg --overview-pyramid=build/pdepend/overview-pyramid.svg ./src || status=$((status+$?))

[/pastacode]

(Voir le lien vers le tuto les-tilleuls.coop plus faut pour le script shell)

Ensuite, il faut configurer après le build :

6-plugins-jdepend-config

7-plugins-jdepend-config

Php mess detector : (site officiel download)

[pastacode lang= »bash » message= » » highlight= » » provider= »manual »]

wget -c http://static.phpmd.org/php/latest/phpmd.phar
chmod +x phpmd.phar

[/pastacode]

Ensuite il faut :
  •  ajouter une ligne au script shell
  • ajouter une action après le build pour montrer le résultat

[pastacode lang= »bash » message= » » highlight= » » provider= »manual »]

php ~/phpmd.phar ./src xml --reportfile build/pmd/phpmd.xml cleancode, codesize, design, naming, unusedcode || status=$((status+$?))

[/pastacode]

8-plugins-phpMD-config

Php Code sniffer : (le github)

[pastacode lang= »bash » message= » » highlight= » » provider= »manual »]

curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar
php phpcs.phar -h

curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcbf.phar
php phpcbf.phar -h

chmod +x phpcs.phar phpcbf.phar

#installation des standards symfony
#je le met dans le répertoire de jenkins
mkdir -p CodeSniffer/Standards

#J'ai utilisé ce repo, n'hésitez pas à commenter si vous en avez d'autres, mieux...
git clone git://github.com/escapestudios/Symfony2-coding-standard.git Symfony2

#config phpcs pour l'ajout du standard symfony
./phpcs.phar --config-set installed_paths CodeSniffer/Standards

[/pastacode]

Dans le script shell :

[pastacode lang= »bash » message= » » highlight= » » provider= »manual »]

#phpcs-ci Find coding standard violations using PHP_CodeSniffer creating a log file for the continuous integration server
php ~/phpcs.phar --report=checkstyle --report-file=build/logs/checkstyle.xml --standard=Symfony2 ./src || status=$((status+$?))

[/pastacode]

Ensuite dans les plugins jenkins, il faut ajouter : « Checkstyle plugin »

Pour finir la config de l’affichage avec le plugin après le build:

9-plugins-checkstyle-config

(un tuto spécifique a code sniffer : http://www.theodo.fr/blog/2014/06/easily-comply-with-symfony2-coding-style-using-codesniffer-and-phpstorm-code-inspection/ )

Pour le déploiement automatique de la preprod, juste un cp ou un rsync à la fin du script shell peut faire l’affaire, il faut penser à ajouter l’utilisateur « jenkins » au groupe « www-data » pour pouvoir écrire dans le répertoire web. Un symlink ou bien un vhost configuré sur le répertoire workspace de Jenkins aurait fonctionné, mais Jenkins utilise l’env de test le symfony2 pour lancer les tests unitaires, et je préfère que ma preprod soit indépendante le Jenkins.

De plus, à termes, je prévois d’utiliser un outil comme Magallanes (pour l’instant il ne gère pas le deploy local)

Pac manager pour gérer les connexions ssh

J’ai plusieurs serveurs auxquels je dois accéder pour faire des mises à jour, et autres interventions, via ssh.

En cherchant un gestionnaire, j’ai rapidement trouvé pac manager, que je vais essayer.

L’install :

(dans mon cas kubuntu 15.04)

1 – télécharger le .deb http://sourceforge.net/projects/pacmanager/
2 – les dépendances :

[pastacode lang= »bash » message= » » highlight= » » provider= »manual »]

sudo apt-get install libgnome2-gconf-perl libexpect-perl libnet-proxy-perl libyaml-perl libcrypt-cbc-perl libcrypt-blowfish-perl libgtk2-gladexml-perl libnet-arp-perl libossp-uuid-perl libcrypt-rijndael-perl libio-stty-perl libossp-uuid16 libgtk2-unique-perl libunique-1.0-0 libgtk2-appindicator-perl

[/pastacode]

3 – install de pac :

[pastacode lang= »bash » message= » » highlight= » » provider= »manual »]

sudo dpkg -i pac-XX-Version.deb

[/pastacode]

4 – ???

5 – profit (Il n’y a plus qu’à tester !)

 

Ajouter un environnement de preproduction à symfony2

Un petit How -to, simple, pour ajouter un environnement à symfony2.

(le cookbook symfony2 sur les environnements)

Par défaut, il y a 3 environnements avec symfony2 :

  • dev
  • prod
  • test

L’environnement de prod (production) est celui chargé lorsqu’on utilise l’url du projet (http:localhost/monProjetSf2/) qui pointe sur web/app.php

Selon la configuration du Vhost, sinon c’est http:localhost/monProjetSf2/web/ si votre vhost pointe sur la racine de votre symfony2.

L’environnement « test » n’a pas de fichier dédié dans web/ il n’est utilisé que pendant les tests fonctionnels, il n’a pas de contrôleur frontal.

Maintenant le contexte:

Je travail en local, c’est l’environnement dev, j’ai une préproduction et un production sur le serveur distant.

Je n’ai pas les mêmes accès base de données, je n’ai pas les même besoins de logging. Composer crée un app/config/parameters.yml avec les configuration de la base de donnée, et le mailer (entre autre) commun à tous les environnements.

J’ajoute donc un environnement preprod, qui va embarquer entre autre la configuration de la base de donnée.

1) app/config/parameters.yml

Je commence par commenter les lignes en rapport avec la BDD , je laisse juste database_driver celui dans mon cas étant identique entre mon local et le distant.

# This file is auto-generated during the composer install
parameters:
    database_driver: pdo_mysql
    #database_host: 127.0.0.1
    #database_port: null
    #database_name: nameOfDatabase
    #database_user: root
    #database_password: MonMotDePasseLocalQuiNestPasBeauAVoir
    mailer_transport: smtp
    mailer_host: 127.0.0.1
    mailer_user: null
    mailer_password: null
    locale: fr
    secret: 'bépoèdljzw'

 

2) fichier de configuration config_preprod.yml

J’ai choisis d’avoir le webProfiler, d’où l’import des routes, les logs « debug » et les warning par mail.

#app/config/config_preprod.yml
imports:
    - { resource: config_prod.yml }

framework:
    router:
        resource: "%kernel.root_dir%/config/routing_preprod.yml"
        strict_requirements: true
    profiler: { only_exceptions: false }

monolog:
    handlers:
        main:
            type:   stream
            path:   "%kernel.logs_dir%/%kernel.environment%.log"
            level:  debug
        swift:
            type:               swift_mailer
            from_email:         monEmailAMoi@bienFaitPourLesRobotsSpammer.com
            to_email:           monEmailAMoi@.com
            subject:            "[LogPreprod]"
            level:              warning

# Doctrine Configuration
doctrine:
    dbal:
        host:     "localhost"
        port:     null
        dbname:   "distantDbName"
        user:     "distantBdUsenName"
        password: "distantUserPassword"
        charset:  UTF8


3) app/config/routing_preprod.yml

J’ai besoin des routes pour le profiler.

_wdt:
    resource: "@WebProfilerBundle/Resources/config/routing/wdt.xml"
    prefix:   /_wdt

_profiler:
    resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml"
    prefix:   /_profiler

_configurator:
    resource: "@SensioDistributionBundle/Resources/config/routing/webconfigurator.xml"
    prefix:   /_configurator

_main:
    resource: routing.yml

4) ne pas oublier config_prod.yml et config_dev.yml

Eux aussi ont besoin d’avoir les configuration de base de données.

# Doctrine Configuration
doctrine:
    dbal:
        host:     "localhost"
        port:     null
        dbname:   "DbName local ou distant prod"
        user:     "BdUserName local ou distant prod"
        password: "UserPassword local ou distant prod"
        charset:  UTF8

5) Le contrôleur frontal pour la preprod web/app_preprod.php

Je me base sur le contrôler de dev, pour avoir le debug :
La sécurité par ip est pas mal, mais si vous ou les testeurs de votre préprod n’ont pas d’ip fixe, vous pouvez commenter et installer simplement un htaccess/htpasswd.

On change simplement le nom de l’environnement.

<?php

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Debug\Debug;

// This check prevents access to debug front controllers that are deployed by accident to production servers.
// Feel free to remove this, extend it, or make something more sophisticated.


if (isset($_SERVER['HTTP_CLIENT_IP'])
    || isset($_SERVER['HTTP_X_FORWARDED_FOR'])
    || !(in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', 'fe80::1', '::1')) || php_sapi_name() === 'cli-server')
) {
    header('HTTP/1.0 403 Forbidden');
    exit('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');
}

$loader = require_once __DIR__.'/../app/bootstrap.php.cache';
Debug::enable();

require_once __DIR__.'/../app/AppKernel.php';

$kernel = new AppKernel('preprod', true);
$kernel->loadClassCache();
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

6) modification app/AppKernel.php

Je veux avoir le profiler, qui n’est chargé que pour le dev, j’ajoute donc une condition pour charger ce dont j’ai besoin pour le debug lors de l’utilisation de l’env de preprod, directement en dessous du bloc conditionnel pour dev et test :

if (in_array($this->getEnvironment(), array('preprod'))) {
    $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
    $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
}

Finis ! A présent, j’ai mon environnement de préproduction, avec les configurations distantes de base de données, le logging fichier + mail spécifique, avec le reste de la configuration le plus proche possible de la prod, comme les assets, twig, les firewall etc.