feat(release): chave deploy_gate — release.yml fala o contrato do tmf-deploy-gate [INFRA-68093] #88

Merged
tarcisio merged 3 commits from feat/INFRA-68093-deploy-gate-contrato into develop 2026-10-09 19:30:02 -03:00
Owner

O que

Chave nova deploy_gate no .forgejo/ci-config.yml (boolean, padrão false) para o release.yml falar o contrato do scripts/tmf-deploy-gate.

  • deploy_gate: false (padrão): nada muda — ssh deploy "bash -s -- <modo> ..." < deploy-remoto.sh e guarda por ssh deploy hostname.
  • deploy_gate: true: deploy, rollback e orfaos vão como ssh deploy "<modo> 'K=v' ..." sem stdin (</dev/null) e sem CODE_DIR/ENV_DIR/CONTAINER, que vêm do /etc/tmf-deploy/<produto>.conf (no ci-config o caminho é relativo; o conf do gate exige absoluto e igual, então quem manda é o conf). A guarda de host usa ssh deploy host.
  • tmf-deploy-gate: modo novo host, que não aceita argumento e só imprime uname -n. O nome não é hostname de propósito: o release com deploy_gate: false manda hostname, e é a recusa dele que faz o deploy falhar cedo.
  • A chave vai do job tag (saída do ci_config.py release) para o job deploy por env: (DEPLOY_GATE). Não há ${{ }} dentro de run: nem bloco secrets:. Valor fora de true|false falha antes do SSH.
  • As aspas simples continuam recusadas, agora sem subshell (antes, a recusa de um valor no meio da linha ARGS=$(q)$(q)… não parava o passo).

Por quê

Hoje o release.yml e o gate falam contratos que não se entendem: com o gate ligado no host, todo deploy quebrava (hostname/bash → "modo não permitido"). Esta chave permite ligar o gate produto a produto, sem mexer em quem não ligou.

Teste negativo

Host com gate e deploy_gate: false: a guarda de host recebe tmf-deploy-gate: recusado — modo não permitido: 'hostname' e o passo para com ::error::o host tem o tmf-deploy-gate: ponha deploy_gate: true no .forgejo/ci-config.yml, antes do status pending e de qualquer deploy. No caso inverso (true numa chave sem gate), a mensagem é "o host não respondeu ao modo 'host'".

Validação

  • tests/release/test_deploy_gate_contrato.py (novo): executa os run: reais do release.yml (guarda, Deploy, Rollback, órfãos) com um ssh falso que entrega o comando ao tmf-deploy-gate real (via SSH_ORIGINAL_COMMAND) e a um deploy-remoto.sh falso:
    • (1) com true, o gate aceita deploy, rollback, orfaos e host;
    • (2) com false, o gate recusa todos, e a guarda falha cedo com a mensagem clara;
    • (3) estrutural: false mantém o comando e o stdin atuais, true vai sem stdin e sem diretórios; valor inválido não chega a fazer SSH; aspas recusadas nos dois modos;
    • o schema, o ci_config.py release e a passagem por env.
  • tests/release/test_tmf_deploy_gate.py: o modo host imprime o hostname, vale fora de MODOS, não aceita argumento e exige conf; hostname continua recusado.
  • Suítes locais (python3 -m unittest discover -s tests/<d>): release 373 OK, ci-config 150 OK (4 skip), ci-unico 118 OK, deploy-guard 82 OK, hub-issue-sync 38 OK, sonar 28 OK, soberania 25 OK, cve-os 21 OK, sync-docs-filtro 21 OK, if-em-caller 18 OK, ci-ok 17 OK, version-code 17 OK, reusable-publico 13 OK, infisical-secrets 9 OK, nginx-log 6 OK. generate-docs: 19 testes, 2 falhas de ambiente local (python: command not found — a máquina só tem python3), em código que este PR não toca.

Docs: docs/guia-de-adocao.md (linha da chave e roteiro para ligar e desligar), templates/ci-config.exemplo.yml, CHANGELOG.md em [Não lançado].

Não verificado: nunca rodou em host real (o gate segue desligado).

🤖 Generated with Claude Code

https://claude.ai/code/session_01ES1xwVdpQV69zEsuLLKzx2

