Corriger la dérive des droits dans un espace de travail CI sur Mac cloud

Corriger la dérive des droits dans un espace de travail CI sur Mac cloud

Après plusieurs jours d’exécution sans incident, une même tâche de build Xcode peut soudainement signaler Permission denied lorsqu’elle écrit dans DerivedData, met à jour le cache des dépendances ou supprime d’anciens artefacts. Un nouveau checkout du dépôt peut rétablir temporairement la situation, avant que le problème ne réapparaisse lors de la prochaine tâche parallèle. Il ne faut alors ni soupçonner Xcode en premier lieu ni élargir directement les droits du répertoire. La cause la plus courante est qu’un script a changé d’utilisateur d’exécution, hérité d’un umask différent ou laissé des ACL supplémentaires dans l’espace de travail.

Préserver d’abord l’état de l’échec

Les problèmes de droits disparaissent facilement après un « nettoyage suivi d’une nouvelle exécution ». Commencez par consigner l’identité d’exécution de la tâche, son répertoire personnel, son répertoire courant et son masque de droits par défaut. Examinez ensuite le premier chemin en échec, sans analyser immédiatement l’ensemble du disque.

printf 'user=%s
' "$(id -un)"
printf 'groups=%s
' "$(id -Gn)"
printf 'home=%s
' "$HOME"
printf 'cwd=%s
' "$PWD"
printf 'umask=%s
' "$(umask)"

target="${FAILED_PATH:?set FAILED_PATH first}"
stat -f 'owner=%Su group=%Sg mode=%Sp path=%N' "$target"
ls -led "$target"

stat indique le propriétaire de l’objet et son mode de base, tandis que ls -le affiche ses ACL. Même si le mode du répertoire semble autoriser l’écriture, l’accès peut échouer : un répertoire parent dépourvu du droit d’exécution, une entrée restrictive dans les ACL ou un fichier appartenant à l’utilisateur d’une autre tâche peut bloquer l’opération.

Examinez uniquement le premier chemin en échec et remontez ses répertoires parents niveau par niveau. Modifier récursivement les droits de tout l’espace de travail détruit l’état utile au diagnostic et risque d’exposer des clés, des caches et des artefacts de build à des processus qui ne devraient pas y accéder.

Distinguer trois formes de dérive des droits

Changement de propriétaire

Le cas le plus fréquent survient lorsqu’une étape installe des dépendances ou copie des fichiers avec des privilèges élevés, puis laisse les éléments produits à un autre utilisateur. Commencez par répertorier tout ce qui n’appartient pas à l’utilisateur de la tâche en cours :

workspace="${WORKSPACE:?set WORKSPACE first}"
find "$workspace" -x ! -user "$(id -un)" -print

Si les résultats se concentrent dans le répertoire d’une seule tâche, il faut remonter au script qui l’a créé plutôt que masquer l’origine du problème avec un chown récursif. Dans l’idéal, l’espace de travail CI appartient à l’utilisateur de la tâche dès sa création. Lorsqu’une opération avec des privilèges élevés est indispensable, sa sortie ne doit pas être écrite dans le dépôt, le cache ou DerivedData.

ACL inattendues

Les opérations effectuées dans Finder, les scripts de migration ou les outils de copie peuvent conserver les ACL. Si ls -le affiche des entrées numérotées sous la ligne des droits de base, il faut en déterminer l’origine. Les ACL ne doivent être supprimées de manière ciblée que si le répertoire est bien un espace de travail temporaire pouvant être recréé :

job_dir="${JOB_DIR:?set JOB_DIR first}"
chmod -RN "$job_dir"

N’exécutez pas cette commande sur le répertoire personnel d’un utilisateur ni sur un répertoire contenant des identifiants. Après la correction, relancez ls -led pour vérifier que les ACL ont disparu et que le mode de base répond toujours aux exigences.

umask incohérent

Une session SSH interactive, un démon CI et un script autonome ne chargent pas nécessairement la même configuration Shell. Si une tâche crée un cache avec 077, la tâche suivante risque de ne pas pouvoir le réutiliser, même si les deux utilisateurs appartiennent au même groupe. Plutôt que de dépendre des fichiers d’initialisation, définissez explicitement le masque au point d’entrée de la tâche :

umask 022
install -d -m 0755 "$JOB_DIR"
install -d -m 0755 "$JOB_DIR/DerivedData"
install -d -m 0755 "$JOB_DIR/Artifacts"

Les données sensibles doivent être placées dans un répertoire distinct avec un mode plus restrictif. Il ne faut pas assouplir uniformément les droits dans le seul but de partager un cache de build.

Utiliser un espace de travail distinct pour chaque tâche

