Fedora-Fr - Communauté francophone Fedora - Linux

Planet de Fedora-Fr : la communauté francophone autour de la distribution Linux Fedora

A propos

Cette page est actualisée toutes les heures.

Cette page est une sélection de blogs autour de Fedora. Fedora-Fr.org décline toute responsabilité au sujet des propos tenus par les auteurs des blogs de ce planet. Leurs propos sont leur entière responsabilité.

Le contenu de ce planet appartient à leurs auteurs respectifs. Merci de consulter leur blogs pour obtenir les licences respectives.

Mot-clefs : Développement

Koschei : intégration continue de la pile PHP dans Fedora

Remi Collet

L'intégration Continue est une préoccupation naturelle de tous développeurs.

La plupart des projets utilisent des outils comme Travis (disponible sous github.com). PHP dispose de sa propre suite de tests. Cela permet de vérifier que les modifications apportées au code source n'apportent pas de régression.

Cela semble suffisant pour une projet, mais pour la "pile PHP" dans sont ensemble, il faut pouvoir :

  • vérifier que la mise à jour d'une dépendance ne casse pas les projets l'utilisant
  • vérifier que la mise à jour de PHP n'entraine pas de régression dans les projets, dès les Release Candidate (les versions de PHP disponibles dans travis/github sont souvent en retard)
  • vérifier que les modifications liés au packaging ne cassent rien

Le projet Koschei permet de répondre à ces besoins en supervisant les mises à jour dans rawhide (la branche développement de Fedora) et en déclenchant une construction à blanc de tous les paquets dépendants.

Comme de nombreux outils et bibliothèques sont disponibles dans Fedora, nous les avons ajoutés pour qu'ils soient surveillés, en particulier :

  • PHP (uniquement la version 5.6 pour l'instant)
  • PHPUnit et toutes ses dépendances
  • Symfony
  • Doctrine
  • Horde
  • etc

Adresse de l'instance : http://koschei.cloud.fedoraproject.org/

Ce nouvel outil aurait pu nous permettre de détecter au plus tôt les régressions introduites dans PHP version 5.4.29 et 5.5.13. Je prévois donc d'importer les prochaines Release Candidate au plus tôt afin de bénéficier de l'outil.

Par exemple, il vient de permettre de détecter un FTBFS de symfony suite à la mise à jour de PHPUnit 4.2.0 (il s'agissait d'un problème spécifique au paquet et déjà corrigé).

Évidement, il est donc nécessaire de disposer de suite de test pour chaque projet et de les exécuter lors de la construction des paquets, mais cela fait, depuis longtemps, partie des bonnes pratiques.

Nous verrons à l'avenir comment l'outil se comporte, mais je pense qu'il apporte un vrai progrès pour l'intégration continue de l'ensemble de la pile PHP.

Il s'agit aussi et surtout d'un outil dont les résultats doivent bénéficier à chaque projet, prouvant l'utilité de la collaboration inter-projets (upstream / downstream), et donnant encore plus d'intérêt, pour chaque projet, à être intégré dans une distribution comme Fedora.

Grunt, Bower, LESS & Bootstrap

Guillaume Kulakowski
Dans mon dernier billet, j'avais évoqué mon engouement pour Bower, une solution de gestion de dépendances web. J'avais alors regretté le fait de ne pas pouvoir exécuter des tâches post-installation afin de retravailler la version distribuée de Bootstrap pour lalléger (comme on peut le faire ici). Comme j'en avais émis l'idée dans ce même billet, j'ai entrepris de ne plus utiliser Bower directement mais de lutiliser au travers de Grunt.

grunt-bower.png

Si vous ne connaissez pas encore Grunt, il s'agit d'un ordonnanceur au même titre que Maven, ant, ou encore Phing, mais basé sur nodejs et utilisant une syntaxe JavaScript.

Installation de Bower & Grunt sous Fedora 20

Après avoir installé nodejs et npm via yum, il faudra installer Bower et Grunt avec npm. En effet, Bower est absent des dépôts Fedora et Grunt est bien disponible mais sans grunt-cli, ce qui en enlève tout lintérêt...

sudo yum install npm -y
sudo npm install bower grunt grunt-cli -g

Notez au passage le fait que l'on fasse l'installation avec l'option -g pour avoir Grunt et Bower installés au niveau du système (/usr/lib/node_modules). Les autres installations se feront toujours à l'aide de npm mais sans cette option. Ceci permet de tout avoir à la racine du projet web, dans un répertoire node_modules (qu'il faudra exclure de la gestion de sources).

Voila, maintenant on rajoute un fichier packages.json et on installe les dépendances avec un npm install :

{
  "name": "Boldy",
  "version": "1.1.2",
  "description": "The Boldy theme for Dotclear",
  "repository": {
    "type": "git",
  },
  "author": "Guillaume Kulakowski",
  "homepage": "http://www.llaumgui.com",
  "devDependencies": {
    "bower": "latest",
    "grunt": "~0.4.2",
    "grunt-contrib-uglify": "latest",
    "grunt-contrib-concat": "latest",
    "grunt-contrib-less": "latest",
    "grunt-bower-task": "latest",
    "grunt-contrib-watch": "latest"
    "grunt-contrib-cssmin": "latest"
  }
}

J'ai maintenant un environnement de travail avec Bower et Grunt ainsi que les plugins nécessaires à mon projet.

Le fichier Gruntfile.js

La configuration des tâches Grunt passe par le fichier Gruntfile.js, j'ai mis le mien et vais en détailler les tâches :

module.exports = function(grunt) {
  'use strict';
 
  // Force use of Unix newlines
  grunt.util.linefeed = '\n';
 
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    bowerrc: grunt.file.readJSON('.bowerrc'),
 
    // Path configuration from Gruntfile.js
    dirs: {
      'vendor': '<%= bowerrc.directory %>',
      'bootstrap': {
        'js': '<%= dirs.vendor %>/bootstrap/js',
        'less': '<%= dirs.vendor %>/bootstrap/less'
      },
      'css': 'css',
      'less': 'less',
      'js': 'js'
    },
 
    src: {
      output: {
        'bootstrap': {
          'js': '<%= dirs.vendor %>/bootstrap/my/js/bootstrap.js',
          'css': '<%= dirs.vendor %>/bootstrap/my/css/bootstrap.css'
        },
        'boldy': {
          css: {
            'screen': '<%= dirs.css %>/screen.css',
            'indefero': '<%= dirs.css %>/indefero.css'
          },
          js: '<%= dirs.js %>/global.js'
        }
      },
 
      input: {
        bootstrap: {
          'js': [
            '<%= dirs.bootstrap.js %>/dropdown.js',
            '<%= dirs.bootstrap.js %>/tooltip.js',
            '<%= dirs.bootstrap.js %>/collapse.js',
            '<%= dirs.bootstrap.js %>/transition.js',
            '<%= dirs.bootstrap.js %>/carousel.js'
          ],
          'less': [
            '<%= dirs.less %>/bootstrap.less'
          ]
        },
        indefero: '<%= dirs.css %>/indefero.src.css',
        less: '<%= dirs.less %>/boldy_boot.less',
        js: [
          '<%= dirs.vendor %>/jquery-cookie/jquery.cookie.js',
          '<%= src.output.bootstrap.js %>',
          '<%= dirs.vendor %>/scroll-to-top/jquery.scrollToTop.min.js',
          '<%= dirs.js %>/js/post.js',
          '<%= dirs.vendor %>/async-gravatars/async-gravatars.js',
          '<%= dirs.vendor %>/jquery-colorbox/jquery.colorbox-min.js',
          '<%= dirs.vendor %>/nwxforms/src/nwxforms.js',
          '<%= dirs.js %>/boldy.js'
        ]
      },
    },
 
    // Banner
    banner: '/*!\n' +
              ' * Boldy for Dotclear v<%= pkg.version %>\n' +
              ' * Original theme by Site5 (http://www.s5themes.com)\n' +
              ' * Under a GPLv2 http://www.gnu.org/licenses/gpl-2.0.txt\n' +
              ' * Ported on Dotclear by <%= pkg.author %> (<%= pkg.homepage %>)\n' +
              ' * Copyright 2012-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
              ' */',
 
 
 
    /********************************** Task **********************************/
    // Bower
    bower: {
      install: {
        options: {
          targetDir: '<%= dirs.vendor %>',
          cleanTargetDir: true,
          layout: 'byComponent',
          install: true,
          copy: false,
          verbose: true
        }
      }
    },
 
 
    // Concatenation
    concat: {
      bootstrap: {
        src: '<%= src.input.bootstrap.js %>',
        dest: '<%= src.output.bootstrap.js %>'
      },
    },
 
 
    // LESS
    less: {
      boldy: {
        options: {
          compress: true,
          cleancss: true,
          report: 'gzip',
          strictImports: true,
        },
        files: { '<%= src.output.boldy.css.screen %>': '<%= src.input.less %>'},
      },
      boldy_debug: {
          options: {
            compress: false,
            cleancss: false,
            report: 'none',
            strictImports: true,
          },
          files: { '<%= src.output.boldy.css.screen %>': '<%= src.input.less %>'},
      },
      bootstrap: {
        files: { '<%= src.output.bootstrap.css %>': '<%= src.input.bootstrap.less %>'}
      }
    },
 
 
    // Watcher
    watch: {
      boldy: {
        files: ['less/*.less'],
        tasks: ['less:boldy_debug'],
        options: {
          spawn: false,
        },
      },
    },
 
 
    // CSSmin
    cssmin: {
      boldy: {
        options: {
          report: 'gzip',
          banner: '<%= banner %>'
        },
        files: {
          '<%= src.output.boldy.css.indefero %>': '<%= src.input.indefero %>'
        }
      }
    },
 
 
    // Uglify
    uglify: {
      boldy: {
        options: {
          report: 'gzip',
          banner: '<%= banner %>'
        },
        files: {
          '<%= src.output.boldy.js %>': '<%= src.input.js %>',
        }
      }
    }
 
  });
 
 
  // Load plugins
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-bower-task');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
 
 
  // Callable tasks
  grunt.registerTask('default', ['boldy', 'cssmin', 'concat:bootstrap', 'uglify']);
  grunt.registerTask('w', ['watch:boldy']);
 
  grunt.registerTask('bootstrap', ['concat:bootstrap', 'less:bootstrap']);
  grunt.registerTask('boldy', ['less:boldy']);
 
  grunt.registerTask('full', ['bower', 'bootstrap', 'default']);
  grunt.registerTask('all', ['full']); // Alias
};

Tâche bower

Via le plugin grunt-bower-task.

Cette tâche est là pour exécuter Bower au travers de Grunt, notez que le plugin n'interprète pas le .bowerrc, je l'ai donc chargé moi même (bowerrc: grunt.file.readJSON('.bowerrc')) afin de passer les mêmes options à la tâche Grunt qu'à Bower.

Tâche Bootstrap

Via les plugins grunt-contrib-less & grunt-contrib-concat.

Le but de cette tâche est de ne pas utiliser la version distribuée de Bootstrap mais de reconstruire ma propre version. Pour celà,

  • je reconstruis le JavaScript à partir des sources et du plugin concat,
  • je recompile les CSS à partir d'une version allégée du bootstrap.less :
/* -- BEGIN LICENSE BLOCK ---------------------------------------
# This file is part of Boldy, a theme for Dotclear
#
# Theme by Site5 (http://www.s5themes.com)
# under a GPLv2 http://www.gnu.org/licenses/gpl-2.0.txt
# Ported on Dotclear by Guillaume Kulakowski (http://www.llaumgui.com)
#
# -- END LICENSE BLOCK ----------------------------------------- */
 
// Core variables and mixins
@import "../vendor/bootstrap/less/variables.less";
@import "../vendor/bootstrap/less/mixins.less";
 
// My override
@import "bootstrap_variables.less";
//@import "bootstrap_mixins.less";
 
// Reset
@import "../vendor/bootstrap/less/normalize.less";
@import "../vendor/bootstrap/less/print.less";
 
