23 Feb 2023

Centralisation des Ci Puppet avec Gitlab

Sommaire


Introduction

I love pdk

Lors de mon utilisation de Puppet j’ai découverts le fonctionnement tests unitairs et leurs utilités. Travaillant principalement seul il m’était très utile de les mettre en place avec deux objectifs principaux:

  • VĂ©rifier que je ne fasse pas un breaking non maĂ®trisĂ©s
  • AmĂ©liorer la qualitĂ© de mon code afin de pouvoir contribuer plus facilement sur du code communautaire (les tests unitaires ont tendance Ă  revoir la façon dont vous concevez votre code)

Utilisation de Pdk

Puppetlabs fournis un kit de développement pour aider à mettre en place les tests unitaires avec pdk. Cet outil permet également un contexte complet pour vos modules, on va retrouver le metadata.json, le répertoire de spec avec les helpers, un Gemfile, un context hiera..

Cet outil est donc très intéressant et facile d’utilisation quand on a un ou deux modules à manager, mais ça devient nettement plus compliqué avec 300. Pour gérer ce contenue et suivre les mises à jour de pdk, plusieurs solutions ont été testées. Allant du simple script bash à une application sinatra + sidekiq, mais toutes les solutions amenaient des complications. Que ce soit pour gérer les versions pdk, le contexte ruby ou encore tout simplement gérer 300 mises à jours faites en même temps.

Après plusieurs tests j’ai finalement choisi une solution à base de template de CI Gitlab. Grâce à ce fonctionnement je voyais déjà plusieurs améliorations:

  • Pouvoir mettre Ă  jour des Ă©tapes de CI sans passer par une mise Ă  jour de PDK (lĂ  oĂą il faut actuellement re-faire le gitlab-ci dans le .sync.yml )
  • Établir une solution autonome de mise Ă  jour de PDK
  • Utiliser un runner gitlab et l’image PDK fournit par Puppetlabs pour garantir son fonctionnement

Mise en place du workflow

Pour le workflow souhaité j’ai tout défini dans un repo dédié et il n’y a qu’un seul fichier de ci à inclure. Ce sera ce dernier qui orchestra le reste, ce qui donne un résultat semblable:

---
default:
  cache:
    paths:
      - vendor/bundle

# Define default variable
variables:
  PDK_TAG: 2.5.0
  IMAGE: pdk:2.5.0.0
  GIT: /opt/puppetlabs/pdk/private/git/bin/git
  PDK: /usr/local/bin/pdk
  LAST_RELEASE: 0.0.1

# Include all my jobs
include:
  - local: /templates/update.yml
  - local: /templates/syntax.yml
  - local: /templates/unittest.yml
  - local: /templates/pages.yml

# Setup my stages
stages:
  - update
  - syntax
  - unittest
  - documentation

Gitlab-ci stages

Les différentes stages se présentent de cette manière (on ignore le stage update pour l’instant)

Syntax

Mise en place d’une vérification de syntax avec les rake mis en place par pdk, mais aussi d’une vérification un peu plus poussé des fichiers yaml avec yamllint.

---
syntax:
  stage: syntax
  image: ruby:2.7.2
  script:
    - bundle exec rake validate lint check rubocop
  rules:
    - when: always

