Jenkins pipeline avec Gitlab et sonarqube pour module NodeJS

TG
  • Tommy Gingras
    Studio Webux S.E.N.C
    17 Juillet 2020

Jenkins pipeline avec Gitlab et sonarqube pour module NodeJS

Introduction

L’objectif de cet article est de montrer comment créer un pipeline Jenkins pour du code NodeJS.

Les technologies utilisées pour le versionning sont gitlab pour l’environnement local et github pour la production.

De plus, pour assurer une qualité du code et le respect des standards, l’outil sonarqube est utilisé.

Pré requis

  • Serveur Gitlab
  • Serveur Jenkins avec / sans worker
  • NodeJS de disponible sur l’instance de Jenkins
  • Serveur Sonarqube

Tous ces outils sont déployés en conteneur avec Docker

Pour Jenkins, mon setup comprend un jenkins Master déployé en Docker et une node installée directement sur un CentOS, toutes les commandes ci-dessous seront exécutées sur la node.

La node a plusieurs outils d’installés (voir annexe pour certains)

  • NodeJS + NPM
  • Sonarqube scanner
  • Docker
  • CA Certificate personnalisé
  • Sendgrid

Étape 1 - Installer les plug-ins dans Jenkins

Il faut installer les plug-ins suivants

  • git
  • Gitlab
  • Gitlab API
  • credentials
  • Gitlab authentication
  • sonarqube scanner
  • blue ocean plugin

Pour installer les plug-ins, allez

Manage JenkinsManage Plugins > Available

Puis vous pouvez utiliser le filtre en haut à droite.

Les plug-ins pour gitlab

Le plug-in sonarqube

Le plug-in blue ocean

Étape 2 - Installer sonarqube scanner sur la machine

Il faut télécharger ce fichier puis l’extraire,

cd /opt
wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.4.0.2170-linux.zip
unzip sonar-scanner-cli-4.4.0.2170-linux.zip

Étape 2.1 - OPTIONEL - Un CA Certificate personnalisé

Dans le cas où vous avez votre propre Certificat, vous pouvez l’ajouter comme ceci

keytool -import -noprompt -trustcacerts -file /etc/pki/ca-trust/source/anchors/cacert.pem -alias studiowebux -keystore /opt/sonar-scanner-4.4.0.2170-linux/jre/lib/security/cacerts  -storepass changeit

Étape 2.2 - Exemple avec Ansible

Voici un script ansible pour configurer le tout,

- name: Download and extract sonarqube cli and add cacert
  hosts: jenkins-worker
  gather_facts: no

  vars:
    url: "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.4.0.2170-linux.zip"
    ca_certificate_name: "studiowebux"

  tasks:
    - name: Download and extract sonar-scanner-cli
      unarchive:
        src: "{{ url }}"
        dest: "/opt"

    - name: Add cacert to java
      shell: |
        keytool -import -noprompt -trustcacerts -file /etc/pki/ca-trust/source/anchors/{{ ca_certificate_name }}.pem -alias studiowebux -keystore /opt/sonar-scanner-4.4.0.2170-linux/jre/lib/security/cacerts  -storepass changeit

Étape 3 - Générer les tokens

Étape 3.1 - Sonarqube

Allez sur votre instance de sonarqube, puis cliquez sur Administration

Puis choisissez le menu Security et cliquez sur User

Générer token sonarqube

Dans ma configuration, seulement le compte administrateur existe, alors vous devez cliquer sur les trois barres pour générer un token.

Donner un nom au token, puis cliquez sur Generate

Ne perdez pas le token, sauvegarder le à un endroit sécuritaire, après avoir fermé la fenêtre, vous ne pourrez plus le voir.

Dans le cas où votre token a été découvert, vous pouvez le révoquer et en générer un nouveau.

Voici un exemple Générer un token sonarqube pour le compte administrateur

Étape 3.2 - Gitlab

Sur gitlab, cliquez sur votre profile en haut à droite, puis choisissez Settings

Gitlab options de l'usager

Dans le menu de gauche, cliquez sur Access Token

Gitlab menu profile

Donner un nom à votre access token

puis cocher l’option API, ensuite Create new access token

Ne perdez pas le token, sauvegarder le à un endroit sécuritaire, après avoir fermé la fenêtre, vous ne pourrez plus le voir.

Dans le cas où votre token a été découvert, vous pouvez le révoquer et en générer un nouveau.

Étape 4 - Configurer Jenkins

Étape 4.1 - Créer les credentials dans jenkins

Manage Jenkins > Manage Credentials

Identifiant Jenkins

Cliquez sur le lien (global) ( dans la deuxième section de la page )

Cliquez sur Add Credentials

Créer identifiants Jenkins

Gitlab

Pour l’identifiant gitlab, choisissez Gitlab API Token puis copier votre access token dans le champ API token Assurez-vous de mettre un ID significatif

Créer identifiant Gitlab API Jenkins

Sonarqube

Pour l’identifiant sonarqube, choisissez Secret Text puis copier votre token dans le champ secret Assurez-vous de mettre un ID significatif