// Core CSS
@import "../vendor/bootstrap/less/scaffolding.less";
@import "../vendor/bootstrap/less/type.less";
@import "../vendor/bootstrap/less/code.less";
@import "../vendor/bootstrap/less/grid.less";
//@import "../vendor/bootstrap/less/tables.less";
@import "../vendor/bootstrap/less/forms.less";
@import "../vendor/bootstrap/less/buttons.less";
 
// Components
@import "../vendor/bootstrap/less/component-animations.less";
//@import "../vendor/bootstrap/less/glyphicons.less";
@import "../vendor/bootstrap/less/dropdowns.less";
//@import "../vendor/bootstrap/less/button-groups.less";
//@import "../vendor/bootstrap/less/input-groups.less";
@import "../vendor/bootstrap/less/navs.less";
@import "../vendor/bootstrap/less/navbar.less";
//@import "../vendor/bootstrap/less/breadcrumbs.less";
@import "../vendor/bootstrap/less/pagination.less";
@import "../vendor/bootstrap/less/pager.less";
//@import "../vendor/bootstrap/less/labels.less";
//@import "../vendor/bootstrap/less/badges.less";
//@import "../vendor/bootstrap/less/jumbotron.less";
@import "../vendor/bootstrap/less/thumbnails.less";
@import "../vendor/bootstrap/less/alerts.less";
//@import "../vendor/bootstrap/less/progress-bars.less";
//@import "../vendor/bootstrap/less/media.less";
@import "../vendor/bootstrap/less/list-group.less";
@import "../vendor/bootstrap/less/panels.less";
@import "../vendor/bootstrap/less/wells.less";
//@import "../vendor/bootstrap/less/close.less";
 
// Components w/ JavaScript
//@import "../vendor/bootstrap/less/modals.less";
@import "../vendor/bootstrap/less/tooltip.less";
//@import "../vendor/bootstrap/less/popovers.less";
@import "../vendor/bootstrap/less/carousel.less";
 
// Utility classes
@import "../vendor/bootstrap/less/utilities.less";
@import "../vendor/bootstrap/less/responsive-utilities.less";

Remarquez que j'utilise mes propres variables après celles de Bootstrap afin de les écraser et de modifier Bootstrap directement à la source.

Tâche Boldy

Via les plugins grunt-contrib-uglify, grunt-contrib-less & grunt-contrib-cssmin.

Comme en recompilant Bootstrap à partir des sources LESS j'ai adhéré au concept de LESS, j'ai réécrit le thème de mon blog en LESS.

Cette tâche boldy sert donc :

  • à générer le CSS minifié à partir des sources LESS (grunt-contrib-less),
  • à minifier la CSS pour mon thème indefero qui reste en CSS (grunt-contrib-cssmin),
  • et à minifier les Javascripts (grunt-contrib-uglify).

Tâche watch

Via le plugin grunt-contrib-watch.

Pour compiler du LESS en CSS, il y a 2 méthodes.

  • soit passer par le less.js qui va parser en JS les fichiers LESS à chaque chargement de page (2 à 3 secondes pour mon blog),
  • soit passer par une tâche watch dans Grunt, qui va écouter les modifications sur les fichiers LESS et recompiler un CSS.

C'est cette dernière solution que j'ai retenu et donc c'est à cela que sert la tâche watch.

En conclusion

Pour conclure, je dirais que la mise en place de ces outils (surtout pour la première fois) est certes chronophage, mais une fois que ça tourne, pour mettre à jour les libs (Bootstrap, jQuery, etc.), une seule ligne de commande suffit (grunt full). De plus, une fois Grunt et Bower en place (et maîtrisés), passer de CSS à LESS a été super simple.

Pour finir, je regrette presque que nodejs n'ait pas été inclus dans Fedora plus tôt car il propose de formidables outils indispensables aux développeurs (front) web.

Et pour voir ce que ça donne, une petite vidéo:

Grunt, Bower, LESS & Bootstrap

Guillaume Kulakowski
Dans mon dernier billet, j'avais évoqué mon engouement pour Bower, une solution de gestion de dépendances web. J'avais alors regretté le fait de ne pas pouvoir exécuter des tâches post-installation afin de retravailler la version distribuée de Bootstrap pour lalléger (comme on peut le faire ici). Comme j'en avais émis l'idée dans ce même billet, j'ai entrepris de ne plus utiliser Bower directement mais de lutiliser au travers de Grunt.

grunt-bower.png

Si vous ne connaissez pas encore Grunt, il s'agit d'un ordonnanceur au même titre que Maven, ant, ou encore Phing, mais basé sur nodejs et utilisant une syntaxe JavaScript.

Installation de Bower & Grunt sous Fedora 20

Après avoir installé nodejs et npm via yum, il faudra installer Bower et Grunt avec npm. En effet, Bower est absent des dépôts Fedora et Grunt est bien disponible mais sans grunt-cli, ce qui en enlève tout lintérêt...

sudo yum install npm -y
sudo npm install bower grunt grunt-cli -g

Notez au passage le fait que l'on fasse l'installation avec l'option -g pour avoir Grunt et Bower installés au niveau du système (/usr/lib/node_modules). Les autres installations se feront toujours à l'aide de npm mais sans cette option. Ceci permet de tout avoir à la racine du projet web, dans un répertoire node_modules (qu'il faudra exclure de la gestion de sources).

Voila, maintenant on rajoute un fichier packages.json et on installe les dépendances avec un npm install :

{
  "name": "Boldy",
  "version": "1.1.2",
  "description": "The Boldy theme for Dotclear",
  "repository": {
    "type": "git",
  },
  "author": "Guillaume Kulakowski",
  "homepage": "http://www.llaumgui.com",
  "devDependencies": {
    "bower": "latest",
    "grunt": "~0.4.2",
    "grunt-contrib-uglify": "latest",
    "grunt-contrib-concat": "latest",
    "grunt-contrib-less": "latest",
    "grunt-bower-task": "latest",
    "grunt-contrib-watch": "latest"
    "grunt-contrib-cssmin": "latest"
  }
}

J'ai maintenant un environnement de travail avec Bower et Grunt ainsi que les plugins nécessaires à mon projet.

Le fichier Gruntfile.js

La configuration des tâches Grunt passe par le fichier Gruntfile.js, j'ai mis le mien et vais en détailler les tâches :

module.exports = function(grunt) {
  'use strict';
 
  // Force use of Unix newlines
  grunt.util.linefeed = '\n';
 
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    bowerrc: grunt.file.readJSON('.bowerrc'),
 
    // Path configuration from Gruntfile.js
    dirs: {
      'vendor': '<%= bowerrc.directory %>',
      'bootstrap': {
        'js': '<%= dirs.vendor %>/bootstrap/js',
        'less': '<%= dirs.vendor %>/bootstrap/less'
      },
      'css': 'css',
      'less': 'less',
      'js': 'js'
    },
 
    src: {
      output: {
        'bootstrap': {
          'js': '<%= dirs.vendor %>/bootstrap/my/js/bootstrap.js',
          'css': '<%= dirs.vendor %>/bootstrap/my/css/bootstrap.css'
        },
        'boldy': {
          css: {
            'screen': '<%= dirs.css %>/screen.css',
            'indefero': '<%= dirs.css %>/indefero.css'
          },
          js: '<%= dirs.js %>/global.js'
        }
      },
 
      input: {
        bootstrap: {
          'js': [
            '<%= dirs.bootstrap.js %>/dropdown.js',
            '<%= dirs.bootstrap.js %>/tooltip.js',
            '<%= dirs.bootstrap.js %>/collapse.js',
            '<%= dirs.bootstrap.js %>/transition.js',
            '<%= dirs.bootstrap.js %>/carousel.js'
          ],
          'less': [
            '<%= dirs.less %>/bootstrap.less'
          ]
        },
        indefero: '<%= dirs.css %>/indefero.src.css',
        less: '<%= dirs.less %>/boldy_boot.less',
        js: [
          '<%= dirs.vendor %>/jquery-cookie/jquery.cookie.js',
          '<%= src.output.bootstrap.js %>',
          '<%= dirs.vendor %>/scroll-to-top/jquery.scrollToTop.min.js',
          '<%= dirs.js %>/js/post.js',
          '<%= dirs.vendor %>/async-gravatars/async-gravatars.js',
          '<%= dirs.vendor %>/jquery-colorbox/jquery.colorbox-min.js',
          '<%= dirs.vendor %>/nwxforms/src/nwxforms.js',
          '<%= dirs.js %>/boldy.js'
        ]
      },
    },
 
    // Banner
    banner: '/*!\n' +
              ' * Boldy for Dotclear v<%= pkg.version %>\n' +
              ' * Original theme by Site5 (http://www.s5themes.com)\n' +
              ' * Under a GPLv2 http://www.gnu.org/licenses/gpl-2.0.txt\n' +
              ' * Ported on Dotclear by <%= pkg.author %> (<%= pkg.homepage %>)\n' +
              ' * Copyright 2012-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
              ' */',
 
 
 
    /********************************** Task **********************************/
    // Bower
    bower: {
      install: {
        options: {
          targetDir: '<%= dirs.vendor %>',
          cleanTargetDir: true,
          layout: 'byComponent',
          install: true,
          copy: false,
          verbose: true
        }
      }
    },
 
 
    // Concatenation
    concat: {
      bootstrap: {
        src: '<%= src.input.bootstrap.js %>',
        dest: '<%= src.output.bootstrap.js %>'
      },
    },
 
 
    // LESS
    less: {
      boldy: {
        options: {
          compress: true,
          cleancss: true,
          report: 'gzip',
          strictImports: true,
        },
        files: { '<%= src.output.boldy.css.screen %>': '<%= src.input.less %>'},
      },
      boldy_debug: {
          options: {
            compress: false,
            cleancss: false,
            report: 'none',
            strictImports: true,
          },
          files: { '<%= src.output.boldy.css.screen %>': '<%= src.input.less %>'},
      },
      bootstrap: {
        files: { '<%= src.output.bootstrap.css %>': '<%= src.input.bootstrap.less %>'}
      }
    },
 
 
    // Watcher
    watch: {
      boldy: {
        files: ['less/*.less'],
        tasks: ['less:boldy_debug'],
        options: {
          spawn: false,
        },
      },
    },
 
 
    // CSSmin
    cssmin: {
      boldy: {
        options: {
          report: 'gzip',
          banner: '<%= banner %>'
        },
        files: {
          '<%= src.output.boldy.css.indefero %>': '<%= src.input.indefero %>'
        }
      }
    },
 
 
    // Uglify
    uglify: {
      boldy: {
        options: {
          report: 'gzip',
          banner: '<%= banner %>'
        },
        files: {
          '<%= src.output.boldy.js %>': '<%= src.input.js %>',
        }
      }
    }
 
  });
 
 
  // Load plugins
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-bower-task');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
 
 
  // Callable tasks
  grunt.registerTask('default', ['boldy', 'cssmin', 'concat:bootstrap', 'uglify']);
  grunt.registerTask('w', ['watch:boldy']);
 
  grunt.registerTask('bootstrap', ['concat:bootstrap', 'less:bootstrap']);
  grunt.registerTask('boldy', ['less:boldy']);
 
  grunt.registerTask('full', ['bower', 'bootstrap', 'default']);
  grunt.registerTask('all', ['full']); // Alias
};

Tâche bower

Via le plugin grunt-bower-task.

Cette tâche est là pour exécuter Bower au travers de Grunt, notez que le plugin n'interprète pas le .bowerrc, je l'ai donc chargé moi même (bowerrc: grunt.file.readJSON('.bowerrc')) afin de passer les mêmes options à la tâche Grunt qu'à Bower.

Tâche Bootstrap

Via les plugins grunt-contrib-less & grunt-contrib-concat.

Le but de cette tâche est de ne pas utiliser la version distribuée de Bootstrap mais de reconstruire ma propre version. Pour celà,

  • je reconstruis le JavaScript à partir des sources et du plugin concat,
  • je recompile les CSS à partir d'une version allégée du bootstrap.less :
/* -- BEGIN LICENSE BLOCK ---------------------------------------
# This file is part of Boldy, a theme for Dotclear
#
# Theme by Site5 (http://www.s5themes.com)
# under a GPLv2 http://www.gnu.org/licenses/gpl-2.0.txt
# Ported on Dotclear by Guillaume Kulakowski (http://www.llaumgui.com)
#
# -- END LICENSE BLOCK ----------------------------------------- */
 
// Core variables and mixins
@import "../vendor/bootstrap/less/variables.less";
@import "../vendor/bootstrap/less/mixins.less";
 
// My override
@import "bootstrap_variables.less";
//@import "bootstrap_mixins.less";
 
// Reset
@import "../vendor/bootstrap/less/normalize.less";
@import "../vendor/bootstrap/less/print.less";
 
// Core CSS
@import "../vendor/bootstrap/less/scaffolding.less";
@import "../vendor/bootstrap/less/type.less";
@import "../vendor/bootstrap/less/code.less";
@import "../vendor/bootstrap/less/grid.less";
//@import "../vendor/bootstrap/less/tables.less";
@import "../vendor/bootstrap/less/forms.less";
@import "../vendor/bootstrap/less/buttons.less";
 
// Components
@import "../vendor/bootstrap/less/component-animations.less";
//@import "../vendor/bootstrap/less/glyphicons.less";
@import "../vendor/bootstrap/less/dropdowns.less";
//@import "../vendor/bootstrap/less/button-groups.less";
//@import "../vendor/bootstrap/less/input-groups.less";
@import "../vendor/bootstrap/less/navs.less";
@import "../vendor/bootstrap/less/navbar.less";
//@import "../vendor/bootstrap/less/breadcrumbs.less";
@import "../vendor/bootstrap/less/pagination.less";
@import "../vendor/bootstrap/less/pager.less";
//@import "../vendor/bootstrap/less/labels.less";
//@import "../vendor/bootstrap/less/badges.less";
//@import "../vendor/bootstrap/less/jumbotron.less";
@import "../vendor/bootstrap/less/thumbnails.less";
@import "../vendor/bootstrap/less/alerts.less";
//@import "../vendor/bootstrap/less/progress-bars.less";
//@import "../vendor/bootstrap/less/media.less";
@import "../vendor/bootstrap/less/list-group.less";
@import "../vendor/bootstrap/less/panels.less";
@import "../vendor/bootstrap/less/wells.less";
//@import "../vendor/bootstrap/less/close.less";
 
// Components w/ JavaScript
//@import "../vendor/bootstrap/less/modals.less";
@import "../vendor/bootstrap/less/tooltip.less";
//@import "../vendor/bootstrap/less/popovers.less";
@import "../vendor/bootstrap/less/carousel.less";
 
// Utility classes
@import "../vendor/bootstrap/less/utilities.less";
@import "../vendor/bootstrap/less/responsive-utilities.less";

Remarquez que j'utilise mes propres variables après celles de Bootstrap afin de les écraser et de modifier Bootstrap directement à la source.

Tâche Boldy

Via les plugins grunt-contrib-uglify, grunt-contrib-less & grunt-contrib-cssmin.

Comme en recompilant Bootstrap à partir des sources LESS j'ai adhéré au concept de LESS, j'ai réécrit le thème de mon blog en LESS.

Cette tâche boldy sert donc :

  • à générer le CSS minifié à partir des sources LESS (grunt-contrib-less),
  • à minifier la CSS pour mon thème indefero qui reste en CSS (grunt-contrib-cssmin),
  • et à minifier les Javascripts (grunt-contrib-uglify).

Tâche watch

Via le plugin grunt-contrib-watch.

Pour compiler du LESS en CSS, il y a 2 méthodes.

  • soit passer par le less.js qui va parser en JS les fichiers LESS à chaque chargement de page (2 à 3 secondes pour mon blog),
  • soit passer par une tâche watch dans Grunt, qui va écouter les modifications sur les fichiers LESS et recompiler un CSS.

C'est cette dernière solution que j'ai retenu et donc c'est à cela que sert la tâche watch.

En conclusion

Pour conclure, je dirais que la mise en place de ces outils (surtout pour la première fois) est certes chronophage, mais une fois que ça tourne, pour mettre à jour les libs (Bootstrap, jQuery, etc.), une seule ligne de commande suffit (grunt full). De plus, une fois Grunt et Bower en place (et maîtrisés), passer de CSS à LESS a été super simple.

Pour finir, je regrette presque que nodejs n'ait pas été inclus dans Fedora plus tôt car il propose de formidables outils indispensables aux développeurs (front) web.

Et pour voir ce que ça donne, une petite vidéo:

Bower pour gérer les dépendances du thème de mon blog

Guillaume Kulakowski

C'est à la lecture de cet excellent article de Raphaël Goetter (dont le livre "CSS avancées vers HTML5 et CSS3" fut un de mes livres de chevet) sur Alsacreations que j'ai décidé de mettre en place Bower pour gérer les dépendances du thème de mon blog.

Pour ceux qui ne connaissent pas (encore) Bower et qui n'ont pas eu la curiosité de cliquer sur les liens ci-dessus: sachez juste que Bower est un gestionnaire de paquets pour le web (JS, CSS, etc). On pourrait même faire le raccourci en disant que Bower est au web ce que Composer est à PHP.

Les avantages immédiats que j'y trouve :

  • Un simple bower update et je mets à jour toutes les librairies tierces.
  • Le cloisonnement entre mon code et le code tiers. En effet, ce dernier se trouve dans un répertoire séparé: vendor (les habitudes de symfony2 ;-)).
  • Comme au travail je fais beaucoup de Symfony2 et d'eZ Publish 5, j'utilise pas mal composer, avec Bower je ne suis donc pas perdu.

Les inconvénients que j'y trouve :

  • Bower ne fait que récupérer les dépendances. Il ne permet pas (encore), comme (entre autre) Composer, de lancer des scripts post-installation ou post-mise-à-jour.
  • Bower récupère Bootstrap dans son intégralité alors que moi, j'aimerais l'optimiser et en réduire le poids.

Avant de pouvoir passer en production cette nouvelle version de mon blog, qui se doit d'être ISO avec la précédente, il me reste donc à régler quelques problèmes :

  • Optimiser ou reconstruire Bootstrap après l'avoir récupéré.
  • Minifier tout mon code.
  • Réfléchir à comment le pousser en production.
    En effet, Bower tournant sous node.js, je n'ai pas spécialement envie de déployer node sur mon serveur. Jusquà présent jutilisais GIT pour déployer le code source et je lançais juste un script shell pour minifier CSS et JS via YUI Compressor. Là je pense que je vais, soit devoir héberger les scripts minifiés sur GIT (ce qui n'est pas une bonne pratique) soit mettre en place un Jenkins (pour la construction du paquet) et un Capistrano (pour le déploiement). Autant au boulot le couple Jenkins/Capistrano m'est indispensable autant là pour déployer une fois de temps en temps une nouvelle version de mon blog, je pense que je vais opter pour les fichiers dans GIT.

Pour ceux que ça intéressent, ma configuration Bower :

# cat .bowerrc
{
    "directory": "vendor"
}
# cat bower.json
{
    "name": "Boldy",
    "version": "1.1",
    "main": [
        "css/screen.css",
        "css/indefero.css",
        "js/global.js"
    ],
    "ignore": [
        ".jshintrc",
        "**/*.txt"
    ],
    "private": true,
    "dependencies": {
        "jquery": "~1.*",
        "bootstrap": "3.0.*",
        "jquery.browser": "latest",
        "jquery-colorbox": "latest",
        "jquery-cookie": "latest",
        "scroll-to-top": "latest",
        "headjs": "latest"
    }
}

Bower pour gérer les dépendances du thème de mon blog

Guillaume Kulakowski

C'est à la lecture de cet excellent article de Raphaël Goetter (dont le livre "CSS avancées vers HTML5 et CSS3" fut un de mes livres de chevet) sur Alsacreations que j'ai décidé de mettre en place Bower pour gérer les dépendances du thème de mon blog.

Pour ceux qui ne connaissent pas (encore) Bower et qui n'ont pas eu la curiosité de cliquer sur les liens ci-dessus: sachez juste que Bower est un gestionnaire de paquets pour le web (JS, CSS, etc). On pourrait même faire le raccourci en disant que Bower est au web ce que Composer est à PHP.

Les avantages immédiats que j'y trouve :

  • Un simple bower update et je mets à jour toutes les librairies tierces.
  • Le cloisonnement entre mon code et le code tiers. En effet, ce dernier se trouve dans un répertoire séparé: vendor (les habitudes de symfony2 ;-)).
  • Comme au travail je fais beaucoup de Symfony2 et d'eZ Publish 5, j'utilise pas mal composer, avec Bower je ne suis donc pas perdu.

Les inconvénients que j'y trouve :

  • Bower ne fait que récupérer les dépendances. Il ne permet pas (encore), comme (entre autre) Composer, de lancer des scripts post-installation ou post-mise-à-jour.
  • Bower récupère Bootstrap dans son intégralité alors que moi, j'aimerais l'optimiser et en réduire le poids.

Avant de pouvoir passer en production cette nouvelle version de mon blog, qui se doit d'être ISO avec la précédente, il me reste donc à régler quelques problèmes :

  • Optimiser ou reconstruire Bootstrap après l'avoir récupéré.
  • Minifier tout mon code.
  • Réfléchir à comment le pousser en production.
    En effet, Bower tournant sous node.js, je n'ai pas spécialement envie de déployer node sur mon serveur. Jusquà présent jutilisais GIT pour déployer le code source et je lançais juste un script shell pour minifier CSS et JS via YUI Compressor. Là je pense que je vais, soit devoir héberger les scripts minifiés sur GIT (ce qui n'est pas une bonne pratique) soit mettre en place un Jenkins (pour la construction du paquet) et un Capistrano (pour le déploiement). Autant au boulot le couple Jenkins/Capistrano m'est indispensable autant là pour déployer une fois de temps en temps une nouvelle version de mon blog, je pense que je vais opter pour les fichiers dans GIT.

Pour ceux que ça intéressent, ma configuration Bower :

# cat .bowerrc
{
    "directory": "vendor"
}
# cat bower.json
{
    "name": "Boldy",
    "version": "1.1",
    "main": [
        "css/screen.css",
        "css/indefero.css",
        "js/global.js"
    ],
    "ignore": [
        ".jshintrc",
        "**/*.txt"
    ],
    "private": true,
    "dependencies": {
        "jquery": "~1.*",
        "bootstrap": "3.0.*",
        "jquery.browser": "latest",
        "jquery-colorbox": "latest",
        "jquery-cookie": "latest",
        "scroll-to-top": "latest",
        "headjs": "latest"
    }
}

Barcamp "Performances web"

Guillaume Kulakowski

A mon travail, on essai de faire une fois par mois un déjeuner autour d'une thématique. J'étais déjà intervenu il y a quelques mois pour présenter GIT, avec pour sanction immédiate que GIT soit devenu notre gestionnaire de sources par défaut et certain de nos grosses TMA ont déjà migré dessus.

Bref, ce mois ci la thématique était autour des performances web avec une présentation dense (plus de 1H30) ayant pour but de présenter les bonnes pratiques à nos nouveaux membres (c'est comme ça qu'on appel les salariés chez CGI) ainsi que de faire une piqure de rappel aux anciens et peut être leur rappeler pourquoi.

La présentation sous reveals est disponible sous licence Creative Commons Attribution-ShareAlike 4.0 International : Performances web.

Barcamp "Performances web"

Guillaume Kulakowski

A mon travail, on essai de faire une fois par mois un déjeuner autour d'une thématique. J'étais déjà intervenu il y a quelques mois pour présenter GIT, avec pour sanction immédiate que GIT soit devenu notre gestionnaire de sources par défaut et certain de nos grosses TMA ont déjà migré dessus.