yaml_lint:
  stage: syntax
  image: sdesbure/yamllint
  script:
    - >
        if [[ -f 'hiera.yaml' ]]; then yamllint **/*.yaml --no-warnings; fi
  rules:
    - when: always

Unittest

Lancement des tests unitaires, cette tâche se fera uniquement lors de merge request ou sur la branch production. Ils sont lancés avec rspec, qui est un framework de test pour Puppet.

---
.unittest:
  stage: unittest
  image: ruby:2.7.2
  script:
    - bundle exec rake parallel_spec
  only:
    - merge_requests
    - production
  coverage: '/coverage:\s*\d+.\d+%/'

spec_puppet:
  extends: .unittest
  variables:
    PUPPET_GEM_VERSION: '~> 7'

À noter que le test est uniquement fait pour Puppet 7, mais on pourrait très bien utiliser parallel-matrix pour effectuer des Puppet 6 et 7.

spec_puppet:
  extends: .unittest
  parallel:
    matrix:
      - PUPPET_GEM_VERSION: ['~> 6', '~> 7']

Pages

Et enfin la dernière étape sert générer la documentation automatique, deux formes différentes.

La première sera avec Gitlab pages et yard/Puppet strings, mais vu que les gens vont rarement voir les pages, on fait une version simplifiée en markdown directement dans le repo. Pour ça il faut un token qui a les accès en écriture sur le repo, il sera à définir en variable dans la variable d’environnement PROJECT_TOKEN_RW (de votre groupe contenant uniquement les projets ou à faire unitairement par projet). Le commit se fera uniquement s’il y a une différence sur le fichier REFERENCE.md, et dans ce cas l’option -o ci.skip sera passé sur le push afin d’éviter de relancer la CI.

---
pages:
  stage: documentation
  image: ruby:3.0-alpine
  script:
    - gem install puppet yard puppet-strings
    - puppet strings generate
    - mv doc public
    - apk add git
    - puppet strings generate --format markdown
    - git add REFERENCE.md
    - "[ -z $(git status --porcelain REFERENCE.md) ] && exit 0"
    - "git config --global user.email 'dev@nu.ll'"
    - "git config --global user.name 'Pdk Ci'"
    - "git remote set-url origin https://git:${PROJECT_TOKEN_RW}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
    - "git commit -m ':books: update REFERENCE.md'"
    - "git push -o ci.skip origin HEAD:${CI_COMMIT_BRANCH}"
  artifacts:
    expire_in: 300 seconds
    paths:
      - public
  only:
    - production

Auto-gestion de la CI

Tout cela est relativement simple à mettre en place et il suffit par la suite d’avoir le bout d’include qui va bien pour que ça fonctionne ex:

---
include:
  - project: <votre repo>
    ref: main  # Or your default branch name
    file: templates/pdk-ci.yml

Mais le principal objectif était de mettre une gestion simplifiée de PDK. Pour ça j’ai rajouté une vérification pour notifier de la nécessité de faire un update e pdk, et j’ai automatisé la mise à jour (et l’installation) dans la CI.

Dans un premier temps j’ai créé un fichier template/release.yaml qui contient simplement une variable RELEASE, puis dans ma CI l’include de ce template mais avec un tag et non sur la branche main.

template/release.yaml:

---
variables:
  RELEASE: 0.0.1

Puis dans le fichier templates/update.yml j’ai:

---
.pdk_update:
  stage: update
  image:
    name: puppet/${IMAGE}
    entrypoint:
      - ''
  script:
    - if [[ ${PDK_RUN} -eq 0 ]]; then exit 0; fi
    - ${GIT} clone --branch ${RELEASE} ${PDK_TEMPLATE} /tmp/pdk_template
    - mkdir -p manifests
    - cp /tmp/pdk_template/configs/sync.yml .sync.yml
    - mkdir -p ~/.pdk/cache/
    - cp /tmp/pdk_template/configs/answers.json ~/.pdk/cache/
    - ${PDK} ${PDK_COMMAND}
    - cp /tmp/pdk_template/configs/gitlab-ci.yml .gitlab-ci.yml
    - touch pdk.yaml
    - ${GIT} add .gitignore
    - ${GIT} config advice.addIgnoredFile false
    - "${GIT} add -A . || :"
    - "${GIT} config --global user.email 'dev@nu.ll'"
    - "${GIT} config --global user.name 'Pdk Ci'"
    - "${GIT} remote set-url origin https://git:${PROJECT_TOKEN_RW}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
    - "${GIT} commit -m \"${GIT_COMMIT}\""
    - ${GIT} push origin HEAD:${CI_COMMIT_BRANCH}

install_pdk:
  extends: .pdk_update
  before_script:
    - PDK_RUN=0
    - >
        [[ ! -f '.sync.yml' ]] && PDK_RUN=1
    - export PDK_RUN=${PDK_RUN}
  variables:
    PDK_COMMAND: "convert --force"
    GIT_COMMIT: ':construction_worker: convert to pdk module'
  only:
    - production

update_pdk:
  extends: .pdk_update
  before_script:
    - PDK_RUN=0
    - >
        [[ -f '.sync.yml' ]]
    - >
        [[ -f '.gitlab-ci.yml' ]]
    - >
        [[ "${LAST_RELEASE}" == "${RELEASE}" ]] || PDK_RUN=1
    - export PDK_RUN=${PDK_RUN}
    - export RELEASE=${LAST_RELEASE}
  variables:
    PDK_COMMAND: "update --force --template-ref=${PDK_TAG}"
    GIT_COMMIT: ':construction_worker: upadte pdk'
  only:
    - production

Avec cette comparaison [[ "${LAST_RELEASE}" == "${RELEASE}" ]] || PDK_RUN=1 je notifie la mise à jour de PDK, et je vérifie également que si le fichier .sync.yml est manquant, c’est qu’il est nécessaire d’installer de Pdk. Pour faire une nouvelle release il faudrat alors passer par 4 étapes:

  1. Mettre à jour le tag à utilsé dans le nouveau gitlab-ci et qui sera copié (stocké dans configs/gitlab-ci.yml)
  2. Mettre Ă  jour la variable RELEASE dans templates/release.yml
  3. Mettre Ă  jour la variable LAST_RELEASE dans templates/pdk-ci.yml
  4. Créer le nouveau tag et pusher le tout

Puis, votre fichier .gitlab-ci.yaml de vos modules final ressemblera Ă :

---
include:
  - project: <votre repo>
    ref: main  # Or your default branch name
    file: templates/pdk-ci.yml
  - project: <votre repo>
    ref: <your actual tag>
    file: templates/release.yml

Pour finir, si vous ne souhaitez pas attendre que le code soit modifié en production pour mettre à jour pdk, vous pouvez mettre un pipeline schedule (de mon côté j’avais mis ça aléatoirement une fois par semaine lors de la création des repo).

ATTENTION: Dans le cas où votre repo qui contient les templates change, il est important de garder le repo actuel le temps de faire une nouvelle release avec les bonnes références dans le gitlab-ci.yaml.


Tags: