Aller au contenu
Développement medium

Données et itération en Rego : collections, some et compréhensions

10 min de lecture

Dans le guide précédent, on a accédé au premier conteneur d’un pod avec containers[0]. En production, un pod en a souvent plusieurs. Si une seule image utilise le tag latest, la politique doit le détecter — peu importe sa position dans la liste.

C’est là qu’intervient l’itération en Rego : non pas une boucle for explicite, mais un mécanisme d’unification implicite qui évalue une règle pour toutes les valeurs possibles d’une variable.

  • Distinguer les trois structures de données Rego : tableau, objet, ensemble
  • Itérer implicitement avec une variable libre ou some ... in
  • Construire des collections filtrées avec les compréhensions
  • Utiliser les fonctions built-in sur les collections (count, sum, object.get…)
  • Écrire une politique qui liste toutes les infractions d’un manifest

Rego manipule trois types de collections, chacune avec une syntaxe et une sémantique distinctes.

Séquence ordonnée, indexée à partir de 0, qui peut contenir des doublons.

images := ["nginx:latest", "redis:7", "postgres:15"]
# Accès par index
premiere_image := images[0] # "nginx:latest"

Map de clé → valeur. Les clés sont des chaînes ou des nombres, les valeurs peuvent être de n’importe quel type.

labels := {
"app": "api-server",
"env": "production",
"team": "platform"
}
# Accès par clé
env := labels["env"] # "production"
env := labels.env # syntaxe équivalente

Collection non ordonnée, sans doublon. La syntaxe utilise des accolades sans séparateur clé/valeur — c’est ce qui les distingue des objets.

namespaces_autorises := {"production", "staging", "monitoring"}
# Opérations d'ensemble
"production" in namespaces_autorises # true
union_ns := namespaces_autorises | {"preprod"}
inter_ns := namespaces_autorises & {"production", "dev"}

En Rego, une variable libre dans un accès de collection itère automatiquement sur tous ses éléments. Il n’y a pas de mot-clé for.

# `i` est une variable libre — cette expression est vraie pour chaque index
# où la valeur du conteneur contient "latest"
image_latest if {
conteneur := input.spec.template.spec.containers[i]
endswith(conteneur.image, ":latest")
}

OPA évalue cette règle pour toutes les valeurs de i. Si un seul conteneur a une image en :latest, la règle est vraie.

C’est le changement de paradigme central : vous ne demandez pas « pour chaque élément, fais ceci » — vous déclarez « il existe un élément tel que ».

some déclare explicitement une variable comme libre. C’est facultatif dans certains contextes mais obligatoire avec rego.v1 pour les variables utilisées comme index d’itération.

import rego.v1
image_latest if {
some i
conteneur := input.spec.template.spec.containers[i]
endswith(conteneur.image, ":latest")
}

some peut aussi déclarer plusieurs variables d’un coup :

some i, j

La forme moderne combine déclaration et itération en une seule expression :

image_latest if {
some conteneur in input.spec.template.spec.containers
endswith(conteneur.image, ":latest")
}

C’est la syntaxe à privilégier avec rego.v1 : plus lisible, et elle évite de manipuler les index manuellement.

Quand l’index ne vous intéresse pas, utilisez _ (underscore). Il itère comme une variable libre mais son nom est jetable — vous ne pouvez pas y accéder après.

image_latest if {
endswith(input.spec.template.spec.containers[_].image, ":latest")
}

Les compréhensions construisent une nouvelle collection à partir d’une itération filtrée. Rego en propose trois formes.

Syntaxe : [expression | corps]

# Liste de toutes les images des conteneurs
toutes_les_images := [c.image | some c in input.spec.template.spec.containers]
# Liste des images non conformes (sans préfixe de registre interne)
images_non_conformes := [
c.image |
some c in input.spec.template.spec.containers
not startswith(c.image, "registry.example.com/")
]

Syntaxe : {expression | corps} — le résultat ne contient pas de doublons.

# Ensemble de tous les namespaces référencés dans plusieurs ressources
namespaces_utilises := {r.metadata.namespace | some r in input.items}
# Ensemble des noms de conteneurs sans readOnlyRootFilesystem
conteneurs_non_readonly := {
c.name |
some c in input.spec.template.spec.containers
not c.securityContext.readOnlyRootFilesystem
}

Syntaxe : {clé: valeur | corps}

# Map nom_conteneur → image
images_par_conteneur := {
c.name: c.image |
some c in input.spec.template.spec.containers
}

OPA fournit un ensemble de fonctions built-in pour opérer sur les collections sans itération manuelle.

conteneurs := input.spec.template.spec.containers
nb_conteneurs := count(conteneurs) # nombre d'éléments
toutes_les_images := {c.image | some c in conteneurs}
# Sur des tableaux de nombres
replicas := [1, 3, 2, 5]
total := sum(replicas) # 11
maximum := max(replicas) # 5
minimum := min(replicas) # 1
trie := sort(replicas) # [1, 2, 3, 5]

Pour les objets :

labels := input.metadata.labels
cles := object.keys(labels) # ensemble des clés
valeur := object.get(labels, "app", "inconnu") # accès avec valeur par défaut

Fil rouge : détecter tous les conteneurs non conformes

Section intitulée « Fil rouge : détecter tous les conteneurs non conformes »

On enrichit la politique du guide précédent. Cette fois, on veut non seulement détecter qu’un problème existe, mais aussi lister tous les conteneurs en infraction pour un message d’erreur exploitable.

package kubernetes.validation
import rego.v1
default allow := false
allow if {
count(violations) == 0
}
# Ensemble de tous les messages de violation
violations := {msg |
some msg in violations_image
} | {msg |
some msg in violations_readonly
}
# Violation : image sans tag explicite ou avec tag `latest`
violations_image := {msg |
some c in input.spec.template.spec.containers
image_non_conforme(c.image)
msg := sprintf("conteneur '%v' : image '%v' interdite (tag latest ou absent)", [c.name, c.image])
}
# Violation : système de fichiers racine non en lecture seule
violations_readonly := {msg |
some c in input.spec.template.spec.containers
not c.securityContext.readOnlyRootFilesystem
msg := sprintf("conteneur '%v' : readOnlyRootFilesystem doit être true", [c.name])
}
# Fonction helper — image non conforme si tag absent ou égal à "latest"
image_non_conforme(image) if {
not contains(image, ":")
}
image_non_conforme(image) if {
endswith(image, ":latest")
}

Évaluez la politique sur un manifest avec plusieurs conteneurs problématiques :

Fenêtre de terminal
opa eval \
--input manifest.json \
--data politique.rego \
"data.kubernetes.validation.violations"

Résultat :

{
"result": [{
"expressions": [{
"value": [
"conteneur 'sidecar' : image 'busybox:latest' interdite (tag latest ou absent)",
"conteneur 'init' : readOnlyRootFilesystem doit être true"
]
}]
}]
}

Ce pattern — allow qui vérifie que violations est vide, et violations construit par compréhension — est le standard dans les politiques OPA et Gatekeeper. Il vous donne à la fois le résultat booléen et le détail des infractions en un seul passage.

  • Rego itère implicitement : une variable libre dans collection[i] s’évalue pour tous les éléments.
  • Préférez some conteneur in collection (forme moderne) à collection[_] pour la lisibilité.
  • Les compréhensions ([... | ...], {... | ...}, {k: v | ...}) construisent des collections filtrées en une expression.
  • count(violations) == 0 comme condition de allow est le pattern de référence pour des erreurs lisibles.
  • object.get pour les champs optionnels, plutôt que de laisser l’unification échouer silencieusement.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn