Helm Charts / Detect changed charts (push) Successful in 1m2s
Helm Charts / Library charts tool (push) Skipped
Helm Charts / Application charts chart (push) Skipped
Helm Charts / Application charts crowdsec (push) Skipped
Helm Charts / Application charts grafana (push) Skipped
Helm Charts / Application charts hashicorp-vault (push) Skipped
Helm Charts / Application charts pgbouncer (push) Skipped
Helm Charts / Application charts pgcat (push) Skipped
Helm Charts / Application charts prometheus (push) Skipped
Helm Charts / Application charts redis (push) Skipped
Helm Charts / Application charts minio (push) Successful in 11s
113 lines
4.5 KiB
Terraform
113 lines
4.5 KiB
Terraform
# Module `minio_app` — une app déclare SES buckets depuis SON dépôt.
|
|
# Décisions : factory/doc/adr/20260726-stockage-objet-minio.md
|
|
|
|
resource "minio_s3_bucket" "app" {
|
|
for_each = toset(var.buckets)
|
|
bucket = each.key
|
|
acl = "private" # l'accès passe par des URL présignées
|
|
force_destroy = false # détruire un bucket doit être un geste explicite
|
|
}
|
|
|
|
# ── Les téléversements ABANDONNÉS ne s'accumulent pas ────────────────────────
|
|
#
|
|
# Un téléversement S3 en plusieurs parts qui n'est jamais refermé laisse ses
|
|
# parts dans le bucket : elles occupent de l'espace et AUCUN objet ne les
|
|
# montre. `mc ls` n'en dit rien, la console non plus. C'est donc une fuite qui
|
|
# ne se voit qu'à la facture — ou à la saturation du volume, qui est ici de
|
|
# 50 Gi et déjà dimensionné à 250 h de cours.
|
|
#
|
|
# L'application abandonne ce qu'elle ouvre quand elle échoue en route. Ce
|
|
# qu'elle ne peut PAS rattraper : un navigateur qui ferme l'onglet, perd le
|
|
# réseau, ou expire. Ce cas-là appartient au BUCKET, pas au code applicatif —
|
|
# c'est la seule place d'où l'on voit un téléversement que plus personne ne
|
|
# suit.
|
|
#
|
|
# ⚠ POURQUOI UN JOUR, ET PAS SEPT. Les URL de parts sont signées pour quelques
|
|
# heures (2 h côté kadans-api). Passé ce délai, un téléversement en cours ne
|
|
# peut PLUS rien recevoir : il est mort, il occupe seulement. Un jour laisse
|
|
# douze fois la marge nécessaire au plus long téléversement possible, sans
|
|
# garder des déchets une semaine.
|
|
#
|
|
# ⚠ CE N'EST PAS PARAMÉTRABLE, ET C'EST VOULU (YAGNI) : un seul cas existe. Le
|
|
# jour où une app signe des parts pour plus de 24 h, ce sera le déclencheur pour
|
|
# en faire une variable — pas avant.
|
|
#
|
|
# ⚠ CE RÉGLAGE-CI FONCTIONNE VRAIMENT, contrairement au CORS par bucket. MinIO
|
|
# édition communautaire stubbe `PutBucketCors` (501, cf. `cmd/dummy-handlers.go`
|
|
# du serveur), ce qui a déjà coûté une tentative d'IaC ; les handlers de CYCLE DE
|
|
# VIE, eux, n'y figurent PAS — vérifié à la source le 2026-08-20.
|
|
resource "minio_ilm_policy" "app" {
|
|
for_each = minio_s3_bucket.app
|
|
bucket = each.value.bucket
|
|
|
|
rule {
|
|
id = "abandon-televersements-incomplets"
|
|
status = "Enabled"
|
|
|
|
abort_incomplete_multipart_upload {
|
|
days_after_initiation = "1d"
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "minio_iam_policy" "app" {
|
|
name = "${var.app}-app"
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
# ⚠ LES DEUX ACTIONS DU MULTIPART QUE `s3:PutObject` NE COUVRE PAS — mesuré
|
|
# le 2026-09-07 sur le homelab, avec les identifiants de kadans-api rejoués
|
|
# depuis un poste : ouvrir un téléversement et déposer ses parts passent
|
|
# (ce sont des PutObject), mais `ListObjectParts` rend 403 AccessDenied.
|
|
# L'app ne pouvait donc JAMAIS refermer un téléversement en parts : toute
|
|
# vidéo qui dépasse la taille d'une part échouait à l'assemblage, à tous
|
|
# les coups, derrière un « stockage injoignable » qui ne nommait rien.
|
|
# `AbortMultipartUpload` était refusé pareil : l'app ne pouvait pas non
|
|
# plus abandonner ce qu'elle avait ouvert — seule la règle de cycle de vie
|
|
# ci-dessus ramassait, un jour plus tard.
|
|
Action = [
|
|
"s3:GetObject", "s3:PutObject", "s3:DeleteObject",
|
|
"s3:ListMultipartUploadParts", "s3:AbortMultipartUpload",
|
|
]
|
|
Resource = [for b in var.buckets : "arn:aws:s3:::${b}/*"]
|
|
},
|
|
{
|
|
Effect = "Allow"
|
|
Action = ["s3:ListBucket", "s3:GetBucketLocation"]
|
|
Resource = [for b in var.buckets : "arn:aws:s3:::${b}"]
|
|
},
|
|
]
|
|
})
|
|
}
|
|
|
|
resource "random_password" "app" {
|
|
length = 40
|
|
special = false # les outils S3 transportent mal certains caractères en URL
|
|
}
|
|
|
|
resource "minio_iam_user" "app" {
|
|
name = "${var.app}-app"
|
|
secret = random_password.app.result
|
|
force_destroy = false # une rotation ne recrée pas l'utilisateur : les objets gardent leur propriétaire
|
|
}
|
|
|
|
resource "minio_iam_user_policy_attachment" "app" {
|
|
user_name = minio_iam_user.app.id
|
|
policy_name = minio_iam_policy.app.id
|
|
}
|
|
|
|
# Lu par le pod de l'app — `app_policy` lui accorde déjà ce chemin.
|
|
resource "vault_kv_secret_v2" "app" {
|
|
mount = "kvv2"
|
|
name = "minio/${var.app}"
|
|
data_json = jsonencode({
|
|
MINIO_ENDPOINT = var.endpoint
|
|
MINIO_BUCKET = var.buckets[0]
|
|
MINIO_BUCKETS = join(",", var.buckets)
|
|
MINIO_ACCESS_KEY = minio_iam_user.app.id
|
|
MINIO_SECRET_KEY = random_password.app.result
|
|
})
|
|
}
|