## O que Chave nova `deploy_gate` no `.forgejo/ci-config.yml` (boolean, **padrão `false`**) para o `release.yml` falar o contrato do `scripts/tmf-deploy-gate`. - `deploy_gate: false` (padrão): nada muda — `ssh deploy "bash -s -- <modo> ..." < deploy-remoto.sh` e guarda por `ssh deploy hostname`. - `deploy_gate: true`: deploy, rollback e orfaos vão como `ssh deploy "<modo> 'K=v' ..."` **sem stdin** (`</dev/null`) e sem `CODE_DIR`/`ENV_DIR`/`CONTAINER`, que vêm do `/etc/tmf-deploy/<produto>.conf` (no ci-config o caminho é relativo; o conf do gate exige absoluto e igual, então quem manda é o conf). A guarda de host usa `ssh deploy host`. - `tmf-deploy-gate`: modo novo `host`, que não aceita argumento e só imprime `uname -n`. O nome **não** é `hostname` de propósito: o release com `deploy_gate: false` manda `hostname`, e é a recusa dele que faz o deploy falhar **cedo**. - A chave vai do job `tag` (saída do `ci_config.py release`) para o job `deploy` por `env:` (`DEPLOY_GATE`). Não há `${{ }}` dentro de `run:` nem bloco `secrets:`. Valor fora de `true|false` falha antes do SSH. - As aspas simples continuam recusadas, agora sem subshell (antes, a recusa de um valor no meio da linha `ARGS=$(q)$(q)…` não parava o passo). ## Por quê Hoje o `release.yml` e o gate falam contratos que não se entendem: com o gate ligado no host, todo deploy quebrava (`hostname`/`bash` → "modo não permitido"). Esta chave permite ligar o gate produto a produto, sem mexer em quem não ligou. ## Teste negativo Host com gate e `deploy_gate: false`: a guarda de host recebe `tmf-deploy-gate: recusado — modo não permitido: 'hostname'` e o passo para com `::error::o host tem o tmf-deploy-gate: ponha deploy_gate: true no .forgejo/ci-config.yml`, antes do status pending e de qualquer deploy. No caso inverso (`true` numa chave sem gate), a mensagem é "o host não respondeu ao modo 'host'". ## Validação - `tests/release/test_deploy_gate_contrato.py` (novo): executa os `run:` reais do release.yml (guarda, Deploy, Rollback, órfãos) com um `ssh` falso que entrega o comando ao `tmf-deploy-gate` real (via `SSH_ORIGINAL_COMMAND`) e a um `deploy-remoto.sh` falso: - (1) com `true`, o gate aceita deploy, rollback, orfaos e host; - (2) com `false`, o gate recusa todos, e a guarda falha cedo com a mensagem clara; - (3) estrutural: `false` mantém o comando e o stdin atuais, `true` vai sem stdin e sem diretórios; valor inválido não chega a fazer SSH; aspas recusadas nos dois modos; - o schema, o `ci_config.py release` e a passagem por env. - `tests/release/test_tmf_deploy_gate.py`: o modo `host` imprime o hostname, vale fora de `MODOS`, não aceita argumento e exige conf; `hostname` continua recusado. - Suítes locais (`python3 -m unittest discover -s tests/<d>`): release 373 OK, ci-config 150 OK (4 skip), ci-unico 118 OK, deploy-guard 82 OK, hub-issue-sync 38 OK, sonar 28 OK, soberania 25 OK, cve-os 21 OK, sync-docs-filtro 21 OK, if-em-caller 18 OK, ci-ok 17 OK, version-code 17 OK, reusable-publico 13 OK, infisical-secrets 9 OK, nginx-log 6 OK. generate-docs: 19 testes, 2 falhas **de ambiente local** (`python: command not found` — a máquina só tem `python3`), em código que este PR não toca. Docs: `docs/guia-de-adocao.md` (linha da chave e roteiro para ligar e desligar), `templates/ci-config.exemplo.yml`, `CHANGELOG.md` em [Não lançado]. **Não verificado:** nunca rodou em host real (o gate segue desligado). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01ES1xwVdpQV69zEsuLLKzx2
O release.yml mandava `bash -s -- <modo> ...` com o deploy-remoto.sh pelo stdin e
`hostname` como guarda de host; o tmf-deploy-gate (comando forçado da chave SSH) só
aceita `<modo> 'K=v' ...`, ignora stdin e recusa `hostname`. Contratos incompatíveis.

- schema/ci_config: chave `deploy_gate` (boolean, padrão false), publicada pelo job tag
  e passada ao job deploy por env (DEPLOY_GATE).
- release.yml: com true, deploy/rollback/orfaos vão como `ssh deploy "<modo> 'K=v' ..."`
  sem stdin e sem CODE_DIR/ENV_DIR/CONTAINER (o conf do gate manda); a guarda de host
  usa `ssh deploy host`. Com false, o comando é o de sempre. Valor fora de true|false
  falha antes do SSH. Aspas simples recusadas sem subshell (o exit vale).
- tmf-deploy-gate: modo `host` (sem argumentos, só `uname -n`). `hostname` continua
  recusado de propósito: host com gate e chave false falha CEDO na guarda, com a
  mensagem "o host tem o tmf-deploy-gate: ponha deploy_gate: true".
- testes: tests/release/test_deploy_gate_contrato.py roda os passos do release.yml com
  ssh falso ligado ao gate real (true passa; false recusado; estrutura stdin/sem stdin);
  modo host em test_tmf_deploy_gate.py.
- docs: guia-de-adocao (chave + roteiro liga/desliga), exemplo do ci-config, CHANGELOG.

Não verificado em host real: o gate segue desligado.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ES1xwVdpQV69zEsuLLKzx2
Merge origin/develop em feat/INFRA-68093-deploy-gate-contrato [INFRA-68093]
All checks were successful
PR checks (ci-shared) / testes (pull_request) Successful in 4m14s
d818ac4918
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ES1xwVdpQV69zEsuLLKzx2
merge: develop em feat/INFRA-68093 — CHANGELOG: deploy_gate fica em [Não lançado] (a 0.2.0 já fechou) [INFRA-68093]
All checks were successful
PR checks (ci-shared) / testes (pull_request) Successful in 4m36s
e37a563f77
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ES1xwVdpQV69zEsuLLKzx2
tarcisio merged commit a251006fae into develop 2026-10-09 19:30:02 -03:00
tarcisio deleted branch feat/INFRA-68093-deploy-gate-contrato 2026-10-09 19:30:02 -03:00
Sign in to join this conversation.
No reviewers
No labels
seguranca
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
TMF/ci-shared!88
No description provided.