feat(tenants): CronJob do resumo diario (N16) de volta aos 4 tenants — agora registrados como contratacoes de demonstracao (V025)

This commit is contained in:
deploy
2026-09-29 23:18:16 +00:00
parent 54159df233
commit d65f4e5638
4 changed files with 564 additions and 0 deletions
+141
View File
@@ -0,0 +1,141 @@
# =============================================================================
# N16 — o resumo AGREGADO do tenant, entregue a plataforma uma vez por dia, para o
# comprador ver todas as escolinhas dele lado a lado no /minha-conta.
#
# AGUARDANDO GRAVACAO no molde (`moldes/consolidado/` do GitOps) e nos tenants que
# ja existem. Nada aqui foi aplicado.
#
# O desenho: o TENANT calcula (runtime-operacao, GET /api/resumo-do-tenant, so
# contadores e somas) e o TENANT envia (este CronJob). A plataforma nao entra no
# namespace nem no banco do tenant para isto.
#
# 1. token do client `operacao` no realm do tenant (o mesmo das tarefas do
# financeiro: Secret `operacao-credentials`);
# 2. GET http://runtime-operacao/api/resumo-do-tenant (lista de 1 ou 2 meses);
# 3. POST http://servico-captacao.plataforma-prod.svc/api/public/resumo-do-tenant/acme
# com o cabecalho `x-atm-resumo-token` — do Secret `resumo-credentials`, que
# so vale para ESTE tenant (HMAC do nome dele com a chave da plataforma).
#
# A saida para a plataforma e pela internet (443 publico), que a deny-cross-tenant
# ja libera: nao precisa de NetworkPolicy nova.
#
# `optional: true` no resumo-credentials: tenant sem o Secret (chave da plataforma
# ainda nao configurada) roda o job, que termina OK dizendo "sem token" — nao gera
# KubeJobFailed nem gasta a cota de e-mail dos alertas.
# =============================================================================
apiVersion: v1
kind: ConfigMap
metadata:
name: resumo-cron
namespace: acme-prod
data:
enviar.sh: |
#!/bin/sh
set -eu
: "${KEYCLOAK_URL:?falta KEYCLOAK_URL}"
: "${CLIENT_SECRET:?falta CLIENT_SECRET}"
: "${ISSUER_HOST:?falta ISSUER_HOST}"
: "${DESTINO:?falta DESTINO}"
if [ -z "${RESUMO_TOKEN:-}" ]; then
echo "sem o Secret resumo-credentials neste tenant: nada a enviar."
exit 0
fi
i=0
until curl -sS -o /dev/null -m 5 "${KEYCLOAK_URL}" 2>/dev/null; do
i=$((i+1))
if [ "$i" -ge 24 ]; then
echo "ERRO: ${KEYCLOAK_URL} inalcancavel depois de 2 min." >&2
exit 1
fi
sleep 5
done
RESP="$(printf 'grant_type=client_credentials&client_id=operacao&client_secret=%s' \
"${CLIENT_SECRET}" \
| curl -sS --fail-with-body -X POST "${KEYCLOAK_URL}" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'X-Forwarded-Proto: https' \
-H 'X-Forwarded-Port: 443' \
-H "X-Forwarded-Host: ${ISSUER_HOST}" -d @-)"
TOKEN="$(printf '%s' "${RESP}" | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')"
if [ -z "${TOKEN}" ]; then
echo "ERRO: nao consegui token pelo client 'operacao'." >&2
exit 1
fi
# Os tokens vao por `-K -` (stdin), e nao na linha de comando: argumento de
# processo aparece em `ps` para quem estiver no no.
CODIGO="$(printf 'header = "Authorization: Bearer %s"\n' "${TOKEN}" \
| curl -sS -K - -o /tmp/resumo.json -w '%{http_code}' http://runtime-operacao/api/resumo-do-tenant)"
if [ "${CODIGO}" != "200" ]; then
echo "ERRO: o runtime-operacao respondeu ${CODIGO} ao resumo." >&2
exit 1
fi
echo "resumo calculado: $(wc -c < /tmp/resumo.json) bytes (so numeros)"
CODIGO="$(printf 'header = "x-atm-resumo-token: %s"\n' "${RESUMO_TOKEN}" \
| curl -sS -K - -X POST "${DESTINO}" -H 'Content-Type: application/json' \
--data-binary @/tmp/resumo.json -o /tmp/saida.json -w '%{http_code}')"
echo "plataforma: ${CODIGO}"
cat /tmp/saida.json; echo
case "${CODIGO}" in
2*) ;;
*) echo "ERRO: a plataforma respondeu ${CODIGO}." >&2; exit 1 ;;
esac
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: resumo-para-a-plataforma
namespace: acme-prod
spec:
# Madrugada, depois da virada do dia em Sao Paulo e fora do horario das
# mensalidades (dia 1, 06h).
schedule: "30 4 * * *"
timeZone: America/Sao_Paulo
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
jobTemplate:
spec:
activeDeadlineSeconds: 600
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: enviar
image: docker.io/curlimages/curl:8.9.1
imagePullPolicy: Never
command: ["/bin/sh", "/script/enviar.sh"]
env:
- name: KEYCLOAK_URL
value: http://keycloak.plataforma-prod.svc:8080/realms/athleticmap-3/protocol/openid-connect/token
- name: ISSUER_HOST
value: auth.athleticmap.com
- name: DESTINO
value: http://servico-captacao.plataforma-prod.svc/api/public/resumo-do-tenant/acme
- name: CLIENT_SECRET
valueFrom:
secretKeyRef:
name: operacao-credentials
key: client-secret
- name: RESUMO_TOKEN
valueFrom:
secretKeyRef:
name: resumo-credentials
key: token
optional: true
resources:
requests: { cpu: 10m, memory: 32Mi }
limits: { cpu: 200m, memory: 64Mi }
volumeMounts:
- name: script
mountPath: /script
volumes:
- name: script
configMap:
name: resumo-cron
+141
View File
@@ -0,0 +1,141 @@
# =============================================================================
# N16 — o resumo AGREGADO do tenant, entregue a plataforma uma vez por dia, para o
# comprador ver todas as escolinhas dele lado a lado no /minha-conta.
#
# AGUARDANDO GRAVACAO no molde (`moldes/consolidado/` do GitOps) e nos tenants que
# ja existem. Nada aqui foi aplicado.
#
# O desenho: o TENANT calcula (runtime-operacao, GET /api/resumo-do-tenant, so
# contadores e somas) e o TENANT envia (este CronJob). A plataforma nao entra no
# namespace nem no banco do tenant para isto.
#
# 1. token do client `operacao` no realm do tenant (o mesmo das tarefas do
# financeiro: Secret `operacao-credentials`);
# 2. GET http://runtime-operacao/api/resumo-do-tenant (lista de 1 ou 2 meses);
# 3. POST http://servico-captacao.plataforma-prod.svc/api/public/resumo-do-tenant/demo
# com o cabecalho `x-atm-resumo-token` — do Secret `resumo-credentials`, que
# so vale para ESTE tenant (HMAC do nome dele com a chave da plataforma).
#
# A saida para a plataforma e pela internet (443 publico), que a deny-cross-tenant
# ja libera: nao precisa de NetworkPolicy nova.
#
# `optional: true` no resumo-credentials: tenant sem o Secret (chave da plataforma
# ainda nao configurada) roda o job, que termina OK dizendo "sem token" — nao gera
# KubeJobFailed nem gasta a cota de e-mail dos alertas.
# =============================================================================
apiVersion: v1
kind: ConfigMap
metadata:
name: resumo-cron
namespace: demo-prod
data:
enviar.sh: |
#!/bin/sh
set -eu
: "${KEYCLOAK_URL:?falta KEYCLOAK_URL}"
: "${CLIENT_SECRET:?falta CLIENT_SECRET}"
: "${ISSUER_HOST:?falta ISSUER_HOST}"
: "${DESTINO:?falta DESTINO}"
if [ -z "${RESUMO_TOKEN:-}" ]; then
echo "sem o Secret resumo-credentials neste tenant: nada a enviar."
exit 0
fi
i=0
until curl -sS -o /dev/null -m 5 "${KEYCLOAK_URL}" 2>/dev/null; do
i=$((i+1))
if [ "$i" -ge 24 ]; then
echo "ERRO: ${KEYCLOAK_URL} inalcancavel depois de 2 min." >&2
exit 1
fi
sleep 5
done
RESP="$(printf 'grant_type=client_credentials&client_id=operacao&client_secret=%s' \
"${CLIENT_SECRET}" \
| curl -sS --fail-with-body -X POST "${KEYCLOAK_URL}" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'X-Forwarded-Proto: https' \
-H 'X-Forwarded-Port: 443' \
-H "X-Forwarded-Host: ${ISSUER_HOST}" -d @-)"
TOKEN="$(printf '%s' "${RESP}" | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')"
if [ -z "${TOKEN}" ]; then
echo "ERRO: nao consegui token pelo client 'operacao'." >&2
exit 1
fi
# Os tokens vao por `-K -` (stdin), e nao na linha de comando: argumento de
# processo aparece em `ps` para quem estiver no no.
CODIGO="$(printf 'header = "Authorization: Bearer %s"\n' "${TOKEN}" \
| curl -sS -K - -o /tmp/resumo.json -w '%{http_code}' http://runtime-operacao/api/resumo-do-tenant)"
if [ "${CODIGO}" != "200" ]; then
echo "ERRO: o runtime-operacao respondeu ${CODIGO} ao resumo." >&2
exit 1
fi
echo "resumo calculado: $(wc -c < /tmp/resumo.json) bytes (so numeros)"
CODIGO="$(printf 'header = "x-atm-resumo-token: %s"\n' "${RESUMO_TOKEN}" \
| curl -sS -K - -X POST "${DESTINO}" -H 'Content-Type: application/json' \
--data-binary @/tmp/resumo.json -o /tmp/saida.json -w '%{http_code}')"
echo "plataforma: ${CODIGO}"
cat /tmp/saida.json; echo
case "${CODIGO}" in
2*) ;;
*) echo "ERRO: a plataforma respondeu ${CODIGO}." >&2; exit 1 ;;
esac
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: resumo-para-a-plataforma
namespace: demo-prod
spec:
# Madrugada, depois da virada do dia em Sao Paulo e fora do horario das
# mensalidades (dia 1, 06h).
schedule: "30 4 * * *"
timeZone: America/Sao_Paulo
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
jobTemplate:
spec:
activeDeadlineSeconds: 600
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: enviar
image: docker.io/curlimages/curl:8.9.1
imagePullPolicy: Never
command: ["/bin/sh", "/script/enviar.sh"]
env:
- name: KEYCLOAK_URL
value: http://keycloak.plataforma-prod.svc:8080/realms/athleticmap-2/protocol/openid-connect/token
- name: ISSUER_HOST
value: auth.athleticmap.com
- name: DESTINO
value: http://servico-captacao.plataforma-prod.svc/api/public/resumo-do-tenant/demo
- name: CLIENT_SECRET
valueFrom:
secretKeyRef:
name: operacao-credentials
key: client-secret
- name: RESUMO_TOKEN
valueFrom:
secretKeyRef:
name: resumo-credentials
key: token
optional: true
resources:
requests: { cpu: 10m, memory: 32Mi }
limits: { cpu: 200m, memory: 64Mi }
volumeMounts:
- name: script
mountPath: /script
volumes:
- name: script
configMap:
name: resumo-cron
+141
View File
@@ -0,0 +1,141 @@
# =============================================================================
# N16 — o resumo AGREGADO do tenant, entregue a plataforma uma vez por dia, para o
# comprador ver todas as escolinhas dele lado a lado no /minha-conta.
#
# AGUARDANDO GRAVACAO no molde (`moldes/consolidado/` do GitOps) e nos tenants que
# ja existem. Nada aqui foi aplicado.
#
# O desenho: o TENANT calcula (runtime-operacao, GET /api/resumo-do-tenant, so
# contadores e somas) e o TENANT envia (este CronJob). A plataforma nao entra no
# namespace nem no banco do tenant para isto.
#
# 1. token do client `operacao` no realm do tenant (o mesmo das tarefas do
# financeiro: Secret `operacao-credentials`);
# 2. GET http://runtime-operacao/api/resumo-do-tenant (lista de 1 ou 2 meses);
# 3. POST http://servico-captacao.plataforma-prod.svc/api/public/resumo-do-tenant/escolinha
# com o cabecalho `x-atm-resumo-token` — do Secret `resumo-credentials`, que
# so vale para ESTE tenant (HMAC do nome dele com a chave da plataforma).
#
# A saida para a plataforma e pela internet (443 publico), que a deny-cross-tenant
# ja libera: nao precisa de NetworkPolicy nova.
#
# `optional: true` no resumo-credentials: tenant sem o Secret (chave da plataforma
# ainda nao configurada) roda o job, que termina OK dizendo "sem token" — nao gera
# KubeJobFailed nem gasta a cota de e-mail dos alertas.
# =============================================================================
apiVersion: v1
kind: ConfigMap
metadata:
name: resumo-cron
namespace: escolinha-prod
data:
enviar.sh: |
#!/bin/sh
set -eu
: "${KEYCLOAK_URL:?falta KEYCLOAK_URL}"
: "${CLIENT_SECRET:?falta CLIENT_SECRET}"
: "${ISSUER_HOST:?falta ISSUER_HOST}"
: "${DESTINO:?falta DESTINO}"
if [ -z "${RESUMO_TOKEN:-}" ]; then
echo "sem o Secret resumo-credentials neste tenant: nada a enviar."
exit 0
fi
i=0
until curl -sS -o /dev/null -m 5 "${KEYCLOAK_URL}" 2>/dev/null; do
i=$((i+1))
if [ "$i" -ge 24 ]; then
echo "ERRO: ${KEYCLOAK_URL} inalcancavel depois de 2 min." >&2
exit 1
fi
sleep 5
done
RESP="$(printf 'grant_type=client_credentials&client_id=operacao&client_secret=%s' \
"${CLIENT_SECRET}" \
| curl -sS --fail-with-body -X POST "${KEYCLOAK_URL}" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'X-Forwarded-Proto: https' \
-H 'X-Forwarded-Port: 443' \
-H "X-Forwarded-Host: ${ISSUER_HOST}" -d @-)"
TOKEN="$(printf '%s' "${RESP}" | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')"
if [ -z "${TOKEN}" ]; then
echo "ERRO: nao consegui token pelo client 'operacao'." >&2
exit 1
fi
# Os tokens vao por `-K -` (stdin), e nao na linha de comando: argumento de
# processo aparece em `ps` para quem estiver no no.
CODIGO="$(printf 'header = "Authorization: Bearer %s"\n' "${TOKEN}" \
| curl -sS -K - -o /tmp/resumo.json -w '%{http_code}' http://runtime-operacao/api/resumo-do-tenant)"
if [ "${CODIGO}" != "200" ]; then
echo "ERRO: o runtime-operacao respondeu ${CODIGO} ao resumo." >&2
exit 1
fi
echo "resumo calculado: $(wc -c < /tmp/resumo.json) bytes (so numeros)"
CODIGO="$(printf 'header = "x-atm-resumo-token: %s"\n' "${RESUMO_TOKEN}" \
| curl -sS -K - -X POST "${DESTINO}" -H 'Content-Type: application/json' \
--data-binary @/tmp/resumo.json -o /tmp/saida.json -w '%{http_code}')"
echo "plataforma: ${CODIGO}"
cat /tmp/saida.json; echo
case "${CODIGO}" in
2*) ;;
*) echo "ERRO: a plataforma respondeu ${CODIGO}." >&2; exit 1 ;;
esac
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: resumo-para-a-plataforma
namespace: escolinha-prod
spec:
# Madrugada, depois da virada do dia em Sao Paulo e fora do horario das
# mensalidades (dia 1, 06h).
schedule: "30 4 * * *"
timeZone: America/Sao_Paulo
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
jobTemplate:
spec:
activeDeadlineSeconds: 600
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: enviar
image: docker.io/curlimages/curl:8.9.1
imagePullPolicy: Never
command: ["/bin/sh", "/script/enviar.sh"]
env:
- name: KEYCLOAK_URL
value: http://keycloak.plataforma-prod.svc:8080/realms/athleticmap-4/protocol/openid-connect/token
- name: ISSUER_HOST
value: auth.athleticmap.com
- name: DESTINO
value: http://servico-captacao.plataforma-prod.svc/api/public/resumo-do-tenant/escolinha
- name: CLIENT_SECRET
valueFrom:
secretKeyRef:
name: operacao-credentials
key: client-secret
- name: RESUMO_TOKEN
valueFrom:
secretKeyRef:
name: resumo-credentials
key: token
optional: true
resources:
requests: { cpu: 10m, memory: 32Mi }
limits: { cpu: 200m, memory: 64Mi }
volumeMounts:
- name: script
mountPath: /script
volumes:
- name: script
configMap:
name: resumo-cron
+141
View File
@@ -0,0 +1,141 @@
# =============================================================================
# N16 — o resumo AGREGADO do tenant, entregue a plataforma uma vez por dia, para o
# comprador ver todas as escolinhas dele lado a lado no /minha-conta.
#
# AGUARDANDO GRAVACAO no molde (`moldes/consolidado/` do GitOps) e nos tenants que
# ja existem. Nada aqui foi aplicado.
#
# O desenho: o TENANT calcula (runtime-operacao, GET /api/resumo-do-tenant, so
# contadores e somas) e o TENANT envia (este CronJob). A plataforma nao entra no
# namespace nem no banco do tenant para isto.
#
# 1. token do client `operacao` no realm do tenant (o mesmo das tarefas do
# financeiro: Secret `operacao-credentials`);
# 2. GET http://runtime-operacao/api/resumo-do-tenant (lista de 1 ou 2 meses);
# 3. POST http://servico-captacao.plataforma-prod.svc/api/public/resumo-do-tenant/piloto
# com o cabecalho `x-atm-resumo-token` — do Secret `resumo-credentials`, que
# so vale para ESTE tenant (HMAC do nome dele com a chave da plataforma).
#
# A saida para a plataforma e pela internet (443 publico), que a deny-cross-tenant
# ja libera: nao precisa de NetworkPolicy nova.
#
# `optional: true` no resumo-credentials: tenant sem o Secret (chave da plataforma
# ainda nao configurada) roda o job, que termina OK dizendo "sem token" — nao gera
# KubeJobFailed nem gasta a cota de e-mail dos alertas.
# =============================================================================
apiVersion: v1
kind: ConfigMap
metadata:
name: resumo-cron
namespace: piloto-prod
data:
enviar.sh: |
#!/bin/sh
set -eu
: "${KEYCLOAK_URL:?falta KEYCLOAK_URL}"
: "${CLIENT_SECRET:?falta CLIENT_SECRET}"
: "${ISSUER_HOST:?falta ISSUER_HOST}"
: "${DESTINO:?falta DESTINO}"
if [ -z "${RESUMO_TOKEN:-}" ]; then
echo "sem o Secret resumo-credentials neste tenant: nada a enviar."
exit 0
fi
i=0
until curl -sS -o /dev/null -m 5 "${KEYCLOAK_URL}" 2>/dev/null; do
i=$((i+1))
if [ "$i" -ge 24 ]; then
echo "ERRO: ${KEYCLOAK_URL} inalcancavel depois de 2 min." >&2
exit 1
fi
sleep 5
done
RESP="$(printf 'grant_type=client_credentials&client_id=operacao&client_secret=%s' \
"${CLIENT_SECRET}" \
| curl -sS --fail-with-body -X POST "${KEYCLOAK_URL}" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'X-Forwarded-Proto: https' \
-H 'X-Forwarded-Port: 443' \
-H "X-Forwarded-Host: ${ISSUER_HOST}" -d @-)"
TOKEN="$(printf '%s' "${RESP}" | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')"
if [ -z "${TOKEN}" ]; then
echo "ERRO: nao consegui token pelo client 'operacao'." >&2
exit 1
fi
# Os tokens vao por `-K -` (stdin), e nao na linha de comando: argumento de
# processo aparece em `ps` para quem estiver no no.
CODIGO="$(printf 'header = "Authorization: Bearer %s"\n' "${TOKEN}" \
| curl -sS -K - -o /tmp/resumo.json -w '%{http_code}' http://runtime-operacao/api/resumo-do-tenant)"
if [ "${CODIGO}" != "200" ]; then
echo "ERRO: o runtime-operacao respondeu ${CODIGO} ao resumo." >&2
exit 1
fi
echo "resumo calculado: $(wc -c < /tmp/resumo.json) bytes (so numeros)"
CODIGO="$(printf 'header = "x-atm-resumo-token: %s"\n' "${RESUMO_TOKEN}" \
| curl -sS -K - -X POST "${DESTINO}" -H 'Content-Type: application/json' \
--data-binary @/tmp/resumo.json -o /tmp/saida.json -w '%{http_code}')"
echo "plataforma: ${CODIGO}"
cat /tmp/saida.json; echo
case "${CODIGO}" in
2*) ;;
*) echo "ERRO: a plataforma respondeu ${CODIGO}." >&2; exit 1 ;;
esac
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: resumo-para-a-plataforma
namespace: piloto-prod
spec:
# Madrugada, depois da virada do dia em Sao Paulo e fora do horario das
# mensalidades (dia 1, 06h).
schedule: "30 4 * * *"
timeZone: America/Sao_Paulo
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
jobTemplate:
spec:
activeDeadlineSeconds: 600
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: enviar
image: docker.io/curlimages/curl:8.9.1
imagePullPolicy: Never
command: ["/bin/sh", "/script/enviar.sh"]
env:
- name: KEYCLOAK_URL
value: http://keycloak.plataforma-prod.svc:8080/realms/athleticmap-1/protocol/openid-connect/token
- name: ISSUER_HOST
value: auth.athleticmap.com
- name: DESTINO
value: http://servico-captacao.plataforma-prod.svc/api/public/resumo-do-tenant/piloto
- name: CLIENT_SECRET
valueFrom:
secretKeyRef:
name: operacao-credentials
key: client-secret
- name: RESUMO_TOKEN
valueFrom:
secretKeyRef:
name: resumo-credentials
key: token
optional: true
resources:
requests: { cpu: 10m, memory: 32Mi }
limits: { cpu: 200m, memory: 64Mi }
volumeMounts:
- name: script
mountPath: /script
volumes:
- name: script
configMap:
name: resumo-cron