@zenifra/cli 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -4
- package/bin/zenifra.mjs +572 -30
- package/examples/http-git-project.json +38 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,9 +54,15 @@ zenifra plans
|
|
|
54
54
|
zenifra plans --type http
|
|
55
55
|
zenifra plans --type valkey
|
|
56
56
|
zenifra plans --type storage --json
|
|
57
|
+
zenifra git providers
|
|
58
|
+
zenifra git runtimes
|
|
59
|
+
zenifra git connections
|
|
60
|
+
zenifra git repositories resolve --connection <connection-id> --path equipe/aplicacao
|
|
61
|
+
zenifra git branches --connection <connection-id> --repository <repository-id>
|
|
57
62
|
zenifra create project
|
|
58
63
|
zenifra create project --name <name> --plan free --payment-mode hourly --config @examples/http-project.json
|
|
59
64
|
zenifra create project --name <name> --plan basic --payment-mode hourly --config @examples/http-github-project.json
|
|
65
|
+
zenifra create project --name <name> --plan basic --payment-mode hourly --config @examples/http-git-project.json
|
|
60
66
|
zenifra create project --name <name> --plan premium --payment-mode hourly --config @examples/http-autoscaling-project.json
|
|
61
67
|
zenifra create project --name <name> --plan db-basic --payment-mode monthly --config @examples/postgresql-project.json
|
|
62
68
|
zenifra create project --name <name> --plan db-basic --payment-mode monthly --config @examples/mariadb-project.json
|
|
@@ -82,6 +88,9 @@ zenifra project network --project <project-id> --view summary
|
|
|
82
88
|
zenifra project image set --project <project-id> --image ghcr.io/zenifra/app:tag
|
|
83
89
|
zenifra project github --project <project-id>
|
|
84
90
|
zenifra project github deploy-settings set --project <project-id> --mode branch
|
|
91
|
+
zenifra project source --project <project-id>
|
|
92
|
+
zenifra project source branches --project <project-id>
|
|
93
|
+
zenifra project source deploy-settings set --project <project-id> --mode branch
|
|
85
94
|
zenifra project github deploy-settings set --project <project-id> --mode tag --tag-pattern "v*"
|
|
86
95
|
zenifra project github deploy-settings set --project <project-id> --mode release --tag-pattern "v*" --include-prereleases true
|
|
87
96
|
zenifra project envs --project <project-id>
|
|
@@ -138,9 +147,9 @@ zenifra project metrics capabilities --project <project-id> --json
|
|
|
138
147
|
|
|
139
148
|
---
|
|
140
149
|
|
|
141
|
-
## Builds
|
|
150
|
+
## Builds e deployments Git
|
|
142
151
|
|
|
143
|
-
Use `zenifra builds` para listar o historico de builds e `zenifra builds logs` para ler os logs
|
|
152
|
+
Use `zenifra builds` para listar o historico de builds e `zenifra builds logs` para ler os logs de um build de projeto com origem Git.
|
|
144
153
|
|
|
145
154
|
```bash
|
|
146
155
|
zenifra builds --project <project-id>
|
|
@@ -153,10 +162,37 @@ zenifra deploy watch --project <project-id> --build <build-id>
|
|
|
153
162
|
Fluxos:
|
|
154
163
|
|
|
155
164
|
- `zenifra project logs`: logs da aplicacao em execucao
|
|
156
|
-
- `zenifra builds logs`: logs do build
|
|
157
|
-
- `zenifra deploy`: dispara o build/deploy
|
|
165
|
+
- `zenifra builds logs`: logs do build Git
|
|
166
|
+
- `zenifra deploy`: dispara o build/deploy Git e retorna o `build_id`
|
|
158
167
|
- `zenifra deploy watch`: usa esse `build_id` para acompanhar o build em tempo real e imprimir os logs incrementais ate o fim
|
|
159
168
|
|
|
169
|
+
Para criar um projeto HTTP com uma origem Git Forgejo, primeiro uma pessoa proprietaria da organizacao cria a conexao no Console. A CLI lista e usa conexoes existentes; ela nao solicita nem exibe credenciais do provedor.
|
|
170
|
+
|
|
171
|
+
Use os catalogos para conferir provedores, capacidades e runtimes, depois resolva o caminho explicito do repositorio. O ID opaco retornado pertence a essa conexao e deve ser usado com ela ao listar branches:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
zenifra git providers --json
|
|
175
|
+
zenifra git runtimes
|
|
176
|
+
zenifra git connections --json
|
|
177
|
+
zenifra git repositories resolve --connection <connection-id> --path equipe/aplicacao --json
|
|
178
|
+
zenifra git branches --connection <connection-id> --repository <repository-id>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Use os IDs confirmados em `config.source` e `config.build`, como no arquivo `examples/http-git-project.json`, para criar o projeto. Esse exemplo contem valores ilustrativos e nao credenciais:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
zenifra create project --name api-web --plan basic --payment-mode hourly --config @examples/http-git-project.json
|
|
185
|
+
zenifra project source --project <project-id> --json
|
|
186
|
+
zenifra project source branches --project <project-id>
|
|
187
|
+
zenifra project source deploy-settings set --project <project-id> --mode branch
|
|
188
|
+
zenifra deploy --project <project-id> --branch main
|
|
189
|
+
zenifra deploy watch --project <project-id> --build <build-id>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
O deploy manual retorna um ID de build para acompanhar com `zenifra deploy watch`. Para ativar deploy por tag ou release, use `zenifra project source deploy-settings set --project <project-id> --mode tag --tag-pattern "v*"` ou o modo `release`; prereleases podem ser habilitadas somente no modo `release`. O comando preserva a origem e as configuracoes de build e confirma a alteracao com uma leitura posterior. A API nao oferece precondicao de revisao para essa atualizacao; evite alterar a origem ao mesmo tempo pelo Console ou por outro cliente. Projetos com configuracao GitHub legada continuam usando `zenifra project github` e `zenifra project github deploy-settings set`.
|
|
193
|
+
|
|
194
|
+
Em APIs antigas que ainda nao anunciam as rotas Git neutras, a CLI usa as rotas anteriores somente quando consegue confirmar a origem GitHub legada do projeto. Essa verificacao pode exigir permissao de leitura do projeto. Se a API negar essa leitura, a CLI encerra o comando sem tentar a rota antiga; projetos Forgejo ou com outra origem generica nunca sao tratados como GitHub.
|
|
195
|
+
|
|
160
196
|
Alguns builds antigos podem disponibilizar somente um resumo terminal. Nesse caso, a saida legivel avisa que eventos detalhados nao estavam disponiveis; `--json` preserva a resposta recebida da API.
|
|
161
197
|
|
|
162
198
|
### Modos de deploy GitHub
|
|
@@ -309,6 +345,7 @@ Use os arquivos em `examples/` como base para `zenifra create project`:
|
|
|
309
345
|
- `examples/http-github-project.json`: projeto HTTP com build via GitHub
|
|
310
346
|
- `examples/http-github-tag-project.json`: projeto HTTP com deploy por tag e padrao exato
|
|
311
347
|
- `examples/http-github-release-project.json`: projeto HTTP com deploy por release e padrao `v*`
|
|
348
|
+
- `examples/http-git-project.json`: projeto HTTP com origem Git por conexao configurada no Console/API
|
|
312
349
|
- `examples/http-autoscaling-project.json`: projeto HTTP pago criado com auto-scaling
|
|
313
350
|
- `examples/postgresql-project.json`: projeto PostgreSQL
|
|
314
351
|
- `examples/mariadb-project.json`: projeto MariaDB
|
|
@@ -348,6 +385,7 @@ Valores aceitos:
|
|
|
348
385
|
- `config.github.runtime` (quando houver GitHub em projeto HTTP): `nodejs` ou `python`
|
|
349
386
|
- `config.github.auto_deploy`: use `true` para o modo `branch`; mantenha `false` ao habilitar `version_deploy`
|
|
350
387
|
- `config.github.version_deploy`: use `enabled: true`, `event: "tag"` ou `"release"` e um `tag_pattern` explicito; `include_prereleases` e opcional e padrao `false`
|
|
388
|
+
- `config.source` e `config.build` (quando houver origem Git por conexao): use os IDs retornados pela configuracao segura no Console/API; nao informe credenciais Git nesses campos
|
|
351
389
|
- `config.autoscaling` (somente HTTP pago): `enabled: true`, `max_instances` maior ou igual a `config.instances` e alvos opcionais de CPU/memoria entre 1 e 100
|
|
352
390
|
|
|
353
391
|
Observacoes do wizard:
|
package/bin/zenifra.mjs
CHANGED
|
@@ -37,6 +37,7 @@ const KNOWN_FLAG_NAMES = new Set([
|
|
|
37
37
|
'challengeToken',
|
|
38
38
|
'code',
|
|
39
39
|
'connectionFile',
|
|
40
|
+
'connection',
|
|
40
41
|
'commitSha',
|
|
41
42
|
'config',
|
|
42
43
|
'count',
|
|
@@ -71,6 +72,7 @@ const KNOWN_FLAG_NAMES = new Set([
|
|
|
71
72
|
'plan',
|
|
72
73
|
'profile',
|
|
73
74
|
'project',
|
|
75
|
+
'repository',
|
|
74
76
|
'revoke',
|
|
75
77
|
'showValues',
|
|
76
78
|
'status',
|
|
@@ -131,6 +133,15 @@ const GITHUB_PUBLIC_CONFIG_FIELDS = [
|
|
|
131
133
|
'build_command',
|
|
132
134
|
];
|
|
133
135
|
const GITHUB_PUBLIC_VERSION_DEPLOY_FIELDS = ['enabled', 'event', 'tag_pattern', 'include_prereleases'];
|
|
136
|
+
const GIT_BUILD_SETTINGS_FIELDS = [
|
|
137
|
+
'runtime',
|
|
138
|
+
'version',
|
|
139
|
+
'start_command',
|
|
140
|
+
'pre_build_command',
|
|
141
|
+
'build_command',
|
|
142
|
+
'dockerfile_path',
|
|
143
|
+
'context_path',
|
|
144
|
+
];
|
|
134
145
|
const ALLOWED_EXPOSURE_VALUES = new Set(['public', 'private']);
|
|
135
146
|
const GITHUB_RUNTIME_VERSIONS = {
|
|
136
147
|
nodejs: ['24', '22', '20'],
|
|
@@ -177,9 +188,10 @@ const ANSI = {
|
|
|
177
188
|
};
|
|
178
189
|
|
|
179
190
|
class CliError extends Error {
|
|
180
|
-
constructor(message, exitCode = 1) {
|
|
191
|
+
constructor(message, exitCode = 1, httpStatus) {
|
|
181
192
|
super(message);
|
|
182
193
|
this.exitCode = exitCode;
|
|
194
|
+
if (httpStatus !== undefined) this.httpStatus = httpStatus;
|
|
183
195
|
}
|
|
184
196
|
}
|
|
185
197
|
|
|
@@ -201,6 +213,11 @@ Usage:
|
|
|
201
213
|
zenifra orgs [--json]
|
|
202
214
|
zenifra org set [--org <id>]
|
|
203
215
|
zenifra plans [--type <all|http|database|storage|valkey>] [--json]
|
|
216
|
+
zenifra git providers [--json]
|
|
217
|
+
zenifra git runtimes [--json]
|
|
218
|
+
zenifra git connections [--json]
|
|
219
|
+
zenifra git repositories resolve --connection <id> --path <owner/repo> [--json]
|
|
220
|
+
zenifra git branches --connection <id> --repository <opaque-id> [--json]
|
|
204
221
|
zenifra create project
|
|
205
222
|
zenifra create project --name <name> --plan <plan> --payment-mode <mode> --config <json|@file> [--description <text>] [--org <id>] [--json]
|
|
206
223
|
zenifra projects [--json] [--org <id>] [--type <http|postgresql|mariadb|valkey>] [--page <n>] [--limit <n>]
|
|
@@ -224,6 +241,9 @@ Usage:
|
|
|
224
241
|
zenifra project image set --project <id> --image <image> [--json]
|
|
225
242
|
zenifra project github --project <id> [--json]
|
|
226
243
|
zenifra project github deploy-settings set --project <id> --mode <manual|branch|tag|release> [--tag-pattern <pattern>] [--include-prereleases <true|false>] [--json]
|
|
244
|
+
zenifra project source --project <id> [--json]
|
|
245
|
+
zenifra project source branches --project <id> [--json]
|
|
246
|
+
zenifra project source deploy-settings set --project <id> --mode <manual|branch|tag|release> [--tag-pattern <pattern>] [--include-prereleases <true|false>] [--json]
|
|
227
247
|
zenifra project exposure set --project <id> --exposure <public|private> [--json]
|
|
228
248
|
zenifra project envs --project <id> [--json] [--show-values]
|
|
229
249
|
zenifra project env add --project <id> --name <name> --value <value> [--json]
|
|
@@ -400,6 +420,68 @@ const HELP_SPECS = [
|
|
|
400
420
|
output: 'HTTP\nPlano Hora Mes Ano Recursos\nfree R$ 0,00 R$ 0,00 R$ 0,00 1 GB Armazenamento Efemero',
|
|
401
421
|
jsonOutput: '{"http":[{"plan":"free","prices":{"hourly":0,"monthly":0,"yearly":0},"features":["1 GB Armazenamento Efemero"]}],"database":[],"storage":[]}',
|
|
402
422
|
},
|
|
423
|
+
{
|
|
424
|
+
command: 'git',
|
|
425
|
+
usage: 'zenifra git\n zenifra git providers [--json]\n zenifra git runtimes [--json]\n zenifra git connections [--json]\n zenifra git repositories resolve --connection <id> --path <owner/repo> [--json]\n zenifra git branches --connection <id> --repository <opaque-id> [--json]',
|
|
426
|
+
description: 'Consulta provedores, runtimes, conexoes e repositorios Git disponiveis na organizacao.',
|
|
427
|
+
examples: ['zenifra git providers', 'zenifra git connections', 'zenifra git repositories resolve --connection conn-1 --path equipe/aplicacao'],
|
|
428
|
+
output: 'Zenifra CLI - git',
|
|
429
|
+
notes: ['A conexao Forgejo e criada no Console por uma pessoa proprietaria da organizacao. A CLI lista e usa conexoes existentes.'],
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
command: 'git providers',
|
|
433
|
+
usage: 'zenifra git providers [--json]',
|
|
434
|
+
description: 'Lista provedores Git e as capacidades disponiveis para a organizacao selecionada.',
|
|
435
|
+
flags: ['--json Imprime a resposta publica da API em JSON.'],
|
|
436
|
+
examples: ['zenifra git providers', 'zenifra git providers --json'],
|
|
437
|
+
output: 'Provedor Disponivel Descoberta Deploy por branch Deploy por versao\nforgejo sim nao sim sim',
|
|
438
|
+
jsonOutput: '{"status":"success","data":{"api_version":1,"providers":[{"id":"forgejo","available":true,"capabilities":{"repositoryDiscovery":false,"pushDeploy":true,"nativePreviews":false,"versionDeploy":true}}]}}',
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
command: 'git runtimes',
|
|
442
|
+
usage: 'zenifra git runtimes [--json]',
|
|
443
|
+
description: 'Lista runtimes e versoes disponiveis para builds de repositorios Git.',
|
|
444
|
+
flags: ['--json Imprime a resposta publica da API em JSON.'],
|
|
445
|
+
examples: ['zenifra git runtimes', 'zenifra git runtimes --json'],
|
|
446
|
+
output: 'Runtime Versao padrao Versoes\nnodejs 24 24, 22, 20',
|
|
447
|
+
jsonOutput: '{"status":"success","data":[{"id":"nodejs","label":"Node.js","default_version":"24","default_port":3000,"default_start_command":"npm start","default_build_command":"npm run build","versions":[{"id":"24","label":"24"}]}]}',
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
command: 'git connections',
|
|
451
|
+
usage: 'zenifra git connections [--json]',
|
|
452
|
+
description: 'Lista as conexoes Git disponiveis para a organizacao selecionada.',
|
|
453
|
+
flags: ['--json Imprime as conexoes publicas em JSON.'],
|
|
454
|
+
examples: ['zenifra git connections', 'zenifra git connections --json'],
|
|
455
|
+
output: 'ID Provedor Nome Status\nconnection-1 forgejo Forgejo team active',
|
|
456
|
+
jsonOutput: '{"status":"success","data":[{"id":"connection-1","provider_id":"forgejo","display_name":"Forgejo team","status":"active","capabilities":{"repositoryDiscovery":false,"pushDeploy":true,"nativePreviews":false,"versionDeploy":true}}]}',
|
|
457
|
+
notes: ['A conexao precisa ter sido criada por uma pessoa proprietaria no Console. As credenciais nao aparecem na resposta.'],
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
command: 'git repositories',
|
|
461
|
+
usage: 'zenifra git repositories resolve --connection <id> --path <owner/repo> [--json]',
|
|
462
|
+
description: 'Agrupa comandos para resolver repositorios por caminho explicito.',
|
|
463
|
+
examples: ['zenifra git repositories resolve --connection conn-1 --path equipe/aplicacao'],
|
|
464
|
+
output: 'Zenifra CLI - git repositories',
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
command: 'git repositories resolve',
|
|
468
|
+
usage: 'zenifra git repositories resolve --connection <id> --path <owner/repo> [--json]',
|
|
469
|
+
description: 'Valida e resolve o caminho explicito de um repositorio na conexao selecionada.',
|
|
470
|
+
flags: ['--connection <id> ID da conexao Git da organizacao.', '--path <owner/repo> Caminho explicito do repositorio.', '--json Imprime o resultado em JSON.'],
|
|
471
|
+
examples: ['zenifra git repositories resolve --connection conn-1 --path equipe/aplicacao --json'],
|
|
472
|
+
output: 'ID Caminho Branch padrao Privado\nrepo_opaque_1 equipe/aplicacao main sim',
|
|
473
|
+
jsonOutput: '{"status":"success","data":{"id":"repository-id","path":"equipe/aplicacao","default_branch":"main","private":true}}',
|
|
474
|
+
notes: ['Use o ID opaco retornado somente com a mesma conexao.'],
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
command: 'git branches',
|
|
478
|
+
usage: 'zenifra git branches --connection <id> --repository <opaque-id> [--json]',
|
|
479
|
+
description: 'Lista branches de um repositorio resolvido pela conexao informada.',
|
|
480
|
+
flags: ['--connection <id> ID da conexao Git da organizacao.', '--repository <id> ID opaco retornado por git repositories resolve.', '--json Imprime a resposta em JSON.'],
|
|
481
|
+
examples: ['zenifra git branches --connection conn-1 --repository repository-id'],
|
|
482
|
+
output: 'Branch Commit\nmain abc123',
|
|
483
|
+
jsonOutput: '{"status":"success","data":[{"name":"main","commit_sha":"<commit-sha>"}]}',
|
|
484
|
+
},
|
|
403
485
|
{
|
|
404
486
|
command: 'valkey',
|
|
405
487
|
usage: 'zenifra valkey\n zenifra valkey status --project <id> [--json]\n zenifra valkey connection --project <id> [--json]\n zenifra valkey credentials rotate --project <id> [--idempotency-key <key>] [--wait] [--interval <seconds>] [--timeout <seconds>] [--json]\n zenifra valkey credentials status --project <id> --operation <id> [--json]',
|
|
@@ -470,11 +552,61 @@ const HELP_SPECS = [
|
|
|
470
552
|
},
|
|
471
553
|
{
|
|
472
554
|
command: 'project',
|
|
473
|
-
usage: 'zenifra project\n zenifra project info --project <id> [--json]\n zenifra project stop --project <id> [--json]\n zenifra project resume --project <id> [--json]\n zenifra project delete --project <id> --yes [--json]\n zenifra project url --project <id> [--json]\n zenifra project logs --project <id> [--instance <id>] [--json]\n zenifra project metrics --project <id> [--instance <id>] [--json]\n zenifra project metrics capabilities --project <id> [--json]\n zenifra project healthcheck get --project <id> [--json]\n zenifra project healthcheck set --project <id> --path /health [--json]\n zenifra project healthcheck disable --project <id> [--json]\n zenifra project healthcheck failures --project <id> [--page <n>] [--limit <n>] [--json]\n zenifra project network --project <id> [--view <summary|status-codes|routes|user-agents|request-events|source-ips>] [--json]\n zenifra project image set --project <id> --image <image> [--json]\n zenifra project exposure set --project <id> --exposure <public|private> [--json]\n zenifra project envs --project <id> [--json] [--show-values]\n zenifra project env add --project <id> --name <name> --value <value> [--json]\n zenifra project env update --project <id> --name <name> --value <value> [--json]\n zenifra project env remove --project <id> --name <name> [--json]\n zenifra project autoscaling --project <id> [--json]\n zenifra project autoscaling set --project <id> --min <n> --max <n> [--cpu <percent>] [--memory <percent>] [--json]\n zenifra project autoscaling disable --project <id> [--json]\n zenifra project autoscaling events --project <id> [--direction <scale_up|scale_down>] [--from <iso>] [--to <iso>] [--page <n>] [--limit <n>] [--json]\n zenifra project billing usage --project <id> [--from <iso>] [--to <iso>] [--page <n>] [--limit <n>] [--json]\n zenifra project instances --project <id> [--json]\n zenifra project instances set --project <id> --count <n> [--json]',
|
|
555
|
+
usage: 'zenifra project\n zenifra project info --project <id> [--json]\n zenifra project source --project <id> [--json]\n zenifra project source branches --project <id> [--json]\n zenifra project source deploy-settings set --project <id> --mode <manual|branch|tag|release> [--tag-pattern <pattern>] [--include-prereleases <true|false>] [--json]\n zenifra project stop --project <id> [--json]\n zenifra project resume --project <id> [--json]\n zenifra project delete --project <id> --yes [--json]\n zenifra project url --project <id> [--json]\n zenifra project logs --project <id> [--instance <id>] [--json]\n zenifra project metrics --project <id> [--instance <id>] [--json]\n zenifra project metrics capabilities --project <id> [--json]\n zenifra project healthcheck get --project <id> [--json]\n zenifra project healthcheck set --project <id> --path /health [--json]\n zenifra project healthcheck disable --project <id> [--json]\n zenifra project healthcheck failures --project <id> [--page <n>] [--limit <n>] [--json]\n zenifra project network --project <id> [--view <summary|status-codes|routes|user-agents|request-events|source-ips>] [--json]\n zenifra project image set --project <id> --image <image> [--json]\n zenifra project exposure set --project <id> --exposure <public|private> [--json]\n zenifra project envs --project <id> [--json] [--show-values]\n zenifra project env add --project <id> --name <name> --value <value> [--json]\n zenifra project env update --project <id> --name <name> --value <value> [--json]\n zenifra project env remove --project <id> --name <name> [--json]\n zenifra project autoscaling --project <id> [--json]\n zenifra project autoscaling set --project <id> --min <n> --max <n> [--cpu <percent>] [--memory <percent>] [--json]\n zenifra project autoscaling disable --project <id> [--json]\n zenifra project autoscaling events --project <id> [--direction <scale_up|scale_down>] [--from <iso>] [--to <iso>] [--page <n>] [--limit <n>] [--json]\n zenifra project billing usage --project <id> [--from <iso>] [--to <iso>] [--page <n>] [--limit <n>] [--json]\n zenifra project instances --project <id> [--json]\n zenifra project instances set --project <id> --count <n> [--json]',
|
|
474
556
|
description: 'Agrupa comandos operacionais e de introspecao sobre um projeto especifico.',
|
|
475
|
-
examples: ['zenifra project', 'zenifra project info --project proj_1', 'zenifra project env add --project proj_1 --name NODE_ENV --value production'],
|
|
557
|
+
examples: ['zenifra project', 'zenifra project info --project proj_1', 'zenifra project source --project proj_1', 'zenifra project env add --project proj_1 --name NODE_ENV --value production'],
|
|
476
558
|
output: 'Zenifra CLI - project',
|
|
477
|
-
notes: ['Use "zenifra help project <subcomando>" para detalhes de info, stop, resume, delete, url, logs, metrics, capabilities, network, image, github, exposure, autoscaling, billing, envs e instances.'],
|
|
559
|
+
notes: ['Use "zenifra help project <subcomando>" para detalhes de info, stop, resume, delete, url, logs, metrics, capabilities, network, image, github, source, exposure, autoscaling, billing, envs e instances.'],
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
command: 'project source',
|
|
563
|
+
usage: 'zenifra project source --project <id> [--json]\n zenifra project source branches --project <id> [--json]\n zenifra project source deploy-settings set --project <id> --mode <manual|branch|tag|release> [--tag-pattern <pattern>] [--include-prereleases <true|false>] [--json]',
|
|
564
|
+
description: 'Consulta a origem Git generica de um projeto, lista branches ou altera seu modo de deploy.',
|
|
565
|
+
examples: ['zenifra project source --project proj_1', 'zenifra project source branches --project proj_1'],
|
|
566
|
+
output: 'Zenifra CLI - project source',
|
|
567
|
+
notes: ['Use project github para ler ou alterar configuracoes GitHub legadas.'],
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
command: 'project source branches',
|
|
571
|
+
usage: 'zenifra project source branches --project <id> [--json]',
|
|
572
|
+
description: 'Lista branches disponiveis para a origem Git generica atual do projeto.',
|
|
573
|
+
flags: ['--project <id> ID do projeto.', '--json Imprime a resposta em JSON.'],
|
|
574
|
+
examples: ['zenifra project source branches --project proj_1'],
|
|
575
|
+
output: 'Branch Commit\nmain abc123',
|
|
576
|
+
jsonOutput: '{"status":"success","data":[{"name":"main","commit_sha":"<commit-sha>"}]}',
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
command: 'project source deploy-settings',
|
|
580
|
+
usage: 'zenifra project source deploy-settings set --project <id> --mode <manual|branch|tag|release> [--tag-pattern <pattern>] [--include-prereleases <true|false>] [--json]',
|
|
581
|
+
description: 'Agrupa os modos de deploy da origem Git generica.',
|
|
582
|
+
examples: ['zenifra project source deploy-settings set --project proj_1 --mode branch'],
|
|
583
|
+
output: 'Zenifra CLI - project source deploy-settings',
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
command: 'project source deploy-settings set',
|
|
587
|
+
usage: 'zenifra project source deploy-settings set --project <id> --mode <manual|branch|tag|release> [--tag-pattern <pattern>] [--include-prereleases <true|false>] [--json]',
|
|
588
|
+
description: 'Define e confirma o modo de deploy da origem Git Forgejo do projeto.',
|
|
589
|
+
flags: [
|
|
590
|
+
'--project <id> ID do projeto.',
|
|
591
|
+
'--mode <manual|branch|tag|release> Modo de deploy exclusivo.',
|
|
592
|
+
'--tag-pattern <pattern> Obrigatorio para tag e release; aceita * e ?.',
|
|
593
|
+
'--include-prereleases <true|false> Inclui prereleases somente no modo release. Padrao: false.',
|
|
594
|
+
'--json Imprime a configuracao confirmada em JSON.',
|
|
595
|
+
],
|
|
596
|
+
examples: [
|
|
597
|
+
'zenifra project source deploy-settings set --project proj_1 --mode manual',
|
|
598
|
+
'zenifra project source deploy-settings set --project proj_1 --mode branch',
|
|
599
|
+
'zenifra project source deploy-settings set --project proj_1 --mode tag --tag-pattern "v*"',
|
|
600
|
+
'zenifra project source deploy-settings set --project proj_1 --mode release --tag-pattern "v*" --include-prereleases true',
|
|
601
|
+
],
|
|
602
|
+
output: 'Configuracao de deploy da origem Git confirmada: release (v*).',
|
|
603
|
+
jsonOutput: '{"status":"success","data":{"source":{"connection_id":"connection-id","repository_id":"repository-id","branch":"main","auto_deploy":false,"version_deploy":{"enabled":true,"event":"release","tag_pattern":"v*","include_prereleases":true},"provider_id":"forgejo","repository_path":"equipe/aplicacao"},"build":{"runtime":"nodejs","version":"24","start_command":"npm start"},"source_revision":2}}',
|
|
604
|
+
notes: [
|
|
605
|
+
'Tag e release exigem --tag-pattern; prereleases so podem ser habilitadas para release.',
|
|
606
|
+
'O comando preserva identidade, branch e configuracao de build da origem e confirma o resultado com uma nova leitura.',
|
|
607
|
+
'A API substitui source e build juntos e nao oferece precondicao de revisao. Evite alterar a origem ao mesmo tempo por outro cliente.',
|
|
608
|
+
'Para projetos com origem GitHub legada, use "zenifra project github deploy-settings set".',
|
|
609
|
+
],
|
|
478
610
|
},
|
|
479
611
|
{
|
|
480
612
|
command: 'project github',
|
|
@@ -770,7 +902,7 @@ const HELP_SPECS = [
|
|
|
770
902
|
{
|
|
771
903
|
command: 'builds',
|
|
772
904
|
usage: 'zenifra builds --project <id> [--page <n>] [--limit <n>] [--branch <name>] [--status <status>] [--json]',
|
|
773
|
-
description: 'Lista builds
|
|
905
|
+
description: 'Lista builds de um projeto com origem Git.',
|
|
774
906
|
flags: ['--project <id> ID do projeto.', '--page <n> Pagina.', '--limit <n> Itens por pagina.', '--branch <name> Filtra branch.', '--status <status> Filtra status.', '--json Imprime a resposta em JSON.'],
|
|
775
907
|
examples: ['zenifra builds --project 507f1f77bcf86cd799439012 --status success'],
|
|
776
908
|
output: 'Build Status Branch Commit\nbuild_123 success main abc123',
|
|
@@ -779,7 +911,7 @@ const HELP_SPECS = [
|
|
|
779
911
|
{
|
|
780
912
|
command: 'builds logs',
|
|
781
913
|
usage: 'zenifra builds logs --project <id> --build <id> [--cursor <n>] [--limit <n>] [--follow] [--interval <seconds>] [--timeout <seconds>] [--json]',
|
|
782
|
-
description: '
|
|
914
|
+
description: 'Le os logs de um build Git existente e opcionalmente acompanha novos eventos em tempo real.',
|
|
783
915
|
flags: [
|
|
784
916
|
'--project <id> ID do projeto.',
|
|
785
917
|
'--build <id> ID do build.',
|
|
@@ -797,7 +929,7 @@ const HELP_SPECS = [
|
|
|
797
929
|
{
|
|
798
930
|
command: 'deployments',
|
|
799
931
|
usage: 'zenifra deployments --project <id> [--page <n>] [--limit <n>] [--branch <name>] [--status <status>] [--json]',
|
|
800
|
-
description: 'Alias para listar builds
|
|
932
|
+
description: 'Alias para listar builds e deployments Git de um projeto.',
|
|
801
933
|
flags: ['--project <id> ID do projeto.', '--page <n> Pagina.', '--limit <n> Itens por pagina.', '--branch <name> Filtra branch.', '--status <status> Filtra status.', '--json Imprime a resposta em JSON.'],
|
|
802
934
|
examples: ['zenifra deployments --project 507f1f77bcf86cd799439012'],
|
|
803
935
|
output: 'Build Status Branch Commit\nbuild_123 success main abc123',
|
|
@@ -806,7 +938,7 @@ const HELP_SPECS = [
|
|
|
806
938
|
{
|
|
807
939
|
command: 'deploy',
|
|
808
940
|
usage: 'zenifra deploy --project <id> [--branch <name>] [--commit-sha <sha>] [--json]',
|
|
809
|
-
description: 'Dispara um build/deploy
|
|
941
|
+
description: 'Dispara um build/deploy Git para o projeto e retorna o build_id para acompanhamento posterior.',
|
|
810
942
|
flags: ['--project <id> ID do projeto.', '--branch <name> Branch a publicar.', '--commit-sha <sha> Commit especifico.', '--json Imprime a resposta em JSON.'],
|
|
811
943
|
examples: [
|
|
812
944
|
'zenifra deploy --project 507f1f77bcf86cd799439012 --branch main',
|
|
@@ -900,7 +1032,7 @@ function removedProjectsCreateMessage() {
|
|
|
900
1032
|
}
|
|
901
1033
|
|
|
902
1034
|
function isNamespaceCommand(command) {
|
|
903
|
-
return ['auth', 'profile', 'project', 'org', 'valkey'].includes(command);
|
|
1035
|
+
return ['auth', 'profile', 'project', 'org', 'valkey', 'git'].includes(command);
|
|
904
1036
|
}
|
|
905
1037
|
|
|
906
1038
|
function parseArgs(argv) {
|
|
@@ -1692,30 +1824,393 @@ async function request(session, flags, method, path, {
|
|
|
1692
1824
|
|
|
1693
1825
|
if (!response.ok) {
|
|
1694
1826
|
const message = payload?.message || payload?.error || `Zenifra API retornou HTTP ${response.status}`;
|
|
1827
|
+
const httpError = (errorMessage) => new CliError(errorMessage, 1, response.status);
|
|
1695
1828
|
if (response.status === 401) {
|
|
1696
1829
|
if (credential?.type === 'api_key') {
|
|
1697
|
-
throw
|
|
1830
|
+
throw httpError(`${message}. Verifique se a API key esta ativa e se o IP atual esta permitido.`);
|
|
1698
1831
|
}
|
|
1699
|
-
throw
|
|
1832
|
+
throw httpError(`${message}. Rode "zenifra auth login" para renovar sua sessao.`);
|
|
1700
1833
|
}
|
|
1701
1834
|
if (response.status === 403) {
|
|
1702
|
-
throw
|
|
1835
|
+
throw httpError(message);
|
|
1703
1836
|
}
|
|
1704
1837
|
if (response.status === 429 && Number.isFinite(Number(payload?.retry_after_seconds))) {
|
|
1705
|
-
throw
|
|
1838
|
+
throw httpError(`${message} Tente novamente em ${Number(payload.retry_after_seconds)} segundo(s).`);
|
|
1706
1839
|
}
|
|
1707
1840
|
if (response.status === 402 && path.includes('/metrics') && /support metrics/i.test(String(message))) {
|
|
1708
|
-
throw
|
|
1841
|
+
throw httpError('Este projeto nao possui acesso a metricas.');
|
|
1709
1842
|
}
|
|
1710
1843
|
if (String(message).includes('missing x-organization-id')) {
|
|
1711
|
-
throw
|
|
1844
|
+
throw httpError('Organizacao nao selecionada. Rode "zenifra org set" ou use --org <id>.');
|
|
1712
1845
|
}
|
|
1713
|
-
throw
|
|
1846
|
+
throw httpError(message);
|
|
1714
1847
|
}
|
|
1715
1848
|
|
|
1716
1849
|
return payload;
|
|
1717
1850
|
}
|
|
1718
1851
|
|
|
1852
|
+
function hasValidGitProvidersApiV1(payload) {
|
|
1853
|
+
if (payload?.status !== 'success' || !isRecord(payload.data) || payload.data.api_version !== 1 || !Array.isArray(payload.data.providers)) {
|
|
1854
|
+
return false;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
return payload.data.providers.every((provider) => (
|
|
1858
|
+
isRecord(provider)
|
|
1859
|
+
&& typeof provider.id === 'string'
|
|
1860
|
+
&& /^[a-z0-9][a-z0-9_-]{0,63}$/.test(provider.id)
|
|
1861
|
+
&& typeof provider.available === 'boolean'
|
|
1862
|
+
&& isRecord(provider.capabilities)
|
|
1863
|
+
&& typeof provider.capabilities.repositoryDiscovery === 'boolean'
|
|
1864
|
+
&& typeof provider.capabilities.pushDeploy === 'boolean'
|
|
1865
|
+
&& typeof provider.capabilities.nativePreviews === 'boolean'
|
|
1866
|
+
));
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
async function supportsNeutralGitRoutes(session, flags, orgId) {
|
|
1870
|
+
let payload;
|
|
1871
|
+
try {
|
|
1872
|
+
payload = await request(session, flags, 'GET', '/git/providers', { orgId });
|
|
1873
|
+
} catch (error) {
|
|
1874
|
+
if ([404, 405, 501].includes(error?.httpStatus)) return false;
|
|
1875
|
+
throw error;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
if (!hasValidGitProvidersApiV1(payload)) {
|
|
1879
|
+
throw new CliError('A API retornou uma resposta invalida para as capacidades de origem Git.');
|
|
1880
|
+
}
|
|
1881
|
+
return true;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
function hasLegacyGithubSource(project) {
|
|
1885
|
+
if (!isRecord(project)) return false;
|
|
1886
|
+
// Public Git source projections must never be rewritten as legacy GitHub input.
|
|
1887
|
+
// preview.source is separate preview metadata and does not identify the primary project source.
|
|
1888
|
+
const genericSourceFields = ['source', 'build', 'git_source', 'git_build'];
|
|
1889
|
+
if (genericSourceFields.some((field) => project[field] !== undefined || project.config?.[field] !== undefined)) {
|
|
1890
|
+
return false;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
const github = project.github || project.config?.github;
|
|
1894
|
+
return isRecord(github)
|
|
1895
|
+
&& typeof github.repository_owner === 'string'
|
|
1896
|
+
&& github.repository_owner.trim().length > 0
|
|
1897
|
+
&& typeof github.repository_name === 'string'
|
|
1898
|
+
&& github.repository_name.trim().length > 0;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
async function resolveGitProjectApiPath(session, flags, projectId, orgId) {
|
|
1902
|
+
if (await supportsNeutralGitRoutes(session, flags, orgId)) {
|
|
1903
|
+
return `/project/${projectId}`;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
const project = await getProject(session, flags, projectId, orgId);
|
|
1907
|
+
if (!hasLegacyGithubSource(project)) {
|
|
1908
|
+
throw new CliError('Esta API antiga so permite builds e deploys de projetos com uma origem GitHub legada compativel.');
|
|
1909
|
+
}
|
|
1910
|
+
return `/project/${projectId}/github`;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
function requiredGitOpaqueId(flags, name, commandKey) {
|
|
1914
|
+
const value = flags[name];
|
|
1915
|
+
if (typeof value !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/.test(value)) {
|
|
1916
|
+
printCommandHelpAndFail(commandKey);
|
|
1917
|
+
return null;
|
|
1918
|
+
}
|
|
1919
|
+
return value;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
function requiredGitRepositoryPath(flags, commandKey) {
|
|
1923
|
+
const value = flags.path;
|
|
1924
|
+
const segments = typeof value === 'string' ? value.split('/') : [];
|
|
1925
|
+
if (typeof value !== 'string'
|
|
1926
|
+
|| value.length < 3
|
|
1927
|
+
|| value.length > 1024
|
|
1928
|
+
|| segments.length < 2
|
|
1929
|
+
|| segments.some((segment) => segment.length === 0)
|
|
1930
|
+
|| /[\u0000-\u001f\u007f-\u009f\\?#%]/u.test(value)) {
|
|
1931
|
+
printCommandHelpAndFail(commandKey);
|
|
1932
|
+
return null;
|
|
1933
|
+
}
|
|
1934
|
+
return value;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
function yesNo(value) {
|
|
1938
|
+
if (value === true) return 'sim';
|
|
1939
|
+
if (value === false) return 'nao';
|
|
1940
|
+
return 'indisponivel';
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
async function handleGitProviders(session, flags) {
|
|
1944
|
+
const orgId = await resolveOrgId(session, flags);
|
|
1945
|
+
const payload = await request(session, flags, 'GET', '/git/providers', { orgId });
|
|
1946
|
+
if (!hasValidGitProvidersApiV1(payload)) {
|
|
1947
|
+
throw new CliError('A API retornou uma resposta invalida para o catalogo de provedores Git.');
|
|
1948
|
+
}
|
|
1949
|
+
if (flags.json) return printJson(payload);
|
|
1950
|
+
|
|
1951
|
+
printTable(payload.data.providers, [
|
|
1952
|
+
{ label: 'Provedor', value: (provider) => provider.id },
|
|
1953
|
+
{ label: 'Disponivel', value: (provider) => yesNo(provider.available) },
|
|
1954
|
+
{ label: 'Descoberta', value: (provider) => yesNo(provider.capabilities.repositoryDiscovery) },
|
|
1955
|
+
{ label: 'Deploy por branch', value: (provider) => yesNo(provider.capabilities.pushDeploy) },
|
|
1956
|
+
{ label: 'Deploy por versao', value: (provider) => yesNo(provider.capabilities.versionDeploy) },
|
|
1957
|
+
]);
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
async function handleGitRuntimes(session, flags) {
|
|
1961
|
+
const orgId = await resolveOrgId(session, flags);
|
|
1962
|
+
const payload = await request(session, flags, 'GET', '/git/runtime-catalog', { orgId });
|
|
1963
|
+
const runtimes = unwrapData(payload);
|
|
1964
|
+
if (!Array.isArray(runtimes)) {
|
|
1965
|
+
throw new CliError('A API retornou uma resposta invalida para o catalogo de runtimes Git.');
|
|
1966
|
+
}
|
|
1967
|
+
if (flags.json) return printJson(payload);
|
|
1968
|
+
|
|
1969
|
+
printTable(runtimes, [
|
|
1970
|
+
{ label: 'Runtime', value: (runtime) => runtime.label || runtime.id || '-' },
|
|
1971
|
+
{ label: 'Versao padrao', value: (runtime) => runtime.default_version || '-' },
|
|
1972
|
+
{ label: 'Versoes', value: (runtime) => Array.isArray(runtime.versions) ? runtime.versions.map((version) => version.id).join(', ') : '-' },
|
|
1973
|
+
]);
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
async function handleGitConnections(session, flags) {
|
|
1977
|
+
const orgId = await resolveOrgId(session, flags);
|
|
1978
|
+
const payload = await request(session, flags, 'GET', '/git/connections', { orgId });
|
|
1979
|
+
const connections = unwrapData(payload);
|
|
1980
|
+
if (!Array.isArray(connections)) {
|
|
1981
|
+
throw new CliError('A API retornou uma resposta invalida para as conexoes Git.');
|
|
1982
|
+
}
|
|
1983
|
+
if (flags.json) return printJson(payload);
|
|
1984
|
+
|
|
1985
|
+
printTable(connections, [
|
|
1986
|
+
{ label: 'ID', value: (connection) => connection.id || '-' },
|
|
1987
|
+
{ label: 'Provedor', value: (connection) => connection.provider_id || '-' },
|
|
1988
|
+
{ label: 'Nome', value: (connection) => connection.display_name || '-' },
|
|
1989
|
+
{ label: 'Status', value: (connection) => connection.status || '-' },
|
|
1990
|
+
]);
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
async function handleGitRepositoryResolve(session, flags) {
|
|
1994
|
+
const connectionId = requiredGitOpaqueId(flags, 'connection', 'git repositories resolve');
|
|
1995
|
+
const repositoryPath = requiredGitRepositoryPath(flags, 'git repositories resolve');
|
|
1996
|
+
if (!connectionId || !repositoryPath) return;
|
|
1997
|
+
|
|
1998
|
+
const orgId = await resolveOrgId(session, flags);
|
|
1999
|
+
const endpoint = '/git/connections/' + encodeURIComponent(connectionId) + '/repositories/resolve';
|
|
2000
|
+
const payload = await request(session, flags, 'POST', endpoint, {
|
|
2001
|
+
orgId,
|
|
2002
|
+
body: { path: repositoryPath },
|
|
2003
|
+
});
|
|
2004
|
+
if (flags.json) return printJson(payload);
|
|
2005
|
+
|
|
2006
|
+
const repository = unwrapData(payload);
|
|
2007
|
+
printTable([repository], [
|
|
2008
|
+
{ label: 'ID', value: (entry) => entry.id || '-' },
|
|
2009
|
+
{ label: 'Caminho', value: (entry) => entry.path || '-' },
|
|
2010
|
+
{ label: 'Branch padrao', value: (entry) => entry.default_branch || '-' },
|
|
2011
|
+
{ label: 'Privado', value: (entry) => yesNo(entry.private) },
|
|
2012
|
+
]);
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
async function handleGitBranches(session, flags) {
|
|
2016
|
+
const connectionId = requiredGitOpaqueId(flags, 'connection', 'git branches');
|
|
2017
|
+
const repositoryId = requiredGitOpaqueId(flags, 'repository', 'git branches');
|
|
2018
|
+
if (!connectionId || !repositoryId) return;
|
|
2019
|
+
|
|
2020
|
+
const orgId = await resolveOrgId(session, flags);
|
|
2021
|
+
const endpoint = '/git/connections/' + encodeURIComponent(connectionId)
|
|
2022
|
+
+ '/repositories/' + encodeURIComponent(repositoryId) + '/branches';
|
|
2023
|
+
const payload = await request(session, flags, 'GET', endpoint, { orgId });
|
|
2024
|
+
const branches = unwrapData(payload);
|
|
2025
|
+
if (!Array.isArray(branches)) {
|
|
2026
|
+
throw new CliError('A API retornou uma resposta invalida para as branches Git.');
|
|
2027
|
+
}
|
|
2028
|
+
if (flags.json) return printJson(payload);
|
|
2029
|
+
|
|
2030
|
+
printTable(branches, [
|
|
2031
|
+
{ label: 'Branch', value: (branch) => branch.name || '-' },
|
|
2032
|
+
{ label: 'Commit', value: (branch) => branch.commit_sha || '-' },
|
|
2033
|
+
]);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
function printGitProjectSource(data) {
|
|
2037
|
+
if (!isRecord(data?.source)) {
|
|
2038
|
+
process.stdout.write('Este projeto nao possui uma origem Git generica configurada.\n');
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
const source = data.source;
|
|
2043
|
+
const build = isRecord(data.build) ? data.build : {};
|
|
2044
|
+
const mode = source.provider_id === 'github' ? null : gitSourceDeploymentMode(source);
|
|
2045
|
+
const rows = [
|
|
2046
|
+
{ field: 'Provedor', value: source.provider_id || '-' },
|
|
2047
|
+
{ field: 'Repositorio', value: source.repository_path || source.repository_id || '-' },
|
|
2048
|
+
{ field: 'Branch', value: source.branch || '-' },
|
|
2049
|
+
{ field: 'Runtime', value: build.runtime ? build.runtime + '@' + (build.version || '-') : '-' },
|
|
2050
|
+
{ field: 'Modo de deploy', value: mode || (source.provider_id === 'github' ? 'consulte project github' : 'indisponivel') },
|
|
2051
|
+
];
|
|
2052
|
+
if (mode === 'tag' || mode === 'release') {
|
|
2053
|
+
rows.push(
|
|
2054
|
+
{ field: 'Padrao de tags', value: source.version_deploy?.tag_pattern || '-' },
|
|
2055
|
+
{ field: 'Incluir prereleases', value: source.version_deploy?.include_prereleases === true ? 'sim' : 'nao' },
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
printTable(rows, [
|
|
2059
|
+
{ label: 'Campo', value: (row) => row.field },
|
|
2060
|
+
{ label: 'Valor', value: (row) => row.value },
|
|
2061
|
+
]);
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
async function handleProjectSource(session, flags) {
|
|
2065
|
+
const projectId = requireProjectId(flags, 'project source');
|
|
2066
|
+
if (!projectId) return;
|
|
2067
|
+
const orgId = await resolveOrgId(session, flags);
|
|
2068
|
+
const payload = await request(session, flags, 'GET', '/project/' + encodeURIComponent(projectId) + '/source', { orgId });
|
|
2069
|
+
const source = unwrapData(payload);
|
|
2070
|
+
if (flags.json) return printJson(payload);
|
|
2071
|
+
printGitProjectSource(source);
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
async function handleProjectSourceBranches(session, flags) {
|
|
2075
|
+
const projectId = requireProjectId(flags, 'project source branches');
|
|
2076
|
+
if (!projectId) return;
|
|
2077
|
+
const orgId = await resolveOrgId(session, flags);
|
|
2078
|
+
const endpoint = '/project/' + encodeURIComponent(projectId) + '/source/branches';
|
|
2079
|
+
const payload = await request(session, flags, 'GET', endpoint, { orgId });
|
|
2080
|
+
const branches = unwrapData(payload);
|
|
2081
|
+
if (!Array.isArray(branches)) {
|
|
2082
|
+
throw new CliError('A API retornou uma resposta invalida para as branches da origem Git.');
|
|
2083
|
+
}
|
|
2084
|
+
if (flags.json) return printJson(payload);
|
|
2085
|
+
printTable(branches, [
|
|
2086
|
+
{ label: 'Branch', value: (branch) => branch.name || '-' },
|
|
2087
|
+
{ label: 'Commit', value: (branch) => branch.commit_sha || '-' },
|
|
2088
|
+
]);
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
function projectGitSourceInput(source) {
|
|
2092
|
+
if (!isRecord(source)
|
|
2093
|
+
|| typeof source.connection_id !== 'string'
|
|
2094
|
+
|| typeof source.repository_id !== 'string'
|
|
2095
|
+
|| typeof source.branch !== 'string'
|
|
2096
|
+
|| typeof source.auto_deploy !== 'boolean') {
|
|
2097
|
+
throw new CliError('A API retornou uma origem Git incompleta; nenhuma alteracao foi enviada.');
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
const input = {
|
|
2101
|
+
connection_id: source.connection_id,
|
|
2102
|
+
repository_id: source.repository_id,
|
|
2103
|
+
branch: source.branch,
|
|
2104
|
+
auto_deploy: source.auto_deploy,
|
|
2105
|
+
};
|
|
2106
|
+
if (source.version_deploy === undefined || source.version_deploy === null) return input;
|
|
2107
|
+
if (!isRecord(source.version_deploy)) {
|
|
2108
|
+
throw new CliError('A API retornou configuracoes de deploy Git invalidas; nenhuma alteracao foi enviada.');
|
|
2109
|
+
}
|
|
2110
|
+
const versionDeploy = {};
|
|
2111
|
+
for (const field of GITHUB_PUBLIC_VERSION_DEPLOY_FIELDS) {
|
|
2112
|
+
if (Object.hasOwn(source.version_deploy, field)) versionDeploy[field] = source.version_deploy[field];
|
|
2113
|
+
}
|
|
2114
|
+
input.version_deploy = versionDeploy;
|
|
2115
|
+
return input;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
function projectGitBuildSettingsInput(build) {
|
|
2119
|
+
if (!isRecord(build)) {
|
|
2120
|
+
throw new CliError('A API retornou configuracoes de build incompletas; nenhuma alteracao foi enviada.');
|
|
2121
|
+
}
|
|
2122
|
+
const input = {};
|
|
2123
|
+
for (const field of GIT_BUILD_SETTINGS_FIELDS) {
|
|
2124
|
+
if (Object.hasOwn(build, field)) input[field] = build[field];
|
|
2125
|
+
}
|
|
2126
|
+
return input;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
function gitSourceDeploymentMode(source) {
|
|
2130
|
+
const versionDeployEnabled = source?.version_deploy?.enabled === true;
|
|
2131
|
+
if (source?.auto_deploy === true && versionDeployEnabled) return null;
|
|
2132
|
+
if (source?.auto_deploy === true) return 'branch';
|
|
2133
|
+
if (!versionDeployEnabled) return 'manual';
|
|
2134
|
+
if (!ALLOWED_GITHUB_VERSION_DEPLOY_EVENTS.has(source.version_deploy.event)) return null;
|
|
2135
|
+
return source.version_deploy.event;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
function projectGitSourceMatches(data, expectedSource, expectedBuild, desired) {
|
|
2139
|
+
if (!isRecord(data) || !isRecord(data.source) || !isRecord(data.build)) return false;
|
|
2140
|
+
const source = data.source;
|
|
2141
|
+
if (source.provider_id !== 'forgejo'
|
|
2142
|
+
|| source.connection_id !== expectedSource.connection_id
|
|
2143
|
+
|| source.repository_id !== expectedSource.repository_id
|
|
2144
|
+
|| source.branch !== expectedSource.branch
|
|
2145
|
+
|| gitSourceDeploymentMode(source) !== desired.mode) return false;
|
|
2146
|
+
|
|
2147
|
+
if (desired.mode === 'tag' || desired.mode === 'release') {
|
|
2148
|
+
if (source.version_deploy?.tag_pattern !== desired.body.version_deploy.tag_pattern
|
|
2149
|
+
|| (source.version_deploy?.include_prereleases === true) !== desired.includePrereleases) return false;
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
return Object.entries(expectedBuild).every(([field, value]) => data.build[field] === value);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
async function handleProjectSourceDeploySettingsSet(session, flags) {
|
|
2156
|
+
const desired = parseGithubDeploySettings(flags);
|
|
2157
|
+
const projectId = requireProjectId(flags, 'project source deploy-settings set');
|
|
2158
|
+
if (!projectId) return;
|
|
2159
|
+
const orgId = await resolveOrgId(session, flags);
|
|
2160
|
+
const endpoint = '/project/' + encodeURIComponent(projectId) + '/source';
|
|
2161
|
+
let current;
|
|
2162
|
+
try {
|
|
2163
|
+
current = unwrapData(await request(session, flags, 'GET', endpoint, { orgId }));
|
|
2164
|
+
} catch (error) {
|
|
2165
|
+
if (error?.httpStatus === 404) {
|
|
2166
|
+
throw new CliError('A origem Git generica nao esta disponivel para este projeto ou API. Para projetos GitHub legados, use "zenifra project github deploy-settings set".', 1, 404);
|
|
2167
|
+
}
|
|
2168
|
+
throw error;
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
if (!isRecord(current?.source) || !isRecord(current?.build)) {
|
|
2172
|
+
throw new CliError('Este projeto nao tem uma origem Git generica configurada. Configure-a antes de alterar o modo de deploy.');
|
|
2173
|
+
}
|
|
2174
|
+
if (current.source.provider_id === 'github') {
|
|
2175
|
+
throw new CliError('Esta origem GitHub nao pode ser alterada por este comando. Consulte e altere com "zenifra project github deploy-settings set".');
|
|
2176
|
+
}
|
|
2177
|
+
if (current.source.provider_id !== 'forgejo') {
|
|
2178
|
+
throw new CliError('O provedor da origem nao e suportado por este comando; nenhuma alteracao foi enviada.');
|
|
2179
|
+
}
|
|
2180
|
+
if (desired.mode === 'branch' && current.capabilities?.pushDeploy !== true) {
|
|
2181
|
+
throw new CliError('A origem Git nao oferece deploy automatico por branch.');
|
|
2182
|
+
}
|
|
2183
|
+
if ((desired.mode === 'tag' || desired.mode === 'release') && current.capabilities?.versionDeploy !== true) {
|
|
2184
|
+
throw new CliError('A origem Git nao oferece deploy automatico por tag ou release.');
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
const source = projectGitSourceInput(current.source);
|
|
2188
|
+
const build = projectGitBuildSettingsInput(current.build);
|
|
2189
|
+
source.auto_deploy = desired.body.auto_deploy;
|
|
2190
|
+
delete source.version_deploy;
|
|
2191
|
+
if (desired.mode === 'tag' || desired.mode === 'release') {
|
|
2192
|
+
source.version_deploy = desired.body.version_deploy;
|
|
2193
|
+
}
|
|
2194
|
+
const expectedSource = {
|
|
2195
|
+
connection_id: source.connection_id,
|
|
2196
|
+
repository_id: source.repository_id,
|
|
2197
|
+
branch: source.branch,
|
|
2198
|
+
};
|
|
2199
|
+
|
|
2200
|
+
await request(session, flags, 'PUT', endpoint, {
|
|
2201
|
+
orgId,
|
|
2202
|
+
body: { source, build },
|
|
2203
|
+
});
|
|
2204
|
+
const confirmed = unwrapData(await request(session, flags, 'GET', endpoint, { orgId }));
|
|
2205
|
+
if (!projectGitSourceMatches(confirmed, expectedSource, build, desired)) {
|
|
2206
|
+
throw new CliError('A configuracao solicitada nao foi confirmada pela API. Consulte "zenifra project source".');
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
if (flags.json) return printJson({ status: 'success', data: confirmed });
|
|
2210
|
+
process.stdout.write('Configuracao de deploy da origem Git confirmada: ' + desired.mode
|
|
2211
|
+
+ ((desired.mode === 'tag' || desired.mode === 'release') ? ' (' + desired.body.version_deploy.tag_pattern + ')' : '') + '.\n');
|
|
2212
|
+
}
|
|
2213
|
+
|
|
1719
2214
|
function unwrapData(payload) {
|
|
1720
2215
|
return payload?.data ?? payload;
|
|
1721
2216
|
}
|
|
@@ -3067,6 +3562,17 @@ function validateCreateInput({ plan, paymentMode, config, valkeyCatalog }) {
|
|
|
3067
3562
|
throw new CliError(`type_project invalido: "${config?.type_project}". Valores aceitos: ${formatAllowedValues(ALLOWED_TYPE_PROJECT_VALUES)}. Docs: ${DOCS_CREATE_HTTP_URL}`);
|
|
3068
3563
|
}
|
|
3069
3564
|
|
|
3565
|
+
const hasGenericGitConfig = config?.source !== undefined || config?.build !== undefined;
|
|
3566
|
+
if (hasGenericGitConfig && (!isRecord(config.source) || !isRecord(config.build))) {
|
|
3567
|
+
throw new CliError('config.source e config.build devem ser informados juntos para projetos com origem Git.');
|
|
3568
|
+
}
|
|
3569
|
+
if (hasGenericGitConfig && nextTypeProject !== 'http') {
|
|
3570
|
+
throw new CliError('config.source e config.build so podem ser usados em projetos HTTP.');
|
|
3571
|
+
}
|
|
3572
|
+
if (hasGenericGitConfig && config.github !== undefined) {
|
|
3573
|
+
throw new CliError('Use config.github ou config.source/config.build, sem misturar os formatos de origem Git.');
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3070
3576
|
const nextConfig = { ...config, type_project: nextTypeProject };
|
|
3071
3577
|
if (nextTypeProject === 'valkey') {
|
|
3072
3578
|
return {
|
|
@@ -3765,6 +4271,12 @@ async function handleProjectCreate(session, flags) {
|
|
|
3765
4271
|
: undefined;
|
|
3766
4272
|
const validated = validateCreateInput({ plan, paymentMode, config, valkeyCatalog });
|
|
3767
4273
|
|
|
4274
|
+
if (validated.config.source !== undefined || validated.config.build !== undefined) {
|
|
4275
|
+
if (!await supportsNeutralGitRoutes(session, flags, orgId)) {
|
|
4276
|
+
throw new CliError('Esta API nao oferece suporte a criacao de projetos com origem Git generica. Atualize a API ou use a configuracao GitHub legada compativel.');
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
|
|
3768
4280
|
const payload = await request(session, flags, 'POST', '/project', {
|
|
3769
4281
|
orgId,
|
|
3770
4282
|
headers: idempotencyKey === undefined ? {} : { 'Idempotency-Key': idempotencyKey },
|
|
@@ -4742,8 +5254,9 @@ async function handleDeployments(session, flags) {
|
|
|
4742
5254
|
return
|
|
4743
5255
|
}
|
|
4744
5256
|
const orgId = await resolveOrgId(session, flags);
|
|
5257
|
+
const projectApiPath = await resolveGitProjectApiPath(session, flags, projectId, orgId);
|
|
4745
5258
|
const query = buildQuery(flags, ['page', 'limit', 'branch', 'status']);
|
|
4746
|
-
const data = unwrapData(await request(session, flags, 'GET',
|
|
5259
|
+
const data = unwrapData(await request(session, flags, 'GET', `${projectApiPath}/builds${query}`, { orgId }));
|
|
4747
5260
|
const builds = asArray(data);
|
|
4748
5261
|
|
|
4749
5262
|
if (flags.json) return printJson(data);
|
|
@@ -4764,11 +5277,12 @@ async function handleDeploy(session, flags) {
|
|
|
4764
5277
|
return;
|
|
4765
5278
|
}
|
|
4766
5279
|
const orgId = await resolveOrgId(session, flags);
|
|
5280
|
+
const projectApiPath = await resolveGitProjectApiPath(session, flags, projectId, orgId);
|
|
4767
5281
|
const body = {};
|
|
4768
5282
|
if (flags.branch) body.branch = String(flags.branch);
|
|
4769
5283
|
if (flags.commitSha) body.commit_sha = String(flags.commitSha);
|
|
4770
5284
|
|
|
4771
|
-
const payload = await request(session, flags, 'POST',
|
|
5285
|
+
const payload = await request(session, flags, 'POST', `${projectApiPath}/deploy`, {
|
|
4772
5286
|
orgId,
|
|
4773
5287
|
body,
|
|
4774
5288
|
});
|
|
@@ -4778,9 +5292,9 @@ async function handleDeploy(session, flags) {
|
|
|
4778
5292
|
process.stdout.write(`Deploy iniciado${buildId ? `: ${buildId}` : '.'}\n`);
|
|
4779
5293
|
}
|
|
4780
5294
|
|
|
4781
|
-
async function getBuildLogs(session, flags,
|
|
5295
|
+
async function getBuildLogs(session, flags, projectApiPath, buildId, orgId, { cursor = 0, limit = 200 } = {}) {
|
|
4782
5296
|
const query = buildQuery({ cursor, limit }, ['cursor', 'limit'])
|
|
4783
|
-
return unwrapData(await request(session, flags, 'GET',
|
|
5297
|
+
return unwrapData(await request(session, flags, 'GET', `${projectApiPath}/builds/${buildId}/logs${query}`, { orgId }))
|
|
4784
5298
|
}
|
|
4785
5299
|
|
|
4786
5300
|
function parseIntegerOption(value, flagName, { defaultValue, min, max }) {
|
|
@@ -4831,7 +5345,7 @@ function printBuildLogEventsJson(logs) {
|
|
|
4831
5345
|
}
|
|
4832
5346
|
}
|
|
4833
5347
|
|
|
4834
|
-
async function followBuildLogs(session, flags,
|
|
5348
|
+
async function followBuildLogs(session, flags, projectApiPath, buildId, orgId, {
|
|
4835
5349
|
initialCursor = 0,
|
|
4836
5350
|
limit = 200,
|
|
4837
5351
|
intervalSeconds = 5,
|
|
@@ -4844,7 +5358,7 @@ async function followBuildLogs(session, flags, projectId, buildId, orgId, {
|
|
|
4844
5358
|
const startedAt = Date.now()
|
|
4845
5359
|
|
|
4846
5360
|
while (true) {
|
|
4847
|
-
const result = await getBuildLogs(session, flags,
|
|
5361
|
+
const result = await getBuildLogs(session, flags, projectApiPath, buildId, orgId, {
|
|
4848
5362
|
cursor,
|
|
4849
5363
|
limit: pollLimit,
|
|
4850
5364
|
})
|
|
@@ -4885,20 +5399,27 @@ async function handleBuildLogs(session, flags) {
|
|
|
4885
5399
|
return
|
|
4886
5400
|
}
|
|
4887
5401
|
|
|
4888
|
-
const orgId = await resolveOrgId(session, flags);
|
|
4889
5402
|
const cursor = parseIntegerOption(flags.cursor, '--cursor', { defaultValue: 0, min: 0 })
|
|
4890
5403
|
const limit = parseIntegerOption(flags.limit, '--limit', { defaultValue: 200, min: 1, max: 500 })
|
|
5404
|
+
const intervalSeconds = flags.follow
|
|
5405
|
+
? parseSecondsOption(flags.interval, '--interval', { defaultValue: 5, min: 0.1 })
|
|
5406
|
+
: undefined;
|
|
5407
|
+
const timeoutSeconds = flags.follow
|
|
5408
|
+
? parseSecondsOption(flags.timeout, '--timeout', { defaultValue: 900, min: 1 })
|
|
5409
|
+
: undefined;
|
|
5410
|
+
const orgId = await resolveOrgId(session, flags);
|
|
5411
|
+
const projectApiPath = await resolveGitProjectApiPath(session, flags, projectId, orgId);
|
|
4891
5412
|
|
|
4892
5413
|
if (flags.follow) {
|
|
4893
|
-
return followBuildLogs(session, flags,
|
|
5414
|
+
return followBuildLogs(session, flags, projectApiPath, buildId, orgId, {
|
|
4894
5415
|
initialCursor: cursor,
|
|
4895
5416
|
limit,
|
|
4896
|
-
intervalSeconds
|
|
4897
|
-
timeoutSeconds
|
|
5417
|
+
intervalSeconds,
|
|
5418
|
+
timeoutSeconds,
|
|
4898
5419
|
})
|
|
4899
5420
|
}
|
|
4900
5421
|
|
|
4901
|
-
const result = await getBuildLogs(session, flags,
|
|
5422
|
+
const result = await getBuildLogs(session, flags, projectApiPath, buildId, orgId, { cursor, limit })
|
|
4902
5423
|
if (flags.json) return printJson(result)
|
|
4903
5424
|
printBuildLogs(result?.logs)
|
|
4904
5425
|
}
|
|
@@ -4911,12 +5432,15 @@ async function handleDeployWatch(session, flags) {
|
|
|
4911
5432
|
return;
|
|
4912
5433
|
}
|
|
4913
5434
|
|
|
5435
|
+
const intervalSeconds = parseSecondsOption(flags.interval, '--interval', { defaultValue: 5, min: 0.1 });
|
|
5436
|
+
const timeoutSeconds = parseSecondsOption(flags.timeout, '--timeout', { defaultValue: 900, min: 1 });
|
|
4914
5437
|
const orgId = await resolveOrgId(session, flags);
|
|
4915
|
-
|
|
5438
|
+
const projectApiPath = await resolveGitProjectApiPath(session, flags, projectId, orgId);
|
|
5439
|
+
return followBuildLogs(session, flags, projectApiPath, buildId, orgId, {
|
|
4916
5440
|
initialCursor: 0,
|
|
4917
5441
|
limit: 200,
|
|
4918
|
-
intervalSeconds
|
|
4919
|
-
timeoutSeconds
|
|
5442
|
+
intervalSeconds,
|
|
5443
|
+
timeoutSeconds,
|
|
4920
5444
|
})
|
|
4921
5445
|
}
|
|
4922
5446
|
|
|
@@ -4962,6 +5486,15 @@ async function main() {
|
|
|
4962
5486
|
if (command === 'login') return await handleLogin(session, flags);
|
|
4963
5487
|
if (command === 'logout') return handleLogout(session, flags);
|
|
4964
5488
|
if (command === 'plans') return handlePlans(session, flags);
|
|
5489
|
+
if (command === 'git' && subcommand === 'providers') return handleGitProviders(session, flags);
|
|
5490
|
+
if (command === 'git' && subcommand === 'runtimes') return handleGitRuntimes(session, flags);
|
|
5491
|
+
if (command === 'git' && subcommand === 'connections') return handleGitConnections(session, flags);
|
|
5492
|
+
if (command === 'git' && subcommand === 'repositories' && positional.length === 2) {
|
|
5493
|
+
process.stdout.write(commandHelp(['git', 'repositories']));
|
|
5494
|
+
return;
|
|
5495
|
+
}
|
|
5496
|
+
if (command === 'git' && subcommand === 'repositories' && positional[2] === 'resolve') return handleGitRepositoryResolve(session, flags);
|
|
5497
|
+
if (command === 'git' && subcommand === 'branches') return handleGitBranches(session, flags);
|
|
4965
5498
|
if (command === 'create' && subcommand === 'project') return handleProjectCreate(session, flags);
|
|
4966
5499
|
if (command === 'orgs') return handleOrgs(session, flags);
|
|
4967
5500
|
if (command === 'whoami') return handleWhoami(session, flags);
|
|
@@ -4989,6 +5522,15 @@ async function main() {
|
|
|
4989
5522
|
return;
|
|
4990
5523
|
}
|
|
4991
5524
|
if (command === 'project' && subcommand === 'github' && positional.length === 2) return handleProjectGithub(session, flags);
|
|
5525
|
+
if (command === 'project' && subcommand === 'source' && positional.length === 2) return handleProjectSource(session, flags);
|
|
5526
|
+
if (command === 'project' && subcommand === 'source' && positional[2] === 'branches') return handleProjectSourceBranches(session, flags);
|
|
5527
|
+
if (command === 'project' && subcommand === 'source' && positional[2] === 'deploy-settings' && positional[3] === 'set') {
|
|
5528
|
+
return handleProjectSourceDeploySettingsSet(session, flags);
|
|
5529
|
+
}
|
|
5530
|
+
if (command === 'project' && subcommand === 'source' && positional[2] === 'deploy-settings') {
|
|
5531
|
+
process.stdout.write(commandHelp(['project', 'source', 'deploy-settings']));
|
|
5532
|
+
return;
|
|
5533
|
+
}
|
|
4992
5534
|
if (command === 'project' && subcommand === 'info') return handleProjectInfo(session, flags);
|
|
4993
5535
|
if (command === 'project' && subcommand === 'stop') return handleProjectLifecycleMutation(session, flags, 'stop');
|
|
4994
5536
|
if (command === 'project' && subcommand === 'resume') return handleProjectLifecycleMutation(session, flags, 'resume');
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type_project": "http",
|
|
3
|
+
"exposure": "public",
|
|
4
|
+
"source": {
|
|
5
|
+
"connection_id": "connection-id-from-console",
|
|
6
|
+
"repository_id": "repository-id-from-connection",
|
|
7
|
+
"branch": "main",
|
|
8
|
+
"auto_deploy": true
|
|
9
|
+
},
|
|
10
|
+
"build": {
|
|
11
|
+
"runtime": "nodejs",
|
|
12
|
+
"version": "24",
|
|
13
|
+
"start_command": "npm start",
|
|
14
|
+
"pre_build_command": null,
|
|
15
|
+
"build_command": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"port": 3000,
|
|
18
|
+
"instances": 1,
|
|
19
|
+
"storage": {
|
|
20
|
+
"persistent": false,
|
|
21
|
+
"capacity": 1
|
|
22
|
+
},
|
|
23
|
+
"envs": [
|
|
24
|
+
{
|
|
25
|
+
"name": "NODE_ENV",
|
|
26
|
+
"value": "production"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"network_access": {
|
|
30
|
+
"ingress_white_list": [
|
|
31
|
+
{
|
|
32
|
+
"cidr": "0.0.0.0/0",
|
|
33
|
+
"description": "Allow all"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"ingress_black_list": []
|
|
37
|
+
}
|
|
38
|
+
}
|