feat(ci): add multi-platform CI testing via Docker
CI / lint (push) Failing after 1m53s
CI / matrix (push) Failing after 12s
CI / test (push) Has been skipped

Add CI infrastructure that tests the Ansible role on every distro
declared in meta/main.yml (EL 8/9, Debian bullseye/bookworm,
Ubuntu focal/jammy/noble) using Docker containers.

- ci/build_matrix.py: parse meta/main.yml platforms into JSON matrix
- ci/test_playbook.yml: test playbook (state=present + validate)
- ci/Dockerfile.el, ci/Dockerfile.debian: per-family Docker images
- Makefile: orchestrator (make test, make test-<slug>, make lint)
- .gitea/workflows/ci.yml: Gitea Actions with dynamic matrix
- .gitlab-ci.yml: GitLab CI pipeline
- Jenkinsfile: Jenkins pipeline with parallel stages
- .yamllint.yml: linter configuration
- .dockerignore: exclude .git and CI configs from Docker context
This commit is contained in:
2026-04-14 00:17:42 +02:00
parent 1987c5791c
commit 54287ad9a0
10 changed files with 272 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
.git
.gitea
.gitlab-ci.yml
Jenkinsfile
docs/
+43
View File
@@ -0,0 +1,43 @@
---
name: CI
on:
push:
branches: ["*"]
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install linters
run: pip install ansible-lint yamllint
- name: Lint
run: make lint
matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.build.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- name: Install PyYAML
run: pip install pyyaml
- name: Build matrix
id: build
run: echo "matrix=$(python3 ci/build_matrix.py)" >> "$GITHUB_OUTPUT"
test:
needs: [lint, matrix]
runs-on: ubuntu-latest
strategy:
matrix:
include: ${{ fromJson(needs.matrix.outputs.matrix) }}
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Install PyYAML
run: pip install pyyaml
- name: Test ${{ matrix.slug }}
run: make test-${{ matrix.slug }}
+24
View File
@@ -0,0 +1,24 @@
---
stages:
- lint
- test
lint:
stage: lint
image: python:3.11-slim
before_script:
- pip install ansible-lint yamllint pyyaml
script:
- make lint
test:
stage: test
image: docker:latest
services:
- docker:dind
variables:
DOCKER_TLS_CERTDIR: ""
before_script:
- apk add --no-cache python3 py3-pip py3-yaml make bash
script:
- make test
+10
View File
@@ -0,0 +1,10 @@
---
extends: default
rules:
line-length:
max: 120
truthy:
check-keys: false
comments:
min-spaces-from-content: 1
Vendored
+35
View File
@@ -0,0 +1,35 @@
pipeline {
agent any
stages {
stage('Lint') {
steps {
sh 'pip install ansible-lint yamllint pyyaml'
sh 'make lint'
}
}
stage('Test Matrix') {
steps {
script {
def matrixJson = sh(script: 'python3 ci/build_matrix.py', returnStdout: true).trim()
def matrix = readJSON(text: matrixJson)
def parallelStages = [:]
matrix.each { entry ->
def slug = entry.slug
parallelStages[slug] = {
sh "make test-${slug}"
}
}
parallel parallelStages
}
}
}
}
post {
always {
sh 'make clean || true'
}
}
}
+57
View File
@@ -0,0 +1,57 @@
.PHONY: help matrix lint test clean
SHELL := /bin/bash
IMAGE_PREFIX := remote-users-fact-test
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*##"}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
matrix: ## Print the test matrix as JSON
@python3 ci/build_matrix.py
lint: ## Run yamllint and ansible-lint
yamllint roles/
ansible-lint
test: ## Run tests on all distros from the matrix
@python3 ci/build_matrix.py | python3 -c "\
import sys, json; \
matrix = json.load(sys.stdin); \
[print(e['slug'] + ' ' + e['image'] + ' ' + e['platform']) for e in matrix]" | \
while read slug image platform; do \
$(MAKE) _test-one SLUG=$$slug IMAGE=$$image PLATFORM=$$platform; \
done
test-%: ## Run test on a single distro (e.g. make test-el9)
@python3 ci/build_matrix.py | python3 -c "\
import sys, json; \
slug = '$(*)';\
matrix = json.load(sys.stdin); \
matches = [e for e in matrix if e['slug'] == slug]; \
assert matches, f'Unknown slug: {slug}. Valid: {[e[\"slug\"] for e in matrix]}'; \
e = matches[0]; print(e['slug'] + ' ' + e['image'] + ' ' + e['platform'])" | \
while read slug image platform; do \
$(MAKE) _test-one SLUG=$$slug IMAGE=$$image PLATFORM=$$platform; \
done
_test-one:
@echo "=== Testing $(SLUG) ($(IMAGE)) ==="
@dockerfile=ci/Dockerfile.el; \
if [ "$(PLATFORM)" = "Debian" ] || [ "$(PLATFORM)" = "Ubuntu" ]; then \
dockerfile=ci/Dockerfile.debian; \
fi; \
docker build \
--build-arg "IMAGE=$(IMAGE)" \
-t "$(IMAGE_PREFIX)-$(SLUG)" \
-f "$$dockerfile" . \
&& docker run --rm "$(IMAGE_PREFIX)-$(SLUG)"
@echo "=== $(SLUG) OK ==="
clean: ## Remove test Docker images
@python3 ci/build_matrix.py | python3 -c "\
import sys, json; \
[print('$(IMAGE_PREFIX)-' + e['slug']) for e in json.load(sys.stdin)]" | \
while read img; do \
docker rmi $$img 2>/dev/null || true; \
done
@echo "Clean done."
+14
View File
@@ -0,0 +1,14 @@
ARG IMAGE
FROM ${IMAGE}
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ansible iproute2 procps bash sudo \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workspace
COPY . /workspace
CMD ["ansible-playbook", "-i", "localhost,", "ci/test_playbook.yml"]
+11
View File
@@ -0,0 +1,11 @@
ARG IMAGE
FROM ${IMAGE}
RUN dnf install -y epel-release \
&& dnf install -y ansible-core iproute procps-ng bash sudo \
&& dnf clean all
WORKDIR /workspace
COPY . /workspace
CMD ["ansible-playbook", "-i", "localhost,", "ci/test_playbook.yml"]
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""Parse meta/main.yml platforms and output a Docker test matrix as JSON."""
import json
import sys
from pathlib import Path
try:
import yaml
except ImportError:
print("ERROR: PyYAML is required. Install with: pip install pyyaml", file=sys.stderr)
sys.exit(1)
# Mapping Galaxy platform name -> Docker image prefix
PLATFORM_IMAGE_MAP = {
"EL": "rockylinux",
"Debian": "debian",
"Ubuntu": "ubuntu",
}
def build_matrix(meta_path: str = "roles/remote_users_fact/meta/main.yml") -> list[dict]:
meta_file = Path(meta_path)
if not meta_file.exists():
print(f"ERROR: {meta_file} not found", file=sys.stderr)
sys.exit(1)
with open(meta_file) as f:
meta = yaml.safe_load(f)
platforms = meta.get("galaxy_info", {}).get("platforms", [])
if not platforms:
print("ERROR: No platforms found in meta/main.yml", file=sys.stderr)
sys.exit(1)
matrix = []
for platform in platforms:
name = platform["name"]
if name not in PLATFORM_IMAGE_MAP:
print(f"ERROR: Unknown platform '{name}'. Known: {list(PLATFORM_IMAGE_MAP.keys())}", file=sys.stderr)
sys.exit(1)
image_prefix = PLATFORM_IMAGE_MAP[name]
for version in platform.get("versions", []):
version_str = str(version)
slug = f"{name.lower()}{version_str}" if name == "EL" else f"{name.lower()}-{version_str}"
matrix.append({
"slug": slug,
"image": f"{image_prefix}:{version_str}",
"platform": name,
"version": version_str,
})
return matrix
if __name__ == "__main__":
print(json.dumps(build_matrix(), indent=2))
+17
View File
@@ -0,0 +1,17 @@
---
# ci/test_playbook.yml — Playbook de test CI
# Execute le role avec state=present et validation complete
- name: Test remote_users_fact
hosts: localhost
connection: local
become: true
gather_facts: true
vars:
remote_users_fact_state: present
remote_users_fact_validate: true
remote_users_fact_display_summary: true
roles:
- role: remote_users_fact