Bref, ce mois ci la thématique était autour des performances web avec une présentation dense (plus de 1H30) ayant pour but de présenter les bonnes pratiques à nos nouveaux membres (c'est comme ça qu'on appel les salariés chez CGI) ainsi que de faire une piqure de rappel aux anciens et peut être leur rappeler pourquoi.

La présentation sous reveals est disponible sous licence Creative Commons Attribution-ShareAlike 4.0 International : Performances web.

Introduction aux Software Collections

Remi Collet

Dans le cadre l'Open Source Developers' Conference 2013 (Open World Forum 2013), j'ai pu présenter une conférence Introduction aux Software Collections.

Je pense que le sujet a intéressé les personnes présentes et j'ai pu discuté et répondre à de nombreuses questions sur les SCL sur les stands Fedora et Red Hat.

La présentation (en anglais) est disponible : Software Collections Introduction

La démonstration d'un serveur RHEL-6 avec Apache 2.4 en avant et 3 services FPM (PHP 5.3, 5.4 et 5.5) en arrière m'a semble intéressante pour démontrer l'utilité et la simplicité des SCL.

La conférence a été filmée, je posterai dès que possible, le lien.

Support de JSON dans PHP

Remi Collet

Une compagne de FUD relaie depuis quelques jours une information erronée sur la suppression du support de JSON dans PHP 5.5.

Il me semble donc important d'essayer de clarifier les choses.

L'extension PECL json offre depuis 2006 le support du format JSON. Cette extension a été intégrée dans PHP 5.2.0.

Il n'y a aucun projet de supprimer ce support de PHP.

Documentation : Notation Objet JavaScript

Exemple d'utilisation de json_encode :

php -r 'var_dump(json_encode(array("foo","bar")));'
string(13) "["foo","bar"]"

Exemple d'utilisation de json_deceode :

php -r 'var_dump(json_decode("[\"foo\",\"bar\"]"));'
array(2) {
[0] =>
string(3) "foo"
[1] =>
string(3) "bar"
}

Lors d'une revue de licence, le projet Debian a découvert un problème dans cette extension et rapporté le problème au projet PHP (Bug #63520). En effet le code provenant de json.org n'est pas libre, car il contient une restriction contraire à la liberté 0 (utilisation) : "The Software shall be used for Good, not Evil". (Inutile de discuter ce fait, la FSF a statué dans ce sens, et cet avis est suivi par la plupart des distributions Linux).

J'ai pu rapidement libérer la partie encodage en PHP 5.4.10 en supprimant du code non-libre et inutile (Bug #63588).

Pour l'analyseur, j'ai commencer l'écriture d'une nouvelle extension PECL jsonc utilisant la bibliothèque libre json-c (sous license MIT). Le code de l'encodeur est identique à celui de PHP 5.5. Ce développement a été l'occasion de contribuer directement à la bibliothèque :

  • PR51 : profondeur dynamique de l'analyseur
  • PR52 : analyse des nombres flottants indépendante de la locale
  • PR90 : les entiers ne peuvent commencer par zéro
  • PR94 : les chaines entre apostrophe ne sont ps autorisées
  • PR94 : les commentaires ne sont pas autorisés
  • PR94 : les caractères supplémentaires après les données ne sont pas autorisés
  • etc

La bibliothèque json-c offrant un analyseur incrémental, j'en ai profité pour enrichir l'extension de cette fonctionalité.

Exemple d'utilisation de la classe JsonIncrementalParser :

$parser = new JsonIncrementalParser();
$fic = fopen("somefile.json", "r");
do {
$buf = fgets($fic);
$ret = $parser->parse($buf);
} while ($buf && ($ret==JsonIncrementalParser::JSON_PARSER_CONTINUE));
$result = $parser->get();

On peut aussi analyser directement un fichier sans le charger en mémoire :

$ret = $parser->parseFile("somefile.json");
$result = $parser->get();

L'analyseur dispose aussi de 2 modes de sévérité, le mode strict, utilisé par défaut, et proche de celui originel de PHP et un mode standard plus permissif, qui autorise, par exemple, les commentaires ou des listes mal construites. L'utilisation de la nouvelle option JSON_PARSER_NOTSTRICT  permet d'accepter par exemple le fichier de configuration suivant (ce qui peut être utile) :

/*
Configuration file
*/
{
"temp": "\/tmp", // directory
"debug": true, // boolean
}

Actuellement, avec la version 1.3.1 (beta), il reste quelque différences détectées par la suite de tests de l'extension originale qui sont corrigées dans la version 1.3.2 (stable) en cours de préparation et qui devrait sortir très prochainement.

Le seule différence restant et l'implémentation partielle de l'analyse des grands entiers (et de l'option JSON_BIGINT_AS_STRING). Elle ne fonctionnera que pour les versions 32bits et pour les entiers pouvant être représentés sur 64bits (non gérés par PHP, donc récupérés en chaine ou en nombre flottant). Sachant qu'aucun encodeur ne générera ces valeurs, cela ne me semble pas un problème critique.

Distributions Linux :

Debian a supprimé l'extension non libre à partir de PHP 5.5 et fournit désormais le paquet php5-json (installé par défaut) construit à partir des sources de l'extension jsonc.

Fedora a supprimé l'extension non libre à partir de PHP 5.5 / Fedora 19 et fournit désormais le paquet php-pecl-jsonc (installé par défaut).

Mageia a remplacé l'extension non libre par l'extension jsonc (commit 442375).

Conclusion

Quelque soit la méthode d'installation de PHP ou le système utilisé, le support de PHP sera présent.

Pool de thread avec limite de parallélisation

Patrice Ferlet

Je travaillais cette semaine sur un script python qui récupérait des centaines de milliers de données depuis un serveur. Pour gagner en temps de calcul, j'ai décidé de faire des threads. Limitant la taille de mes tâches parallèles à un nombre spécifique, le script bloquait... car j'utilisais des fonctions threadées qui lançaient récursivement d'autres threads. Sauf que la Queue que j'utilisais pour faire le pool se bloquait si trop de threads se lançaient en même temps attendant indéfiniment que quelqu'un veuille bien lire la Queue... J'ai alors trouvé ma solution, et je la partage avec vous. Je vous explique le problème, je vous montre un exemple, et ensuite on voit la solution.

J'ai cherché un peu partout sur le net, et je n'ai pas trouvé de solution à mon problème. Ainsi, pour vous montrer ce qui coince, je vous ai préparé un exemple de script très simple. Le script suivant lance 5 Threads qui eux même vont en créer... Pour limiter le nombre de tâche parallèles je passe par une classe assez connue (depuis python recipes) qui utilise la classe Queue avec une limite de taille.

Chaque fois qu'on ajoute une tâche dans la classe, la Queue est remplie... les workers vont alors lire en boucle dans cette dernière pour lancer la tâche et les arguments stoqués. Tant que la Queue est remplie, il faut attendre que quelqu'un la lise... sinon l'appel à "put" reste bloqué.

Voici la classe de base que j'ai utilisée (et modifié):

# -*- encoding: utf-8 -*-
# file threadingpoolbad.py
 
import logging
from threading import Thread
from Queue import Queue
 
class ThreadPool:
 
    class _ThreadQueue(Thread):
 
        def __init__(self, pool, *args, **kwargs):
            """ Get tasks queue then launch thread """
            super(ThreadPool._ThreadQueue, self).__init__(*args, **kwargs)
            self.tasks = pool.tasks
            self.daemon = True
            self.start()
 
        def run(self):
            """ Read tasks from limited size queue, then launch task """
            while True:
                # read bloking Queue ..
                task,args = self.tasks.get(True)
                try:
                    task(*args)
                except Exception, e:
                    logging.exception(e)
                    print "Error"
                finally:
                    self.tasks.task_done()
 
    def __init__(self, num=10):
        """ Create a limited pool with "num" threads """
        self.tasks = Queue(num)
 
        for _ in range(num):
            self._ThreadQueue(self)
 
    def add_task(self, target, args):
        """ Write in unlimited size queue which will be
        read in "run" method of a thread
        Block if tasks Queue is full !
        """
        self.tasks.put((target, args))
 
    def wait_completion(self):
        """ Wait for tasks to be completed """
        self.tasks.join()

Voilà comment utiliser cette classe:

import time
from threadingpoolbad import ThreadPool
 
def test(pool, num=0):
    num += 1
    print "num is %d" % num
    if num < 5:
        pool.add_task(target=test, args=(q, num))
        time.sleep(0.5)
 
#create a pool of 2 threads, launch test function
pool = ThreadPool(2)
pool.add_task(target=test, args=(pool,))

Jusqu'ici tout va bien... mais admettons que j'ajoute 5 ou 6 appels à add_task:

# ...
#create a pool of 2 threads, launch test function
pool = ThreadPool(2)
pool.add_task(target=test, args=(pool,))
pool.add_task(target=test, args=(pool,))
pool.add_task(target=test, args=(pool,))
pool.add_task(target=test, args=(pool,))
pool.add_task(target=test, args=(pool,))

Si vous lancez le test... ça coince. Pour arrêter le script faites CTRL+Z puis "kill %%".

Tout le souci se trouve entre deux méthodes:

  • add_tasks qui tente d'écrire dans une queue, l'appel à "put" bloque si la Queue "tasks" est pleine
  • dans "run" on libère un espace au moment où la fonction "task" est terminée

Or, dans notre exemple, la fonction "test" ne libère pas la queue de suite, car elle écrit dans la Queue via add_task... mais comme d'autres threads sont aussi en cours, la Queue est pleine et aucun espace n'est libre. On se retrouve en attente indéfinie...

Pour la plupart des scripts, vous n'allez pas vous retrouver dans cette situation, mais voilà... pour moi c'est arrivé.

Comment corriger le souci ? revoir l'algo ? c'est dommage... tout ce qui nous manque c'est de pouvoir écire dans la Queue sans limite, mais limiter quand même le nombre de threads.

La classe Queue peut tout à fait ne pas bloquer, et avoir une taille indéfinie. Si on lui passe un entier en argument de constructeur elle se limite à bloquer sa taille max à ce nombre. Mais si on ne lui donne pas de paramètres, ou "0" ou un nombre négatif, alors sa taille est illimitée.

Or, je veux tout de même limiter le nombre de process simultanés. Tout ce dont j'ai besoin c'est de ne pas bloquer l'insert de tâches... vous me suivez ?

C'est alors très simple ! il suffit de gérer tout ça avec 2 Queue:

  • une qui n'est pas limitée, elle gardera toutes les taches à lancer, quelque soit le nombre
  • une qui va limiter le nombre de thread, celle-ci sera limité à nombre qui correspond au nombre de threads. C'est quasiement le même fonctionnement que la classe de threadingpoolbad.py

On y va:

# -*- encoding: utf-8 -*-
#file threadingpool.py
import logging
from threading import Thread
from Queue import Queue
 
 
class ThreadPool:
 
    class _ThreadQueue(Thread):
 
        def __init__(self, pool, *args, **kwargs):
            """ Get task and pool Queue. Then launch thread.
            """
            super(ThreadPool._ThreadQueue, self).__init__(*args, **kwargs)
            self.pool = pool.pool
            self.tasks = pool.tasks
            self.daemon = True
            self.start()
 
        def run(self):
            """ Run unlimited while Queues are not joined """
            while True:
                # reinsert the nonblocking queue 
                # in blocking queue, that should block
                # if "tasks" queue is full
                self.tasks.put(self.pool.get(True))
 
                #and read this queue...
                task,args = self.tasks.get(True)
                try:
                    task(*args)
                except Exception, e:
                    logging.exception(e)
                finally:
                    self.tasks.task_done()
                    self.pool.task_done()
 
 
    def __init__(self, num=10):
        """ Create the thread queue with "num" thread in parallel"""
        self.tasks = Queue(num)
        self.pool = Queue()
 
        for _ in range(num):
            self._ThreadQueue(self)
 
    def add_task(self, target, args):
        """ Write in unlimited size queue which will be
        read in "run" method of a thread
        That should not block !
        """
        self.pool.put((target, args))
 
 
    def wait_completion(self):
        """ Wait for the all threads to be completed """
        self.pool.join()
        self.tasks.join()

Vous voyez ici les deux queues, "tasks" et "pool". La première est limitée, l'autre non.

Et cette fois ci, ça marche: j'ai bien deux threads qui tournent mais je ne bloque plus lors de l'appel à "add_task":

import time
from threadingpool import ThreadPool
 
def test(pool, num=0):
    num += 1
    print "num is %d" % num
    if num < 5:
        pool.add_task(target=test, args=(q, num))
        time.sleep(0.5)
 
#create a pool of 2 threads, launch test function
pool = ThreadPool(2)
pool.add_task(target=test, args=(pool,))
pool.add_task(target=test, args=(pool,))
pool.add_task(target=test, args=(pool,))
pool.add_task(target=test, args=(pool,))
pool.add_task(target=test, args=(pool,))
pool.add_task(target=test, args=(pool,))
#...

Et voilà ! Désormais, mes 120 000 tâches que j'ai à lancer ne se bloquent plus, elles se placent en file l'attente et j'ai bien un nombre limité de tâches en parallèle.

Je ne vous montre pas mon script d'import, mais croyez moi, la récursion (bien contrôlée) est énorme, et avoir résolut mon problème a été un soulagement énorme.

check_bdd_request : Plugin Nagios tout terrain pour requêter des bases de données

Alexandre Frandemiche

Bonjour à tous, j'ai eu un besoin particulier concernant le monitoring en place au sein d'Opti-Time. du coup, ni une, ni deux après une brève recherche, je n'ai pas trouvé mon bonheur, alors hier soir , j'ai pris mes 2 mains et hop au boulot ! Ce fût rapide et vous le verrez il n'y a rien d'extraordinaire ! Mais voilà que ça a donnée naissance à un projet de plus : http://www.slobberbone.net/dotclear/index.php?pages/check_bdd_request

Je vous recopie la page ici même pour en faire profiter un maximum ;)" class="smiley !

