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:
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:
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
Les différentes stages se présentent de cette manière (on ignore le stage update pour l’instant)
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
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']
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
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:
gitlab-ci
et qui sera copié (stocké dans configs/gitlab-ci.yml
)RELEASE
dans templates/release.yml
LAST_RELEASE
dans templates/pdk-ci.yml
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
.