-
Notifications
You must be signed in to change notification settings - Fork 0
191 lines (175 loc) · 9.31 KB
/
Copy pathpr-validate.yml
File metadata and controls
191 lines (175 loc) · 9.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
name: PR Validate (LayoutParserApi)
# Gate de build/teste para PRs contra develop e master (fluxo real: develop -> master), de QUALQUER branch de origem.
#
# Por que este workflow existe separado do ci-dev.yml: ci-dev.yml, alem de compilar/testar,
# TAMBEM FAZ DEPLOY (historico). Rodar deploy em todo
# pull_request seria perigoso e sem sentido - uma PR ainda nao mergeada nao deve parar/reiniciar
# o servico de dev nem escrever no destino do deploy. Este workflow cobre so o gate funcional
# (build + testes), sem tocar em servico/deploy - resolve a issue #122 sem herdar o risco de
# rodar deploy em pull_request.
#
# Roda em runner GITHUB-HOSTED (ubuntu-latest), de proposito: este ambiente (host Linux self-hosted)
# E producao. Validar PR (build, testes, gate SCS, oasdiff) executa codigo ainda nao revisado e
# NAO deve rodar no host de producao. Aqui nao ha deploy, nem segredos, nem acesso a rede interna.
# Repo publico: minutos de Actions gratis. Deploy continua restrito aos workflows *-linux.yml.
# Nao depende do .NET Framework 4.8.1 / Decrypt (a API nao compila contra ele).
on:
pull_request:
branches:
- develop
- master
# Minimo necessario: checkout deste repo + LayoutParserDecrypt (via
# GH_PAT_TOKEN, fora do escopo do GITHUB_TOKEN) - nenhum step comenta PR, cria release ou
# escreve outro recurso do GitHub. GITHUB_TOKEN so participa do proprio checkout.
permissions:
contents: read
# Contract check de OpenAPI (issue #414) - COMO PROMOVER DE ADVISORY PARA BLOQUEANTE:
# basta trocar o valor abaixo para 'true'. Nada mais precisa mudar: o step passa a sair com
# codigo 1 quando houver breaking change (nivel ERR do oasdiff) e o continue-on-error do step
# (calculado a partir desta mesma variavel) deixa de mascarar a falha. Em modo advisory
# (padrao) o step reporta no resumo do job e como aviso, mas nunca reprova a PR.
#
# Antes de promover: (1) observar algumas PRs em advisory para medir falso positivo;
# (2) lembrar que sem branch protection nativa (plano free, repo privado) um check vermelho
# NAO impede o merge - para o gate valer de fato, adicionar este job como required status
# check (exige GitHub Pro/Team) ou manter a convencao de nao mergear PR vermelha; (3) mudanca
# breaking INTENCIONAL passa a exigir aprovacao explicita do dono (ex.: marcar a PR e trocar
# a variavel temporariamente, ou ignorar o check conscientemente).
env:
OPENAPI_CONTRACT_BLOCKING: 'false'
concurrency:
group: pr-validate-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
build-and-test:
# Nome do job = contexto do check ('build'), compativel com o check das PRs antigas.
name: build
# Nunca executar codigo de fork (PR de fork nao recebe secrets e o codigo
# nao e confiavel). Dependabot abre branch no proprio repo: roda com token read-only, sem secrets.
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout LayoutParserApi
uses: actions/checkout@v7
with:
path: LayoutParserApi
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: '10.0.x'
- name: Build LayoutParserApi (Release)
run: |
set -euo pipefail
dotnet restore LayoutParserApi/LayoutParserApi.csproj
dotnet build LayoutParserApi/LayoutParserApi.csproj --configuration Release --no-restore 2>&1 | tee scs-build.log
# Gate de SAST (SecurityCodeScan): achado alto fora do baseline bloqueia.
- name: Security Code Scan - gate por severidade
run: |
python3 - <<'PY'
import json, os, re, sys
log = "scs-build.log"
if not os.path.exists(log):
print("Log de build ausente - pulando gate de SCS."); sys.exit(0)
altos = {"SCS0002","SCS0003","SCS0007","SCS0016","SCS0018","SCS0026","SCS0028","SCS0029","SCS0031"}
base = set()
bf = "LayoutParserApi/security-code-scan-baseline.json"
if os.path.exists(bf):
for f in json.load(open(bf)).get("findings", []):
base.add("%s:%s:%s" % (f["code"], f["file"], f["line"]))
print("Baseline: %d achado(s)" % len(base))
total, conhecidos, novos = {}, set(), set()
for l in open(log, errors="replace"):
m = re.search(r"warning (SCS\d+)", l)
if not m: continue
c = m.group(1); total[c] = total.get(c, 0) + 1
f = re.search(r"(?P<f>[^():\s][^():]*\.cs)\((?P<l>\d+),\d+\):", l)
if c in altos and f:
rel = f.group("f").replace("\\", "/")
for mk in ("/Controllers/", "/Services/"):
i = rel.find(mk)
if i >= 0:
rel = rel[i+1:]; break
k = "%s:%s:%s" % (c, rel, f.group("l"))
(conhecidos if k in base else novos).add(k)
for c, n in total.items(): print(" %s : %d" % (c, n))
for k in sorted(conhecidos): print(" baseline:", k)
if novos:
for k in sorted(novos): print(" NOVO:", k)
print("SecurityCodeScan: %d achado(s) NOVO(S) fora do baseline." % len(novos)); sys.exit(1)
print("Gate de SCS OK.")
PY
- name: Testes (xUnit)
run: |
dotnet test LayoutParserApi/tests/LayoutParserApi.Tests/LayoutParserApi.Tests.csproj --configuration Release --logger "console;verbosity=minimal"
- name: Checkout base da PR (develop ou master) para comparar OpenAPI
continue-on-error: ${{ env.OPENAPI_CONTRACT_BLOCKING != 'true' }}
uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.base.sha }}
path: LayoutParserApi-base
# Advisory por padrao (ver OPENAPI_CONTRACT_BLOCKING). oasdiff fixado por versao e cacheado.
- name: Contract check de OpenAPI (oasdiff, advisory)
continue-on-error: ${{ env.OPENAPI_CONTRACT_BLOCKING != 'true' }}
run: |
set -euo pipefail
OAS_VER="1.32.1"
BLOQ="${OPENAPI_CONTRACT_BLOCKING:-false}"
RAIZ="$PWD"
CACHE="${RUNNER_TOOL_CACHE:-$RUNNER_TEMP}"
SCR="$RUNNER_TEMP/openapi-contract"; rm -rf "$SCR"; mkdir -p "$SCR"
OAS_DIR="$CACHE/oasdiff/$OAS_VER"; OAS="$OAS_DIR/oasdiff"
if [ ! -x "$OAS" ]; then
mkdir -p "$OAS_DIR"
ARQ="oasdiff_${OAS_VER}_linux_amd64.tar.gz"
BASE_URL="https://github.com/oasdiff/oasdiff/releases/download/v$OAS_VER"
curl -fsSL -o "$SCR/oas.tgz" "$BASE_URL/$ARQ"
curl -fsSL -o "$SCR/checksums.txt" "$BASE_URL/checksums.txt"
ESPERADO=$(awk -v f="$ARQ" '$2==f {print $1}' "$SCR/checksums.txt")
[ -n "$ESPERADO" ] || { echo "SHA256 de $ARQ ausente em checksums.txt"; exit 1; }
echo "$ESPERADO $SCR/oas.tgz" | sha256sum -c -
tar -xzf "$SCR/oas.tgz" -C "$OAS_DIR"
fi
echo "oasdiff: $("$OAS" --version)"
gen() {
proj="$1"; out="$2"
ver=$(grep -oP 'Swashbuckle\.AspNetCore"\s+Version="\K[^"]+' "$proj/LayoutParserApi.csproj" | head -1)
[ -n "$ver" ] || { echo "Versao do Swashbuckle nao encontrada"; return 1; }
tool="$CACHE/swashbuckle-cli/$ver"
[ -x "$tool/swagger" ] || dotnet tool install Swashbuckle.AspNetCore.Cli --version "$ver" --tool-path "$tool"
dll="$proj/bin/Release/net10.0/LayoutParserApi.dll"
if [ ! -f "$dll" ]; then
dotnet restore "$proj/LayoutParserApi.csproj"
dotnet build "$proj/LayoutParserApi.csproj" -c Release --no-restore -p:RunAnalyzers=false -v q
fi
( cd "$proj" && ASPNETCORE_ENVIRONMENT=Testing "$tool/swagger" tofile --output "$out" "$dll" v1 )
}
gen "$RAIZ/LayoutParserApi-base" "$SCR/base.json"
gen "$RAIZ/LayoutParserApi" "$SCR/pr.json"
"$OAS" breaking "$SCR/base.json" "$SCR/pr.json" --format json > "$SCR/breaking.json"
"$OAS" changelog "$SCR/base.json" "$SCR/pr.json" --format json > "$SCR/changelog.json"
python3 - "$SCR" "$OAS_VER" "$BLOQ" <<'PY'
import json, os, sys
scr, ver, bloq = sys.argv[1], sys.argv[2], sys.argv[3] == "true"
def ld(p):
t = open(p).read().strip()
return json.loads(t) if t.startswith("[") else []
br, cl = ld(scr + "/breaking.json"), ld(scr + "/changelog.json")
erros = [c for c in br if c.get("level", 0) >= 3]
avisos = [c for c in br if c.get("level") == 2]
adit = [c for c in cl if c.get("level", 0) <= 1]
r = ["## Contract check de OpenAPI (oasdiff %s)" % ver, "",
"Modo: **%s**" % ("BLOQUEANTE" if bloq else "advisory"), "",
"| Categoria | Qtde |", "|---|---|",
"| Breaking changes (erro) | %d |" % len(erros),
"| Avisos | %d |" % len(avisos),
"| Aditivas / informativas | %d |" % len(adit), ""]
for c in erros + avisos:
r.append("- [%s] `%s %s` - %s" % ("ERRO" if c["level"] >= 3 else "AVISO", c.get("operation"), c.get("path"), c.get("text")))
s = os.environ.get("GITHUB_STEP_SUMMARY")
if s: open(s, "a").write("\n".join(r) + "\n")
for c in erros[:20]:
print("::warning title=OpenAPI breaking change::%s %s - %s" % (c.get("operation"), c.get("path"), c.get("text")))
print("OpenAPI: %d breaking, %d aviso(s), %d aditiva(s)." % (len(erros), len(avisos), len(adit)))
if erros and bloq: sys.exit(1)
PY