feat(miroirs) — un dépôt personnel n'est pas une organisation
Le rôle gitea_repo ne savait viser qu'un propriétaire : l'organisation, des deux côtés à la fois. Les dépôts qui vivent sous le compte personnel `arcodange` ne pouvaient donc pas sortir du homelab — ni être balayés par gitea_sync, qui n'interroge que /orgs/<org>/repos. Trois séparations, toutes rétrocompatibles (les défauts reconduisent le comportement org-vers-org des dix dépôts déjà en miroir) : - le propriétaire côté Gitea (`gitea_repo_owner`) n'est plus le même objet que celui d'en face (`github_owner`, `gitlab_owner`) ; - un compte personnel n'est pas une organisation : GitHub ne crée pas le dépôt au même endroit, d'où `github_owner_is_org` qui route vers POST /user/repos ; - GitLab devient facultatif (`gitea_mirror_gitlab`). Il ne l'était pas : sa création attendait un 201 sans ignore_errors, si bien qu'un échec GitLab avortait l'itération — y compris la moitié GitHub, qui n'y était pour rien. Deux défauts corrigés au passage, tous deux silencieux : - les trois listages de gitea_sync ne paginaient pas (30 chez GitHub, 20 chez GitLab). Sous la taille d'une page tout va bien ; au-delà, la différence entre forges désigne de FAUX dépôts manquants et le rôle les « répare » ; - la migration entrante posait `repo_owner: github_organization` pour désigner le propriétaire DANS Gitea. Et un piège découvert en exécutant : un dépôt GitHub créé vide adopte comme branche par défaut la PREMIÈRE branche que le miroir lui pousse — `kadans` a atterri sur `arcodange/adr-ddd-front`. Le rôle réaligne désormais sur la branche par défaut de Gitea ; le miroir étant asynchrone, l'alignement échoue au run qui crée le dépôt et réussit au suivant, d'où le failed_when permissif. Ce qui sort du homelab reste un CHOIX : playbooks/07_mirrors.yml parcourt une liste explicite et relue (`gitea_mirrored_repos`) plutôt que la différence automatique entre forges, qui recréerait un dépôt supprimé exprès. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
@@ -6,4 +6,38 @@ gitea_username: arcodange
|
||||
gitea_organization: arcodange-org
|
||||
|
||||
# URL de base du serveur Gitea
|
||||
gitea_base_url: http://{{ groups.gitea[0] }}:3000
|
||||
gitea_base_url: http://{{ groups.gitea[0] }}:3000
|
||||
|
||||
# Propriétaire du dépôt CÔTÉ GITEA. Par défaut l'organisation, pour ne rien
|
||||
# changer aux dépôts déjà en miroir ; à surcharger (« arcodange ») pour les
|
||||
# dépôts qui vivent sous le compte personnel.
|
||||
gitea_repo_owner: "{{ gitea_organization }}"
|
||||
|
||||
# Propriétaires en FACE, forge par forge. Ils suivent le propriétaire Gitea par
|
||||
# défaut, mais un dépôt personnel peut viser un compte personnel.
|
||||
github_owner: "{{ github_organization }}"
|
||||
gitlab_owner: "{{ gitlab_root_group }}"
|
||||
|
||||
# Un compte personnel n'est pas une organisation : GitHub ne crée pas un dépôt
|
||||
# au même endroit (POST /user/repos contre POST /orgs/<org>/repos).
|
||||
github_owner_is_org: true
|
||||
|
||||
# Identifiant du groupe OU de l'utilisateur GitLab qui accueille le projet.
|
||||
# https://gitlab.com/groups/arcodange-org/-/edit
|
||||
gitlab_namespace_id: 89826881
|
||||
|
||||
# Quelles forges recevoir en miroir. GitLab devient facultatif : sans ça, un
|
||||
# échec côté GitLab avorte toute l'itération, y compris la partie GitHub.
|
||||
gitea_mirror_github: true
|
||||
gitea_mirror_gitlab: true
|
||||
|
||||
# Le miroir pousse ; c'est le dépôt d'en face qui doit être privé.
|
||||
github_repo_private: true
|
||||
gitlab_repo_visibility: private
|
||||
|
||||
# Nom d'utilisateur porté par le miroir push (le mot de passe est le jeton).
|
||||
github_mirror_username: "{{ gitea_username }}"
|
||||
gitlab_mirror_username: "{{ gitea_username }}"
|
||||
|
||||
# Cadence de rafraîchissement des miroirs, en plus du push à chaque commit.
|
||||
gitea_mirror_interval: "8h"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- name: Vérifier si le dépôt existe dans Gitea
|
||||
uri:
|
||||
url: "{{ gitea_base_url }}/api/v1/repos/{{ gitea_organization }}/{{ gitea_repo_name }}"
|
||||
url: "{{ gitea_base_url }}/api/v1/repos/{{ gitea_repo_owner }}/{{ gitea_repo_name }}"
|
||||
method: GET
|
||||
headers:
|
||||
Authorization: "token {{ gitea_api_token }}"
|
||||
@@ -10,26 +10,36 @@
|
||||
|
||||
- name: Vérifier si le dépôt existe sur GitLab
|
||||
uri:
|
||||
url: "https://gitlab.com/api/v4/projects/{{ gitlab_root_group }}%2F{{ gitea_repo_name }}"
|
||||
url: "https://gitlab.com/api/v4/projects/{{ gitlab_owner }}%2F{{ gitea_repo_name }}"
|
||||
method: GET
|
||||
headers:
|
||||
Authorization: "Bearer {{ gitlab_api_token }}"
|
||||
status_code: 200
|
||||
register: gitlab_repo_check
|
||||
ignore_errors: yes
|
||||
when: gitea_mirror_gitlab | bool
|
||||
|
||||
- name: Vérifier si le dépôt existe sur GitHub
|
||||
uri:
|
||||
url: "https://api.github.com/repos/{{ github_organization }}/{{ gitea_repo_name }}"
|
||||
url: "https://api.github.com/repos/{{ github_owner }}/{{ gitea_repo_name }}"
|
||||
method: GET
|
||||
headers:
|
||||
Authorization: "token {{ github_api_token }}"
|
||||
status_code: 200
|
||||
register: github_repo_check
|
||||
ignore_errors: yes
|
||||
when: gitea_mirror_github | bool
|
||||
|
||||
# Une tâche sautée n'enregistre pas de « status » : sans le default(0), la
|
||||
# condition suivante explose dès qu'une forge est désactivée.
|
||||
- name: Retenir l'état de chaque forge
|
||||
set_fact:
|
||||
gitlab_repo_present: "{{ (gitlab_repo_check.status | default(0)) == 200 }}"
|
||||
github_repo_present: "{{ (github_repo_check.status | default(0)) == 200 }}"
|
||||
gitea_repo_present: "{{ (gitea_repo_check.status | default(0)) == 200 }}"
|
||||
|
||||
- name: Importer un dépôt GitLab/GitHub vers Gitea
|
||||
when: gitea_repo_check.status != 200 and (gitlab_repo_check.status == 200 or github_repo_check.status == 200)
|
||||
when: not gitea_repo_present and (gitlab_repo_present or github_repo_present)
|
||||
uri:
|
||||
url: "{{ gitea_base_url }}/api/v1/repos/migrate"
|
||||
method: POST
|
||||
@@ -38,16 +48,16 @@
|
||||
status_code: 201
|
||||
body_format: json
|
||||
body:
|
||||
service: "{{ (gitlab_repo_check.status == 200) | ternary('gitlab','github') }}"
|
||||
service: "{{ gitlab_repo_present | ternary('gitlab','github') }}"
|
||||
# URL du dépôt GitHub/GitLab
|
||||
clone_addr: >-
|
||||
{{ (gitlab_repo_check.status == 200) | ternary(gitlab_mirror_url,github_mirror_url) }}
|
||||
{{ gitlab_repo_present | ternary(gitlab_mirror_url,github_mirror_url) }}
|
||||
auth_username: "{{ gitea_username }}" # Nom d'utilisateur pour l'authentification si nécessaire
|
||||
# token d'accès
|
||||
auth_token: >-
|
||||
{{ (gitlab_repo_check.status == 200) | ternary(gitlab_api_token,github_api_token) }}
|
||||
{{ gitlab_repo_present | ternary(gitlab_api_token,github_api_token) }}
|
||||
repo_name: "{{ gitea_repo_name }}" # Nom du dépôt dans Gitea
|
||||
repo_owner: "{{ github_organization }}" # Propriétaire du dépôt dans Gitea (utilisateur ou organisation
|
||||
repo_owner: "{{ gitea_repo_owner }}" # Propriétaire du dépôt dans Gitea (utilisateur ou organisation)
|
||||
mirror: false # Activer le mirroring pour synchroniser les changements
|
||||
register: migration_result
|
||||
|
||||
@@ -66,15 +76,20 @@
|
||||
body:
|
||||
name: "{{ gitea_repo_name }}"
|
||||
path: "{{ gitea_repo_name }}"
|
||||
namespace_id: "{{ gitlab_namespace_id }}" # Remplacez par l'ID du groupe ou de l'utilisateur où le projet doit être créé
|
||||
visibility: "{{ gitlab_repo_visibility | default('private') }}" # Définir la visibilité (private, internal, public)
|
||||
namespace_id: "{{ gitlab_namespace_id }}" # ID du groupe ou de l'utilisateur où le projet doit être créé
|
||||
visibility: "{{ gitlab_repo_visibility }}" # Définir la visibilité (private, internal, public)
|
||||
description: "{{ gitea_repo_description | default('') }}"
|
||||
status_code: 201
|
||||
when: gitlab_repo_check.status != 200
|
||||
when: (gitea_mirror_gitlab | bool) and not gitlab_repo_present
|
||||
|
||||
# Un compte personnel n'a pas d'endpoint /orgs/<nom>/repos : GitHub crée alors
|
||||
# le dépôt sous le compte porteur du jeton, via POST /user/repos.
|
||||
- name: Créer un dépôt sur GitHub si nécessaire
|
||||
uri:
|
||||
url: "https://api.github.com/orgs/{{ github_organization }}/repos"
|
||||
url: >-
|
||||
{{ (github_owner_is_org | bool)
|
||||
| ternary('https://api.github.com/orgs/' ~ github_owner ~ '/repos',
|
||||
'https://api.github.com/user/repos') }}
|
||||
method: POST
|
||||
headers:
|
||||
Authorization: "token {{ github_api_token }}"
|
||||
@@ -82,13 +97,13 @@
|
||||
body:
|
||||
name: "{{ gitea_repo_name }}"
|
||||
description: "{{ gitea_repo_description | default('') }}"
|
||||
private: "{{ github_repo_private | default(true) }}" # Définir si le dépôt est privé ou public
|
||||
private: "{{ github_repo_private | bool }}" # Définir si le dépôt est privé ou public
|
||||
status_code: 201
|
||||
when: github_repo_check.status != 200
|
||||
when: (gitea_mirror_github | bool) and not github_repo_present
|
||||
|
||||
- name: Vérifier l'existence des miroirs push sur GitHub et GitLab
|
||||
uri:
|
||||
url: "{{ gitea_base_url }}/api/v1/repos/{{ gitea_organization }}/{{ gitea_repo_name }}/push_mirrors"
|
||||
url: "{{ gitea_base_url }}/api/v1/repos/{{ gitea_repo_owner }}/{{ gitea_repo_name }}/push_mirrors"
|
||||
method: GET
|
||||
headers:
|
||||
Authorization: "token {{ gitea_api_token }}"
|
||||
@@ -97,32 +112,68 @@
|
||||
|
||||
- name: Ajouter un miroir push vers GitHub si nécessaire
|
||||
uri:
|
||||
url: "{{ gitea_base_url }}/api/v1/repos/{{ gitea_organization }}/{{ gitea_repo_name }}/push_mirrors"
|
||||
url: "{{ gitea_base_url }}/api/v1/repos/{{ gitea_repo_owner }}/{{ gitea_repo_name }}/push_mirrors"
|
||||
method: POST
|
||||
headers:
|
||||
Authorization: "token {{ gitea_api_token }}"
|
||||
body_format: json
|
||||
body:
|
||||
interval: "8h"
|
||||
interval: "{{ gitea_mirror_interval }}"
|
||||
remote_address: "{{ github_mirror_url }}"
|
||||
remote_username: "{{ gitea_username }}"
|
||||
remote_username: "{{ github_mirror_username }}"
|
||||
remote_password: "{{ github_api_token }}"
|
||||
sync_on_commit: true
|
||||
status_code: 200
|
||||
when: "github_mirror_url not in existing_mirrors.json | map(attribute='remote_address') | list"
|
||||
when:
|
||||
- gitea_mirror_github | bool
|
||||
- github_mirror_url not in existing_mirrors.json | map(attribute='remote_address') | list
|
||||
|
||||
- name: Ajouter un miroir push vers GitLab si nécessaire
|
||||
uri:
|
||||
url: "{{ gitea_base_url }}/api/v1/repos/{{ gitea_organization }}/{{ gitea_repo_name }}/push_mirrors"
|
||||
url: "{{ gitea_base_url }}/api/v1/repos/{{ gitea_repo_owner }}/{{ gitea_repo_name }}/push_mirrors"
|
||||
method: POST
|
||||
headers:
|
||||
Authorization: "token {{ gitea_api_token }}"
|
||||
body_format: json
|
||||
body:
|
||||
interval: "8h"
|
||||
interval: "{{ gitea_mirror_interval }}"
|
||||
remote_address: "{{ gitlab_mirror_url }}"
|
||||
remote_username: "{{ gitea_username }}"
|
||||
remote_username: "{{ gitlab_mirror_username }}"
|
||||
remote_password: "{{ gitlab_api_token }}"
|
||||
sync_on_commit: true
|
||||
status_code: 200
|
||||
when: "gitlab_mirror_url not in existing_mirrors.json | map(attribute='remote_address') | list"
|
||||
when:
|
||||
- gitea_mirror_gitlab | bool
|
||||
- gitlab_mirror_url not in existing_mirrors.json | map(attribute='remote_address') | list
|
||||
|
||||
# Un dépôt GitHub créé vide adopte comme branche par défaut la PREMIÈRE branche
|
||||
# que le miroir lui pousse — souvent une branche de travail, pas « main ». Le
|
||||
# miroir étant asynchrone, l'alignement échoue au run qui crée le dépôt et
|
||||
# réussit au suivant : d'où le failed_when permissif plutôt qu'un blocage.
|
||||
- name: Aligner la branche par défaut de GitHub sur celle de Gitea
|
||||
uri:
|
||||
url: "https://api.github.com/repos/{{ github_owner }}/{{ gitea_repo_name }}"
|
||||
method: PATCH
|
||||
headers:
|
||||
Authorization: "token {{ github_api_token }}"
|
||||
body_format: json
|
||||
body:
|
||||
default_branch: "{{ gitea_repo_check.json.default_branch }}"
|
||||
status_code: 200
|
||||
register: github_default_branch
|
||||
failed_when: false
|
||||
when:
|
||||
- gitea_mirror_github | bool
|
||||
- gitea_repo_present
|
||||
- (github_repo_check.json.default_branch | default('')) != gitea_repo_check.json.default_branch
|
||||
|
||||
- name: Signaler une branche par défaut encore désalignée
|
||||
debug:
|
||||
msg: >-
|
||||
La branche par défaut de github.com/{{ github_owner }}/{{ gitea_repo_name }}
|
||||
n'a pas pu être alignée sur « {{ gitea_repo_check.json.default_branch }} » :
|
||||
le miroir ne l'a probablement pas encore poussée. Relancer après la synchro.
|
||||
when:
|
||||
- github_default_branch is defined
|
||||
- github_default_branch is not skipped
|
||||
- (github_default_branch.status | default(0)) != 200
|
||||
|
||||
@@ -3,8 +3,8 @@ gitlab_api_token: '{{ hostvars[groups.gitea[0]].gitea_vault.gitlab_api_token }}'
|
||||
|
||||
github_organization: '{{ gitea_organization }}'
|
||||
gitlab_root_group: '{{ gitea_organization }}'
|
||||
gitlab_namespace_id: 89826881 # https://gitlab.com/groups/arcodange-org/-/edit
|
||||
|
||||
# URLs des miroirs sur GitLab et GitHub
|
||||
gitlab_mirror_url: "https://gitlab.com/{{ gitlab_root_group | default(gitlab_username | default(gitea_username)) }}/{{ gitea_repo_name }}.git"
|
||||
github_mirror_url: "https://github.com/{{ github_organization | default(github_username | default(gitea_username)) }}/{{ gitea_repo_name }}.git"
|
||||
# URLs des miroirs sur GitLab et GitHub — elles suivent le propriétaire visé sur
|
||||
# chaque forge (cf. github_owner / gitlab_owner dans defaults/).
|
||||
gitlab_mirror_url: "https://gitlab.com/{{ gitlab_owner }}/{{ gitea_repo_name }}.git"
|
||||
github_mirror_url: "https://github.com/{{ github_owner }}/{{ gitea_repo_name }}.git"
|
||||
|
||||
@@ -4,4 +4,18 @@ gitea_organization: arcodange-org
|
||||
# URL de base du serveur Gitea
|
||||
gitea_base_url: http://{{ groups.gitea[0] }}:3000
|
||||
|
||||
gitea_token_fact_name: arcodange_factory_gitea_sync_token
|
||||
gitea_token_fact_name: arcodange_factory_gitea_sync_token
|
||||
|
||||
# Propriétaire balayé. Par défaut l'organisation ; mettre « arcodange » et
|
||||
# gitea_sync_owner_is_org à false pour balayer le compte personnel.
|
||||
gitea_sync_owner: "{{ gitea_organization }}"
|
||||
gitea_sync_owner_is_org: true
|
||||
|
||||
# Les trois API paginent (30 par défaut chez GitHub, 20 chez GitLab). Sous la
|
||||
# taille d'une page, la différence entre forges désigne de faux dépôts manquants.
|
||||
gitea_sync_page_size: 100
|
||||
|
||||
# Forges comparées. Balayer une forge qu'on ne veut pas alimenter ferait passer
|
||||
# tous ses dépôts pour « incomplets ».
|
||||
gitea_mirror_github: true
|
||||
gitea_mirror_gitlab: true
|
||||
|
||||
@@ -1,40 +1,71 @@
|
||||
# Un compte personnel n'est pas une organisation : ni GitHub ni GitLab ne
|
||||
# servent ses dépôts au même endroit.
|
||||
- name: Lister les dépôts de l'organisation GitHub
|
||||
uri:
|
||||
url: "https://api.github.com/orgs/{{ github_organization }}/repos"
|
||||
url: >-
|
||||
{{ (gitea_sync_owner_is_org | bool)
|
||||
| ternary('https://api.github.com/orgs/' ~ github_owner ~ '/repos',
|
||||
'https://api.github.com/users/' ~ github_owner ~ '/repos')
|
||||
}}?per_page={{ gitea_sync_page_size }}
|
||||
method: GET
|
||||
headers:
|
||||
Authorization: "token {{ github_api_token }}"
|
||||
status_code: 200
|
||||
register: github_repos
|
||||
when: gitea_mirror_github | bool
|
||||
|
||||
- name: Lister les dépôts du groupe GitLab
|
||||
uri:
|
||||
url: "https://gitlab.com/api/v4/groups/{{ gitlab_root_group }}/projects"
|
||||
url: >-
|
||||
{{ (gitea_sync_owner_is_org | bool)
|
||||
| ternary('https://gitlab.com/api/v4/groups/' ~ gitlab_owner ~ '/projects',
|
||||
'https://gitlab.com/api/v4/users/' ~ gitlab_owner ~ '/projects')
|
||||
}}?per_page={{ gitea_sync_page_size }}
|
||||
method: GET
|
||||
headers:
|
||||
Authorization: "Bearer {{ gitlab_api_token }}"
|
||||
status_code: 200
|
||||
register: gitlab_repos
|
||||
when: gitea_mirror_gitlab | bool
|
||||
|
||||
- name: Lister les dépôts de l'organisation Gitea
|
||||
uri:
|
||||
url: "{{ gitea_base_url }}/api/v1/orgs/{{ gitea_organization }}/repos"
|
||||
url: >-
|
||||
{{ (gitea_sync_owner_is_org | bool)
|
||||
| ternary(gitea_base_url ~ '/api/v1/orgs/' ~ gitea_sync_owner ~ '/repos',
|
||||
gitea_base_url ~ '/api/v1/users/' ~ gitea_sync_owner ~ '/repos')
|
||||
}}?limit={{ gitea_sync_page_size }}
|
||||
method: GET
|
||||
headers:
|
||||
Authorization: "token {{ gitea_api_token }}"
|
||||
status_code: 200
|
||||
register: gitea_repos
|
||||
|
||||
# Une forge désactivée ne doit pas peser dans la différence : on la remplace par
|
||||
# la liste Gitea elle-même, qui la rend neutre à l'intersection.
|
||||
- name: Établir la liste des dépôts incomplets
|
||||
set_fact:
|
||||
gitea_repo_names: "{{ gitea_repos.json | map(attribute='name') | list }}"
|
||||
github_repo_names: >-
|
||||
{{ (gitea_mirror_github | bool)
|
||||
| ternary(github_repos.json | default([]) | map(attribute='name') | list,
|
||||
gitea_repos.json | map(attribute='name') | list) }}
|
||||
gitlab_repo_names: >-
|
||||
{{ (gitea_mirror_gitlab | bool)
|
||||
| ternary(gitlab_repos.json | default([]) | map(attribute='name') | list,
|
||||
gitea_repos.json | map(attribute='name') | list) }}
|
||||
|
||||
- name: Réduire aux dépôts absents d'au moins une forge
|
||||
set_fact:
|
||||
repos_incomplete: >-
|
||||
{{ (github_repo_names | union(gitlab_repo_names) | union(gitea_repo_names))
|
||||
| difference(github_repo_names | intersect(gitlab_repo_names) | intersect(gitea_repo_names)) }}
|
||||
|
||||
- name: Synchroniser
|
||||
include_role:
|
||||
name: arcodange.factory.gitea_repo
|
||||
vars:
|
||||
github_repo_names: "{{ github_repos.json | map(attribute='name') | list }}"
|
||||
gitlab_repo_names: "{{ gitlab_repos.json | map(attribute='name') | list }}"
|
||||
gitea_repo_names: "{{ gitea_repos.json | map(attribute='name') | list }}"
|
||||
all_repos: "{{ github_repo_names | union(gitlab_repo_names) | union(gitea_repo_names) }}"
|
||||
repos_common_to_all: "{{ github_repo_names | intersect(gitlab_repo_names) | intersect(gitea_repo_names) }}"
|
||||
repos_incomplete: "{{ all_repos | difference(repos_common_to_all) }}"
|
||||
gitea_repo_owner: "{{ gitea_sync_owner }}"
|
||||
loop: "{{ repos_incomplete }}"
|
||||
loop_control:
|
||||
loop_var: gitea_repo_name
|
||||
|
||||
@@ -2,4 +2,8 @@ github_api_token: '{{ hostvars[groups.gitea[0]].gitea_vault.github_api_token }}'
|
||||
gitlab_api_token: '{{ hostvars[groups.gitea[0]].gitea_vault.gitlab_api_token }}'
|
||||
|
||||
github_organization: '{{ gitea_organization }}'
|
||||
gitlab_root_group: '{{ gitea_organization }}'
|
||||
gitlab_root_group: '{{ gitea_organization }}'
|
||||
|
||||
# Les propriétaires en face suivent celui qu'on balaie côté Gitea.
|
||||
github_owner: '{{ gitea_sync_owner }}'
|
||||
gitlab_owner: '{{ gitea_sync_owner }}'
|
||||
|
||||
Reference in New Issue
Block a user