Créer identifiant Sonarqube API Jenkins

Github

Pour cet identifiant je vous recommande de générer une clé SSH sur Github, pour faire mes tests, j’ai simplement entré le username/password

Voici ce que vous devriez avoir

Jenkins identifiant global

Étape 4.2 - Configurer Jenkins et les plug-ins

Allez à Manage JenkinsConfigure System

Étape 4.2.1 - Configurer sonarqube

Voici ma configuration, vous devez modifier le tout pour ajuster avec votre configuration.

Jenkins configuration sonarqube

le nom que vous configurez est important il sera utilisé dans le jenkinsfile Pour l’authentification, choissisez le token qui a été créé à l’étape précédente

Pour terminer la configuration de sonarqube, il faut définir le répertoire où se trouve l’outil (/opt/sonar-scanner-cli-4.4.0.2170-linux)

Naviguer à Manage JenkinsGlobal Tool Configuration > la section Sonarqube Scanner

Voici ma configuration

Jenkins configuration de l'outil de sonarqube

Pour continuer la configuration, il faut retourner à la page Manage JenkinsConfigure System

Étape 4.2.2 - Configurer Gitlab

Voici ma configuration, vous devez modifier le tout pour ajuster avec votre configuration.

Même chose, choissisez le token précédemment créé,

Jenkins configuration gitlab

Étape 4.2.3 - Configurer sendgrid

Voici ma configuration

Jenkins configuration sendgrid

Étape 5 - Création d’un Jenkinsfile

Voici un exemple avec un de mes modules NodeJS

@studiowebux/socket

pipeline {
  agent {
    node {
      label 'nodejs'
    }

  }

  stages {
    stage('Preparation') {
      steps {
        sh 'git remote add prod https://github.com/studiowebux/webux-sql.git || true'
      }
    }

    stage('Dependencies') {
      steps {
        sh 'npm install'
      }
    }

    stage('Lint') {
      steps {
        sh 'npm run-script lint'
      }
    }

    stage('Code Analysis') {
      steps {
        script {
          def scannerHome = tool 'sonarqube';
              withSonarQubeEnv("sonarqube") {
              sh "${tool("sonarqube")}/bin/sonar-scanner"
              }
        }
      }
    }

    stage('Test') {
      steps {
        sh 'npm run test'
      }
    }

    stage('Versionning') {
      steps {
        script {
          env.RELEASE_SCOPE = input message: 'User input required', ok: 'Continue',
                            parameters: [choice(name: 'RELEASE_SCOPE', choices: 'patch\nminor\nmajor', description: 'What is the release scope?')]
        }
        echo "${env.RELEASE_SCOPE}"
      }
    }

    stage('Staging') {


      steps {
        withCredentials([usernamePassword(credentialsId: 'git:1f00e77842774986a932a1367b515be6efb49cae2d1a134a1988a651d8ff094b', usernameVariable: 'GIT_USERNAME', passwordVariable: 'GIT_PASSWORD')]){
          sh('''
              git config --local credential.helper "!f() { echo username=\\$GIT_USERNAME; echo password=\\$GIT_PASSWORD; }; f"
              git push origin master
              git config --unset credential.helper
          ''')
        }
        withCredentials([usernamePassword(credentialsId: 'GitHub', usernameVariable: 'GIT_USERNAME', passwordVariable: 'GIT_PASSWORD')]){
          sh('''
              git config --local credential.helper "!f() { echo username=\\$GIT_USERNAME; echo password=\\$GIT_PASSWORD; }; f"
              git push prod master
              git config --unset credential.helper
          ''')
        }
        sh "npm version ${env.RELEASE_SCOPE}"
        sh 'npm publish --registry=https://npm.webux.lab'
        input 'Deploy to  production ?'
      }
    }

    stage('Production') {


      steps {
        withCredentials([usernamePassword(credentialsId: 'git:1f00e77842774986a932a1367b515be6efb49cae2d1a134a1988a651d8ff094b', usernameVariable: 'GIT_USERNAME', passwordVariable: 'GIT_PASSWORD')]){
          sh('''
              git config --local credential.helper "!f() { echo username=\\$GIT_USERNAME; echo password=\\$GIT_PASSWORD; }; f"
              git push origin master
              git config --unset credential.helper
          ''')
        }
        withCredentials([usernamePassword(credentialsId: 'GitHub', usernameVariable: 'GIT_USERNAME', passwordVariable: 'GIT_PASSWORD')]){
          sh('''
              git config --local credential.helper "!f() { echo username=\\$GIT_USERNAME; echo password=\\$GIT_PASSWORD; }; f"
              git push prod master
              git config --unset credential.helper
          ''')
        }

        sh 'npm publish --access public'
        mail(to: 'tommy@studiowebux.com', subject: 'Webux-sql - Published', body: 'Webux-sql has been published to production')
      }
    }

  }
  post {
    failure {
        mail to: 'tommy@studiowebux.com',
        subject: "Failed Pipeline ${currentBuild.fullDisplayName}",
        body: " For details about the failure, see ${env.BUILD_URL}"


        sh 'git config --unset credential.helper'
    }
  }
}