Lorsque des tâches parallèles partagent le même répertoire DerivedData ou le même répertoire d’archives, le nettoyage effectué par l’une peut entrer en conflit avec les fichiers dans lesquels une autre est en train d’écrire. Il est recommandé de construire le chemin à partir de l’identifiant du dépôt, de celui du commit et du numéro de tâche, puis de transmettre explicitement ce chemin à la commande de build.

run_id="${CI_RUN_ID:?set CI_RUN_ID first}"
root="$HOME/ci-runs/$run_id"
src="$root/source"
derived="$root/DerivedData"
artifacts="$root/Artifacts"

umask 022
install -d -m 0755 "$src" "$derived" "$artifacts"

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Release \
  -derivedDataPath "$derived" \
  archive \
  -archivePath "$artifacts/App.xcarchive"

Les caches partagés doivent être séparés des sorties propres aux tâches : le cache de téléchargement des dépendances peut être réutilisé en lecture seule, tandis que DerivedData, les archives et les journaux doivent être isolés par tâche. Cette organisation réduit la contamination mutuelle des droits et préserve l’état complet de l’échec jusqu’à la fin de la tâche.

Type de chemin Propriétaire recommandé Durée de vie Écritures parallèles autorisées
Checkout du code source Une seule tâche Une exécution Non
DerivedData Une seule tâche Une exécution Non
Archives et journaux Une seule tâche Nettoyage après validation Non
Cache de téléchargement Utilisateur de tâche fixe Plusieurs tâches Mise à jour uniquement par le processus de cache

Définir des contrôles avant et après le build

Le contrôle des droits doit faire partie du point d’entrée de la tâche, et non d’une intervention manuelle après l’apparition d’une erreur. Avant le build, vérifiez que les répertoires sont accessibles en écriture, qu’ils ont le bon propriétaire et qu’ils ne comportent aucune ACL inattendue. Après le build, recherchez les fichiers appartenant à un autre utilisateur. Le contrôle ci-dessous interrompt immédiatement la tâche dès qu’une dérive est détectée :

test -d "$JOB_DIR"
test -w "$JOB_DIR"

unexpected_owner="$(
  find "$JOB_DIR" -x ! -user "$(id -un)" -print -quit
)"

if [ -n "$unexpected_owner" ]; then
  printf 'unexpected owner: %s
' "$unexpected_owner" >&2
  exit 1
fi

if ls -led "$JOB_DIR" | tail -n +2 | grep -q '^[[:space:]]*[0-9]:'; then
  printf 'unexpected ACL on job directory
' >&2
  exit 1
fi

Si l’équipe a besoin d’un cache partagé, elle doit définir séparément le processus autorisé à y écrire, le moment des mises à jour et la méthode de remplacement atomique. Toutes les tâches de build ne doivent pas modifier simultanément le même répertoire. Le nettoyage effectué à la fin d’une tâche doit également se limiter au chemin correspondant à la run_id en cours, après avoir vérifié que celui-ci se trouve bien sous le répertoire racine attendu.

Corriger le problème à la source dans les scripts

Une correction fiable doit répondre à quatre questions : quelle étape a créé le fichier anormal, sous quel utilisateur a-t-il été créé, quel umask a été hérité et pourquoi la sortie a-t-elle été écrite dans un répertoire partagé ? Une fois les réponses obtenues, la création des répertoires, l’identité d’exécution et l’emplacement des sorties doivent être définis dans le script, plutôt que de dépendre de l’environnement établi après une connexion manuelle.

Lors de l’exécution de la CI sur les Mac cloud de RunnerVM, il convient également d’afficher un instantané minimal de l’environnement au début de chaque tâche et de vérifier dans la console les configurations actuellement disponibles. Après un redémarrage de la machine ou la migration d’une tâche, le build ne dépendra pas de l’état laissé par l’exécution précédente si le script d’entrée sait rétablir la base attendue pour les répertoires et leurs droits. L’objectif final n’est pas de supprimer toutes les restrictions d’accès, mais de rendre prévisibles le créateur de chaque fichier, son périmètre de lecture et d’écriture ainsi que la responsabilité de son nettoyage.

Questions fréquentes

Faut-il exécuter chmod 777 sur tout l’espace de travail après une erreur Permission denied ?

Non. Cette commande masque les défauts de propriétaire et d’ACL tout en ouvrant les fichiers à d’autres processus. Inspectez le chemin concerné avec stat et ls -le, puis corrigez uniquement le répertoire de la tâche.

Pourquoi un script fonctionne-t-il en SSH mais échoue-t-il dans une tâche CI ?

L’utilisateur, HOME, l’umask, PATH et l’environnement de lancement peuvent différer. Relevez ces valeurs au début de la tâche et définissez explicitement la base de droits dans le script.

Runner M4

Faites fonctionner votre prochaine compilation sur un Mac physique dédié

Choisissez un nœud Mac dans le cloud adapté à la durée de votre tâche, puis gérez vos commandes, informations de connexion et tickets d’assistance depuis la console.

Choisir une offre et commander