Configuration ci cd

This commit is contained in:
bastien
2026-07-10 16:00:20 +02:00
parent 5366fa3794
commit 412f7958fe
8 changed files with 535 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
---
- name: Récupérer l'ancienne version
ansible.builtin.shell: "dpkg-query -W -f='${Version}' {{ vhost.type }}-{{ vhost.package_name }} || echo 'N/A'"
register: old_version
changed_when: false
- name: Installer le package
ansible.builtin.apt:
name: "{{ vhost.type }}-{{ vhost.package_name }}={{ vhost.version }}"
state: present
- name: Récupérer la nouvelle version
ansible.builtin.shell: "dpkg-query -W -f='${Version}' {{ vhost.type }}-{{ vhost.package_name }} || echo 'N/A'"
register: new_version
changed_when: false
- name: Notification en cas de mise à jour
block:
- name: Changer le propriétaire et permissions du projet
file:
path: /var/www/{{ vhost.package_name }}
state: directory
recurse: yes
owner: www-data
group: www-data
mode: '0750'
- name: Créer le fichier de log
file:
path: /var/log/nginx/{{ vhost.package_name }}.log
state: touch
owner: www-data
group: www-data
mode: '0664'
- name: Afficher l'évolution de la version
ansible.builtin.debug:
msg: "📦 Mise à jour du paquet {{ vhost.package_name }} de {{ old_version.stdout }} vers {{ new_version.stdout }}"
- name: Envoyer notification Telegram
ansible.builtin.uri:
url: "https://api.telegram.org/bot{{ telegram_bot_token }}/sendMessage"
method: POST
body_format: json
body:
chat_id: "{{ telegram_chat_id }}"
text: |
📦 {{ vhost.package_name }} mis à jour !
Ancienne version : {{ old_version.stdout }}
Nouvelle version : {{ new_version.stdout }}
headers:
Content-Type: "application/json"
when: old_version.stdout != new_version.stdout
- name: Déployer le fichier .env
ansible.builtin.template:
src: "{{ vhost.env_template | default('host.env.j2') }}"
dest: "/var/www/{{ vhost.package_name }}/.env"
owner: www-data
group: www-data
mode: "0640"
- name: Exécuter les commandes artisan post-déploiement
ansible.builtin.shell: >
runuser -u www-data --
/usr/bin/php{{ vhost.php_version }} artisan {{ item }}
args:
chdir: "/var/www/{{ vhost.package_name }}"
loop: "{{ vhost.artisan_post_deploy_commands | default([]) }}"
loop_control:
label: "{{ item }}"
when: vhost.artisan_post_deploy_commands is defined
+46
View File
@@ -0,0 +1,46 @@
---
- name: Créer le répertoire de logs
file:
path: "/var/log/laravel-worker"
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: Déployer le fichier de configuration
copy:
dest: /etc/supervisor/conf.d/{{ worker.name }}.conf
content: |
[program:{{ worker.name }}]
command=/usr/bin/php{{ worker.php_version }} /var/www/{{ worker.package_name }}/artisan queue:work --queue={{ worker.queue }}{% if worker.options is defined %} {{ worker.options }}{% endif %}
autostart=true
autorestart=true
stderr_logfile=/var/log/laravel-worker/{{ worker.name }}.err.log
stdout_logfile=/var/log/laravel-worker/{{ worker.name }}.out.log
stopasgroup=true
killasgroup=true
loop: "{{ laravel_workers }}"
loop_control:
loop_var: worker
label: "{{ worker.name }}"
- name: Créer le fichier de log du worker
ansible.builtin.file:
path: "/var/log/laravel-worker/{{ worker.name }}.log"
state: touch
owner: www-data
group: www-data
mode: '0664'
loop: "{{ laravel_workers }}"
loop_control:
loop_var: worker
label: "{{ worker.name }}"
- name: Recharger la configuration de supervisord
command: supervisorctl reread
- name: Mettre à jour les processus supervisord
command: supervisorctl update
- name: Redémarrer les workers
command: supervisorctl restart all
+52
View File
@@ -0,0 +1,52 @@
FROM eclipse-temurin:21-jdk
ARG ANDROID_API_VERSION=36
ARG ANDROID_BUILD_TOOLS_VERSION=36.0.0
ARG FLUTTER_CHANNEL=stable
ENV ANDROID_SDK_ROOT=/opt/android-sdk \
ANDROID_HOME=/opt/android-sdk \
FLUTTER_HOME=/opt/flutter \
DEBIAN_FRONTEND=noninteractive \
HOME=/root \
PUB_CACHE=/root/.pub-cache \
GRADLE_USER_HOME=/root/.gradle
ENV PATH=${FLUTTER_HOME}/bin:${FLUTTER_HOME}/bin/cache/dart-sdk/bin:${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:${ANDROID_SDK_ROOT}/platform-tools:${PATH}
RUN apt-get update && apt-get install -y --no-install-recommends \
bash \
ca-certificates \
curl \
git \
libglu1-mesa \
openssh-client \
unzip \
wget \
xz-utils \
zip \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools \
&& curl -fsSL -o /tmp/commandlinetools.zip https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip \
&& unzip -q /tmp/commandlinetools.zip -d ${ANDROID_SDK_ROOT}/cmdline-tools \
&& mv ${ANDROID_SDK_ROOT}/cmdline-tools/cmdline-tools ${ANDROID_SDK_ROOT}/cmdline-tools/latest \
&& rm /tmp/commandlinetools.zip
RUN yes | sdkmanager --licenses >/dev/null \
&& sdkmanager \
"platform-tools" \
"platforms;android-${ANDROID_API_VERSION}" \
"build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \
"cmdline-tools;latest"
RUN git clone --depth 1 --branch ${FLUTTER_CHANNEL} https://github.com/flutter/flutter.git ${FLUTTER_HOME} \
&& git config --global --add safe.directory ${FLUTTER_HOME}
RUN flutter config --no-analytics \
&& flutter precache --android \
&& flutter doctor -v || true
WORKDIR /workspace
CMD ["bash"]
+121
View File
@@ -0,0 +1,121 @@
def call(Map config = [:]) {
def packageName = config.get('name', 'flutter-app')
def apiVersion = config.get('api', '36')
def dockerImage = "jenkins/build-flutter-android:${apiVersion}"
// URLs configurables depuis le Jenkinsfile si besoin
def debugApiBaseUrl = config.get('debugApiBaseUrl', 'http://192.168.1.37:8000/api/v1')
def releaseApiBaseUrl = config.get('releaseApiBaseUrl', 'https://laundry-dev.abpcode.fr//api/v1')
pipeline {
agent any
environment {
PUB_CACHE = "${WORKSPACE}/.pub-cache"
GRADLE_USER_HOME = "${WORKSPACE}/.gradle"
}
stages {
stage('Prepare docker') {
steps {
script {
writeFile file: 'Dockerfile', text: libraryResource("dockerfiles/flutter/android/${apiVersion}/Dockerfile")
docker.build(dockerImage, "--pull=false .")
}
}
}
stage('Checkout') {
steps {
cleanWs()
checkout scm
}
}
stage('Flutter doctor') {
steps {
script {
docker.image(dockerImage).inside('-u root') {
sh 'git config --global --add safe.directory "$WORKSPACE" || true'
sh 'flutter --version'
sh 'flutter doctor -v'
}
}
}
}
stage('Dependencies') {
steps {
script {
docker.image(dockerImage).inside('-u root') {
sh 'git config --global --add safe.directory "$WORKSPACE" || true'
sh 'flutter pub get'
}
}
}
}
stage('Analyze') {
steps {
script {
docker.image(dockerImage).inside('-u root') {
sh 'git config --global --add safe.directory "$WORKSPACE" || true'
sh 'flutter analyze'
}
}
}
}
stage('Test') {
steps {
script {
docker.image(dockerImage).inside('-u root') {
sh 'git config --global --add safe.directory "$WORKSPACE" || true'
sh 'flutter test'
}
}
}
}
stage('Build Android') {
steps {
script {
docker.image(dockerImage).inside('-u root') {
sh 'git config --global --add safe.directory "$WORKSPACE" || true'
if (env.TAG_NAME) {
sh """
flutter build apk --release \
--dart-define=API_BASE_URL=${releaseApiBaseUrl}
"""
} else {
sh """
flutter build apk --debug \
--dart-define=API_BASE_URL=${debugApiBaseUrl}
"""
}
}
}
}
}
stage('Publish') {
steps {
script {
if (env.TAG_NAME) {
env.deployment = 'prod'
env.version = "${env.TAG_NAME}-${env.BUILD_NUMBER}"
sh "cp build/app/outputs/flutter-apk/app-release.apk ./${packageName}-${env.deployment}-${env.version}.apk"
} else {
env.deployment = 'debug'
env.version = "${env.BUILD_NUMBER}"
sh "cp build/app/outputs/flutter-apk/app-debug.apk ./${packageName}-${env.deployment}-${env.version}.apk"
}
archiveArtifacts artifacts: "${packageName}-${env.deployment}-${env.version}.apk", fingerprint: true
}
}
}
}
}
}
+47
View File
@@ -0,0 +1,47 @@
APP_NAME='{{ vhost.package_name }}'
APP_ENV='{{ vhost.app_env | default('production') }}'
APP_DEBUG='{{ vhost.app_debug | default('false') }}'
APP_URL='{{ vhost.app_url }}'
APP_KEY='{{ vhost.app_key }}'
APP_TIMEZONE='{{ vhost.app_timezone | default('Europe/Paris') }}'
APP_LOCALE='fr'
APP_FALLBACK_LOCALE='fr'
LOG_CHANNEL='stack'
LOG_STACK='single'
LOG_LEVEL='{{ vhost.app_log_lvl | default('info') }}'
DB_CONNECTION='mysql'
DB_HOST='localhost'
DB_PORT='8001'
DB_DATABASE='{{ vhost.app_db_name }}'
DB_USERNAME='{{ vhost.app_db_user }}'
DB_PASSWORD='{{ vhost.app_db_password }}'
SESSION_DRIVER='{{ vhost.app_session_driver | default('database') }}'
SESSION_LIFETIME='120'
QUEUE_CONNECTION='{{ vhost.app_queue_connection | default('database') }}'
CACHE_STORE='{{ vhost.app_cache_store | default('database') }}'
BROADCAST_CONNECTION='log'
FILESYSTEM_DISK='local'
STRIPE_KEY='{{ vhost.app_stripe_key | default('') }}'
STRIPE_SECRET='{{ vhost.app_stripe_secret_key | default('') }}'
STRIPE_WEBHOOK_SECRET='{{ vhost.app_stripe_webhook_secret | default('') }}'
CORS_ALLOWED_ORIGINS='{{ vhost.app_cors_allowed_origins | default(vhost.app_url) }}'
LAVERIE_MACHINE_API_KEY='{{ vhost.app_laverie_machine_api_key | default('') }}'
LAVERIE_SIM_CYCLE_SECONDS='{{ vhost.app_laverie_sim_cycle_seconds | default('30') }}'
LAVERIE_BOOKING_FEE='{{ vhost.app_laverie_booking_fee | default('1.00') }}'
LAVERIE_BOOKING_CANCEL_GRACE_HOURS='{{ vhost.app_laverie_booking_cancel_grace_hours | default('2') }}'
LAVERIE_BOOKING_NO_SHOW_GRACE_MINUTES='{{ vhost.app_laverie_booking_no_show_grace_minutes | default('15') }}'
LAVERIE_BOOKING_PENALTY_AMOUNT='{{ vhost.app_laverie_booking_penalty_amount | default('3.00') }}'
LAVERIE_MACHINE_OFFLINE_THRESHOLD_MINUTES='{{ vhost.app_laverie_machine_offline_threshold_minutes | default('10') }}'
LAVERIE_MACHINE_OFFLINE_CHECK_ENABLED='{{ vhost.app_laverie_machine_offline_check_enabled | default('false') }}'
LAVERIE_PAYMENT_PROVIDER='{{ vhost.app_laverie_payment_provider | default('stripe') }}'
LAVERIE_TOP_UP_MIN='{{ vhost.app_laverie_top_up_min | default('5') }}'
LAVERIE_TOP_UP_MAX='{{ vhost.app_laverie_top_up_max | default('150') }}'
+30
View File
@@ -0,0 +1,30 @@
- package_name: 'laundry-backend'
server_name:
- 'laundry-dev.abpcode.fr'
php_version: '8.3'
type: 'laravel'
version: '*'
env_template: 'host-laundry.env.j2'
app_env: 'production'
app_debug: 'false'
app_log_lvl: 'info'
app_url: 'https://laundry-dev.abpcode.fr/'
app_key: 'VFMopd0BT6Ll6tQ28gtmif4JSjnaijdU'
app_db_name: ''
app_db_user: ''
app_db_password: ''
app_queue_connection: 'database'
app_cache_store: 'database'
app_session_driver: 'database'
app_cors_allowed_origins: 'https://laundry-dev.abpcode.fr'
app_laverie_machine_api_key: 'prod-laundry-machine-api-k7x9m2n4p8q1'
app_laverie_machine_offline_check_enabled: 'false'
app_laverie_payment_provider: 'stripe'
app_stripe_key: ''
app_stripe_secret_key: ''
artisan_post_deploy_commands:
- 'migrate --force'
- 'config:cache'
- 'route:cache'
- 'view:cache'
+128
View File
@@ -0,0 +1,128 @@
def call(Map config = [:]) {
def packageName = config.get('name', 'web')
def phpVersion = config.get('phpVersion', '7.4')
def dockerImage = "jenkins/build-web:${phpVersion}"
def packageType = "laravel"
pipeline {
agent any
stages {
stage('Prepare docker') {
steps {
script {
writeFile file: 'Dockerfile', text: libraryResource("dockerfiles/web/${phpVersion}/Dockerfile")
docker.build(dockerImage, "--pull=false .")
}
}
}
stage('Checkout') {
steps {
cleanWs()
checkout scm
}
}
stage('Dependencies') {
parallel {
stage('Composer') {
steps {
script {
docker.image(dockerImage).inside("-u jenkins --mount type=bind,source=${JENKINS_SSH_PATH},target=/home/jenkins/.ssh -w /app") {
dir('src') {
sh 'composer install --no-interaction --prefer-dist --no-dev -v'
}
}
}
}
}
stage('Node') {
steps {
script {
docker.image(dockerImage).inside('-u jenkins -w /app') {
dir('src') {
sh 'npm install'
}
}
}
}
}
}
}
stage('Build') {
steps {
script {
docker.image(dockerImage).inside('-u jenkins -w /app') {
dir('src') {
sh 'npm run prod'
}
}
}
}
}
stage('Packaging') {
steps {
script {
if (env.TAG_NAME) {
env['packageVersion'] = sh(returnStdout: true, script: "echo ${TAG_NAME}-${BUILD_NUMBER}").trim()
} else {
env['packageVersion'] = sh(returnStdout: true, script: "echo ${BUILD_NUMBER}").trim()
}
dir('src') {
sh """
mkdir -p ../fpm/var/www/${packageName}
rsync -a . ../fpm/var/www/${packageName} \
--exclude=".env" \
--exclude=".env.testing" \
--exclude=".env.exemple" \
--exclude="conf.php" \
--exclude="*test*" \
--exclude="vendor/phpunit" \
--exclude="phpunit.xml" \
--exclude="package-lock.json" \
--exclude="composer.lock"
"""
}
dir('fpm') {
sh """
fpm --deb-no-default-config-files -s dir -t deb -a all -m technique@abpcode.fr \
-n ${packageType}-${packageName} \
-v ${packageVersion} \
--url ${GIT_URL} \
-p ../${packageType}-${packageName}-${packageVersion}.deb ./
"""
}
}
}
}
stage('Publish') {
steps {
script {
archiveArtifacts artifacts: "${packageType}-${packageName}-${packageVersion}.deb", fingerprint: true
if (env.TAG_NAME) {
def debFile = sh(
script: "ls -1 ${packageType}-${packageName}-${packageVersion}.deb",
returnStdout: true
).trim()
sh """
scp -P 2224 -i /var/jenkins_home/.ssh/id_rsa -o StrictHostKeyChecking=no \
${debFile} jenkins@repo.abpcode.fr:/repo/pool/.tmp.deb
ssh -p 2224 -i /var/jenkins_home/.ssh/id_rsa -o StrictHostKeyChecking=no \
jenkins@repo.abpcode.fr "mv /repo/pool/.tmp.deb /repo/pool/${debFile}"
"""
}
}
}
}
}
}
}
+39
View File
@@ -0,0 +1,39 @@
# Utiliser PHP 8.3 CLI
FROM php:8.3-cli
# Installer Git, unzip/zip et curl
RUN apt-get update && apt-get install -y \
git \
unzip \
zip \
curl \
openssh-client \
&& rm -rf /var/lib/apt/lists/*
# Installer Composer 2
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Installer Node.js
RUN curl -sL https://deb.nodesource.com/setup_22.x | bash -
RUN apt install -y nodejs
# Créer l'utilisateur jenkins avec home et bash
RUN useradd -m -s /bin/bash jenkins
# Créer le répertoire .ssh pour Jenkins
RUN mkdir -p /home/jenkins/.ssh \
&& chmod 700 /home/jenkins/.ssh
# Définir l'utilisateur jenkins pour la suite
USER jenkins
# Définir le répertoire de travail
WORKDIR /app
# Définir GIT_SSH_COMMAND pour tous les builds
ENV GIT_SSH_COMMAND="ssh -i /home/jenkins/.ssh/id_rsa -o StrictHostKeyChecking=no"
# Optionnel : copier un script d'entrée ou autres fichiers si nécessaire
# COPY entrypoint.sh /usr/local/bin/entrypoint.sh
# RUN chmod +x /usr/local/bin/entrypoint.sh
# ENTRYPOINT ["entrypoint.sh"]