Présentation

Ce projet est un projet sous licence GPL v2. Cette commande est un plugin pour Nagios 3.x, développé en PERL.

Son objectif est de permettre dexécuter n'importe quelle requête à un serveur de base de données. Dans un premier temps, cela ne fonctionne qu'avec un serveur MySQL, mais ça ne doit aps être compliqué de faire ce qu'il faut pour PostgreSQL, Oracle, etc ...

Ce plugin permet 2 choses pour le moment :

  • Retourner une information dans Nagios sans qu'elle est un impact en terme d'alerte (check_type=TEXT). Le but est de remonter par exemple la version d'un logiciel stockée en base, ou tout autre indicateur quelconque qu'il soit un chiffre ou du texte.
  • Retourner un compteur de résultat de la requête SQL exécutée (check_type=NUMERIC). D'autres plugin Nagios font déjà ce travail, hors j'ai un premier besoin qui est de pouvoir lever une alerte alors que nous passons les seuils fixés, ou à l'inverse, lorsque la valeur n'atteint pas le seuil voulu. Parcourant le résultat, cette commande pourra très facilement être adaptée pour traiter des résultats plus complexes (prochaines versions).

Pour le reste, je vous laisse parcourir le fichier, tout doit être dedans ;)  et assez explicite ! Sinon, vous savez où me trouver ;)" class="smiley

Installation

Vous l'aurez compris, pour l'utiliser, il suffit d'un serveur Nagios (Installer Nagios sur CentOS sans RPM) sur lequel vous aurez installé les packages suivant :

# yum install perl perl-DBD-MySQL perl-DBI

Une fois fais, il ne vous reste qu'à placer check_bdd_request.pl dans le répertoire libexec de votre arborescence Nagios :

# chmod +x check_bdd_request.pl

Et voilà, le tour est joué !

$ perl check_bdd_request.pl --help

Check_MySQL_request for Nagios, version 1.0
GPL licence, (c)2013 Slobberbone

Site http://www.slobberbone.net

Usage: ./check_bdd_request.pl -H <host> -u <user> -p <password> -d <database> -P <port> -q request -t check_type -o <operator> [-w <warn_level> -c <crit_level> -T <timeout>] -V
-h, --help
print this help message
-H, --hostname=HOST
name or IP address of MySQL server
-u, --user=USER
mysql user to login
-p, --password=PASSWORD
mysql password
-d, --database=DATABASE
mysql database name
-P, --port=PORT
Http port
-q, --request="REQUEST"
Request to execute on the server
-t, --check_type=TYPE
Type of return :
NUMERIC,
TEXT
-o, --operator=OPERATOR
Operator :
EQ for =,
LT for <,
GT for >,
LE for <=,
GE for >=,
DIF for !=
-w, --warn=INTEGER
number that will cause a warning completed by the OPERATOR
-1 for no warning
-c, --critical=INTEGER
number that will cause an error completed by the OPERATOR
-T, --timeout=INTEGER
timeout in seconds (Default: 15)
-V, --version
prints version number
Note :
The script will return
* With warn and critical options:
OK if we are able to connect to the MySQL server and #request count result respect <warn_level>,
WARNING if we are able to connect to the MySQL server and #request count result unrespect <warn_level>,
CRITICAL if we are able to connect to the MySQL server and #request count result unrespect <crit_level>,
UNKNOWN if we aren't able to connect to the MySQL server

Configuration

Pour que ce plugin soit opérationnel avec notre serveur de supervision Nagios (ou Shinken), il faut ajouter dans le fichier commands.cfg :

# 'check_bdd_request_numeric' command definition
define command{
    command_name    check_bdd_request_numeric
    command_line    $USER1$/check_bdd_request.pl -H $HOSTADDRESS$ -u $ARG1$ -p $ARG2$ -d $ARG3$ -P $ARG4$ -q $ARG5$ -t $ARG6$ -o $ARG7$ -w $ARG8$ -c $ARG9$
    }

# 'check_bdd_request_text' command definition
define command{
    command_name    check_bdd_request_text
    command_line    $USER1$/check_bdd_request.pl -H $HOSTADDRESS$ -u $ARG1$ -p $ARG2$ -d $ARG3$ -P $ARG4$ -q $ARG5$ -t $ARG6$
    }

Puis, il ne vous reste plus qu'à l'appeler de la manière suivante :

define service{
    use            generic-service
    host_name        my_bdd_server
    service_description    A text value for display information like a version for example
    check_command        check_bdd_request_text!user!password!database!3307!"Select column_name from my table where row_text='value'"!TEXT
    normal_check_interval    60
}

define service{
    use            generic-service
    host_name        my_bdd_server
    service_description    A numeric value for display information and compare with operator to the Warning or Critical values
    check_command        check_bdd_request_text!user!password!database!3307!"Select column_name from my table where row_text='value'"!NUMERIC!LT!2!5
    normal_check_interval    60
}

Et voilà, il ne reste plus qu'à modifier ce projet pour vos propres besoins ! Pour ça, direction SourceForge.net !
Pour le reste, les commentaires sont ouverts sur la page du projet !

Coloriser la sortie d'un programme

Patrice Ferlet