Ce pipeline fait plusieurs choses,

  • Installer les dépendances directement sur le worker
  • Vérifier le lint en utilisant eslint
  • Lancer les tests en utilisant jest
  • Lancer le scanner de sonarqube
  • Demander quelle version à déployer (patch | minor | major)
  • Deployer le code sur le npm local
  • Demander si on veut déployer en production (sur npmjs)

Voici un build réussi

Jenkins pipeline tâches réussies

Étape 6 - Ajouter ce pipeline à votre Jenkins

Il faut aller dans Open Blue Ocean (à partir du menu de gauche)

Jenkins Open Blue Ocean

Cliquer sur New Pipeline

Jenkins Open Blue Ocean - new pipeline

Choisissez git et entrer l’URL de votre repository ensuite choisissez l’identifiant qui a été ajouté plus tot.

Jenkins Open Blue Ocean - new pipeline formulaire

Et pour créer le tout, cliquer sur Create Pipeline

Conclusion

Voilà !

Vous avez maintenant un Jenkins pipeline intégré avec Gitlab, GitHub, Sonarqube, Jest, eslint et tout ce dont vous voulez ajouter.

C’est la première fois que je travaille avec les pipelines, je vais continuer d’explorer le tout et ajouter du contenu sur mes découvertes.

Annexe

Scripts Ansible

Configuration initiale du worker

- name: Install Utilities
  hosts: jenkins-worker
  gather_facts: no

  tasks:
    - name: Install Tools and Utilities
      package:
        name:
          - nano
          - git
          - unzip
          - net-tools
          - tmux
        state: latest

    - name: Install NodeJS repository
      shell: "curl -sL https://rpm.nodesource.com/setup_12.x | bash -"

    - name: Install NodeJS
      yum:
        name: nodejs
        state: latest

Ajout du CA certificate pour NPM local (verdaccio)

- name: Add the CA certificate to npm
  hosts: jenkins-worker
  gather_facts: no

  vars:
    ca_certificate_name: "studiowebux_CA"

  tasks:
    - name: add the CA certificate to NPM
      shell: "npm config set cafile /etc/pki/ca-trust/source/anchors/{{ ca_certificate_name }}.pem"

    - name: Validate the CA certificate
      shell: "npm config get cafile"
      register: cafile_output

    - debug:
        msg: "{{ cafile_output }}"

    - name: Set the registry to npmjs
      shell: "npm config set registry http://registry.npmjs.org/"

Installation et configuration personnalisée de Docker

- name: Install and Configure Docker CE on CentOS / RHEL
  hosts: docker
  gather_facts: no

  vars:
    srv_DOCKER: "/srv/DOCKER"
    # Be sure to specify a range that will not interfere with libvirt or other networks
    docker_network: "--default-address-pool base=172.24.0.0/13,size=24"

  tasks:
    - name: Create `{{ srv_DOCKER }}/data` directory
      file:
        path: "{{ srv_DOCKER }}/data"
        state: directory

    - name: Install `dependencies`
      package:
        name:
          - yum-utils
          - device-mapper-persistent-data
          - lvm2
        state: present

    - name: Add `docker-ce` repository
      shell: |
        yum-config-manager \
        --add-repo \
        https://download.docker.com/linux/centos/docker-ce.repo

    - name: Install `docker-ce`
      package:
        name:
          - docker-ce
          - docker-ce-cli
          - containerd.io
        state: present

    # https://linuxconfig.org/how-to-move-docker-s-default-var-lib-docker-to-another-directory-on-ubuntu-debian-linux
    - name: Configure docker data directory to `{{ srv_DOCKER }}/data` and configure the network
      lineinfile:
        path: /lib/systemd/system/docker.service
        regexp: "ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock"
        line: "ExecStart=/usr/bin/dockerd -g {{ srv_DOCKER }}/data -H fd:// --containerd=/run/containerd/containerd.sock {{ docker_network }}"

    - name: Remove `/var/lib/docker`
      file:
        path: "/var/lib/docker/"
        state: absent

    - name: Enable and Start Docker
      systemd:
        daemon_reload: yes
        name: docker
        state: started
        enabled: yes

    - name: Install `docker-compose` 1.26.0
      shell: 'curl -L "https://github.com/docker/compose/releases/download/1.26.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose'

    - name: Set `docker-compose` Permission
      file:
        path: "/usr/local/bin/docker-compose"
        mode: 0755

    - name: Create link to /usr/bin/docker-compose
      file:
        src: "/usr/local/bin/docker-compose"
        dest: "/usr/bin/docker-compose"
        state: link

    # This module was causing issue with libvirtd
    # But after some reading, the conflict was caused by the default docker network.
    - name: Blacklist br_netfilter module
      shell: "echo install br_netfilter /bin/true > /etc/modprobe.d/disable-br-netfilter.conf"

    - debug:
        msg: "Add your user to docker group like that : `sudo usermod -aG docker [your-user]`"