Développant sur Google Appengine en ce moment, en python, j'utilise les logs à foison. Ces logs sont pratique et ont un niveau d'information (WARNING, INFO, ERROR...). Mais la lisibilité n'est pas forcément adaptée, car en effet les logs sont simplement des lignes de texte. C'est alors qu'une idée m'a frappée (sans gravité, j'ai pas eut mal): et si je colorisai la sortie. Ma méthode fonctionne pour à peu près tous les programmes que vous utilisez dans un terminal, et peut être adaptée à pas mal de situations

L'idée est simple, il suffit de rediriger les sorties du programme dans un descripteur qui va modifier la sortie. Avant tout, laissez moi vous expliquer comment coloriser du texte dans un terminal (si vous ne savez pas comment), puis comment utiliser `sed` avec la subtilité. Enfin, on va passer par une commande `exec` qui va opérer notre transformation.

Premier point, les couleurs dans un terminal.

Bash (ou d'autres shell) permet de coloriser une chaine de caractères. Le principe étant de donner un caractère spécial, puis un code couleur, puis la chaine.

Ainsi, pour coloriser en rouge, en entre la séquence "\033[31m" puis la chaine. Il faut juste comprendre cela:

  • \033 => caractère qui prévient qu'on va changer de couleur, de poids de fonte...
  • [ => on va donner une couleur
  • 33 => code couleur du rouge
  • m => terminé

Cette séquence retourne un caractère spécial c'est important de bien le comprendre, car en réalité toute la subtilité de ce que nous allons faire repose sur ce principe. Ce caractère est invisible, il est interprété par le terminal pour changer la couleur.

Cela ne suffit pas, il faudra que le terminal sache gérer les caractères échapés, je parle du "\033". La commande "echo" sait bien le faire:

echo -e "\033[31mEt hop une couleur"

Le souci, c'est que là votre console passe en rouge... et ne revient pas au mode "normal". Pour cela, on utilise la couleur "0"

echo -e "\033[0mOn revient à la normale"

Pour faire plus simple, on peut directement activer la couleur, entrer la chaine, puis revenir à la normale:

echo -e "\033[31mHoulla une erreur en rouge\033[0m"

Les couleurs qu'on va utiliser:

  • 31 rouge => pour les erreurs
  • 33 orange => pour les warnings
  • 34 bleu => pour les infos

Bien, passons à `sed`. Cet outil permet de faire des opération sur du texte assez facilement en utilisant une syntaxe proche du perl. Ainsi, la commande "s" de sed permet de "substituer" du texte, comprennez "le remplacer"

Dans une commande sed "s", le caractère "&" correspond à la chaine trouvée. Ainsi, pour remplacer "WARNING" en "toto-WARNING-fin" on fera:

echo "WARNING there is an error" | sed 's/WARNING/toto-&-fin/'
#affiche: toto-WARNING-fin there is an error

Bon, vous avez compris, il faut remplacer maintenant "WARNING" en "\033[33mWARNING\033[0m". Sauf que voilà, sed ne sait pas utiliser le mode "échappé". Cela n'est rien, on va récupérer le caractère que retourne "echo -e" pour le placer dans la chaine de remplacement. Voilà un exemple:

NORMAL=$(echo -e "\033[0m")
ORANGE=$(echo -e "\033[33m")

echo "WARNING there is an error" | sed "s/WARNING/$ORANGE&$NORMAL/"

Simple en fait, relisez bien vous allez comprendre.

Bien, reste à faire ça pour toutes les lignes de sortie d'un programme. Reprenons notre exemple avec GAE, j'ai des message "WARNING, INFO et ERROR".

Le souci, c'est que la sortie est "en continue", mais on va utiliser une commande qui va gérer une redirection de flux: exec

Si vous ne le savez pas, un shell utilise des descripteurs connus: 1 = sortie standard, 2 = sortie d'erreur. Si je redirige une sortie dans un de ces flux, il sera traité de différente manières.

Pour rediriger dans un flux, on utilise la séquence "N>&M" où N est le flux d'entrée, et M le flux où on redirige. Ainsi:

#on redirige la sortie normale dans le flux d'erreur
echo "pouet pouet" 1>&2 
#1 est utilisé par défaut, donc cela fait la même chose:
echo "pouet pouet" >&2 

#Par exemple
echo "pouet pouet"  2>fichier_erreur 1>fichier_normal 1>&2
#ici, le fichier_normal ne contien rien, par contre fichier_erreur 
#contient pouet pouet

OK, même si cela reste obscure pour certains, ne paniquez pas... suivez juste le reste de mon article.

"exec" peut ouvrir un descripteur, simplement en lançant "exec 3> ..." On aura alors un nouveau flux portant le numéro 3.

Donc:

exec 3>fichier
echo "pouet pouet" >&3

ici, on redirige le flux 3 dans un fichier, donc en écrivant dans le flux, on écrit dans un fichier...

Autre subtilité des shells: ">(une commande)" (surtout pas d'espace après le >) la commande entre parenthèse est considéré comme un fichier

Ainsi:

exec 3> >(sed 's/toto/pouet/g')
echo "j'ai pas toto mais j'aime truc" >&3
echo "toto toto :)" >&3

Vous comprennez ce qu'il se passe: tout ce que j'écris dans le "descripteur 3" passe dans la commande "sed..." Ha... on y arrive !

NORMAL=$(echo -e "\033[0m")
ORANGE=$(echo -e "\033[33m")
RED=$(echo -e "\033[31m")
BLUE=$(echo -e "\033[34m")

exec 3> >(sed "s/WARNING/$ORANGE&$NORMAL/g,   s/ERROR/$RED&$NORMAL/g, s/INFO/$BLUE&$NORMAL/g" )

ma_commande >&3

Et voilà ! Toute mes sorties de "ma_commande" passerons dans le flux "3" et on verra un remplacement de mot par ce même mot en couleur.

Si "ma_commande" utilise les flux de sortie d'erreur, vous pouvez carrément faire:

ma_commande 2>&1 1>&3

Alors, ici, on utilise des couleurs, mais vous pouvez utiliser cette technique pour mettre des balises XML, mettre le texte en minscule, ou rediriger dans d'autre descripteurs... en gros la méthode est ouverte à pas mal de variantes.

Voilà pour aujourd'hui !

Progress on HyperKitty

Aurélien Bompard

Well, long time no blog. But not as long as last time. Oh well.

There was FUDCon (Fedora Users and Developers Conference) in Paris this weekend. We had a great time, got some work done, but more importantly we got a chance to meet each other. I met Spot, my manager, and part of the team. And I met again the other French contributors to Fedora, which is always nice.

I tried to demo the project I’m working on, HyperKitty, but unfortunately I did it at the wrong time, so too few people were interested. This blog post will hopefully spread the word more widely. And who knows, maybe I’ll even take the habit of blogging more often. Sounds familiar ? D’uh.

Below are the slides I made for FUDCon, and some explanations to go with it. Just click to get to the next slide, and right-click to go back.

The current version of Mailman is 2.1, a version we have known to love (or hate every month) over the years. But Mailman 3 is in the works, and very close to release. We’re talking weeks here, a couple of months at most.

Mailman 3 is a complete redesign of the project. It focuses on a core server, which runs components, each running agents and passing email around.

You’ll find more info on Mailman 3’s architecture in its chapter of the AOSA book. There are many very interesting new features in this new version, including improvements in code quality and stability (which is a feature!).

The archiver which was shipped with Mailman 2.1, Pipermail, was stripped off, and an API was designed to enable any archiver to plug into Mailman3. It ships with plugins for The Mail Archive, MHonArc, and a basic archiver which stores the mails in maildirs. This is not how we, at the Fedora Project, think mailing-list would be best archived, viewed, and interacted with. So a few people in the Fedora Engineering Team started the HyperKitty project, working on the design, making prototypes, and recently hiring me to implement it. The links to the early design mockups can be found on the HyperKitty project page.

So, where are we ? I’ve worked a lot on the backend, called KittyStore, and I’ve only recently begun to touch the front-end, written in Python/Django. Fortunately, the work done by other contributors before me (Pingou, Toshio and Syst3mW0rm) was already very usable. A test server was setup with the latest developments, feel free to browse the imported archives there, but don’t be too surprised if it suddenly crashes on you, I may be uploading a new snapshot at the same time. Please report any bug you can find on the project’s Trac instance.

My aim is to have something stable with a basic feature set when Mailman 3 is released. I’ll implement the very nice features designed by Máirín later on. It’s going rather well, I’m fixing a few bugs and implementing the last necessary features such as importing from Pipermail and keeping Pipermail-compatible URL to transparently redirect to the new ones. I think we can meet the Mailman 3 release.

If you want to help, you can by installing it (and writing down the process!), testing it, importing from you own archive, testing the UI, reporting the bugs, etc. It’s a little early for new features, but if you’re motivated please go ahead ! They will probably be merged in a post-1.0 release however.

Go, routines et canaux - introduction au langage

Patrice Ferlet

Il y tellement de nouveaux langages, quand un de ceux là sort du lot c'est en général parce qu'il offre des concepts particuliers. Go est un de ceux là, et les concepts de routines, de canaux et de fonctions différées ne sont qu'une infime partie des possibilités que vous pouvez découvrir dans ce langage. Testons un peu ce langage.

Jai la chance daimer mon métier, et dans ce métier on apprend souvent de nouvelles choses, des concepts, des langages... Dans le panel de langages informatique que jai put entrevoir, certains mont vraiment surpris dans leur concept. Le premier dont jai eut beaucoup de mal à mapprocher est le Haskell, et depuis peu je me penche sur Go. Je mempresse donc de vous montrer un peu lintérêt que je vois dans ce langage.

Cest Nicolas Engel, avec qui jai travaillé sur un projet, qui men a reparlé et lenvie de me repencher sur Go sest vite manifesté.

Alors Go cest quoi ?

Ce langage a été créé par une équipe de Google depuis 2010 et a pour vocation de réduire des problématiques que beaucoup de développeurs doivent supporter dans dautres langages. Vous pouvez utiliser le compilateur Go à télécharger ici: http://golang.org/ ou utiliser gcc-go (package Fedora disponible dans les dépots officiels)

Cest un langage compilé, performant et sous licence BSD... La seule complication, et vous allez voir quelle est dominante au début, cest que le concept est vraiment différent de pas mal de langages. Amis développeurs Python, Java, PHP et même C/C++... vous allez être un peu surpris.

La raison principale de la création de ce langage a été motivée par la réduction de problématiques telles que les threads, les sémaphores, les transferts interprocessus, les fractures mémoire, et limpérativité des langages communs. Vous allez voir que des concepts ont été ajoutés à Go pour rendre tout cela vraiment très simple... si si, cest finalement très simple.

Tout dabord, cest un langage fortement typé, les habitués de langages typés dynamiquement vont devoir se faire une raison: il va falloir déclarer vos variables. Ensuite, Go nest pas un langage purement objet mais vous allez facilement pouvoir déclarer des struct avec des méthodes attachées. Jentends dici pas mal dentre vous rire doucement sur un fond de “huhu aujourdhui faire un langage non objet... cest juste revenir dans le passé, autant faire du C”. Je tiens à donner mon avis là dessus, les langages orientés objet ont leurs avantages certains, mais Go est surtout fait pour être performant dans un premier lieu, et le C na rien de vieillot car même votre Linux, votre Mac ou votre Windows est un système dexploitation quasiment entièrement écrit en C.

Enfin, pour ma part, je nai aucun mal à imaginer développer une grosse infrastructure logiciel avec un langage structuré non objet. Et surtout si cest en Go. Dans tous les cas, Go est adressé aux développeurs désirant un langage proche du processeur, un peu comme C/C++ tout en gardant des simplification en terme de threads, sémaphores et gestion mémoire. Bien que Go permette de créer aussi des application web, et se retrouve même dans Google AppEngine. Voyez le guide de démarrage: go appengine.

Bref, passons la polémique trollesque et passons au langage lui même.

On commence par installer le compilateur. Soit vous utilisez gcc-go (dans les dépots) soit vous utilisez le compilateur de google à cette adresse: Golang. Je vous conseille, pour commencer, d'utiliser celui de google et de suivre la manière dont on utilise le runtime. En général, cela revient à faire la commande

go run monfichier.go

Pour le compiler réellement:

go build monfichier.go

Je ne vais pas vous détailler les bases du langage, mais un exemple rapide pour vous donner la syntaxe de base:

package main
 
//fmt est le package de formatage de chaine
import “fmt”
 
//on retrouve une fonction main
func main () {
     fmt.Println(“Hello le mondo !);
}

Bon rien de bien compliqué, un hello world bateau. Vous remarquez donc quon a une notion de package, et donc quon pourra séparer des fonctions et structures dans différent package (ce qui est un plus quand on se réfère au C). Cela ressemble fortement aux espace de nom (namespace) mais lidée est plus claire selon moi.

Petite parenthèse, vous allez rapidement vous rendre compte au fur et à mesure que la plupart des concepts en Go se retrouve dune manière ou dune autre dans C.

Alors ya quoi de nouveau ?

Dabord, comment on déclare une variable ? et bien de deux façons:

// i variable de type int
var i int
i = 4
 
//ou bien
//i est une entier valant 4, le typage est déduit par inférence
i:=4

Pour faire simple, utiliser ":=" (oubliez pas les deux points) pour déclarer en assignant, sinon l'assignation se fait avec un simple "="

Voilà, pas plus pas moins... on va pas parler de pointeur et de structure de suite, mais vous naurez pas de mal à comprendre en lisant la documentation ou en faisant le gotour (voir les liens en fin de billtet)

Je vais vous lister 3 concepts qui mont plut dans ce langage. Le premier est le concept de “goroutine” qui est foncièrement parlant une méthode pour créer des threads sans se prendre la tignasse et se taper le front sur le clavier. Le second concept est le principe de canal (channel) qui permet tellement de choses quon sattriste de ne pas les avoir dans les autres langages (du moins pas sous cette forme). Un canal permet de faire transiter des valeurs dun processus à lautre, de bloquer un processus tant quun autre nest pas terminé et en plus de bufferiser tout ça. Cest le concept de sémaphore (mutex) amélioré dans sa conception. Le troisième concept est la possibilité de différer un appel dans le temps (defer). Cela rend le code lisible à souhait.

Un autre concept, que lon retrouve un peu en python, est le fait de pouvoir retourner plusieurs valeurs de plusieurs types depuis une fonction. Ceux qui nont pas compris lintérêt devrait penser simplement à cela: imaginez une fonction qui retourne un résultat et un code derreur. Plutôt que de devoir retourner une structure, ou un objet pour les langages qui le permettent, on retourne une liste simple de variables.

Bref, passons aux goroutines de suite.

Lappel à une routine se fait simplement via le mot clef “go”. La fonction sera appelée dans un thread (mais dans le même processus, on ne parle pas dun fork ici, mais bien dun thread) et rien dautre nest à faire !

Je ne vous ai pas encore parlé des canaux, donc cet exemple ne va pas fonctionner exactement comme on le veut, mais le concept est là. Ne cherchez pas si ça fonctionne mal chez vous.

package main
 
import "fmt"
 
func hello(you string){
    for i:=0; i<5; i++ {
        fmt.Println("hello you:" + you)
    }
}
 
func main (){
    go hello("Pierre")
    hello ("Paul")
}

Ce qui va vous donner (selon la manière dont les threads sont créé sur votre OS):

hello you:Paul
hello you:Pierre
hello you:Paul
hello you:Pierre
hello you:Paul
hello you:Pierre
hello you:Pierre
hello you:Pierre
hello you:Paul
hello you:Paul

Vous lavez remarqué, jai parfois 3 fois “Pierre” qui apparait, parfois 2 fois “Paul”. Cela est le fait que la routine est géré selon les disponibilités du CPU.

Bon, on passe aux canaux. Là vous risquez de perdre un peu le fil, mais je vous assure que la capacité de ce concept est tout bonnement génial.

Un canal est un type dont le principe est dempiler des valeurs et de bloquer le processus si on cherche à lire dedans, et que celui-ci si il est vide. Cest le principe du FIFO. Lintérêt est sa syntaxe aisée et lisible.

Il faut utiliser la fonction “make” (genre de malloc, mais orienté fabrique ou factory) pour créer un “chan”.

//canal prenant des entiers
c := make (chan int)

Un canal peut prendre n'importe quel type, entier, flottants, chaines... et même des structures.

Ensuite cest plutôt simple... On utilise “<-” pour placer des valeurs dans le flux.

Donc:

//empile un entier (1) dans le canal
c <- 1

Et pour lire le canal

//bloque tant que rien nest empilé dans c
//on peut récupérer la valeur empilée
int val := <-c2
//ou simplement attendre une écriture dans la canal
<-c2

Faisons simple, on va juste comprendre le principe du canal:

package main
 
import "fmt"
 
func routine(a, b chan int){
     i := <-a
     fmt.Printf("je viens de lire dans le canal: %d\n", i);
 
     //'main' attend toujours, je vais écrire dans le canal b
     //pour le débloquer
     b <- 0
}
 
func main(){
    a,b := make(chan int), make(chan int)
 
    //on écrit dans a
    a <- 2
    //on lance une fonction en parallèle
    //qui reçoit les deux canaux en paramètres
    go routine1(a,b)
 
    //on va attendre la fin de la routine
    //la routine doit écrire dans le canal b 
    //pour que cette instruction se débloque
    <- b
    fmt.Println("voilà, j'ai fini, b a été lut")
}

Pour le moment on s'amuse à débloquer des canaux en écrivant dedans. Sachez que vous pouvez fermer un canal avec la fonction "close" mais si vous en avez besoin, c'est que vous être dans un cas particulier. Car en fait, Go n'aime pas que des canaux restent ouverts avec une valeur bloquée et non lut... pensez à traiter correctement les canaux, sinon forcez la fermeture avec "close"

Voyons un autre exemple de code qui va fonctionner avec un canal dentier. Un peu plus salé par contre...

package main
 
import "fmt"
import "time"
 
 
func thread1 ( sem, done chan int) {
    fmt.Println("je suis le thread 1, je commence à bosser")
 
    //grosse fonction qui prend du temps
    time.Sleep(1 * 1e9)
 
    //on débloque le sémaphore
    sem<-0
    //on travaille encore un peu
    time.Sleep(1 * 1e9)
    fmt.Println("je suis le thread 1, je préviens main que je termine")
    //je préviens que la routine est finie
    done<-1
}
 
//voir plus bas (dans thread2), 
//cette méthode va permettre
//une exécution différée
func send(c chan int, num int) {
    c <- num
}
 
func thread2 (sem, done chan int) {
    //autre manière de s'assurer qu'on écrit bien à la
    //fin de la fonction :
    //defer permet de différer l'appel à "send" 
    //à la fin de ma fonction
    defer send(done, 2) 
 
    fmt.Println("je suis le thread 2, je commence à bosser")
 
    //on attend la fin de thread 1
    <-sem
    fmt.Println("je suis le thread 2, on m'a débloqué")
 
    //on bosse encore un peu
    time.Sleep(2 * 1e9)
 
    //quant sleep a terminé, la fonction différée va
    //être exécutée
}
 
func main(){
    //sem va me permettre de gérer les bloquage et débloquage
    //des goroutines
    sem := make (chan int)
 
    //ce canal sera emplilé par la seconde routine pour dire
    //à la fonction main qu'il a terminé
    done := make (chan int)
 
    //on lance les deux routine en parallèle, elle vont communiquer
    //au traver de sem
    //on remplira "done" pour dire que le thread est terminé
    go thread1 (sem, done)
    go thread2 (sem, done)
 
    fmt.Println("je suis dans main, j'attend la fin en lisant le canal 'done'")
 
    //les routines vont écrire dans "done",
    //j'attend que ce soit le cas pour les deux threads
    //donc on attend 2 fois
    //la variable i récupérera la valeur envoyé dans le canal
    var i int
    i = <-done
    fmt.Printf("routine %d vient d'écrire dans le canal 'done'\n", i)
    i = <-done
    fmt.Printf("routine %d vient d'écrire dans le canal 'done'\n", i)
 
    fmt.Println("je suis dans la fonction main, on m'a débloqué")
 
}

Voilà ce que donne mon programme:

je suis dans main, j'attend la fin en lisant le canal 'done'
je suis le thread 1, je commence à bosser
je suis le thread 2, je commence à bosser
je suis le thread 2, on m'a débloqué
je suis le thread 1, je préviens main que je termine
routine 1 vient d'écrire dans le canal 'done'
routine 2 vient d'écrire dans le canal 'done'
je suis dans la fonction main, on m'a débloqué

Ce qui est exactement ce que je voulais. La fonction main sest mise en attente, les deux threads on commencé à travailler en parralèle. La routine 1 déblique la routine 2, mais cette routine 1 fini certaines choses en même temps. Enfin, la fonction “main” remprend la main (non cest pas un jeu de mot)...

Plutôt que de créer une fonction "send" qui n'est utilisée que dans le thread 2, on peut utiliser un système de closure. Car en effet, les fonctions en Go sont des closures. Revoyons le code du thread 2:

func thread2 (sem, done chan int) {
    //autre manière de s'assurer qu'on écrit bien à la
    //fin de la fonction :
    //on crée une closure exécutée en fin de fonction
    //notez que c est local à la closure, la closure prend "done"
    //en argument, donc c correspondra à done
    defer func (c chan int) {
        c <- 2 
   }(done)
 
    fmt.Println("je suis le thread 2, je commence à bosser")
 
    //on attend la fin de thread 1
    <-sem
    fmt.Println("je suis le thread 2, on m'a débloqué")
 
    //on bosse encore un peu
    time.Sleep(2 * 1e9)
 
    //quant sleep a terminé, la fonction différée va
    //être exécutée
}

Vous pouvez donc supprimer la fonction "send". On retrouve ici un concept de fonction "inline" interne à une fonction. En bref, la capacité qu'à Go à vous simplifier la vie est vraiment intéressante.

Bref, là je nai fait quune introduction à Go... je nai pas le recul ni lexpérience nécessaire pour aller plus dans le détail (bien que je mamuse comme un fou avec des tests bourrins). Je vous conseille daller lire le “gotour” ici : GoTour. Je vous conseille de bien vous pencher sur les notions de "range", de retour de plusieurs variables depuis une fonction mais aussi sur les concepts de structure et fonctions de structure. Ce billet ne visait qu'à vous éclairer sur ce que peut vous apporter Go coté parallélisation.

Go est désormais utilisable dans appengine, vous pouvez aussi créer votre propre miniserveur HTTP très facilement avec le package (inclu) "net/http" et que des portage de Gtk, WX etc... ont vu le jour et fonctionnenent.

En ce qui concerne les deux compilateurs, le compilateur (et runtime) Go de Google compile de manière statique mais donne de superbe résultats en terme de performances. Vous pouvez utiliser gcc-go (dans les dépots Fedora du moins) qui vous permettra de compiler dynamiquement vos programmes (le compilateur go faisant de la compilation statique...)

Un dernier point, Go vous permet aussi de récupérer très aisément des librairies C et dutiliser les fonctions impémentées dans ce langage.

Google App Engine et le souci os_compat

Patrice Ferlet

Google AppEngine propose est un service de développement web assez puissant et très intéressant qui permet de faire une application "scalable" en Java, Python et maintenant en Go. Le souci, si comme moi vous avez "MyPaint" et par conséquent le paquet protobuf-python... vous allez vous taper une erreur monstrueuse au démarrage de votre application... on va trouver une méthode simple pour contourner ce souci

Car effectivement, au premier lancement de votre application:

 ./google_appengine/dev_appserver.py helloworld/
Traceback (most recent call last):
  File "./google_appengine/dev_appserver.py", line 125, in <module>
    run_file(__file__, globals())
  File "./google_appengine/dev_appserver.py", line 121, in run_file
    execfile(script_path, globals_)
  File "/home/patachou/Dev/google_appengine/google/appengine/tools/dev_appserver_main.py", line 138, in <module>
    from google.appengine.tools import os_compat
ImportError: No module named appengine.tools

Pourquoi ? tout simplement parce que le paquet protobuf contient un module nommé... google ! du coup, python ne va pas voir ailleurs que dans le site-package protobuf et plante misérablement, lachant son dernier souffle en vous insultant... et vous, vous n'avez que pour seule solution de vous rabattre tant bien que mal sur des discussions sur le net où tout le monde vous demande de supprimer protobuf.

Sauf que moi, j'utilise MyPaint et j'ai pas envie de me remettre le paquet à chaque fois que je veux dessiner. Et puis de toutes manières j'aime pas les méthodes où il faut supprimer un paquet tout simplement parce qu'une implémentation sent la fosse septique...

La solution ? oui oui deux secondes...

Elle est simple ma solution... utiliser un environnement virtuel ! Attention, pas un LXC ou une VM qui va vous plomber tout votre espace disque... non ! juste un outil python qui rend tellement service, et dont je me sers tout le temps pour tester mes "setup.py" et autres joyeusetés qui peut simplement foutre le boxon dans mes répertoires.

Bref, on va faire ça propre !

On crée un environnement virtuel

mkdir -p ~/Dev/appengine
virtualenv ~/Dev/appengine

Voilà, c'est fait... on copie maintenant le répertoire "google" fournis avec le SDK Python de Google App Engine:

cp -ra ~chemin/vers/google_appengine/google ~/Dev/appengine/lib/python2.7/site-packages/

Et pour lancer l'engin google, on active l'environnement:

source ~/Dev/appengine/bin/activate

Du coup, on voit notre prompt terminal commencer par: (appengine), chez moi ça donne:

(appengine)[patachou@patrice-laptop ~]$

Vous pouvez maintenant lancer votre programme (prenez le tutorial python 2.7 de google) de cette manières:

~chemin/vers/google_appengine/dev_appserver.py helloworld/

Pour sortir de l'environnement virutel:

deactivate

Voilà, c'est simple, assez propre, et en plus ça marche...

Intégration continue - PHP, Selenium et Xvfb - Partie 1

Patrice Ferlet

Depuis quelques mois je me suis penché sur les outils d'intégration continue pour les projets PHP. Il en existe un bon paquet, mais celui qui pour le moment répond le mieux à mes attentes reste phpUnderControl. L'installation reste tout de même assez complexe et il me parait bon de vous montrer comment mettre en oeuvre ce genre d'outil. Le but étant de vous aider pas à pas à installer les outils qui permettront d'utiliser un service d'intégration continue. Nous allons passer en revue les outils à installer depuis PEAR, puis Selenium et enfin comment intégrer tout ça dans phpUnderControl.

Certes, je ne vais pas vous donner "la solution dans les règles de l'art" car chaque projet est différent, chaque attente est spécifique... Mais vous pourrez adapter au besoin les explications.

Nous allons donc procéder à ces opérations:

  • installer les outils pear: phpcs, phpmd, phpcpd, phpunit
  • installer selenium IDE avec le plugin php (ces outils ne sont pas obligatoires mais rendent la tâche tellement plus simple...)
  • préparer un environnement X virtuel pour lancer les tests selenium
  • installer phpundercontrol
  • configurer la bête...

Les deux derniers points seront dans la partie 2, pour le moment il faut s'assurer d'avoir tous les composants nécessaires à la bonne conduite du projet.

En ce qui concerne Selenium et phpUnderControl, ce sont des outils développés en Java. On nous recommande d'utiliser le jre de oracle (comme ce n'est plus Sun...) mais pour ma part j'utilise openjdk sans aucun souci... alors pourquoi passer au propriétaire quand le libre fonctionne très bien ?

Donc, sur votre Fedora ou Centos, préparons un environnement propre... Je vous conseille d'utiliser un conteneur LXC pour bosser, mais si vous voulez utiliser votre système hôte je ne vais pas vous en empêcher.

On commence par installer les outils important:

  • php-cli, php-pear
  • openjdk

on ouvre un terminal, on se met en root et on installe tout ça

su -
yum install php-cli php-pear java-1.7.0-openjdk

Pour la suite, je sais que certains d'entres vous vont me taper sur les doigts, clamant haut et fort que les dépots Fedora ont les paquets PEAR demandés (ou pas...) mais je vais être très clair: les paquets sur les serveur PEAR sont à jour, et certaines dépendances risque de ne pas marcher si vous utiliser les RPM. C'est le cas aussi sur Debian (j'en ai fait les frais cette semaine). Donc ayez confiance et faites ce que je dis :)" class="smiley

On commence par faire deux ou trois manipulations sur PEAR pour être à l'aise (toujours en tant que root):

pear set-config auto_discover 1
pear upgrade pear
pear install --alldeps pear.phpunit.de/PHPUnit
pear install --alldeps phpunit/PHPUnit_Selenium
pear install --alldeps pear.phpunit.de/phpcpd
pear install --alldeps phpmd/PHP_PMD
pear install --alldeps PHP_CodeSniffer
pear install --alldeps channel://pear.phpdoc.org/PhpDocumentor-2.0.0a1

Si jamais il vous manque des paquets php, installez les, faite un "prear uninstall" du paquet qui a donné une erreur, et relancer l'installation du paquet.

Vous avez donc à présent:

  • phpunit pour faire des tests unitaires de vos développement php
  • phpunit-selenium qui est une classe permettant de piloter le serveur selenium qui lancera un navigateur pour tester des aspect fonctionnels
  • phpcpd (Copy Paste Detector) qui vérifie les "copier coller" de code
  • phpmd (Mess Detector) qui va vérifier des aspects complexe de code (variables non utilisé, code complexe, nom de variables ou fonctions...)
  • phpcs qui va vérifier si votre code est bien formaté selon des standards choisis
  • phpdocumentor qui permet de créer la documentation de vos projets

Cela étant fait, nous allons passer à la suite... Selenium !

Selenium est un ensemble d'outils qui permettent de piloter un navigateur au travers un serveur. Vous allez pouvoir vérifier si une page apparait correctement, si les liens sont bien présents sur une certaine page, et bien plus encore. Le principe est très simple:

  • on enregistre une séquence de manipulations et de tests
  • on l'exporte pour phpunit
  • on lance le test

Mais pour que cela fonctionne, il faut jouer un peu dans le terminal... rien de bien méchant mais important.

Dans votre terminal, en root, téléchargez le serveur:

mkdir /opt/selenium
cd /opt/selenium
wget http://selenium.googlecode.com/files/selenium-server-standalone-2.20.0.jar

Ce serveur, une fois lancé, pilotera un firefox tout seul. Sauf que voilà... si vous travaillez sur une machine de bureau alors aucun souci ne se profile... mais quand on travaille sur un serveur distant... sans écran... ça va aller mal !

La solution la plus simple, selon moi, est d'installer un firefox "standalone" sur /opt/selenium et de lancer tout ça dans un Xvfb (X virtuel). On va donc faire cela... toujours dans votre répertoire /opt/selenium:

wget "http://download.mozilla.org/?product=firefox-11.0&os=linux&lang=fr"
tar jxf firefox*.bz2

Si tout est ok, vous avez un répertoire "firefox" dans /opt/selenium

On va tenter de lancer firefox dans un X virtuel pour être certain que tout se passe bien:

Xvfb :99 &
DISPLAY=:99 /opt/selenium/firefox/firefox &
DISPLAY=:99 import -window root /tmp/snapshot.png

Maintenant, ouvrez /tmp/snapshot.png et vérifiez que vous voyez bel et bien un firefox ouvert. Si oui: nickel ! si non... heu vérifiez si il ne manque pas une librairie pour firefox (genre gdlib-dbus...)

Bref, si Xvfb et firefox ont marché alors on coupe:

killall Xvfb

Notez qu'on aurait put utiliser "xvb-run /opt/selenium/firefox/firefox" et utiliser le port retourné pour faire la capture, mais les lignes su-citées me paraissent plus explicites pour comprendre comment ça fonctionne.

Bref, utilisons maintenant le serveur selenium pour lancer un test.

Xvfb :99 &
sleep 2
PATH=/opt/selenium/firefox:$PATH DISPLAY=:99 java -jar selenium-server-standalone-2.20.0.jar &

Attendez un peu...il faut voir une ligne indiquant que le serveur tourne, ce genre de ligne:

INFO org.openqa.jetty.http.SocketListener - Started SocketListener on 0.0.0.0:4444
INFO org.openqa.jetty.util.Container - Started org.openqa.jetty.jetty.Server@1ff4689e

Cela peut prendre entre 5 et 30 secondes, soyez patient... A partir de là, le serveur écoute le port 4444, on va donc jouer un peu !

Créer un fichier test.php:

<?php
class Example extends PHPUnit_Extensions_SeleniumTestCase
{
  protected function setUp()
  {
    $this->setBrowser("*chrome");
    $this->setBrowserUrl("http://www.google.fr/");
  }

  public function testMyTestCase()
  {
    $this->open("/");
    $this->waitForPageToLoad("30000");
    //on vérifie que "Google" est sur la page...
    $this->verifyTextPresent("Google");

    //ce teste doit donner une erreur
    $this->verifyTextPresent("foo bar baz");

  }
}

Ce teste ne fait rien de compliqué... il ouvre Google et vérifie si le texte "Google" est sur la page...

Vous pouvez lancer le test:

phpunit test.php

Le résultat doit être:

PHPUnit 3.6.10 by Sebastian Bergmann.

F

Time: 5 seconds, Memory: 5.75Mb

There was 1 failure:

1) Example::testMyTestCase
foo bar baz
Failed asserting that false is true.


FAILURES!
Tests: 1, Assertions: 2, Failures: 1.

Cela indique donc que le serveur selenium a bien lancé firefox, la page google et vérifier qu'un texte "Google" apparait. En ce qui concerne "foo bar baz" cela donne une erreur. Donc:

  • 1 test
  • 2 assertions : texte "Google" et texte "foo bar baz"
  • 1 erreur, et une seule erreur !!! si vous en avez 2 alors c'est que cela n'a pas marché !

Voilà, on peut couper selenium et Xvfb:

killall Xvfb
killall $(ps ax | grep selenium | grep -v grep | awk '{print $1}')

Maintenant, on se crée un service pour lancer Xvfb et Selenium.

Avant tout, pour des raison pratiques, je vous conseille de ne *plus du tout* lancer Xvfb et Selenium en root, mais avec un utilisateur précis. Comme nous allons utiliser phpUnderControl qui est un plugin de cruisecontrol, et que ce dernier lancera son service avec un utilisateur recommandé, nous allons le créer de suite et l'utiliser pour lancer Xvfb et Selenium.

useradd -m -s /bin/bash cruisecontrol

Notez ici que je demande la création du répertoire personnel (via l'option -m) car firefox aura besoin de créer un répertoire de profil, de ce fait, /home/cruisecontrol va être créé.

Voilà, maintenant créons le service qui lance Xvfb et Selenium via cet utilisateur. Créer le fichier /etc/init.d/xselenium et déposez ce code:

#!/bin/bash
 
XLOCK="/var/lock/Xvfb.pid"
SLOCK="/var/lock/selenium.pid"
 
 
startXVFB(){
        [[ -f $XLOCK ]] && echo "Already Running" && return 1
        local PID
        PID=$(su cruisecontrol -c 'Xvfb :15 >/dev/null  2>&1 & echo $!')
        echo "Xvfb pid $PID"
        echo $PID > $XLOCK
 
}
 
startSelenium(){
        [[ -f $SLOCK ]] && echo "Selenium Already Running" && return 1
 
        local PID
        PID=$(su cruisecontrol -c 'PATH=/opt/selenium/firefox:$PATH DISPLAY=:15 java -jar /opt/selenium/selenium-server-standalone-2.20.0.jar >/dev/null 2>&1 & echo $!')
        echo "Selenium pid $PID"
        echo $PID > $SLOCK
}
 
case $1 in
        start)
                echo "Starting XVFB"
                startXVFB
                sleep 2
                echo "Starting Selenium server"
                startSelenium
                sleep 2
                ;;
 
        stop)
                kill $(cat $SLOCK)
                sleep 2
                kill $(cat $XLOCK)
                sleep 2
                rm -f $SLOCK $XLOCK
                ;;
       *)
               echo "$0 start|stop"
               ;;
esac

Rendez le executable:

chmod +x /etc/init.d/xselenium

Et lancez le service:

/etc/init.d.xselenium start

Vous devez donc avoir désormais un Xvfb et un selenium actif. Attendez un peu que le port 4444 soit ouvert pour commencer à lancer les tests, pour vérifier le port:

netstat -taupen | grep 4444

Si aucune ligne n'apparait, c'est que le service ne tourne pas.

Voilà pour la partie pear, selenium-server et Xvfb. Dans la partie 2 nous utiliserons Selenium IDE pour créer des tests, mais surtout nous installerons phpUnderControl afin de piloter notre intégration continue.

En espérant avoir donner quelques clefs...

Dêpôt expérimental Fedora 16

Remi Collet

Je travaille souvent sur des fonctionnalités afin de préparer des mises à jour, réaliser des tests, etc.

J'ai donc décidé de mettre ces RPM à votre disposition, uniquement pour fedora 16 x86_64, que j'utilise comme système principal.

Voir : Dépôt remi développement Actuellement on y trouve httpd 2.4.1 et php 5.4.0 (rétro-portage des versions Fedora 18). Bien sûr, son utilisation est vraiment réservé aux experts qui n'ont pas peur de casser leur machine. Pour yum, il suffit d'ajouter dans le fichier /etc/yum.repos.d/remi.repo : [remi-dev]name=Les RPM de remi en DEV pour... Lire Dêpôt expérimental Fedora 16

PHP 5.4 serveur de développement

Remi Collet

Désormais, PHP fournit un petit serveur web de développement permettant de faire fonctionner rapidement une application.

Pour le lancer, il suffit de lui indiquer l'adresse d'écoute et le répertoire racine des pages, exemple $ php -S 127.0.0.1:8080 -t /work/GLPI/0.83-bugfixesPHP 5.4.0RC4-dev Development Server started at Sat Dec 17 09:39:11 2011Listening on 127.0.0.1:8080Document root is /var/www/htmlPress Ctrl-C to quit.Ensuite, depuis le navigateur :... Lire PHP 5.4 serveur de développement

GLPI 0.83 : les groupes

Remi Collet

La prochaine version 0.83 de GLPI est actuellement en période de béta-test.

Gros plan sur une des évolutions de cette version.

La notion de "groupe" dans GLPI est très importante car elle permet d'organiser l'activité : classement des machines, représentation de l'organigramme de l'entreprise, organisation du support par domaine compétence, etc. Plusieurs évolutions régulièrement demandées on été réalisées pour la version 0.83.   1. Arborescence Désormais, les groupes... Lire GLPI 0.83 : les groupes

FluxSphinx, l'intégration de Sphinx dans FluxBB

Guillaume Kulakowski

Il y a de cela quelques mois, nous avons rencontré des montées en charge sur Borsalino (l'ancien serveur de Fedora-Fr). Après enquête, il s'est avéré que la recherche de certains mots (ceux qui remontaient le plus de résultats) sur les forums faisait planter les sites en entrainant une charge sur le serveur MySQL. La sentence est vite tombée et nous avons été contraints de couper la recherche native sur les forums de Fedora-Fr et de basculer sur Google Custom Search Engine.

Avec l'arrivée de Stetson, le nouveau serveur mis à disposition par nos amis d'Ikoula, la question de remettre la recherche en place s'est posée. Cependant, je trouvais frustrant de répondre à un problème de conception (la recherche native de FluxBB bien que très efficace n'est pas adaptée aux grosses volumétries) par une course à l'armement (la RAM dans notre cas). Bref, je travaillais depuis quelques temps à limplémentation de Sphinx pour FluxBB sous le nom de code FluxSphinx. Et depuis ce long week-end de Toussaint, FluxSphinx est en production sur les forums de Fedora-Fr et le code est disponible sur le site du projet.

A noter que la configuration permet d'inclure la librairie PECL compilée plus efficiente que l'API en php (que vous pouvez également utiliser).

Pour le moment FluxSphinx est Iso fonctionnel avec la recherche native de FluxBB, seul le tri par pertinence ainsi que le récapitulatif dans le résultat de la recherche le distingue de la recherche native.

Cette version 0.9 donnera naissance rapidement à une v1.0 lorsque les bugs éventuels auront été remontés et corrigés. Ensuite devrait arriver une version 1.1 avec l'arrivée de facettes.