gitlab-skill 1.0.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/GITLAB_API_COMPREHENSIVE_GUIDE.md +3885 -0
- package/PRACTICAL_SCRIPTS.md +1351 -0
- package/QUICK_REFERENCE.md +682 -0
- package/README.md +461 -0
- package/SKILL.md +789 -0
- package/SKILL_SETUP.md +50 -0
- package/package.json +42 -0
- package/scripts/install-skill.mjs +119 -0
- package/skill-features/00-auth-and-bootstrap.md +81 -0
- package/skill-features/01-discovery-and-projects.md +73 -0
- package/skill-features/02-repository-and-code.md +101 -0
- package/skill-features/03-issues-and-merge-requests.md +69 -0
- package/skill-features/04-cicd-pipelines-jobs.md +86 -0
- package/skill-features/05-security-and-vulnerabilities.md +125 -0
- package/skill-features/06-graphql-and-glab-playbook.md +68 -0
|
@@ -0,0 +1,3885 @@
|
|
|
1
|
+
# GitLab API & CLI - Guía Completa Operativa
|
|
2
|
+
|
|
3
|
+
> **Fecha de recopilación**: 18 de febrero de 2026
|
|
4
|
+
> **Fuentes**: Documentación oficial de GitLab Docs
|
|
5
|
+
> **Enfoque**: Ejemplos prácticos y operativos sin UI web
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Tabla de Contenidos
|
|
10
|
+
|
|
11
|
+
1. [Resumen por URL](#resumen-por-url)
|
|
12
|
+
2. [Patrones de Uso Recomendados](#patrones-de-uso-recomendados)
|
|
13
|
+
3. [Ejemplos Concretos por Features (25+)](#ejemplos-concretos-por-features)
|
|
14
|
+
4. [Troubleshooting y Códigos HTTP](#troubleshooting-y-códigos-http)
|
|
15
|
+
5. [Estructura Modular Recomendada](#estructura-modular-recomendada)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Resumen por URL
|
|
20
|
+
|
|
21
|
+
### 1. GraphQL API (`/api/graphql/`)
|
|
22
|
+
|
|
23
|
+
**Puntos clave accionables:**
|
|
24
|
+
|
|
25
|
+
- **Endpoint único**: `/api/graphql` - API versionless (sin versiones)
|
|
26
|
+
- **Autenticación**: OAuth 2.0, Personal/Project/Group access tokens, Session cookie
|
|
27
|
+
- **Headers de autenticación**: `Authorization: Bearer <token>` (recomendado) o `private_token` parameter
|
|
28
|
+
- **Scopes requeridos**: `api` (read-write) o `read_api` (read-only)
|
|
29
|
+
- **Límites**:
|
|
30
|
+
- Complejidad máxima: 200 (no autenticado), 250 (autenticado)
|
|
31
|
+
- Tamaño máximo de query: 10,000 caracteres
|
|
32
|
+
- Paginación: max 100 registros por página
|
|
33
|
+
- Blobs: límite de 20 MB para múltiples blobs
|
|
34
|
+
- Timeout: 30 segundos
|
|
35
|
+
- **Identificadores**: Global IDs (`gid://gitlab/Issue/123`), full paths, IIDs
|
|
36
|
+
- **Multiplex**: Soporta batching de múltiples queries en una sola request
|
|
37
|
+
- **Introspección**: Schemas estáticos disponibles en producción
|
|
38
|
+
- **Breaking changes**: Deprecación de 6 releases antes de remover
|
|
39
|
+
- **Testing futuro**: `?remove_deprecated=true` para verificar queries contra schema futuro
|
|
40
|
+
|
|
41
|
+
**Ventajas operativas:**
|
|
42
|
+
- Solicitar exactamente los datos necesarios (reduce requests)
|
|
43
|
+
- Exploración interactiva con GraphiQL (`/-/graphql-explorer`)
|
|
44
|
+
- Sin necesidad de parseo manual (client-side libraries)
|
|
45
|
+
- Batching de queries para optimizar performance
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
### 2. GraphQL Reference (`/api/graphql/reference/`)
|
|
50
|
+
|
|
51
|
+
**Puntos clave accionables:**
|
|
52
|
+
|
|
53
|
+
- **Referencia completa**: Schema detallado con todos los types, fields, arguments
|
|
54
|
+
- **Query type**: Entry points para todas las queries disponibles
|
|
55
|
+
- **Mutation types**: Operaciones de creación, actualización, eliminación
|
|
56
|
+
- **Deprecation markers**: Campos marcados como deprecated con alternativas
|
|
57
|
+
- **Experiments**: Items experimentales pueden removerse sin aviso
|
|
58
|
+
- **Feature flags**: Campos detrás de feature flags deshabilitados pueden removerse sin proceso de deprecación
|
|
59
|
+
|
|
60
|
+
**Uso práctico:**
|
|
61
|
+
- Consultar antes de implementar queries/mutations
|
|
62
|
+
- Verificar tipos de datos y argumentos requeridos
|
|
63
|
+
- Identificar campos deprecated para migrar código
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
### 3. GitLab CLI - glab (`/cli/`)
|
|
68
|
+
|
|
69
|
+
**Puntos clave accionables:**
|
|
70
|
+
|
|
71
|
+
- **Instalación**: Disponible para Linux, macOS, Windows (ver README)
|
|
72
|
+
- **Autenticación**: `glab auth login` (OAuth o personal access token)
|
|
73
|
+
- **Multi-instancia**: Soporta múltiples instancias autenticadas simultáneamente
|
|
74
|
+
- **Auto-detección**: Detecta hostname desde remotes Git del directorio actual
|
|
75
|
+
|
|
76
|
+
**Variables de entorno clave:**
|
|
77
|
+
- `GITLAB_TOKEN`: Token de autenticación (evita prompts)
|
|
78
|
+
- `GITLAB_HOST` / `GL_HOST`: URL del servidor GitLab (default: gitlab.com)
|
|
79
|
+
- `GLAB_CONFIG_DIR`: Directorio de configuración personalizado
|
|
80
|
+
- `NO_PROMPT`: Deshabilitar prompts interactivos
|
|
81
|
+
- `DEBUG`: Habilitar logging detallado
|
|
82
|
+
- `GLAB_ENABLE_CI_AUTOLOGIN`: Auto-login en CI/CD jobs (experimental)
|
|
83
|
+
|
|
84
|
+
**Comandos principales:**
|
|
85
|
+
- `glab api`: Llamadas directas a REST API
|
|
86
|
+
- `glab mr`: Merge requests (create, list, view, checkout, approve, merge)
|
|
87
|
+
- `glab issue`: Issues (create, list, view, close, reopen)
|
|
88
|
+
- `glab ci`: Pipelines y jobs (view, trace, retry, cancel)
|
|
89
|
+
- `glab repo`: Repositorios (clone, fork, view)
|
|
90
|
+
- `glab release`: Releases (create, list, view, delete)
|
|
91
|
+
- `glab variable`: Variables CI/CD (list, create, update, delete)
|
|
92
|
+
|
|
93
|
+
**Ventajas operativas:**
|
|
94
|
+
- Integración nativa con Git workflow
|
|
95
|
+
- No requiere cambio de contexto (terminal)
|
|
96
|
+
- Scripting y automatización fácil
|
|
97
|
+
- GitLab Duo Chat desde CLI
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### 4. REST API (`/api/rest/`)
|
|
102
|
+
|
|
103
|
+
**Puntos clave accionables:**
|
|
104
|
+
|
|
105
|
+
- **Endpoint base**: `https://gitlab.example.com/api/v4`
|
|
106
|
+
- **Versión**: v4 (semantic versioning, major version)
|
|
107
|
+
- **Formato**: JSON (algunos endpoints soportan plain text)
|
|
108
|
+
- **Autenticación**: Personal/Project/Group access tokens, OAuth 2.0, Job tokens, Session cookie
|
|
109
|
+
- **Headers recomendados**: `PRIVATE-TOKEN: <token>` o `Authorization: Bearer <token>`
|
|
110
|
+
|
|
111
|
+
**Paginación:**
|
|
112
|
+
|
|
113
|
+
**Offset-based (default):**
|
|
114
|
+
- Parámetros: `page` (default: 1), `per_page` (default: 20, max: 100)
|
|
115
|
+
- Headers de respuesta: `x-total`, `x-total-pages`, `x-page`, `x-per-page`, `x-next-page`, `x-prev-page`
|
|
116
|
+
- Link header con `rel="next"`, `rel="prev"`, `rel="first"`, `rel="last"`
|
|
117
|
+
- Límite: max offset configurable (performance)
|
|
118
|
+
|
|
119
|
+
**Keyset-based (más eficiente):**
|
|
120
|
+
- Parámetros: `pagination=keyset`, `order_by`, `sort`, `per_page`
|
|
121
|
+
- Independiente del tamaño de la colección
|
|
122
|
+
- Link header con cursor para siguiente página
|
|
123
|
+
- Headers: `X-NEXT-CURSOR`, `X-PREV-CURSOR`
|
|
124
|
+
- Recursos soportados: Projects, Users, Groups, Audit events, Jobs, Registry tags, etc.
|
|
125
|
+
|
|
126
|
+
**Encoding:**
|
|
127
|
+
- Namespaced paths: URL-encoded (`/` → `%2F`)
|
|
128
|
+
- Ejemplo: `diaspora/diaspora` → `diaspora%2Fdiaspora`
|
|
129
|
+
- File paths, branches, tags: URL-encoded
|
|
130
|
+
- Arrays: `param[]=value1¶m[]=value2`
|
|
131
|
+
- Hashes: `param[key]=value`
|
|
132
|
+
- ISO 8601 dates: `+` → `%2B` (ej: `2017-10-17T23:11:13.000%2B05:30`)
|
|
133
|
+
|
|
134
|
+
**Rate limits:**
|
|
135
|
+
- GitLab.com: Ver límites específicos en documentación
|
|
136
|
+
- Self-managed: Configurables por instancia
|
|
137
|
+
- Headers de respuesta indican límites y uso actual
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### 5. OpenAPI Interactive (`/api/openapi/openapi_interactive/`)
|
|
142
|
+
|
|
143
|
+
**Nota**: Esta URL no pudo ser accedida completamente, pero típicamente proporciona:
|
|
144
|
+
- Interfaz Swagger/OpenAPI para explorar REST API
|
|
145
|
+
- Testing interactivo de endpoints
|
|
146
|
+
- Documentación generada automáticamente
|
|
147
|
+
- Ejemplos de requests/responses
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### 6. REST API Troubleshooting (`/api/rest/troubleshooting/`)
|
|
152
|
+
|
|
153
|
+
**Puntos clave accionables:**
|
|
154
|
+
|
|
155
|
+
**Códigos de estado HTTP:**
|
|
156
|
+
|
|
157
|
+
| Código | Significado | Acción |
|
|
158
|
+
|--------|-------------|--------|
|
|
159
|
+
| `200 OK` | Éxito en GET/PUT/PATCH/DELETE | Recurso retornado como JSON |
|
|
160
|
+
| `201 Created` | Éxito en POST | Recurso creado retornado como JSON |
|
|
161
|
+
| `202 Accepted` | Aceptado para procesamiento | Recurso será procesado |
|
|
162
|
+
| `204 No Content` | Éxito sin contenido | No hay payload en respuesta |
|
|
163
|
+
| `301 Moved Permanently` | Recurso movido | Usar URL en header `Location` |
|
|
164
|
+
| `304 Not Modified` | No modificado desde última request | Usar caché |
|
|
165
|
+
| `400 Bad Request` | Atributo requerido faltante | Verificar parámetros |
|
|
166
|
+
| `401 Unauthorized` | No autenticado | Proporcionar token válido |
|
|
167
|
+
| `403 Forbidden` | No autorizado | Verificar permisos |
|
|
168
|
+
| `404 Not Found` | Recurso no encontrado | Verificar ID/path |
|
|
169
|
+
| `405 Method Not Allowed` | Método no soportado | Usar método correcto |
|
|
170
|
+
| `409 Conflict` | Recurso conflictivo existe | Resolver conflicto |
|
|
171
|
+
| `412 Precondition Failed` | Precondición falló | Verificar headers condicionales |
|
|
172
|
+
| `422 Unprocessable` | Entidad no procesable | Verificar validación |
|
|
173
|
+
| `429 Too Many Requests` | Rate limit excedido | Esperar y reintentar |
|
|
174
|
+
| `500 Server Error` | Error interno del servidor | Reportar a administrador |
|
|
175
|
+
| `503 Service Unavailable` | Servidor sobrecargado | Reintentar más tarde |
|
|
176
|
+
|
|
177
|
+
**Error 400 - Validación:**
|
|
178
|
+
|
|
179
|
+
Formato de error de validación:
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"message": {
|
|
183
|
+
"<property-name>": [
|
|
184
|
+
"<error-message>",
|
|
185
|
+
"<error-message>"
|
|
186
|
+
],
|
|
187
|
+
"<embed-entity>": {
|
|
188
|
+
"<property-name>": [
|
|
189
|
+
"<error-message>"
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Debugging:**
|
|
197
|
+
- Incluir headers: `curl --include` (muestra headers HTTP)
|
|
198
|
+
- Incluir exit code: `curl --fail` (muestra código de salida)
|
|
199
|
+
- Verbose: `curl --verbose` (debug completo)
|
|
200
|
+
|
|
201
|
+
**Spam detection:**
|
|
202
|
+
|
|
203
|
+
Sin CAPTCHA:
|
|
204
|
+
```json
|
|
205
|
+
{"message":{"error":"Your snippet has been recognized as spam and has been discarded."}}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Con CAPTCHA:
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"needs_captcha_response": true,
|
|
212
|
+
"spam_log_id": 42,
|
|
213
|
+
"captcha_site_key": "REDACTED",
|
|
214
|
+
"message": {"error": "Your snippet has been recognized as spam..."}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Resolución:
|
|
219
|
+
1. Obtener CAPTCHA response con `captcha_site_key` (Google reCAPTCHA v2)
|
|
220
|
+
2. Reenviar con headers: `X-GitLab-Captcha-Response`, `X-GitLab-Spam-Log-Id`
|
|
221
|
+
|
|
222
|
+
**Error 404 con reverse proxy:**
|
|
223
|
+
- Problema: Proxy decodifica caracteres URL-encoded antes de pasar a GitLab
|
|
224
|
+
- Solución Apache: Agregar `AllowEncodedSlashes NoDecode` y `nocanon` flag en `ProxyPass`
|
|
225
|
+
- Solución NGINX: Configurar `proxy_pass` sin decodificación automática
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
### 7. GraphQL Examples (`/api/graphql/examples/`)
|
|
230
|
+
|
|
231
|
+
**Puntos clave accionables:**
|
|
232
|
+
|
|
233
|
+
**Métodos de ejecución:**
|
|
234
|
+
1. **GraphiQL** (más fácil): `https://gitlab.com/-/graphql-explorer` o `https://<instance>/-/graphql-explorer`
|
|
235
|
+
2. **Command line**: `curl` con POST a `/api/graphql`
|
|
236
|
+
3. **Rails console**: Para self-managed/dedicated
|
|
237
|
+
|
|
238
|
+
**Estructura básica de query:**
|
|
239
|
+
```graphql
|
|
240
|
+
query {
|
|
241
|
+
project(fullPath: "grupo/proyecto") {
|
|
242
|
+
id
|
|
243
|
+
name
|
|
244
|
+
issues {
|
|
245
|
+
nodes {
|
|
246
|
+
title
|
|
247
|
+
description
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Estructura básica de mutation:**
|
|
255
|
+
```graphql
|
|
256
|
+
mutation {
|
|
257
|
+
createNote(input: {
|
|
258
|
+
noteableId: "gid://gitlab/Issue/123",
|
|
259
|
+
body: "Comentario"
|
|
260
|
+
}) {
|
|
261
|
+
note {
|
|
262
|
+
id
|
|
263
|
+
body
|
|
264
|
+
}
|
|
265
|
+
errors
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Patterns importantes:**
|
|
271
|
+
- Siempre incluir `errors` en mutations
|
|
272
|
+
- Usar `nodes { }` para colecciones (sintaxis corta)
|
|
273
|
+
- Usar `edges { node { } }` cuando necesitas cursor para paginación
|
|
274
|
+
- Paginación: `first`, `last`, `after`, `before`, `endCursor`, `hasNextPage`
|
|
275
|
+
- Sorting: Usar argumentos `sort` donde disponible (ej: `issues(sort: created_asc)`)
|
|
276
|
+
- Introspección: `queryComplexity { score limit }` para verificar complejidad
|
|
277
|
+
|
|
278
|
+
**Ejemplos documentados:**
|
|
279
|
+
- Audit reports
|
|
280
|
+
- Issue boards
|
|
281
|
+
- Query users
|
|
282
|
+
- Custom emoji
|
|
283
|
+
- Branch rules
|
|
284
|
+
- Work items
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Patrones de Uso Recomendados
|
|
289
|
+
|
|
290
|
+
### Autenticación
|
|
291
|
+
|
|
292
|
+
**REST API:**
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
# Método 1: Header (recomendado)
|
|
296
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
297
|
+
--url "https://gitlab.example.com/api/v4/projects"
|
|
298
|
+
|
|
299
|
+
# Método 2: Bearer token (OAuth-compliant)
|
|
300
|
+
curl --header "Authorization: Bearer <token>" \
|
|
301
|
+
--url "https://gitlab.example.com/api/v4/projects"
|
|
302
|
+
|
|
303
|
+
# Método 3: Query parameter (menos seguro)
|
|
304
|
+
curl --url "https://gitlab.example.com/api/v4/projects?private_token=<token>"
|
|
305
|
+
|
|
306
|
+
# Job token (CI/CD)
|
|
307
|
+
curl --header "JOB-TOKEN: $CI_JOB_TOKEN" \
|
|
308
|
+
--url "https://gitlab.example.com/api/v4/projects/1/releases"
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**GraphQL API:**
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
# Header (recomendado)
|
|
315
|
+
curl --request POST \
|
|
316
|
+
--header "Authorization: Bearer $GRAPHQL_TOKEN" \
|
|
317
|
+
--header "Content-Type: application/json" \
|
|
318
|
+
--data '{"query": "query {currentUser {name}}"}' \
|
|
319
|
+
--url "https://gitlab.com/api/graphql"
|
|
320
|
+
|
|
321
|
+
# Query parameter
|
|
322
|
+
curl --request POST \
|
|
323
|
+
--header "Content-Type: application/json" \
|
|
324
|
+
--data '{"query": "query {currentUser {name}}"}' \
|
|
325
|
+
--url "https://gitlab.com/api/graphql?private_token=<token>"
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**glab CLI:**
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# Autenticación interactiva
|
|
332
|
+
glab auth login
|
|
333
|
+
|
|
334
|
+
# Variable de entorno
|
|
335
|
+
export GITLAB_TOKEN="<token>"
|
|
336
|
+
glab api projects
|
|
337
|
+
|
|
338
|
+
# Archivo de configuración
|
|
339
|
+
glab config set token <token>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
### Paginación
|
|
345
|
+
|
|
346
|
+
**REST - Offset-based:**
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
# Página 2, 50 items por página
|
|
350
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
351
|
+
--url "https://gitlab.example.com/api/v4/projects?page=2&per_page=50"
|
|
352
|
+
|
|
353
|
+
# Usar Link header para siguiente página
|
|
354
|
+
# Link: <https://gitlab.example.com/api/v4/projects?page=3&per_page=50>; rel="next"
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**REST - Keyset-based (más eficiente):**
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
# Primera página
|
|
361
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
362
|
+
--url "https://gitlab.example.com/api/v4/projects?pagination=keyset&per_page=50&order_by=id&sort=asc"
|
|
363
|
+
|
|
364
|
+
# Siguiente página (usar cursor del Link header)
|
|
365
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
366
|
+
--url "https://gitlab.example.com/api/v4/projects?pagination=keyset&per_page=50&order_by=id&sort=asc&id_after=42"
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**GraphQL:**
|
|
370
|
+
|
|
371
|
+
```graphql
|
|
372
|
+
# Primera página
|
|
373
|
+
query {
|
|
374
|
+
project(fullPath: "grupo/proyecto") {
|
|
375
|
+
issues(first: 10) {
|
|
376
|
+
edges {
|
|
377
|
+
node {
|
|
378
|
+
title
|
|
379
|
+
}
|
|
380
|
+
cursor
|
|
381
|
+
}
|
|
382
|
+
pageInfo {
|
|
383
|
+
endCursor
|
|
384
|
+
hasNextPage
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
# Siguiente página (usar endCursor)
|
|
391
|
+
query {
|
|
392
|
+
project(fullPath: "grupo/proyecto") {
|
|
393
|
+
issues(first: 10, after: "eyJpZCI6IjI3MDM4OTMzIn0") {
|
|
394
|
+
edges {
|
|
395
|
+
node {
|
|
396
|
+
title
|
|
397
|
+
}
|
|
398
|
+
cursor
|
|
399
|
+
}
|
|
400
|
+
pageInfo {
|
|
401
|
+
endCursor
|
|
402
|
+
hasNextPage
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
### Manejo de Errores
|
|
412
|
+
|
|
413
|
+
**REST API:**
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
# Capturar código de estado
|
|
417
|
+
HTTP_CODE=$(curl --write-out '%{http_code}' --silent --output /dev/null \
|
|
418
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
419
|
+
--url "https://gitlab.example.com/api/v4/projects/123")
|
|
420
|
+
|
|
421
|
+
if [ "$HTTP_CODE" -eq 200 ]; then
|
|
422
|
+
echo "Success"
|
|
423
|
+
elif [ "$HTTP_CODE" -eq 404 ]; then
|
|
424
|
+
echo "Project not found"
|
|
425
|
+
elif [ "$HTTP_CODE" -eq 401 ]; then
|
|
426
|
+
echo "Unauthorized - check token"
|
|
427
|
+
elif [ "$HTTP_CODE" -eq 429 ]; then
|
|
428
|
+
echo "Rate limit exceeded - wait and retry"
|
|
429
|
+
fi
|
|
430
|
+
|
|
431
|
+
# Incluir headers para debugging
|
|
432
|
+
curl --include --header "PRIVATE-TOKEN: <token>" \
|
|
433
|
+
--url "https://gitlab.example.com/api/v4/projects/123"
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**GraphQL API:**
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
# GraphQL siempre retorna 200, verificar errors en response
|
|
440
|
+
RESPONSE=$(curl --request POST \
|
|
441
|
+
--header "Authorization: Bearer $TOKEN" \
|
|
442
|
+
--header "Content-Type: application/json" \
|
|
443
|
+
--data '{"query": "query {project(fullPath: \"invalid\") {id}}"}' \
|
|
444
|
+
--url "https://gitlab.com/api/graphql")
|
|
445
|
+
|
|
446
|
+
# Verificar si hay errores
|
|
447
|
+
if echo "$RESPONSE" | jq -e '.errors' > /dev/null; then
|
|
448
|
+
echo "GraphQL errors found:"
|
|
449
|
+
echo "$RESPONSE" | jq '.errors'
|
|
450
|
+
fi
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**glab CLI:**
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
# glab retorna códigos de salida estándar
|
|
457
|
+
if glab api projects/123 > /dev/null 2>&1; then
|
|
458
|
+
echo "Project exists"
|
|
459
|
+
else
|
|
460
|
+
echo "Project not found or error: $?"
|
|
461
|
+
fi
|
|
462
|
+
|
|
463
|
+
# Capturar output y errores
|
|
464
|
+
OUTPUT=$(glab api projects/123 2>&1)
|
|
465
|
+
if [ $? -ne 0 ]; then
|
|
466
|
+
echo "Error: $OUTPUT"
|
|
467
|
+
fi
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
### Rate Limits
|
|
473
|
+
|
|
474
|
+
**Estrategias:**
|
|
475
|
+
|
|
476
|
+
```bash
|
|
477
|
+
# 1. Verificar headers de rate limit
|
|
478
|
+
curl --include --header "PRIVATE-TOKEN: <token>" \
|
|
479
|
+
--url "https://gitlab.example.com/api/v4/projects" | grep -i "ratelimit"
|
|
480
|
+
|
|
481
|
+
# 2. Implementar retry con backoff exponencial
|
|
482
|
+
retry_with_backoff() {
|
|
483
|
+
local max_attempts=5
|
|
484
|
+
local timeout=1
|
|
485
|
+
local attempt=1
|
|
486
|
+
|
|
487
|
+
while [ $attempt -le $max_attempts ]; do
|
|
488
|
+
HTTP_CODE=$(curl --write-out '%{http_code}' --silent --output response.json \
|
|
489
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
490
|
+
"$1")
|
|
491
|
+
|
|
492
|
+
if [ "$HTTP_CODE" -eq 429 ]; then
|
|
493
|
+
echo "Rate limited, waiting ${timeout}s..."
|
|
494
|
+
sleep $timeout
|
|
495
|
+
timeout=$((timeout * 2))
|
|
496
|
+
attempt=$((attempt + 1))
|
|
497
|
+
elif [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
|
498
|
+
cat response.json
|
|
499
|
+
return 0
|
|
500
|
+
else
|
|
501
|
+
echo "Error: HTTP $HTTP_CODE"
|
|
502
|
+
return 1
|
|
503
|
+
fi
|
|
504
|
+
done
|
|
505
|
+
|
|
506
|
+
echo "Max retries exceeded"
|
|
507
|
+
return 1
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
# 3. Usar keyset pagination para grandes datasets
|
|
511
|
+
# (más eficiente que offset pagination)
|
|
512
|
+
|
|
513
|
+
# 4. Batch operations cuando sea posible
|
|
514
|
+
# GraphQL multiplex:
|
|
515
|
+
curl --request POST \
|
|
516
|
+
--header "Authorization: Bearer $TOKEN" \
|
|
517
|
+
--header "Content-Type: application/json" \
|
|
518
|
+
--data '[
|
|
519
|
+
{"query": "query {project(fullPath: \"grupo/proyecto1\") {id}}"},
|
|
520
|
+
{"query": "query {project(fullPath: \"grupo/proyecto2\") {id}}"}
|
|
521
|
+
]' \
|
|
522
|
+
--url "https://gitlab.com/api/graphql"
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## Ejemplos Concretos por Features
|
|
528
|
+
|
|
529
|
+
### 1. Projects
|
|
530
|
+
|
|
531
|
+
#### REST API
|
|
532
|
+
|
|
533
|
+
**Listar todos los proyectos:**
|
|
534
|
+
```bash
|
|
535
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
536
|
+
--url "https://gitlab.example.com/api/v4/projects"
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**Obtener proyecto específico:**
|
|
540
|
+
```bash
|
|
541
|
+
# Por ID
|
|
542
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
543
|
+
--url "https://gitlab.example.com/api/v4/projects/123"
|
|
544
|
+
|
|
545
|
+
# Por path (URL-encoded)
|
|
546
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
547
|
+
--url "https://gitlab.example.com/api/v4/projects/grupo%2Fproyecto"
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**Crear proyecto:**
|
|
551
|
+
```bash
|
|
552
|
+
curl --request POST \
|
|
553
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
554
|
+
--header "Content-Type: application/json" \
|
|
555
|
+
--data '{
|
|
556
|
+
"name": "Mi Proyecto",
|
|
557
|
+
"path": "mi-proyecto",
|
|
558
|
+
"description": "Descripción del proyecto",
|
|
559
|
+
"visibility": "private",
|
|
560
|
+
"initialize_with_readme": true
|
|
561
|
+
}' \
|
|
562
|
+
--url "https://gitlab.example.com/api/v4/projects"
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**Actualizar configuración del proyecto:**
|
|
566
|
+
```bash
|
|
567
|
+
# Deshabilitar forking
|
|
568
|
+
curl --request PUT \
|
|
569
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
570
|
+
--header "Content-Type: application/json" \
|
|
571
|
+
--data '{"forking_access_level": "disabled"}' \
|
|
572
|
+
--url "https://gitlab.example.com/api/v4/projects/123"
|
|
573
|
+
|
|
574
|
+
# Configurar CI/CD
|
|
575
|
+
curl --request PUT \
|
|
576
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
577
|
+
--header "Content-Type: application/json" \
|
|
578
|
+
--data '{
|
|
579
|
+
"builds_access_level": "enabled",
|
|
580
|
+
"auto_devops_enabled": true,
|
|
581
|
+
"auto_cancel_pending_pipelines": "enabled"
|
|
582
|
+
}' \
|
|
583
|
+
--url "https://gitlab.example.com/api/v4/projects/123"
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
**Archivar/Desarchivar proyecto:**
|
|
587
|
+
```bash
|
|
588
|
+
# Archivar
|
|
589
|
+
curl --request POST \
|
|
590
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
591
|
+
--url "https://gitlab.example.com/api/v4/projects/123/archive"
|
|
592
|
+
|
|
593
|
+
# Desarchivar
|
|
594
|
+
curl --request POST \
|
|
595
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
596
|
+
--url "https://gitlab.example.com/api/v4/projects/123/unarchive"
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
#### GraphQL API
|
|
600
|
+
|
|
601
|
+
**Query proyecto con issues:**
|
|
602
|
+
```graphql
|
|
603
|
+
query {
|
|
604
|
+
project(fullPath: "grupo/proyecto") {
|
|
605
|
+
id
|
|
606
|
+
name
|
|
607
|
+
description
|
|
608
|
+
visibility
|
|
609
|
+
webUrl
|
|
610
|
+
createdAt
|
|
611
|
+
issues(first: 10, state: OPENED) {
|
|
612
|
+
nodes {
|
|
613
|
+
iid
|
|
614
|
+
title
|
|
615
|
+
state
|
|
616
|
+
createdAt
|
|
617
|
+
}
|
|
618
|
+
pageInfo {
|
|
619
|
+
hasNextPage
|
|
620
|
+
endCursor
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
**Mutation: Actualizar proyecto:**
|
|
628
|
+
```graphql
|
|
629
|
+
mutation {
|
|
630
|
+
updateProject(input: {
|
|
631
|
+
projectPath: "grupo/proyecto",
|
|
632
|
+
description: "Nueva descripción"
|
|
633
|
+
}) {
|
|
634
|
+
project {
|
|
635
|
+
id
|
|
636
|
+
description
|
|
637
|
+
}
|
|
638
|
+
errors
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
#### glab CLI
|
|
644
|
+
|
|
645
|
+
```bash
|
|
646
|
+
# Listar proyectos
|
|
647
|
+
glab repo list
|
|
648
|
+
|
|
649
|
+
# Ver proyecto actual
|
|
650
|
+
glab repo view
|
|
651
|
+
|
|
652
|
+
# Clonar proyecto
|
|
653
|
+
glab repo clone grupo/proyecto
|
|
654
|
+
|
|
655
|
+
# Fork proyecto
|
|
656
|
+
glab repo fork grupo/proyecto
|
|
657
|
+
|
|
658
|
+
# Archivar proyecto
|
|
659
|
+
glab api --method POST projects/123/archive
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
### 2. Groups
|
|
665
|
+
|
|
666
|
+
#### REST API
|
|
667
|
+
|
|
668
|
+
**Listar grupos:**
|
|
669
|
+
```bash
|
|
670
|
+
# Todos los grupos
|
|
671
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
672
|
+
--url "https://gitlab.example.com/api/v4/groups"
|
|
673
|
+
|
|
674
|
+
# Grupos con proyectos
|
|
675
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
676
|
+
--url "https://gitlab.example.com/api/v4/groups?with_projects=true"
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
**Obtener grupo específico:**
|
|
680
|
+
```bash
|
|
681
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
682
|
+
--url "https://gitlab.example.com/api/v4/groups/grupo-id"
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
**Crear grupo:**
|
|
686
|
+
```bash
|
|
687
|
+
curl --request POST \
|
|
688
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
689
|
+
--header "Content-Type: application/json" \
|
|
690
|
+
--data '{
|
|
691
|
+
"name": "Mi Grupo",
|
|
692
|
+
"path": "mi-grupo",
|
|
693
|
+
"description": "Descripción del grupo",
|
|
694
|
+
"visibility": "private"
|
|
695
|
+
}' \
|
|
696
|
+
--url "https://gitlab.example.com/api/v4/groups"
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
**Listar proyectos de un grupo:**
|
|
700
|
+
```bash
|
|
701
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
702
|
+
--url "https://gitlab.example.com/api/v4/groups/grupo-id/projects"
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
#### GraphQL API
|
|
706
|
+
|
|
707
|
+
**Query grupo con proyectos:**
|
|
708
|
+
```graphql
|
|
709
|
+
query {
|
|
710
|
+
group(fullPath: "mi-grupo") {
|
|
711
|
+
id
|
|
712
|
+
name
|
|
713
|
+
fullPath
|
|
714
|
+
projects {
|
|
715
|
+
nodes {
|
|
716
|
+
id
|
|
717
|
+
name
|
|
718
|
+
visibility
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
#### glab CLI
|
|
726
|
+
|
|
727
|
+
```bash
|
|
728
|
+
# Listar grupos
|
|
729
|
+
glab api groups
|
|
730
|
+
|
|
731
|
+
# Ver grupo específico
|
|
732
|
+
glab api groups/grupo-id
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
### 3. Issues
|
|
738
|
+
|
|
739
|
+
#### REST API
|
|
740
|
+
|
|
741
|
+
**Listar issues de un proyecto:**
|
|
742
|
+
```bash
|
|
743
|
+
# Todas las issues
|
|
744
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
745
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues"
|
|
746
|
+
|
|
747
|
+
# Issues abiertas
|
|
748
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
749
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues?state=opened"
|
|
750
|
+
|
|
751
|
+
# Issues con filtros
|
|
752
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
753
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues?labels=bug,critical&assignee_id=5"
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
**Obtener issue específica:**
|
|
757
|
+
```bash
|
|
758
|
+
# Por IID (internal ID)
|
|
759
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
760
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues/42"
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
**Crear issue:**
|
|
764
|
+
```bash
|
|
765
|
+
curl --request POST \
|
|
766
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
767
|
+
--header "Content-Type: application/json" \
|
|
768
|
+
--data '{
|
|
769
|
+
"title": "Bug en login",
|
|
770
|
+
"description": "El login no funciona con usuarios nuevos",
|
|
771
|
+
"labels": "bug,high-priority",
|
|
772
|
+
"assignee_ids": [5, 10]
|
|
773
|
+
}' \
|
|
774
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues"
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
**Actualizar issue:**
|
|
778
|
+
```bash
|
|
779
|
+
curl --request PUT \
|
|
780
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
781
|
+
--header "Content-Type: application/json" \
|
|
782
|
+
--data '{
|
|
783
|
+
"state_event": "close",
|
|
784
|
+
"labels": "bug,resolved"
|
|
785
|
+
}' \
|
|
786
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues/42"
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
**Agregar comentario a issue:**
|
|
790
|
+
```bash
|
|
791
|
+
curl --request POST \
|
|
792
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
793
|
+
--header "Content-Type: application/json" \
|
|
794
|
+
--data '{
|
|
795
|
+
"body": "Este issue ha sido resuelto en el commit abc123"
|
|
796
|
+
}' \
|
|
797
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues/42/notes"
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
#### GraphQL API
|
|
801
|
+
|
|
802
|
+
**Query issues con filtros:**
|
|
803
|
+
```graphql
|
|
804
|
+
query {
|
|
805
|
+
project(fullPath: "grupo/proyecto") {
|
|
806
|
+
issues(
|
|
807
|
+
state: OPENED
|
|
808
|
+
labelName: ["bug", "critical"]
|
|
809
|
+
first: 20
|
|
810
|
+
) {
|
|
811
|
+
nodes {
|
|
812
|
+
iid
|
|
813
|
+
title
|
|
814
|
+
description
|
|
815
|
+
state
|
|
816
|
+
labels {
|
|
817
|
+
nodes {
|
|
818
|
+
title
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
assignees {
|
|
822
|
+
nodes {
|
|
823
|
+
username
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
createdAt
|
|
827
|
+
updatedAt
|
|
828
|
+
}
|
|
829
|
+
pageInfo {
|
|
830
|
+
hasNextPage
|
|
831
|
+
endCursor
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
**Mutation: Crear issue:**
|
|
839
|
+
```graphql
|
|
840
|
+
mutation {
|
|
841
|
+
createIssue(input: {
|
|
842
|
+
projectPath: "grupo/proyecto",
|
|
843
|
+
title: "Bug en login",
|
|
844
|
+
description: "El login no funciona con usuarios nuevos",
|
|
845
|
+
labelIds: ["gid://gitlab/Label/1", "gid://gitlab/Label/2"]
|
|
846
|
+
}) {
|
|
847
|
+
issue {
|
|
848
|
+
iid
|
|
849
|
+
title
|
|
850
|
+
webUrl
|
|
851
|
+
}
|
|
852
|
+
errors
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
**Mutation: Cerrar issue:**
|
|
858
|
+
```graphql
|
|
859
|
+
mutation {
|
|
860
|
+
issueSetLocked(input: {
|
|
861
|
+
projectPath: "grupo/proyecto",
|
|
862
|
+
iid: "42",
|
|
863
|
+
locked: true
|
|
864
|
+
}) {
|
|
865
|
+
issue {
|
|
866
|
+
id
|
|
867
|
+
iid
|
|
868
|
+
}
|
|
869
|
+
errors
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
mutation {
|
|
874
|
+
updateIssue(input: {
|
|
875
|
+
projectPath: "grupo/proyecto",
|
|
876
|
+
iid: "42",
|
|
877
|
+
stateEvent: CLOSE
|
|
878
|
+
}) {
|
|
879
|
+
issue {
|
|
880
|
+
iid
|
|
881
|
+
state
|
|
882
|
+
}
|
|
883
|
+
errors
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
**Mutation: Agregar comentario:**
|
|
889
|
+
```graphql
|
|
890
|
+
mutation {
|
|
891
|
+
createNote(input: {
|
|
892
|
+
noteableId: "gid://gitlab/Issue/123456",
|
|
893
|
+
body: "Este issue ha sido resuelto en el commit abc123"
|
|
894
|
+
}) {
|
|
895
|
+
note {
|
|
896
|
+
id
|
|
897
|
+
body
|
|
898
|
+
createdAt
|
|
899
|
+
}
|
|
900
|
+
errors
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
#### glab CLI
|
|
906
|
+
|
|
907
|
+
```bash
|
|
908
|
+
# Listar issues
|
|
909
|
+
glab issue list
|
|
910
|
+
|
|
911
|
+
# Listar issues abiertas con filtro
|
|
912
|
+
glab issue list --state opened --label bug
|
|
913
|
+
|
|
914
|
+
# Ver issue específica
|
|
915
|
+
glab issue view 42
|
|
916
|
+
|
|
917
|
+
# Ver issue en browser
|
|
918
|
+
glab issue view 42 --web
|
|
919
|
+
|
|
920
|
+
# Crear issue
|
|
921
|
+
glab issue create --title "Bug en login" \
|
|
922
|
+
--description "El login no funciona" \
|
|
923
|
+
--label bug,high-priority
|
|
924
|
+
|
|
925
|
+
# Cerrar issue
|
|
926
|
+
glab issue close 42
|
|
927
|
+
|
|
928
|
+
# Reabrir issue
|
|
929
|
+
glab issue reopen 42
|
|
930
|
+
|
|
931
|
+
# Agregar comentario
|
|
932
|
+
glab issue note 42 --message "Resuelto en commit abc123"
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
---
|
|
936
|
+
|
|
937
|
+
### 4. Merge Requests
|
|
938
|
+
|
|
939
|
+
#### REST API
|
|
940
|
+
|
|
941
|
+
**Listar merge requests:**
|
|
942
|
+
```bash
|
|
943
|
+
# Todos los MRs del proyecto
|
|
944
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
945
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests"
|
|
946
|
+
|
|
947
|
+
# MRs abiertos
|
|
948
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
949
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests?state=opened"
|
|
950
|
+
|
|
951
|
+
# MRs con filtros
|
|
952
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
953
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests?state=opened&labels=feature&author_id=5"
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
**Obtener MR específico:**
|
|
957
|
+
```bash
|
|
958
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
959
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10"
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
**Crear merge request:**
|
|
963
|
+
```bash
|
|
964
|
+
curl --request POST \
|
|
965
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
966
|
+
--header "Content-Type: application/json" \
|
|
967
|
+
--data '{
|
|
968
|
+
"source_branch": "feature-branch",
|
|
969
|
+
"target_branch": "main",
|
|
970
|
+
"title": "Add new feature",
|
|
971
|
+
"description": "This MR adds a new feature",
|
|
972
|
+
"assignee_id": 5,
|
|
973
|
+
"labels": "feature,ready-for-review"
|
|
974
|
+
}' \
|
|
975
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests"
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
**Actualizar merge request:**
|
|
979
|
+
```bash
|
|
980
|
+
curl --request PUT \
|
|
981
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
982
|
+
--header "Content-Type: application/json" \
|
|
983
|
+
--data '{
|
|
984
|
+
"title": "Updated title",
|
|
985
|
+
"labels": "feature,reviewed"
|
|
986
|
+
}' \
|
|
987
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10"
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
**Aprobar merge request:**
|
|
991
|
+
```bash
|
|
992
|
+
curl --request POST \
|
|
993
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
994
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/approve"
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
**Merge merge request:**
|
|
998
|
+
```bash
|
|
999
|
+
curl --request PUT \
|
|
1000
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1001
|
+
--header "Content-Type: application/json" \
|
|
1002
|
+
--data '{
|
|
1003
|
+
"merge_commit_message": "Merge feature branch",
|
|
1004
|
+
"squash": true,
|
|
1005
|
+
"should_remove_source_branch": true
|
|
1006
|
+
}' \
|
|
1007
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/merge"
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
**Obtener cambios del MR:**
|
|
1011
|
+
```bash
|
|
1012
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1013
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/changes"
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
**Obtener commits del MR:**
|
|
1017
|
+
```bash
|
|
1018
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1019
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/commits"
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
#### GraphQL API
|
|
1023
|
+
|
|
1024
|
+
**Query merge requests:**
|
|
1025
|
+
```graphql
|
|
1026
|
+
query {
|
|
1027
|
+
project(fullPath: "grupo/proyecto") {
|
|
1028
|
+
mergeRequests(
|
|
1029
|
+
state: OPENED
|
|
1030
|
+
first: 20
|
|
1031
|
+
) {
|
|
1032
|
+
nodes {
|
|
1033
|
+
iid
|
|
1034
|
+
title
|
|
1035
|
+
description
|
|
1036
|
+
state
|
|
1037
|
+
sourceBranch
|
|
1038
|
+
targetBranch
|
|
1039
|
+
author {
|
|
1040
|
+
username
|
|
1041
|
+
}
|
|
1042
|
+
assignees {
|
|
1043
|
+
nodes {
|
|
1044
|
+
username
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
labels {
|
|
1048
|
+
nodes {
|
|
1049
|
+
title
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
createdAt
|
|
1053
|
+
updatedAt
|
|
1054
|
+
webUrl
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
```
|
|
1060
|
+
|
|
1061
|
+
**Mutation: Crear merge request:**
|
|
1062
|
+
```graphql
|
|
1063
|
+
mutation {
|
|
1064
|
+
mergeRequestCreate(input: {
|
|
1065
|
+
projectPath: "grupo/proyecto",
|
|
1066
|
+
sourceBranch: "feature-branch",
|
|
1067
|
+
targetBranch: "main",
|
|
1068
|
+
title: "Add new feature",
|
|
1069
|
+
description: "This MR adds a new feature"
|
|
1070
|
+
}) {
|
|
1071
|
+
mergeRequest {
|
|
1072
|
+
iid
|
|
1073
|
+
title
|
|
1074
|
+
webUrl
|
|
1075
|
+
}
|
|
1076
|
+
errors
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
**Mutation: Actualizar merge request:**
|
|
1082
|
+
```graphql
|
|
1083
|
+
mutation {
|
|
1084
|
+
mergeRequestUpdate(input: {
|
|
1085
|
+
projectPath: "grupo/proyecto",
|
|
1086
|
+
iid: "10",
|
|
1087
|
+
title: "Updated title"
|
|
1088
|
+
}) {
|
|
1089
|
+
mergeRequest {
|
|
1090
|
+
iid
|
|
1091
|
+
title
|
|
1092
|
+
}
|
|
1093
|
+
errors
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
**Mutation: Merge merge request:**
|
|
1099
|
+
```graphql
|
|
1100
|
+
mutation {
|
|
1101
|
+
mergeRequestMerge(input: {
|
|
1102
|
+
projectPath: "grupo/proyecto",
|
|
1103
|
+
iid: "10",
|
|
1104
|
+
squash: true,
|
|
1105
|
+
shouldRemoveSourceBranch: true
|
|
1106
|
+
}) {
|
|
1107
|
+
mergeRequest {
|
|
1108
|
+
iid
|
|
1109
|
+
state
|
|
1110
|
+
}
|
|
1111
|
+
errors
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
```
|
|
1115
|
+
|
|
1116
|
+
#### glab CLI
|
|
1117
|
+
|
|
1118
|
+
```bash
|
|
1119
|
+
# Listar merge requests
|
|
1120
|
+
glab mr list
|
|
1121
|
+
|
|
1122
|
+
# Listar MRs abiertos con filtro
|
|
1123
|
+
glab mr list --state opened --label feature
|
|
1124
|
+
|
|
1125
|
+
# Ver merge request
|
|
1126
|
+
glab mr view 10
|
|
1127
|
+
|
|
1128
|
+
# Ver MR en browser
|
|
1129
|
+
glab mr view 10 --web
|
|
1130
|
+
|
|
1131
|
+
# Crear merge request
|
|
1132
|
+
glab mr create --source-branch feature-branch \
|
|
1133
|
+
--target-branch main \
|
|
1134
|
+
--title "Add new feature" \
|
|
1135
|
+
--description "This MR adds a new feature" \
|
|
1136
|
+
--label feature
|
|
1137
|
+
|
|
1138
|
+
# Crear MR con template
|
|
1139
|
+
glab mr create --fill
|
|
1140
|
+
|
|
1141
|
+
# Checkout merge request localmente
|
|
1142
|
+
glab mr checkout 10
|
|
1143
|
+
|
|
1144
|
+
# Aprobar merge request
|
|
1145
|
+
glab mr approve 10
|
|
1146
|
+
|
|
1147
|
+
# Merge merge request
|
|
1148
|
+
glab mr merge 10
|
|
1149
|
+
|
|
1150
|
+
# Ver diff del MR
|
|
1151
|
+
glab mr diff 10
|
|
1152
|
+
|
|
1153
|
+
# Ver issues relacionados
|
|
1154
|
+
glab mr issues 10
|
|
1155
|
+
|
|
1156
|
+
# Agregar comentario
|
|
1157
|
+
glab mr note 10 --message "LGTM!"
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
---
|
|
1161
|
+
|
|
1162
|
+
### 5. Pipelines
|
|
1163
|
+
|
|
1164
|
+
#### REST API
|
|
1165
|
+
|
|
1166
|
+
**Listar pipelines:**
|
|
1167
|
+
```bash
|
|
1168
|
+
# Todos los pipelines
|
|
1169
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1170
|
+
--url "https://gitlab.example.com/api/v4/projects/123/pipelines"
|
|
1171
|
+
|
|
1172
|
+
# Pipelines con filtros
|
|
1173
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1174
|
+
--url "https://gitlab.example.com/api/v4/projects/123/pipelines?status=running&ref=main"
|
|
1175
|
+
|
|
1176
|
+
# Pipelines por fecha
|
|
1177
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1178
|
+
--url "https://gitlab.example.com/api/v4/projects/123/pipelines?updated_after=2026-01-01T00:00:00Z"
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
**Obtener pipeline específico:**
|
|
1182
|
+
```bash
|
|
1183
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1184
|
+
--url "https://gitlab.example.com/api/v4/projects/123/pipelines/456"
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
**Obtener último pipeline:**
|
|
1188
|
+
```bash
|
|
1189
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1190
|
+
--url "https://gitlab.example.com/api/v4/projects/123/pipelines/latest?ref=main"
|
|
1191
|
+
```
|
|
1192
|
+
|
|
1193
|
+
**Crear pipeline:**
|
|
1194
|
+
```bash
|
|
1195
|
+
# Pipeline simple
|
|
1196
|
+
curl --request POST \
|
|
1197
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1198
|
+
--url "https://gitlab.example.com/api/v4/projects/123/pipeline?ref=main"
|
|
1199
|
+
|
|
1200
|
+
# Pipeline con variables
|
|
1201
|
+
curl --request POST \
|
|
1202
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1203
|
+
--header "Content-Type: application/json" \
|
|
1204
|
+
--data '{
|
|
1205
|
+
"ref": "main",
|
|
1206
|
+
"variables": [
|
|
1207
|
+
{"key": "DEPLOY_ENV", "value": "production"},
|
|
1208
|
+
{"key": "DEBUG", "value": "true"}
|
|
1209
|
+
]
|
|
1210
|
+
}' \
|
|
1211
|
+
--url "https://gitlab.example.com/api/v4/projects/123/pipeline"
|
|
1212
|
+
|
|
1213
|
+
# Pipeline con inputs
|
|
1214
|
+
curl --request POST \
|
|
1215
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1216
|
+
--header "Content-Type: application/json" \
|
|
1217
|
+
--data '{
|
|
1218
|
+
"ref": "main",
|
|
1219
|
+
"inputs": {
|
|
1220
|
+
"environment": "production",
|
|
1221
|
+
"scan_security": true,
|
|
1222
|
+
"level": 3
|
|
1223
|
+
}
|
|
1224
|
+
}' \
|
|
1225
|
+
--url "https://gitlab.example.com/api/v4/projects/123/pipeline"
|
|
1226
|
+
```
|
|
1227
|
+
|
|
1228
|
+
**Reintentar pipeline:**
|
|
1229
|
+
```bash
|
|
1230
|
+
curl --request POST \
|
|
1231
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1232
|
+
--url "https://gitlab.example.com/api/v4/projects/123/pipelines/456/retry"
|
|
1233
|
+
```
|
|
1234
|
+
|
|
1235
|
+
**Cancelar pipeline:**
|
|
1236
|
+
```bash
|
|
1237
|
+
curl --request POST \
|
|
1238
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1239
|
+
--url "https://gitlab.example.com/api/v4/projects/123/pipelines/456/cancel"
|
|
1240
|
+
```
|
|
1241
|
+
|
|
1242
|
+
**Eliminar pipeline:**
|
|
1243
|
+
```bash
|
|
1244
|
+
curl --request DELETE \
|
|
1245
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1246
|
+
--url "https://gitlab.example.com/api/v4/projects/123/pipelines/456"
|
|
1247
|
+
```
|
|
1248
|
+
|
|
1249
|
+
**Obtener variables del pipeline:**
|
|
1250
|
+
```bash
|
|
1251
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1252
|
+
--url "https://gitlab.example.com/api/v4/projects/123/pipelines/456/variables"
|
|
1253
|
+
```
|
|
1254
|
+
|
|
1255
|
+
**Obtener test report:**
|
|
1256
|
+
```bash
|
|
1257
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1258
|
+
--url "https://gitlab.example.com/api/v4/projects/123/pipelines/456/test_report"
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
#### GraphQL API
|
|
1262
|
+
|
|
1263
|
+
**Query pipelines:**
|
|
1264
|
+
```graphql
|
|
1265
|
+
query {
|
|
1266
|
+
project(fullPath: "grupo/proyecto") {
|
|
1267
|
+
pipelines(first: 20, status: RUNNING) {
|
|
1268
|
+
nodes {
|
|
1269
|
+
id
|
|
1270
|
+
iid
|
|
1271
|
+
status
|
|
1272
|
+
ref
|
|
1273
|
+
sha
|
|
1274
|
+
createdAt
|
|
1275
|
+
updatedAt
|
|
1276
|
+
duration
|
|
1277
|
+
coverage
|
|
1278
|
+
user {
|
|
1279
|
+
username
|
|
1280
|
+
}
|
|
1281
|
+
jobs {
|
|
1282
|
+
nodes {
|
|
1283
|
+
name
|
|
1284
|
+
status
|
|
1285
|
+
stage
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
```
|
|
1293
|
+
|
|
1294
|
+
**Mutation: Crear pipeline:**
|
|
1295
|
+
```graphql
|
|
1296
|
+
mutation {
|
|
1297
|
+
pipelineCreate(input: {
|
|
1298
|
+
projectPath: "grupo/proyecto",
|
|
1299
|
+
ref: "main"
|
|
1300
|
+
}) {
|
|
1301
|
+
pipeline {
|
|
1302
|
+
id
|
|
1303
|
+
iid
|
|
1304
|
+
status
|
|
1305
|
+
}
|
|
1306
|
+
errors
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
**Mutation: Cancelar pipeline:**
|
|
1312
|
+
```graphql
|
|
1313
|
+
mutation {
|
|
1314
|
+
pipelineCancel(input: {
|
|
1315
|
+
id: "gid://gitlab/Ci::Pipeline/456"
|
|
1316
|
+
}) {
|
|
1317
|
+
pipeline {
|
|
1318
|
+
id
|
|
1319
|
+
status
|
|
1320
|
+
}
|
|
1321
|
+
errors
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
```
|
|
1325
|
+
|
|
1326
|
+
**Mutation: Reintentar pipeline:**
|
|
1327
|
+
```graphql
|
|
1328
|
+
mutation {
|
|
1329
|
+
pipelineRetry(input: {
|
|
1330
|
+
id: "gid://gitlab/Ci::Pipeline/456"
|
|
1331
|
+
}) {
|
|
1332
|
+
pipeline {
|
|
1333
|
+
id
|
|
1334
|
+
status
|
|
1335
|
+
}
|
|
1336
|
+
errors
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
```
|
|
1340
|
+
|
|
1341
|
+
#### glab CLI
|
|
1342
|
+
|
|
1343
|
+
```bash
|
|
1344
|
+
# Listar pipelines
|
|
1345
|
+
glab ci list
|
|
1346
|
+
glab pipeline list
|
|
1347
|
+
|
|
1348
|
+
# Ver pipeline específico
|
|
1349
|
+
glab ci view 456
|
|
1350
|
+
glab pipeline view 456
|
|
1351
|
+
|
|
1352
|
+
# Ver status del pipeline actual
|
|
1353
|
+
glab ci status
|
|
1354
|
+
|
|
1355
|
+
# Ver trace de pipeline
|
|
1356
|
+
glab ci trace
|
|
1357
|
+
|
|
1358
|
+
# Reintentar pipeline
|
|
1359
|
+
glab pipeline retry 456
|
|
1360
|
+
|
|
1361
|
+
# Cancelar pipeline
|
|
1362
|
+
glab pipeline cancel 456
|
|
1363
|
+
|
|
1364
|
+
# Eliminar pipeline
|
|
1365
|
+
glab pipeline delete 456
|
|
1366
|
+
|
|
1367
|
+
# Ejecutar pipeline manualmente
|
|
1368
|
+
glab pipeline run
|
|
1369
|
+
glab pipeline run --branch main --variables "DEPLOY_ENV=production,DEBUG=true"
|
|
1370
|
+
```
|
|
1371
|
+
|
|
1372
|
+
---
|
|
1373
|
+
|
|
1374
|
+
### 6. Jobs
|
|
1375
|
+
|
|
1376
|
+
#### REST API
|
|
1377
|
+
|
|
1378
|
+
**Listar jobs de un proyecto:**
|
|
1379
|
+
```bash
|
|
1380
|
+
# Todos los jobs
|
|
1381
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1382
|
+
--url "https://gitlab.example.com/api/v4/projects/123/jobs"
|
|
1383
|
+
|
|
1384
|
+
# Jobs de un pipeline específico
|
|
1385
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1386
|
+
--url "https://gitlab.example.com/api/v4/projects/123/pipelines/456/jobs"
|
|
1387
|
+
|
|
1388
|
+
# Jobs con filtros
|
|
1389
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1390
|
+
--url "https://gitlab.example.com/api/v4/projects/123/jobs?scope[]=failed&scope[]=running"
|
|
1391
|
+
```
|
|
1392
|
+
|
|
1393
|
+
**Obtener job específico:**
|
|
1394
|
+
```bash
|
|
1395
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1396
|
+
--url "https://gitlab.example.com/api/v4/projects/123/jobs/789"
|
|
1397
|
+
```
|
|
1398
|
+
|
|
1399
|
+
**Obtener trace/log de un job:**
|
|
1400
|
+
```bash
|
|
1401
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1402
|
+
--url "https://gitlab.example.com/api/v4/projects/123/jobs/789/trace"
|
|
1403
|
+
```
|
|
1404
|
+
|
|
1405
|
+
**Reintentar job:**
|
|
1406
|
+
```bash
|
|
1407
|
+
curl --request POST \
|
|
1408
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1409
|
+
--url "https://gitlab.example.com/api/v4/projects/123/jobs/789/retry"
|
|
1410
|
+
```
|
|
1411
|
+
|
|
1412
|
+
**Cancelar job:**
|
|
1413
|
+
```bash
|
|
1414
|
+
curl --request POST \
|
|
1415
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1416
|
+
--url "https://gitlab.example.com/api/v4/projects/123/jobs/789/cancel"
|
|
1417
|
+
```
|
|
1418
|
+
|
|
1419
|
+
**Ejecutar job manual:**
|
|
1420
|
+
```bash
|
|
1421
|
+
curl --request POST \
|
|
1422
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1423
|
+
--url "https://gitlab.example.com/api/v4/projects/123/jobs/789/play"
|
|
1424
|
+
```
|
|
1425
|
+
|
|
1426
|
+
**Descargar artifacts:**
|
|
1427
|
+
```bash
|
|
1428
|
+
# Artifacts de un job específico
|
|
1429
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1430
|
+
--output artifacts.zip \
|
|
1431
|
+
--url "https://gitlab.example.com/api/v4/projects/123/jobs/789/artifacts"
|
|
1432
|
+
|
|
1433
|
+
# Artifacts por job name y ref
|
|
1434
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1435
|
+
--output artifacts.zip \
|
|
1436
|
+
--url "https://gitlab.example.com/api/v4/projects/123/jobs/artifacts/main/download?job=build"
|
|
1437
|
+
```
|
|
1438
|
+
|
|
1439
|
+
**Obtener archivo específico de artifacts:**
|
|
1440
|
+
```bash
|
|
1441
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1442
|
+
--url "https://gitlab.example.com/api/v4/projects/123/jobs/789/artifacts/path/to/file.txt"
|
|
1443
|
+
```
|
|
1444
|
+
|
|
1445
|
+
#### GraphQL API
|
|
1446
|
+
|
|
1447
|
+
**Query jobs:**
|
|
1448
|
+
```graphql
|
|
1449
|
+
query {
|
|
1450
|
+
project(fullPath: "grupo/proyecto") {
|
|
1451
|
+
pipeline(iid: "456") {
|
|
1452
|
+
jobs {
|
|
1453
|
+
nodes {
|
|
1454
|
+
id
|
|
1455
|
+
name
|
|
1456
|
+
status
|
|
1457
|
+
stage
|
|
1458
|
+
duration
|
|
1459
|
+
createdAt
|
|
1460
|
+
finishedAt
|
|
1461
|
+
webPath
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
```
|
|
1468
|
+
|
|
1469
|
+
#### glab CLI
|
|
1470
|
+
|
|
1471
|
+
```bash
|
|
1472
|
+
# Listar jobs del pipeline actual
|
|
1473
|
+
glab ci view
|
|
1474
|
+
|
|
1475
|
+
# Ver trace de un job
|
|
1476
|
+
glab ci trace
|
|
1477
|
+
glab ci trace <job-id>
|
|
1478
|
+
|
|
1479
|
+
# Ver trace de job específico por nombre
|
|
1480
|
+
glab ci trace --job build
|
|
1481
|
+
|
|
1482
|
+
# Reintentar job
|
|
1483
|
+
glab job retry <job-id>
|
|
1484
|
+
|
|
1485
|
+
# Cancelar job
|
|
1486
|
+
glab job cancel <job-id>
|
|
1487
|
+
|
|
1488
|
+
# Ejecutar job manual
|
|
1489
|
+
glab job play <job-id>
|
|
1490
|
+
|
|
1491
|
+
# Descargar artifacts
|
|
1492
|
+
glab job artifacts <job-id>
|
|
1493
|
+
```
|
|
1494
|
+
|
|
1495
|
+
---
|
|
1496
|
+
|
|
1497
|
+
### 7. Repository & Files
|
|
1498
|
+
|
|
1499
|
+
#### REST API - Repository
|
|
1500
|
+
|
|
1501
|
+
**Obtener información del repositorio:**
|
|
1502
|
+
```bash
|
|
1503
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1504
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository"
|
|
1505
|
+
```
|
|
1506
|
+
|
|
1507
|
+
**Listar branches:**
|
|
1508
|
+
```bash
|
|
1509
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1510
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/branches"
|
|
1511
|
+
```
|
|
1512
|
+
|
|
1513
|
+
**Crear branch:**
|
|
1514
|
+
```bash
|
|
1515
|
+
curl --request POST \
|
|
1516
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1517
|
+
--header "Content-Type: application/json" \
|
|
1518
|
+
--data '{
|
|
1519
|
+
"branch": "new-feature",
|
|
1520
|
+
"ref": "main"
|
|
1521
|
+
}' \
|
|
1522
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/branches"
|
|
1523
|
+
```
|
|
1524
|
+
|
|
1525
|
+
**Eliminar branch:**
|
|
1526
|
+
```bash
|
|
1527
|
+
curl --request DELETE \
|
|
1528
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1529
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/branches/feature-branch"
|
|
1530
|
+
```
|
|
1531
|
+
|
|
1532
|
+
**Listar tags:**
|
|
1533
|
+
```bash
|
|
1534
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1535
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/tags"
|
|
1536
|
+
```
|
|
1537
|
+
|
|
1538
|
+
**Crear tag:**
|
|
1539
|
+
```bash
|
|
1540
|
+
curl --request POST \
|
|
1541
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1542
|
+
--header "Content-Type: application/json" \
|
|
1543
|
+
--data '{
|
|
1544
|
+
"tag_name": "v1.0.0",
|
|
1545
|
+
"ref": "main",
|
|
1546
|
+
"message": "Release version 1.0.0"
|
|
1547
|
+
}' \
|
|
1548
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/tags"
|
|
1549
|
+
```
|
|
1550
|
+
|
|
1551
|
+
**Listar commits:**
|
|
1552
|
+
```bash
|
|
1553
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1554
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/commits"
|
|
1555
|
+
|
|
1556
|
+
# Con filtros
|
|
1557
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1558
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/commits?ref_name=main&since=2026-01-01T00:00:00Z"
|
|
1559
|
+
```
|
|
1560
|
+
|
|
1561
|
+
**Obtener commit específico:**
|
|
1562
|
+
```bash
|
|
1563
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1564
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/commits/abc123"
|
|
1565
|
+
```
|
|
1566
|
+
|
|
1567
|
+
**Listar árbol de archivos:**
|
|
1568
|
+
```bash
|
|
1569
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1570
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/tree"
|
|
1571
|
+
|
|
1572
|
+
# Subdirectorio específico
|
|
1573
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1574
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/tree?path=src/components"
|
|
1575
|
+
```
|
|
1576
|
+
|
|
1577
|
+
#### REST API - Files
|
|
1578
|
+
|
|
1579
|
+
**Obtener archivo:**
|
|
1580
|
+
```bash
|
|
1581
|
+
# Información completa (Base64 encoded)
|
|
1582
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1583
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/files/src%2Fapp.js?ref=main"
|
|
1584
|
+
|
|
1585
|
+
# Contenido raw
|
|
1586
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1587
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/files/src%2Fapp.js/raw?ref=main"
|
|
1588
|
+
|
|
1589
|
+
# Usar HEAD para default branch
|
|
1590
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1591
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/files/README.md?ref=HEAD"
|
|
1592
|
+
```
|
|
1593
|
+
|
|
1594
|
+
**Crear archivo:**
|
|
1595
|
+
```bash
|
|
1596
|
+
curl --request POST \
|
|
1597
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1598
|
+
--header "Content-Type: application/json" \
|
|
1599
|
+
--data '{
|
|
1600
|
+
"branch": "main",
|
|
1601
|
+
"commit_message": "Add new file",
|
|
1602
|
+
"content": "console.log(\"Hello World\");",
|
|
1603
|
+
"author_name": "John Doe",
|
|
1604
|
+
"author_email": "john@example.com"
|
|
1605
|
+
}' \
|
|
1606
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/files/src%2Fnew-file.js"
|
|
1607
|
+
```
|
|
1608
|
+
|
|
1609
|
+
**Actualizar archivo:**
|
|
1610
|
+
```bash
|
|
1611
|
+
curl --request PUT \
|
|
1612
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1613
|
+
--header "Content-Type: application/json" \
|
|
1614
|
+
--data '{
|
|
1615
|
+
"branch": "main",
|
|
1616
|
+
"commit_message": "Update file",
|
|
1617
|
+
"content": "console.log(\"Updated content\");"
|
|
1618
|
+
}' \
|
|
1619
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/files/src%2Fapp.js"
|
|
1620
|
+
```
|
|
1621
|
+
|
|
1622
|
+
**Eliminar archivo:**
|
|
1623
|
+
```bash
|
|
1624
|
+
curl --request DELETE \
|
|
1625
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1626
|
+
--header "Content-Type: application/json" \
|
|
1627
|
+
--data '{
|
|
1628
|
+
"branch": "main",
|
|
1629
|
+
"commit_message": "Delete file"
|
|
1630
|
+
}' \
|
|
1631
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/files/src%2Fold-file.js"
|
|
1632
|
+
```
|
|
1633
|
+
|
|
1634
|
+
**Obtener blame de archivo:**
|
|
1635
|
+
```bash
|
|
1636
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1637
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/files/src%2Fapp.js/blame?ref=main"
|
|
1638
|
+
|
|
1639
|
+
# Rango específico
|
|
1640
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1641
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/files/src%2Fapp.js/blame?ref=main&range[start]=1&range[end]=50"
|
|
1642
|
+
```
|
|
1643
|
+
|
|
1644
|
+
#### GraphQL API
|
|
1645
|
+
|
|
1646
|
+
**Query repository:**
|
|
1647
|
+
```graphql
|
|
1648
|
+
query {
|
|
1649
|
+
project(fullPath: "grupo/proyecto") {
|
|
1650
|
+
repository {
|
|
1651
|
+
rootRef
|
|
1652
|
+
tree {
|
|
1653
|
+
trees {
|
|
1654
|
+
nodes {
|
|
1655
|
+
name
|
|
1656
|
+
type
|
|
1657
|
+
path
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
blobs {
|
|
1661
|
+
nodes {
|
|
1662
|
+
name
|
|
1663
|
+
path
|
|
1664
|
+
size
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
```
|
|
1672
|
+
|
|
1673
|
+
**Query archivo específico:**
|
|
1674
|
+
```graphql
|
|
1675
|
+
query {
|
|
1676
|
+
project(fullPath: "grupo/proyecto") {
|
|
1677
|
+
repository {
|
|
1678
|
+
blobs(paths: ["README.md", "src/app.js"]) {
|
|
1679
|
+
nodes {
|
|
1680
|
+
path
|
|
1681
|
+
name
|
|
1682
|
+
size
|
|
1683
|
+
rawBlob
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
```
|
|
1690
|
+
|
|
1691
|
+
#### glab CLI
|
|
1692
|
+
|
|
1693
|
+
```bash
|
|
1694
|
+
# Ver repositorio
|
|
1695
|
+
glab repo view
|
|
1696
|
+
|
|
1697
|
+
# Clonar repositorio
|
|
1698
|
+
glab repo clone grupo/proyecto
|
|
1699
|
+
|
|
1700
|
+
# Ver archivo
|
|
1701
|
+
glab repo view --file src/app.js
|
|
1702
|
+
|
|
1703
|
+
# Ver commits
|
|
1704
|
+
glab repo view --commits
|
|
1705
|
+
|
|
1706
|
+
# Ver branches
|
|
1707
|
+
glab repo view --branches
|
|
1708
|
+
```
|
|
1709
|
+
|
|
1710
|
+
---
|
|
1711
|
+
|
|
1712
|
+
### 8. Releases
|
|
1713
|
+
|
|
1714
|
+
#### REST API
|
|
1715
|
+
|
|
1716
|
+
**Listar releases:**
|
|
1717
|
+
```bash
|
|
1718
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1719
|
+
--url "https://gitlab.example.com/api/v4/projects/123/releases"
|
|
1720
|
+
```
|
|
1721
|
+
|
|
1722
|
+
**Obtener release específico:**
|
|
1723
|
+
```bash
|
|
1724
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1725
|
+
--url "https://gitlab.example.com/api/v4/projects/123/releases/v1.0.0"
|
|
1726
|
+
```
|
|
1727
|
+
|
|
1728
|
+
**Crear release:**
|
|
1729
|
+
```bash
|
|
1730
|
+
curl --request POST \
|
|
1731
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1732
|
+
--header "Content-Type: application/json" \
|
|
1733
|
+
--data '{
|
|
1734
|
+
"tag_name": "v1.0.0",
|
|
1735
|
+
"name": "Release 1.0.0",
|
|
1736
|
+
"description": "## Features\n- New feature 1\n- New feature 2\n\n## Bug Fixes\n- Fixed bug 1",
|
|
1737
|
+
"ref": "main",
|
|
1738
|
+
"assets": {
|
|
1739
|
+
"links": [
|
|
1740
|
+
{
|
|
1741
|
+
"name": "Documentation",
|
|
1742
|
+
"url": "https://docs.example.com"
|
|
1743
|
+
}
|
|
1744
|
+
]
|
|
1745
|
+
}
|
|
1746
|
+
}' \
|
|
1747
|
+
--url "https://gitlab.example.com/api/v4/projects/123/releases"
|
|
1748
|
+
```
|
|
1749
|
+
|
|
1750
|
+
**Actualizar release:**
|
|
1751
|
+
```bash
|
|
1752
|
+
curl --request PUT \
|
|
1753
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1754
|
+
--header "Content-Type: application/json" \
|
|
1755
|
+
--data '{
|
|
1756
|
+
"name": "Release 1.0.0 - Updated",
|
|
1757
|
+
"description": "Updated description"
|
|
1758
|
+
}' \
|
|
1759
|
+
--url "https://gitlab.example.com/api/v4/projects/123/releases/v1.0.0"
|
|
1760
|
+
```
|
|
1761
|
+
|
|
1762
|
+
**Eliminar release:**
|
|
1763
|
+
```bash
|
|
1764
|
+
curl --request DELETE \
|
|
1765
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1766
|
+
--url "https://gitlab.example.com/api/v4/projects/123/releases/v1.0.0"
|
|
1767
|
+
```
|
|
1768
|
+
|
|
1769
|
+
#### GraphQL API
|
|
1770
|
+
|
|
1771
|
+
**Query releases:**
|
|
1772
|
+
```graphql
|
|
1773
|
+
query {
|
|
1774
|
+
project(fullPath: "grupo/proyecto") {
|
|
1775
|
+
releases(first: 10) {
|
|
1776
|
+
nodes {
|
|
1777
|
+
tagName
|
|
1778
|
+
name
|
|
1779
|
+
description
|
|
1780
|
+
createdAt
|
|
1781
|
+
releasedAt
|
|
1782
|
+
assets {
|
|
1783
|
+
links {
|
|
1784
|
+
nodes {
|
|
1785
|
+
name
|
|
1786
|
+
url
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
```
|
|
1795
|
+
|
|
1796
|
+
**Mutation: Crear release:**
|
|
1797
|
+
```graphql
|
|
1798
|
+
mutation {
|
|
1799
|
+
releaseCreate(input: {
|
|
1800
|
+
projectPath: "grupo/proyecto",
|
|
1801
|
+
tagName: "v1.0.0",
|
|
1802
|
+
name: "Release 1.0.0",
|
|
1803
|
+
description: "Release notes",
|
|
1804
|
+
ref: "main"
|
|
1805
|
+
}) {
|
|
1806
|
+
release {
|
|
1807
|
+
tagName
|
|
1808
|
+
name
|
|
1809
|
+
}
|
|
1810
|
+
errors
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
```
|
|
1814
|
+
|
|
1815
|
+
#### glab CLI
|
|
1816
|
+
|
|
1817
|
+
```bash
|
|
1818
|
+
# Listar releases
|
|
1819
|
+
glab release list
|
|
1820
|
+
|
|
1821
|
+
# Ver release específico
|
|
1822
|
+
glab release view v1.0.0
|
|
1823
|
+
|
|
1824
|
+
# Crear release
|
|
1825
|
+
glab release create v1.0.0 \
|
|
1826
|
+
--name "Release 1.0.0" \
|
|
1827
|
+
--notes "Release notes"
|
|
1828
|
+
|
|
1829
|
+
# Crear release con assets
|
|
1830
|
+
glab release create v1.0.0 \
|
|
1831
|
+
--name "Release 1.0.0" \
|
|
1832
|
+
--notes "Release notes" \
|
|
1833
|
+
--asset-link '{"name":"Documentation","url":"https://docs.example.com"}'
|
|
1834
|
+
|
|
1835
|
+
# Eliminar release
|
|
1836
|
+
glab release delete v1.0.0
|
|
1837
|
+
```
|
|
1838
|
+
|
|
1839
|
+
---
|
|
1840
|
+
|
|
1841
|
+
### 9. Webhooks (Project Hooks)
|
|
1842
|
+
|
|
1843
|
+
#### REST API
|
|
1844
|
+
|
|
1845
|
+
**Listar hooks:**
|
|
1846
|
+
```bash
|
|
1847
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1848
|
+
--url "https://gitlab.example.com/api/v4/projects/123/hooks"
|
|
1849
|
+
```
|
|
1850
|
+
|
|
1851
|
+
**Obtener hook específico:**
|
|
1852
|
+
```bash
|
|
1853
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1854
|
+
--url "https://gitlab.example.com/api/v4/projects/123/hooks/456"
|
|
1855
|
+
```
|
|
1856
|
+
|
|
1857
|
+
**Crear hook:**
|
|
1858
|
+
```bash
|
|
1859
|
+
curl --request POST \
|
|
1860
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1861
|
+
--header "Content-Type: application/json" \
|
|
1862
|
+
--data '{
|
|
1863
|
+
"url": "https://example.com/webhook",
|
|
1864
|
+
"push_events": true,
|
|
1865
|
+
"merge_requests_events": true,
|
|
1866
|
+
"issues_events": true,
|
|
1867
|
+
"pipeline_events": true,
|
|
1868
|
+
"job_events": true,
|
|
1869
|
+
"token": "secret-token",
|
|
1870
|
+
"enable_ssl_verification": true
|
|
1871
|
+
}' \
|
|
1872
|
+
--url "https://gitlab.example.com/api/v4/projects/123/hooks"
|
|
1873
|
+
```
|
|
1874
|
+
|
|
1875
|
+
**Actualizar hook:**
|
|
1876
|
+
```bash
|
|
1877
|
+
curl --request PUT \
|
|
1878
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1879
|
+
--header "Content-Type: application/json" \
|
|
1880
|
+
--data '{
|
|
1881
|
+
"url": "https://example.com/webhook-updated",
|
|
1882
|
+
"push_events": false
|
|
1883
|
+
}' \
|
|
1884
|
+
--url "https://gitlab.example.com/api/v4/projects/123/hooks/456"
|
|
1885
|
+
```
|
|
1886
|
+
|
|
1887
|
+
**Eliminar hook:**
|
|
1888
|
+
```bash
|
|
1889
|
+
curl --request DELETE \
|
|
1890
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1891
|
+
--url "https://gitlab.example.com/api/v4/projects/123/hooks/456"
|
|
1892
|
+
```
|
|
1893
|
+
|
|
1894
|
+
**Testear hook:**
|
|
1895
|
+
```bash
|
|
1896
|
+
curl --request POST \
|
|
1897
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1898
|
+
--url "https://gitlab.example.com/api/v4/projects/123/hooks/456/test/push_events"
|
|
1899
|
+
```
|
|
1900
|
+
|
|
1901
|
+
#### glab CLI
|
|
1902
|
+
|
|
1903
|
+
```bash
|
|
1904
|
+
# Listar hooks
|
|
1905
|
+
glab api projects/123/hooks
|
|
1906
|
+
|
|
1907
|
+
# Crear hook
|
|
1908
|
+
glab api --method POST projects/123/hooks \
|
|
1909
|
+
--field url=https://example.com/webhook \
|
|
1910
|
+
--field push_events=true \
|
|
1911
|
+
--field merge_requests_events=true
|
|
1912
|
+
|
|
1913
|
+
# Eliminar hook
|
|
1914
|
+
glab api --method DELETE projects/123/hooks/456
|
|
1915
|
+
```
|
|
1916
|
+
|
|
1917
|
+
---
|
|
1918
|
+
|
|
1919
|
+
### 10. Variables CI/CD
|
|
1920
|
+
|
|
1921
|
+
#### REST API
|
|
1922
|
+
|
|
1923
|
+
**Listar variables:**
|
|
1924
|
+
```bash
|
|
1925
|
+
# Variables de proyecto
|
|
1926
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1927
|
+
--url "https://gitlab.example.com/api/v4/projects/123/variables"
|
|
1928
|
+
|
|
1929
|
+
# Variables de grupo
|
|
1930
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1931
|
+
--url "https://gitlab.example.com/api/v4/groups/456/variables"
|
|
1932
|
+
```
|
|
1933
|
+
|
|
1934
|
+
**Obtener variable específica:**
|
|
1935
|
+
```bash
|
|
1936
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
1937
|
+
--url "https://gitlab.example.com/api/v4/projects/123/variables/DEPLOY_KEY"
|
|
1938
|
+
```
|
|
1939
|
+
|
|
1940
|
+
**Crear variable:**
|
|
1941
|
+
```bash
|
|
1942
|
+
curl --request POST \
|
|
1943
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1944
|
+
--header "Content-Type: application/json" \
|
|
1945
|
+
--data '{
|
|
1946
|
+
"key": "DEPLOY_KEY",
|
|
1947
|
+
"value": "secret-value",
|
|
1948
|
+
"protected": true,
|
|
1949
|
+
"masked": true,
|
|
1950
|
+
"environment_scope": "production"
|
|
1951
|
+
}' \
|
|
1952
|
+
--url "https://gitlab.example.com/api/v4/projects/123/variables"
|
|
1953
|
+
```
|
|
1954
|
+
|
|
1955
|
+
**Actualizar variable:**
|
|
1956
|
+
```bash
|
|
1957
|
+
curl --request PUT \
|
|
1958
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1959
|
+
--header "Content-Type: application/json" \
|
|
1960
|
+
--data '{
|
|
1961
|
+
"value": "new-secret-value",
|
|
1962
|
+
"protected": false
|
|
1963
|
+
}' \
|
|
1964
|
+
--url "https://gitlab.example.com/api/v4/projects/123/variables/DEPLOY_KEY"
|
|
1965
|
+
```
|
|
1966
|
+
|
|
1967
|
+
**Eliminar variable:**
|
|
1968
|
+
```bash
|
|
1969
|
+
curl --request DELETE \
|
|
1970
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
1971
|
+
--url "https://gitlab.example.com/api/v4/projects/123/variables/DEPLOY_KEY"
|
|
1972
|
+
```
|
|
1973
|
+
|
|
1974
|
+
#### glab CLI
|
|
1975
|
+
|
|
1976
|
+
```bash
|
|
1977
|
+
# Listar variables
|
|
1978
|
+
glab variable list
|
|
1979
|
+
|
|
1980
|
+
# Obtener variable específica
|
|
1981
|
+
glab variable get DEPLOY_KEY
|
|
1982
|
+
|
|
1983
|
+
# Crear variable
|
|
1984
|
+
glab variable set DEPLOY_KEY "secret-value" \
|
|
1985
|
+
--scope production \
|
|
1986
|
+
--protected \
|
|
1987
|
+
--masked
|
|
1988
|
+
|
|
1989
|
+
# Actualizar variable
|
|
1990
|
+
glab variable update DEPLOY_KEY "new-value"
|
|
1991
|
+
|
|
1992
|
+
# Eliminar variable
|
|
1993
|
+
glab variable delete DEPLOY_KEY
|
|
1994
|
+
```
|
|
1995
|
+
|
|
1996
|
+
---
|
|
1997
|
+
|
|
1998
|
+
### 11. GraphQL - Queries Avanzadas
|
|
1999
|
+
|
|
2000
|
+
#### Query con múltiples recursos:
|
|
2001
|
+
```graphql
|
|
2002
|
+
query {
|
|
2003
|
+
currentUser {
|
|
2004
|
+
id
|
|
2005
|
+
username
|
|
2006
|
+
name
|
|
2007
|
+
}
|
|
2008
|
+
project(fullPath: "grupo/proyecto") {
|
|
2009
|
+
id
|
|
2010
|
+
name
|
|
2011
|
+
issues(first: 5, state: OPENED) {
|
|
2012
|
+
nodes {
|
|
2013
|
+
iid
|
|
2014
|
+
title
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
mergeRequests(first: 5, state: OPENED) {
|
|
2018
|
+
nodes {
|
|
2019
|
+
iid
|
|
2020
|
+
title
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
```
|
|
2026
|
+
|
|
2027
|
+
#### Query con paginación completa:
|
|
2028
|
+
```graphql
|
|
2029
|
+
query($cursor: String) {
|
|
2030
|
+
project(fullPath: "grupo/proyecto") {
|
|
2031
|
+
issues(
|
|
2032
|
+
first: 20
|
|
2033
|
+
after: $cursor
|
|
2034
|
+
state: OPENED
|
|
2035
|
+
sort: CREATED_DESC
|
|
2036
|
+
) {
|
|
2037
|
+
edges {
|
|
2038
|
+
node {
|
|
2039
|
+
iid
|
|
2040
|
+
title
|
|
2041
|
+
description
|
|
2042
|
+
labels {
|
|
2043
|
+
nodes {
|
|
2044
|
+
title
|
|
2045
|
+
color
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
assignees {
|
|
2049
|
+
nodes {
|
|
2050
|
+
username
|
|
2051
|
+
name
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
author {
|
|
2055
|
+
username
|
|
2056
|
+
}
|
|
2057
|
+
createdAt
|
|
2058
|
+
updatedAt
|
|
2059
|
+
}
|
|
2060
|
+
cursor
|
|
2061
|
+
}
|
|
2062
|
+
pageInfo {
|
|
2063
|
+
hasNextPage
|
|
2064
|
+
hasPreviousPage
|
|
2065
|
+
startCursor
|
|
2066
|
+
endCursor
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
```
|
|
2072
|
+
|
|
2073
|
+
Variables:
|
|
2074
|
+
```json
|
|
2075
|
+
{
|
|
2076
|
+
"cursor": null
|
|
2077
|
+
}
|
|
2078
|
+
```
|
|
2079
|
+
|
|
2080
|
+
#### Query con complejidad:
|
|
2081
|
+
```graphql
|
|
2082
|
+
query {
|
|
2083
|
+
queryComplexity {
|
|
2084
|
+
score
|
|
2085
|
+
limit
|
|
2086
|
+
}
|
|
2087
|
+
project(fullPath: "grupo/proyecto") {
|
|
2088
|
+
name
|
|
2089
|
+
issues(first: 10) {
|
|
2090
|
+
nodes {
|
|
2091
|
+
title
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
```
|
|
2097
|
+
|
|
2098
|
+
#### Multiplex queries (batch):
|
|
2099
|
+
```bash
|
|
2100
|
+
curl --request POST \
|
|
2101
|
+
--header "Authorization: Bearer $TOKEN" \
|
|
2102
|
+
--header "Content-Type: application/json" \
|
|
2103
|
+
--data '[
|
|
2104
|
+
{"query": "query {project(fullPath: \"grupo/proyecto1\") {id name}}"},
|
|
2105
|
+
{"query": "query {project(fullPath: \"grupo/proyecto2\") {id name}}"},
|
|
2106
|
+
{"query": "query {currentUser {username}}"}
|
|
2107
|
+
]' \
|
|
2108
|
+
--url "https://gitlab.com/api/graphql"
|
|
2109
|
+
```
|
|
2110
|
+
|
|
2111
|
+
---
|
|
2112
|
+
|
|
2113
|
+
### 12. GraphQL - Mutations Avanzadas
|
|
2114
|
+
|
|
2115
|
+
#### Mutation con múltiples operaciones:
|
|
2116
|
+
```graphql
|
|
2117
|
+
mutation {
|
|
2118
|
+
createIssue: createIssue(input: {
|
|
2119
|
+
projectPath: "grupo/proyecto",
|
|
2120
|
+
title: "New issue"
|
|
2121
|
+
}) {
|
|
2122
|
+
issue {
|
|
2123
|
+
iid
|
|
2124
|
+
}
|
|
2125
|
+
errors
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
updateProject: updateProject(input: {
|
|
2129
|
+
projectPath: "grupo/proyecto",
|
|
2130
|
+
description: "Updated description"
|
|
2131
|
+
}) {
|
|
2132
|
+
project {
|
|
2133
|
+
description
|
|
2134
|
+
}
|
|
2135
|
+
errors
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
```
|
|
2139
|
+
|
|
2140
|
+
#### Mutation con variables:
|
|
2141
|
+
```graphql
|
|
2142
|
+
mutation CreateIssueWithLabels(
|
|
2143
|
+
$projectPath: ID!,
|
|
2144
|
+
$title: String!,
|
|
2145
|
+
$description: String,
|
|
2146
|
+
$labelIds: [LabelID!]
|
|
2147
|
+
) {
|
|
2148
|
+
createIssue(input: {
|
|
2149
|
+
projectPath: $projectPath,
|
|
2150
|
+
title: $title,
|
|
2151
|
+
description: $description,
|
|
2152
|
+
labelIds: $labelIds
|
|
2153
|
+
}) {
|
|
2154
|
+
issue {
|
|
2155
|
+
iid
|
|
2156
|
+
title
|
|
2157
|
+
labels {
|
|
2158
|
+
nodes {
|
|
2159
|
+
title
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
errors
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
```
|
|
2167
|
+
|
|
2168
|
+
Variables:
|
|
2169
|
+
```json
|
|
2170
|
+
{
|
|
2171
|
+
"projectPath": "grupo/proyecto",
|
|
2172
|
+
"title": "Bug en login",
|
|
2173
|
+
"description": "Descripción detallada",
|
|
2174
|
+
"labelIds": [
|
|
2175
|
+
"gid://gitlab/Label/1",
|
|
2176
|
+
"gid://gitlab/Label/2"
|
|
2177
|
+
]
|
|
2178
|
+
}
|
|
2179
|
+
```
|
|
2180
|
+
|
|
2181
|
+
---
|
|
2182
|
+
|
|
2183
|
+
### 13. glab API - Llamadas Directas
|
|
2184
|
+
|
|
2185
|
+
```bash
|
|
2186
|
+
# GET request
|
|
2187
|
+
glab api projects/123
|
|
2188
|
+
|
|
2189
|
+
# GET con parámetros
|
|
2190
|
+
glab api "projects/123/issues?state=opened&labels=bug"
|
|
2191
|
+
|
|
2192
|
+
# POST request
|
|
2193
|
+
glab api --method POST projects/123/issues \
|
|
2194
|
+
--field title="New issue" \
|
|
2195
|
+
--field description="Description"
|
|
2196
|
+
|
|
2197
|
+
# PUT request
|
|
2198
|
+
glab api --method PUT projects/123/issues/42 \
|
|
2199
|
+
--field state_event=close
|
|
2200
|
+
|
|
2201
|
+
# DELETE request
|
|
2202
|
+
glab api --method DELETE projects/123/issues/42
|
|
2203
|
+
|
|
2204
|
+
# Con paginación
|
|
2205
|
+
glab api --paginate projects/123/issues
|
|
2206
|
+
|
|
2207
|
+
# Output en formato específico
|
|
2208
|
+
glab api projects/123 --jq '.name'
|
|
2209
|
+
|
|
2210
|
+
# Headers personalizados
|
|
2211
|
+
glab api --header "Custom-Header: value" projects/123
|
|
2212
|
+
|
|
2213
|
+
# Raw output
|
|
2214
|
+
glab api --raw projects/123
|
|
2215
|
+
```
|
|
2216
|
+
|
|
2217
|
+
---
|
|
2218
|
+
|
|
2219
|
+
### 14. Búsqueda y Filtrado
|
|
2220
|
+
|
|
2221
|
+
#### REST API
|
|
2222
|
+
|
|
2223
|
+
**Buscar proyectos:**
|
|
2224
|
+
```bash
|
|
2225
|
+
# Por nombre
|
|
2226
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2227
|
+
--url "https://gitlab.example.com/api/v4/projects?search=my-project"
|
|
2228
|
+
|
|
2229
|
+
# Por visibilidad
|
|
2230
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2231
|
+
--url "https://gitlab.example.com/api/v4/projects?visibility=public"
|
|
2232
|
+
|
|
2233
|
+
# Proyectos propios
|
|
2234
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2235
|
+
--url "https://gitlab.example.com/api/v4/projects?owned=true"
|
|
2236
|
+
```
|
|
2237
|
+
|
|
2238
|
+
**Buscar issues:**
|
|
2239
|
+
```bash
|
|
2240
|
+
# Por título
|
|
2241
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2242
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues?search=login"
|
|
2243
|
+
|
|
2244
|
+
# Por labels
|
|
2245
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2246
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues?labels=bug,critical"
|
|
2247
|
+
|
|
2248
|
+
# Por assignee
|
|
2249
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2250
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues?assignee_id=5"
|
|
2251
|
+
|
|
2252
|
+
# Por milestone
|
|
2253
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2254
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues?milestone=v1.0"
|
|
2255
|
+
```
|
|
2256
|
+
|
|
2257
|
+
**Buscar merge requests:**
|
|
2258
|
+
```bash
|
|
2259
|
+
# Por autor
|
|
2260
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2261
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests?author_id=5"
|
|
2262
|
+
|
|
2263
|
+
# Por reviewer
|
|
2264
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2265
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests?reviewer_id=10"
|
|
2266
|
+
|
|
2267
|
+
# Por source branch
|
|
2268
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2269
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests?source_branch=feature-branch"
|
|
2270
|
+
```
|
|
2271
|
+
|
|
2272
|
+
#### GraphQL API
|
|
2273
|
+
|
|
2274
|
+
**Buscar con filtros:**
|
|
2275
|
+
```graphql
|
|
2276
|
+
query {
|
|
2277
|
+
projects(
|
|
2278
|
+
search: "my-project"
|
|
2279
|
+
membership: true
|
|
2280
|
+
first: 20
|
|
2281
|
+
) {
|
|
2282
|
+
nodes {
|
|
2283
|
+
id
|
|
2284
|
+
name
|
|
2285
|
+
fullPath
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
```
|
|
2290
|
+
|
|
2291
|
+
**Buscar issues con filtros complejos:**
|
|
2292
|
+
```graphql
|
|
2293
|
+
query {
|
|
2294
|
+
project(fullPath: "grupo/proyecto") {
|
|
2295
|
+
issues(
|
|
2296
|
+
search: "login"
|
|
2297
|
+
labelName: ["bug", "critical"]
|
|
2298
|
+
assigneeUsernames: ["user1", "user2"]
|
|
2299
|
+
state: OPENED
|
|
2300
|
+
sort: CREATED_DESC
|
|
2301
|
+
first: 20
|
|
2302
|
+
) {
|
|
2303
|
+
nodes {
|
|
2304
|
+
iid
|
|
2305
|
+
title
|
|
2306
|
+
state
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
```
|
|
2312
|
+
|
|
2313
|
+
#### glab CLI
|
|
2314
|
+
|
|
2315
|
+
```bash
|
|
2316
|
+
# Buscar issues
|
|
2317
|
+
glab issue list --search "login" --label bug
|
|
2318
|
+
|
|
2319
|
+
# Buscar merge requests
|
|
2320
|
+
glab mr list --search "feature" --author @me
|
|
2321
|
+
|
|
2322
|
+
# Buscar proyectos
|
|
2323
|
+
glab repo list --search "my-project"
|
|
2324
|
+
```
|
|
2325
|
+
|
|
2326
|
+
---
|
|
2327
|
+
|
|
2328
|
+
### 15. Users y Autenticación
|
|
2329
|
+
|
|
2330
|
+
#### REST API
|
|
2331
|
+
|
|
2332
|
+
**Obtener usuario actual:**
|
|
2333
|
+
```bash
|
|
2334
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2335
|
+
--url "https://gitlab.example.com/api/v4/user"
|
|
2336
|
+
```
|
|
2337
|
+
|
|
2338
|
+
**Listar usuarios:**
|
|
2339
|
+
```bash
|
|
2340
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2341
|
+
--url "https://gitlab.example.com/api/v4/users"
|
|
2342
|
+
|
|
2343
|
+
# Con búsqueda
|
|
2344
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2345
|
+
--url "https://gitlab.example.com/api/v4/users?search=john"
|
|
2346
|
+
```
|
|
2347
|
+
|
|
2348
|
+
**Obtener usuario específico:**
|
|
2349
|
+
```bash
|
|
2350
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2351
|
+
--url "https://gitlab.example.com/api/v4/users/123"
|
|
2352
|
+
```
|
|
2353
|
+
|
|
2354
|
+
#### GraphQL API
|
|
2355
|
+
|
|
2356
|
+
**Query usuario actual:**
|
|
2357
|
+
```graphql
|
|
2358
|
+
query {
|
|
2359
|
+
currentUser {
|
|
2360
|
+
id
|
|
2361
|
+
username
|
|
2362
|
+
name
|
|
2363
|
+
email
|
|
2364
|
+
publicEmail
|
|
2365
|
+
avatarUrl
|
|
2366
|
+
webUrl
|
|
2367
|
+
status {
|
|
2368
|
+
message
|
|
2369
|
+
emoji
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
```
|
|
2374
|
+
|
|
2375
|
+
**Query usuarios:**
|
|
2376
|
+
```graphql
|
|
2377
|
+
query {
|
|
2378
|
+
users(search: "john", first: 20) {
|
|
2379
|
+
nodes {
|
|
2380
|
+
id
|
|
2381
|
+
username
|
|
2382
|
+
name
|
|
2383
|
+
publicEmail
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
```
|
|
2388
|
+
|
|
2389
|
+
#### glab CLI
|
|
2390
|
+
|
|
2391
|
+
```bash
|
|
2392
|
+
# Ver usuario actual
|
|
2393
|
+
glab user view
|
|
2394
|
+
|
|
2395
|
+
# Ver usuario específico
|
|
2396
|
+
glab user view username
|
|
2397
|
+
|
|
2398
|
+
# Listar usuarios
|
|
2399
|
+
glab api users
|
|
2400
|
+
```
|
|
2401
|
+
|
|
2402
|
+
---
|
|
2403
|
+
|
|
2404
|
+
### 16. Labels
|
|
2405
|
+
|
|
2406
|
+
#### REST API
|
|
2407
|
+
|
|
2408
|
+
**Listar labels:**
|
|
2409
|
+
```bash
|
|
2410
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2411
|
+
--url "https://gitlab.example.com/api/v4/projects/123/labels"
|
|
2412
|
+
```
|
|
2413
|
+
|
|
2414
|
+
**Crear label:**
|
|
2415
|
+
```bash
|
|
2416
|
+
curl --request POST \
|
|
2417
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2418
|
+
--header "Content-Type: application/json" \
|
|
2419
|
+
--data '{
|
|
2420
|
+
"name": "bug",
|
|
2421
|
+
"color": "#FF0000",
|
|
2422
|
+
"description": "Bug tracking label"
|
|
2423
|
+
}' \
|
|
2424
|
+
--url "https://gitlab.example.com/api/v4/projects/123/labels"
|
|
2425
|
+
```
|
|
2426
|
+
|
|
2427
|
+
**Actualizar label:**
|
|
2428
|
+
```bash
|
|
2429
|
+
curl --request PUT \
|
|
2430
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2431
|
+
--header "Content-Type: application/json" \
|
|
2432
|
+
--data '{
|
|
2433
|
+
"new_name": "critical-bug",
|
|
2434
|
+
"color": "#CC0000"
|
|
2435
|
+
}' \
|
|
2436
|
+
--url "https://gitlab.example.com/api/v4/projects/123/labels/bug"
|
|
2437
|
+
```
|
|
2438
|
+
|
|
2439
|
+
**Eliminar label:**
|
|
2440
|
+
```bash
|
|
2441
|
+
curl --request DELETE \
|
|
2442
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2443
|
+
--url "https://gitlab.example.com/api/v4/projects/123/labels/bug"
|
|
2444
|
+
```
|
|
2445
|
+
|
|
2446
|
+
#### glab CLI
|
|
2447
|
+
|
|
2448
|
+
```bash
|
|
2449
|
+
# Listar labels
|
|
2450
|
+
glab label list
|
|
2451
|
+
|
|
2452
|
+
# Crear label
|
|
2453
|
+
glab label create bug --color "#FF0000" --description "Bug tracking"
|
|
2454
|
+
|
|
2455
|
+
# Eliminar label
|
|
2456
|
+
glab label delete bug
|
|
2457
|
+
```
|
|
2458
|
+
|
|
2459
|
+
---
|
|
2460
|
+
|
|
2461
|
+
### 17. Milestones
|
|
2462
|
+
|
|
2463
|
+
#### REST API
|
|
2464
|
+
|
|
2465
|
+
**Listar milestones:**
|
|
2466
|
+
```bash
|
|
2467
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2468
|
+
--url "https://gitlab.example.com/api/v4/projects/123/milestones"
|
|
2469
|
+
```
|
|
2470
|
+
|
|
2471
|
+
**Crear milestone:**
|
|
2472
|
+
```bash
|
|
2473
|
+
curl --request POST \
|
|
2474
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2475
|
+
--header "Content-Type: application/json" \
|
|
2476
|
+
--data '{
|
|
2477
|
+
"title": "v1.0",
|
|
2478
|
+
"description": "Release 1.0",
|
|
2479
|
+
"due_date": "2026-12-31",
|
|
2480
|
+
"start_date": "2026-01-01"
|
|
2481
|
+
}' \
|
|
2482
|
+
--url "https://gitlab.example.com/api/v4/projects/123/milestones"
|
|
2483
|
+
```
|
|
2484
|
+
|
|
2485
|
+
**Actualizar milestone:**
|
|
2486
|
+
```bash
|
|
2487
|
+
curl --request PUT \
|
|
2488
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2489
|
+
--header "Content-Type: application/json" \
|
|
2490
|
+
--data '{
|
|
2491
|
+
"state_event": "close"
|
|
2492
|
+
}' \
|
|
2493
|
+
--url "https://gitlab.example.com/api/v4/projects/123/milestones/1"
|
|
2494
|
+
```
|
|
2495
|
+
|
|
2496
|
+
#### glab CLI
|
|
2497
|
+
|
|
2498
|
+
```bash
|
|
2499
|
+
# Listar milestones
|
|
2500
|
+
glab milestone list
|
|
2501
|
+
|
|
2502
|
+
# Ver milestone
|
|
2503
|
+
glab milestone view 1
|
|
2504
|
+
|
|
2505
|
+
# Crear milestone
|
|
2506
|
+
glab milestone create --title "v1.0" --due-date "2026-12-31"
|
|
2507
|
+
|
|
2508
|
+
# Cerrar milestone
|
|
2509
|
+
glab milestone close 1
|
|
2510
|
+
```
|
|
2511
|
+
|
|
2512
|
+
---
|
|
2513
|
+
|
|
2514
|
+
### 18. Snippets
|
|
2515
|
+
|
|
2516
|
+
#### REST API
|
|
2517
|
+
|
|
2518
|
+
**Listar snippets:**
|
|
2519
|
+
```bash
|
|
2520
|
+
# Snippets del usuario
|
|
2521
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2522
|
+
--url "https://gitlab.example.com/api/v4/snippets"
|
|
2523
|
+
|
|
2524
|
+
# Snippets del proyecto
|
|
2525
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2526
|
+
--url "https://gitlab.example.com/api/v4/projects/123/snippets"
|
|
2527
|
+
```
|
|
2528
|
+
|
|
2529
|
+
**Crear snippet:**
|
|
2530
|
+
```bash
|
|
2531
|
+
curl --request POST \
|
|
2532
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2533
|
+
--header "Content-Type: application/json" \
|
|
2534
|
+
--data '{
|
|
2535
|
+
"title": "Example snippet",
|
|
2536
|
+
"file_name": "example.rb",
|
|
2537
|
+
"content": "puts \"Hello World\"",
|
|
2538
|
+
"visibility": "private"
|
|
2539
|
+
}' \
|
|
2540
|
+
--url "https://gitlab.example.com/api/v4/snippets"
|
|
2541
|
+
```
|
|
2542
|
+
|
|
2543
|
+
**Obtener snippet:**
|
|
2544
|
+
```bash
|
|
2545
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2546
|
+
--url "https://gitlab.example.com/api/v4/snippets/456"
|
|
2547
|
+
|
|
2548
|
+
# Raw content
|
|
2549
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2550
|
+
--url "https://gitlab.example.com/api/v4/snippets/456/raw"
|
|
2551
|
+
```
|
|
2552
|
+
|
|
2553
|
+
#### glab CLI
|
|
2554
|
+
|
|
2555
|
+
```bash
|
|
2556
|
+
# Listar snippets
|
|
2557
|
+
glab snippet list
|
|
2558
|
+
|
|
2559
|
+
# Ver snippet
|
|
2560
|
+
glab snippet view 456
|
|
2561
|
+
|
|
2562
|
+
# Crear snippet
|
|
2563
|
+
glab snippet create --title "Example" \
|
|
2564
|
+
--filename "example.rb" \
|
|
2565
|
+
--content "puts 'Hello'"
|
|
2566
|
+
```
|
|
2567
|
+
|
|
2568
|
+
---
|
|
2569
|
+
|
|
2570
|
+
### 19. Runners
|
|
2571
|
+
|
|
2572
|
+
#### REST API
|
|
2573
|
+
|
|
2574
|
+
**Listar runners:**
|
|
2575
|
+
```bash
|
|
2576
|
+
# Runners del proyecto
|
|
2577
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2578
|
+
--url "https://gitlab.example.com/api/v4/projects/123/runners"
|
|
2579
|
+
|
|
2580
|
+
# Todos los runners (admin)
|
|
2581
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2582
|
+
--url "https://gitlab.example.com/api/v4/runners/all"
|
|
2583
|
+
```
|
|
2584
|
+
|
|
2585
|
+
**Obtener runner específico:**
|
|
2586
|
+
```bash
|
|
2587
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2588
|
+
--url "https://gitlab.example.com/api/v4/runners/456"
|
|
2589
|
+
```
|
|
2590
|
+
|
|
2591
|
+
**Actualizar runner:**
|
|
2592
|
+
```bash
|
|
2593
|
+
curl --request PUT \
|
|
2594
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2595
|
+
--header "Content-Type: application/json" \
|
|
2596
|
+
--data '{
|
|
2597
|
+
"description": "Updated runner",
|
|
2598
|
+
"active": true,
|
|
2599
|
+
"tag_list": ["docker", "linux"]
|
|
2600
|
+
}' \
|
|
2601
|
+
--url "https://gitlab.example.com/api/v4/runners/456"
|
|
2602
|
+
```
|
|
2603
|
+
|
|
2604
|
+
**Eliminar runner:**
|
|
2605
|
+
```bash
|
|
2606
|
+
curl --request DELETE \
|
|
2607
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2608
|
+
--url "https://gitlab.example.com/api/v4/runners/456"
|
|
2609
|
+
```
|
|
2610
|
+
|
|
2611
|
+
#### GraphQL API
|
|
2612
|
+
|
|
2613
|
+
**Query runners:**
|
|
2614
|
+
```graphql
|
|
2615
|
+
query {
|
|
2616
|
+
runner(id: "gid://gitlab/Ci::Runner/456") {
|
|
2617
|
+
id
|
|
2618
|
+
description
|
|
2619
|
+
active
|
|
2620
|
+
paused
|
|
2621
|
+
tagList
|
|
2622
|
+
runnerType
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
```
|
|
2626
|
+
|
|
2627
|
+
---
|
|
2628
|
+
|
|
2629
|
+
### 20. Container Registry
|
|
2630
|
+
|
|
2631
|
+
#### REST API
|
|
2632
|
+
|
|
2633
|
+
**Listar repositorios del registry:**
|
|
2634
|
+
```bash
|
|
2635
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2636
|
+
--url "https://gitlab.example.com/api/v4/projects/123/registry/repositories"
|
|
2637
|
+
```
|
|
2638
|
+
|
|
2639
|
+
**Listar tags de un repositorio:**
|
|
2640
|
+
```bash
|
|
2641
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2642
|
+
--url "https://gitlab.example.com/api/v4/projects/123/registry/repositories/456/tags"
|
|
2643
|
+
```
|
|
2644
|
+
|
|
2645
|
+
**Obtener detalles de un tag:**
|
|
2646
|
+
```bash
|
|
2647
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2648
|
+
--url "https://gitlab.example.com/api/v4/projects/123/registry/repositories/456/tags/latest"
|
|
2649
|
+
```
|
|
2650
|
+
|
|
2651
|
+
**Eliminar tag:**
|
|
2652
|
+
```bash
|
|
2653
|
+
curl --request DELETE \
|
|
2654
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2655
|
+
--url "https://gitlab.example.com/api/v4/projects/123/registry/repositories/456/tags/old-tag"
|
|
2656
|
+
```
|
|
2657
|
+
|
|
2658
|
+
**Eliminar tags en bulk:**
|
|
2659
|
+
```bash
|
|
2660
|
+
curl --request DELETE \
|
|
2661
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2662
|
+
--header "Content-Type: application/json" \
|
|
2663
|
+
--data '{
|
|
2664
|
+
"name_regex_delete": ".*-dev",
|
|
2665
|
+
"keep_n": 5,
|
|
2666
|
+
"older_than": "1d"
|
|
2667
|
+
}' \
|
|
2668
|
+
--url "https://gitlab.example.com/api/v4/projects/123/registry/repositories/456/tags"
|
|
2669
|
+
```
|
|
2670
|
+
|
|
2671
|
+
---
|
|
2672
|
+
|
|
2673
|
+
### 21. Deployments
|
|
2674
|
+
|
|
2675
|
+
#### REST API
|
|
2676
|
+
|
|
2677
|
+
**Listar deployments:**
|
|
2678
|
+
```bash
|
|
2679
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2680
|
+
--url "https://gitlab.example.com/api/v4/projects/123/deployments"
|
|
2681
|
+
|
|
2682
|
+
# Con filtros
|
|
2683
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2684
|
+
--url "https://gitlab.example.com/api/v4/projects/123/deployments?environment=production&status=success"
|
|
2685
|
+
```
|
|
2686
|
+
|
|
2687
|
+
**Obtener deployment específico:**
|
|
2688
|
+
```bash
|
|
2689
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2690
|
+
--url "https://gitlab.example.com/api/v4/projects/123/deployments/456"
|
|
2691
|
+
```
|
|
2692
|
+
|
|
2693
|
+
**Crear deployment:**
|
|
2694
|
+
```bash
|
|
2695
|
+
curl --request POST \
|
|
2696
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2697
|
+
--header "Content-Type: application/json" \
|
|
2698
|
+
--data '{
|
|
2699
|
+
"environment": "production",
|
|
2700
|
+
"sha": "abc123",
|
|
2701
|
+
"ref": "main",
|
|
2702
|
+
"tag": false,
|
|
2703
|
+
"status": "success"
|
|
2704
|
+
}' \
|
|
2705
|
+
--url "https://gitlab.example.com/api/v4/projects/123/deployments"
|
|
2706
|
+
```
|
|
2707
|
+
|
|
2708
|
+
#### GraphQL API
|
|
2709
|
+
|
|
2710
|
+
**Query deployments:**
|
|
2711
|
+
```graphql
|
|
2712
|
+
query {
|
|
2713
|
+
project(fullPath: "grupo/proyecto") {
|
|
2714
|
+
deployments(first: 10) {
|
|
2715
|
+
nodes {
|
|
2716
|
+
id
|
|
2717
|
+
iid
|
|
2718
|
+
ref
|
|
2719
|
+
sha
|
|
2720
|
+
createdAt
|
|
2721
|
+
finishedAt
|
|
2722
|
+
status
|
|
2723
|
+
environment {
|
|
2724
|
+
name
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
```
|
|
2731
|
+
|
|
2732
|
+
---
|
|
2733
|
+
|
|
2734
|
+
### 22. Environments
|
|
2735
|
+
|
|
2736
|
+
#### REST API
|
|
2737
|
+
|
|
2738
|
+
**Listar environments:**
|
|
2739
|
+
```bash
|
|
2740
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2741
|
+
--url "https://gitlab.example.com/api/v4/projects/123/environments"
|
|
2742
|
+
```
|
|
2743
|
+
|
|
2744
|
+
**Crear environment:**
|
|
2745
|
+
```bash
|
|
2746
|
+
curl --request POST \
|
|
2747
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2748
|
+
--header "Content-Type: application/json" \
|
|
2749
|
+
--data '{
|
|
2750
|
+
"name": "staging",
|
|
2751
|
+
"external_url": "https://staging.example.com"
|
|
2752
|
+
}' \
|
|
2753
|
+
--url "https://gitlab.example.com/api/v4/projects/123/environments"
|
|
2754
|
+
```
|
|
2755
|
+
|
|
2756
|
+
**Actualizar environment:**
|
|
2757
|
+
```bash
|
|
2758
|
+
curl --request PUT \
|
|
2759
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2760
|
+
--header "Content-Type: application/json" \
|
|
2761
|
+
--data '{
|
|
2762
|
+
"external_url": "https://new-staging.example.com"
|
|
2763
|
+
}' \
|
|
2764
|
+
--url "https://gitlab.example.com/api/v4/projects/123/environments/456"
|
|
2765
|
+
```
|
|
2766
|
+
|
|
2767
|
+
**Stop environment:**
|
|
2768
|
+
```bash
|
|
2769
|
+
curl --request POST \
|
|
2770
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2771
|
+
--url "https://gitlab.example.com/api/v4/projects/123/environments/456/stop"
|
|
2772
|
+
```
|
|
2773
|
+
|
|
2774
|
+
---
|
|
2775
|
+
|
|
2776
|
+
### 23. Protected Branches
|
|
2777
|
+
|
|
2778
|
+
#### REST API
|
|
2779
|
+
|
|
2780
|
+
**Listar protected branches:**
|
|
2781
|
+
```bash
|
|
2782
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2783
|
+
--url "https://gitlab.example.com/api/v4/projects/123/protected_branches"
|
|
2784
|
+
```
|
|
2785
|
+
|
|
2786
|
+
**Proteger branch:**
|
|
2787
|
+
```bash
|
|
2788
|
+
curl --request POST \
|
|
2789
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2790
|
+
--header "Content-Type: application/json" \
|
|
2791
|
+
--data '{
|
|
2792
|
+
"name": "main",
|
|
2793
|
+
"push_access_level": 40,
|
|
2794
|
+
"merge_access_level": 30,
|
|
2795
|
+
"allow_force_push": false
|
|
2796
|
+
}' \
|
|
2797
|
+
--url "https://gitlab.example.com/api/v4/projects/123/protected_branches"
|
|
2798
|
+
```
|
|
2799
|
+
|
|
2800
|
+
**Desproteger branch:**
|
|
2801
|
+
```bash
|
|
2802
|
+
curl --request DELETE \
|
|
2803
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2804
|
+
--url "https://gitlab.example.com/api/v4/projects/123/protected_branches/main"
|
|
2805
|
+
```
|
|
2806
|
+
|
|
2807
|
+
---
|
|
2808
|
+
|
|
2809
|
+
### 24. Commits
|
|
2810
|
+
|
|
2811
|
+
#### REST API
|
|
2812
|
+
|
|
2813
|
+
**Crear commit con múltiples archivos:**
|
|
2814
|
+
```bash
|
|
2815
|
+
curl --request POST \
|
|
2816
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2817
|
+
--header "Content-Type: application/json" \
|
|
2818
|
+
--data '{
|
|
2819
|
+
"branch": "main",
|
|
2820
|
+
"commit_message": "Update multiple files",
|
|
2821
|
+
"actions": [
|
|
2822
|
+
{
|
|
2823
|
+
"action": "create",
|
|
2824
|
+
"file_path": "new-file.txt",
|
|
2825
|
+
"content": "New file content"
|
|
2826
|
+
},
|
|
2827
|
+
{
|
|
2828
|
+
"action": "update",
|
|
2829
|
+
"file_path": "existing-file.txt",
|
|
2830
|
+
"content": "Updated content"
|
|
2831
|
+
},
|
|
2832
|
+
{
|
|
2833
|
+
"action": "delete",
|
|
2834
|
+
"file_path": "old-file.txt"
|
|
2835
|
+
},
|
|
2836
|
+
{
|
|
2837
|
+
"action": "move",
|
|
2838
|
+
"file_path": "renamed-file.txt",
|
|
2839
|
+
"previous_path": "old-name.txt"
|
|
2840
|
+
}
|
|
2841
|
+
]
|
|
2842
|
+
}' \
|
|
2843
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/commits"
|
|
2844
|
+
```
|
|
2845
|
+
|
|
2846
|
+
**Obtener diff de commit:**
|
|
2847
|
+
```bash
|
|
2848
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2849
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/commits/abc123/diff"
|
|
2850
|
+
```
|
|
2851
|
+
|
|
2852
|
+
**Obtener comentarios de commit:**
|
|
2853
|
+
```bash
|
|
2854
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2855
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/commits/abc123/comments"
|
|
2856
|
+
```
|
|
2857
|
+
|
|
2858
|
+
**Agregar comentario a commit:**
|
|
2859
|
+
```bash
|
|
2860
|
+
curl --request POST \
|
|
2861
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2862
|
+
--header "Content-Type: application/json" \
|
|
2863
|
+
--data '{
|
|
2864
|
+
"note": "This commit looks good",
|
|
2865
|
+
"path": "src/app.js",
|
|
2866
|
+
"line": 10,
|
|
2867
|
+
"line_type": "new"
|
|
2868
|
+
}' \
|
|
2869
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/commits/abc123/comments"
|
|
2870
|
+
```
|
|
2871
|
+
|
|
2872
|
+
---
|
|
2873
|
+
|
|
2874
|
+
### 25. Merge Request Approvals
|
|
2875
|
+
|
|
2876
|
+
#### REST API
|
|
2877
|
+
|
|
2878
|
+
**Obtener configuración de approvals:**
|
|
2879
|
+
```bash
|
|
2880
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2881
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/approvals"
|
|
2882
|
+
```
|
|
2883
|
+
|
|
2884
|
+
**Configurar approvals requeridos:**
|
|
2885
|
+
```bash
|
|
2886
|
+
curl --request POST \
|
|
2887
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2888
|
+
--header "Content-Type: application/json" \
|
|
2889
|
+
--data '{
|
|
2890
|
+
"approvals_required": 2
|
|
2891
|
+
}' \
|
|
2892
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/approvals"
|
|
2893
|
+
```
|
|
2894
|
+
|
|
2895
|
+
**Aprobar MR:**
|
|
2896
|
+
```bash
|
|
2897
|
+
curl --request POST \
|
|
2898
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2899
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/approve"
|
|
2900
|
+
```
|
|
2901
|
+
|
|
2902
|
+
**Remover aprobación:**
|
|
2903
|
+
```bash
|
|
2904
|
+
curl --request POST \
|
|
2905
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2906
|
+
--url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/unapprove"
|
|
2907
|
+
```
|
|
2908
|
+
|
|
2909
|
+
---
|
|
2910
|
+
|
|
2911
|
+
## Troubleshooting y Códigos HTTP
|
|
2912
|
+
|
|
2913
|
+
### Códigos de Estado Comunes
|
|
2914
|
+
|
|
2915
|
+
#### 2xx - Éxito
|
|
2916
|
+
|
|
2917
|
+
**200 OK**
|
|
2918
|
+
- **Significado**: Request exitoso (GET/PUT/PATCH/DELETE)
|
|
2919
|
+
- **Acción**: Procesar respuesta JSON
|
|
2920
|
+
- **Ejemplo**:
|
|
2921
|
+
```bash
|
|
2922
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
2923
|
+
--url "https://gitlab.example.com/api/v4/projects/123"
|
|
2924
|
+
# Response: 200 OK con datos del proyecto
|
|
2925
|
+
```
|
|
2926
|
+
|
|
2927
|
+
**201 Created**
|
|
2928
|
+
- **Significado**: Recurso creado exitosamente (POST)
|
|
2929
|
+
- **Acción**: Recurso nuevo disponible en respuesta
|
|
2930
|
+
- **Ejemplo**:
|
|
2931
|
+
```bash
|
|
2932
|
+
curl --request POST \
|
|
2933
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2934
|
+
--header "Content-Type: application/json" \
|
|
2935
|
+
--data '{"title": "New issue"}' \
|
|
2936
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues"
|
|
2937
|
+
# Response: 201 Created con datos de la issue creada
|
|
2938
|
+
```
|
|
2939
|
+
|
|
2940
|
+
**202 Accepted**
|
|
2941
|
+
- **Significado**: Request aceptado, procesamiento asíncrono
|
|
2942
|
+
- **Acción**: Verificar estado posteriormente
|
|
2943
|
+
- **Ejemplo**: Operaciones de importación, exports
|
|
2944
|
+
|
|
2945
|
+
**204 No Content**
|
|
2946
|
+
- **Significado**: Éxito sin contenido en respuesta
|
|
2947
|
+
- **Acción**: Operación completada, no hay datos adicionales
|
|
2948
|
+
- **Ejemplo**: DELETE exitoso
|
|
2949
|
+
|
|
2950
|
+
---
|
|
2951
|
+
|
|
2952
|
+
#### 3xx - Redirección
|
|
2953
|
+
|
|
2954
|
+
**301 Moved Permanently**
|
|
2955
|
+
- **Significado**: Recurso movido permanentemente
|
|
2956
|
+
- **Acción**: Usar URL del header `Location`
|
|
2957
|
+
- **Ejemplo**: Proyecto renombrado o movido
|
|
2958
|
+
```bash
|
|
2959
|
+
# Request a path antiguo
|
|
2960
|
+
curl --verbose --header "PRIVATE-TOKEN: <token>" \
|
|
2961
|
+
--url "https://gitlab.example.com/api/v4/projects/old-group%2Fold-project"
|
|
2962
|
+
# Response: 301 con Location: /api/v4/projects/new-group%2Fnew-project
|
|
2963
|
+
```
|
|
2964
|
+
|
|
2965
|
+
**304 Not Modified**
|
|
2966
|
+
- **Significado**: Recurso no modificado desde última request
|
|
2967
|
+
- **Acción**: Usar caché local
|
|
2968
|
+
- **Ejemplo**: Request condicional con `If-Modified-Since`
|
|
2969
|
+
|
|
2970
|
+
---
|
|
2971
|
+
|
|
2972
|
+
#### 4xx - Errores del Cliente
|
|
2973
|
+
|
|
2974
|
+
**400 Bad Request**
|
|
2975
|
+
- **Significado**: Parámetro requerido faltante o inválido
|
|
2976
|
+
- **Acción**: Verificar parámetros y formato
|
|
2977
|
+
- **Ejemplos**:
|
|
2978
|
+
|
|
2979
|
+
```bash
|
|
2980
|
+
# Error: Atributo faltante
|
|
2981
|
+
curl --request POST \
|
|
2982
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2983
|
+
--header "Content-Type: application/json" \
|
|
2984
|
+
--data '{"description": "Missing title"}' \
|
|
2985
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues"
|
|
2986
|
+
# Response: 400 {"message":"400 (Bad request) \"title\" not given"}
|
|
2987
|
+
```
|
|
2988
|
+
|
|
2989
|
+
```bash
|
|
2990
|
+
# Error: Validación fallida
|
|
2991
|
+
curl --request POST \
|
|
2992
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
2993
|
+
--header "Content-Type: application/json" \
|
|
2994
|
+
--data '{"title": "A", "description": "Too short title"}' \
|
|
2995
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues"
|
|
2996
|
+
# Response: 400 {"message":{"title":["is too short (minimum is 3 characters)"]}}
|
|
2997
|
+
```
|
|
2998
|
+
|
|
2999
|
+
**Resolución**:
|
|
3000
|
+
1. Verificar documentación del endpoint
|
|
3001
|
+
2. Validar todos los campos requeridos
|
|
3002
|
+
3. Verificar formato de datos (JSON válido)
|
|
3003
|
+
4. Revisar tipos de datos (string, integer, boolean)
|
|
3004
|
+
|
|
3005
|
+
---
|
|
3006
|
+
|
|
3007
|
+
**401 Unauthorized**
|
|
3008
|
+
- **Significado**: No autenticado o token inválido
|
|
3009
|
+
- **Acción**: Verificar token de autenticación
|
|
3010
|
+
- **Ejemplos**:
|
|
3011
|
+
|
|
3012
|
+
```bash
|
|
3013
|
+
# Error: Token faltante
|
|
3014
|
+
curl --url "https://gitlab.example.com/api/v4/projects/123"
|
|
3015
|
+
# Response: 401 {"message":"401 Unauthorized"}
|
|
3016
|
+
|
|
3017
|
+
# Error: Token inválido
|
|
3018
|
+
curl --header "PRIVATE-TOKEN: invalid-token" \
|
|
3019
|
+
--url "https://gitlab.example.com/api/v4/projects/123"
|
|
3020
|
+
# Response: 401 {"message":"Invalid token"}
|
|
3021
|
+
```
|
|
3022
|
+
|
|
3023
|
+
**Resolución**:
|
|
3024
|
+
1. Verificar que el token existe y es válido
|
|
3025
|
+
2. Verificar que el token no ha expirado
|
|
3026
|
+
3. Verificar que el header está correctamente formateado
|
|
3027
|
+
4. Para GraphQL: verificar scope del token (`api` o `read_api`)
|
|
3028
|
+
|
|
3029
|
+
```bash
|
|
3030
|
+
# Verificar token
|
|
3031
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
3032
|
+
--url "https://gitlab.example.com/api/v4/user"
|
|
3033
|
+
# Si retorna datos del usuario, el token es válido
|
|
3034
|
+
```
|
|
3035
|
+
|
|
3036
|
+
---
|
|
3037
|
+
|
|
3038
|
+
**403 Forbidden**
|
|
3039
|
+
- **Significado**: Autenticado pero sin permisos
|
|
3040
|
+
- **Acción**: Verificar permisos del usuario/token
|
|
3041
|
+
- **Ejemplos**:
|
|
3042
|
+
|
|
3043
|
+
```bash
|
|
3044
|
+
# Error: Sin permisos para eliminar proyecto
|
|
3045
|
+
curl --request DELETE \
|
|
3046
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
3047
|
+
--url "https://gitlab.example.com/api/v4/projects/123"
|
|
3048
|
+
# Response: 403 {"message":"403 Forbidden"}
|
|
3049
|
+
|
|
3050
|
+
# Error: Scope insuficiente
|
|
3051
|
+
curl --request POST \
|
|
3052
|
+
--header "PRIVATE-TOKEN: <read-only-token>" \
|
|
3053
|
+
--data '{"title": "New issue"}' \
|
|
3054
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues"
|
|
3055
|
+
# Response: 403 {"error":"insufficient_scope"}
|
|
3056
|
+
```
|
|
3057
|
+
|
|
3058
|
+
**Resolución**:
|
|
3059
|
+
1. Verificar rol del usuario (Guest, Reporter, Developer, Maintainer, Owner)
|
|
3060
|
+
2. Verificar scope del token (`api` para write, `read_api` para read-only)
|
|
3061
|
+
3. Verificar permisos del proyecto/grupo
|
|
3062
|
+
4. Para operaciones de admin: verificar que el usuario es administrador
|
|
3063
|
+
|
|
3064
|
+
---
|
|
3065
|
+
|
|
3066
|
+
**404 Not Found**
|
|
3067
|
+
- **Significado**: Recurso no encontrado o sin acceso
|
|
3068
|
+
- **Acción**: Verificar ID/path y permisos
|
|
3069
|
+
- **Ejemplos**:
|
|
3070
|
+
|
|
3071
|
+
```bash
|
|
3072
|
+
# Error: Proyecto no existe
|
|
3073
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
3074
|
+
--url "https://gitlab.example.com/api/v4/projects/999999"
|
|
3075
|
+
# Response: 404 {"message":"404 Project Not Found"}
|
|
3076
|
+
|
|
3077
|
+
# Error: Path incorrecto (falta URL encoding)
|
|
3078
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
3079
|
+
--url "https://gitlab.example.com/api/v4/projects/grupo/proyecto"
|
|
3080
|
+
# Response: 404 (debe ser grupo%2Fproyecto)
|
|
3081
|
+
|
|
3082
|
+
# Error: Issue no existe
|
|
3083
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
3084
|
+
--url "https://gitlab.example.com/api/v4/projects/123/issues/999999"
|
|
3085
|
+
# Response: 404 {"message":"404 Not found"}
|
|
3086
|
+
```
|
|
3087
|
+
|
|
3088
|
+
**Resolución**:
|
|
3089
|
+
1. Verificar que el ID/path es correcto
|
|
3090
|
+
2. URL-encode paths con `/` → `%2F`
|
|
3091
|
+
3. Verificar que el recurso existe
|
|
3092
|
+
4. Verificar permisos de acceso al recurso
|
|
3093
|
+
5. Para proyectos: usar ID numérico en lugar de path si hay problemas
|
|
3094
|
+
|
|
3095
|
+
```bash
|
|
3096
|
+
# Obtener ID de proyecto por path
|
|
3097
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
3098
|
+
--url "https://gitlab.example.com/api/v4/projects/grupo%2Fproyecto" | jq '.id'
|
|
3099
|
+
```
|
|
3100
|
+
|
|
3101
|
+
---
|
|
3102
|
+
|
|
3103
|
+
**409 Conflict**
|
|
3104
|
+
- **Significado**: Recurso conflictivo ya existe
|
|
3105
|
+
- **Acción**: Resolver conflicto (renombrar, actualizar en lugar de crear)
|
|
3106
|
+
- **Ejemplos**:
|
|
3107
|
+
|
|
3108
|
+
```bash
|
|
3109
|
+
# Error: Branch ya existe
|
|
3110
|
+
curl --request POST \
|
|
3111
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
3112
|
+
--data '{"branch": "main", "ref": "main"}' \
|
|
3113
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/branches"
|
|
3114
|
+
# Response: 409 {"message":"Branch already exists"}
|
|
3115
|
+
|
|
3116
|
+
# Error: Label duplicado
|
|
3117
|
+
curl --request POST \
|
|
3118
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
3119
|
+
--data '{"name": "bug", "color": "#FF0000"}' \
|
|
3120
|
+
--url "https://gitlab.example.com/api/v4/projects/123/labels"
|
|
3121
|
+
# Response: 409 {"message":"Label already exists"}
|
|
3122
|
+
```
|
|
3123
|
+
|
|
3124
|
+
**Resolución**:
|
|
3125
|
+
1. Verificar si el recurso ya existe antes de crear
|
|
3126
|
+
2. Usar PUT/PATCH para actualizar en lugar de POST para crear
|
|
3127
|
+
3. Usar nombres únicos para recursos nuevos
|
|
3128
|
+
|
|
3129
|
+
---
|
|
3130
|
+
|
|
3131
|
+
**412 Precondition Failed**
|
|
3132
|
+
- **Significado**: Precondición del request falló
|
|
3133
|
+
- **Acción**: Verificar headers condicionales
|
|
3134
|
+
- **Ejemplo**:
|
|
3135
|
+
|
|
3136
|
+
```bash
|
|
3137
|
+
# Error: Archivo modificado entre lectura y escritura
|
|
3138
|
+
curl --request PUT \
|
|
3139
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
3140
|
+
--header "If-Unmodified-Since: Wed, 01 Jan 2026 00:00:00 GMT" \
|
|
3141
|
+
--data '{"content": "new content"}' \
|
|
3142
|
+
--url "https://gitlab.example.com/api/v4/projects/123/repository/files/file.txt"
|
|
3143
|
+
# Response: 412 (archivo modificado después de la fecha)
|
|
3144
|
+
```
|
|
3145
|
+
|
|
3146
|
+
**Resolución**:
|
|
3147
|
+
1. Refrescar datos antes de actualizar
|
|
3148
|
+
2. Usar `last_commit_id` para verificar versión
|
|
3149
|
+
3. Implementar retry con datos actualizados
|
|
3150
|
+
|
|
3151
|
+
---
|
|
3152
|
+
|
|
3153
|
+
**422 Unprocessable Entity**
|
|
3154
|
+
- **Significado**: Entidad no procesable (validación falló)
|
|
3155
|
+
- **Acción**: Verificar validación de campos
|
|
3156
|
+
- **Ejemplos**:
|
|
3157
|
+
|
|
3158
|
+
```bash
|
|
3159
|
+
# Error: Email inválido
|
|
3160
|
+
curl --request POST \
|
|
3161
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
3162
|
+
--data '{"email": "invalid-email", "name": "User"}' \
|
|
3163
|
+
--url "https://gitlab.example.com/api/v4/users"
|
|
3164
|
+
# Response: 422 {"message":{"email":["is invalid"]}}
|
|
3165
|
+
|
|
3166
|
+
# Error: Valor fuera de rango
|
|
3167
|
+
curl --request PUT \
|
|
3168
|
+
--header "PRIVATE-TOKEN: <token>" \
|
|
3169
|
+
--data '{"per_page": 150}' \
|
|
3170
|
+
--url "https://gitlab.example.com/api/v4/projects/123"
|
|
3171
|
+
# Response: 422 {"message":{"per_page":["must be less than or equal to 100"]}}
|
|
3172
|
+
```
|
|
3173
|
+
|
|
3174
|
+
**Resolución**:
|
|
3175
|
+
1. Leer mensaje de error detallado
|
|
3176
|
+
2. Verificar formato de campos (email, URL, etc.)
|
|
3177
|
+
3. Verificar rangos de valores numéricos
|
|
3178
|
+
4. Verificar longitud de strings
|
|
3179
|
+
5. Verificar valores enum válidos
|
|
3180
|
+
|
|
3181
|
+
---
|
|
3182
|
+
|
|
3183
|
+
**429 Too Many Requests**
|
|
3184
|
+
- **Significado**: Rate limit excedido
|
|
3185
|
+
- **Acción**: Esperar y reintentar con backoff exponencial
|
|
3186
|
+
- **Ejemplo**:
|
|
3187
|
+
|
|
3188
|
+
```bash
|
|
3189
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
3190
|
+
--url "https://gitlab.example.com/api/v4/projects/123"
|
|
3191
|
+
# Response: 429 {"message":"429 Too Many Requests"}
|
|
3192
|
+
```
|
|
3193
|
+
|
|
3194
|
+
**Resolución con retry automático**:
|
|
3195
|
+
|
|
3196
|
+
```bash
|
|
3197
|
+
#!/bin/bash
|
|
3198
|
+
|
|
3199
|
+
retry_with_backoff() {
|
|
3200
|
+
local url="$1"
|
|
3201
|
+
local max_attempts=5
|
|
3202
|
+
local timeout=1
|
|
3203
|
+
local attempt=1
|
|
3204
|
+
|
|
3205
|
+
while [ $attempt -le $max_attempts ]; do
|
|
3206
|
+
echo "Attempt $attempt of $max_attempts..."
|
|
3207
|
+
|
|
3208
|
+
HTTP_CODE=$(curl --write-out '%{http_code}' \
|
|
3209
|
+
--silent \
|
|
3210
|
+
--output response.json \
|
|
3211
|
+
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
|
|
3212
|
+
"$url")
|
|
3213
|
+
|
|
3214
|
+
if [ "$HTTP_CODE" -eq 429 ]; then
|
|
3215
|
+
echo "Rate limited. Waiting ${timeout}s before retry..."
|
|
3216
|
+
sleep $timeout
|
|
3217
|
+
timeout=$((timeout * 2)) # Backoff exponencial
|
|
3218
|
+
attempt=$((attempt + 1))
|
|
3219
|
+
elif [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
|
3220
|
+
echo "Success!"
|
|
3221
|
+
cat response.json
|
|
3222
|
+
return 0
|
|
3223
|
+
else
|
|
3224
|
+
echo "Error: HTTP $HTTP_CODE"
|
|
3225
|
+
cat response.json
|
|
3226
|
+
return 1
|
|
3227
|
+
fi
|
|
3228
|
+
done
|
|
3229
|
+
|
|
3230
|
+
echo "Max retries exceeded"
|
|
3231
|
+
return 1
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
# Uso
|
|
3235
|
+
retry_with_backoff "https://gitlab.example.com/api/v4/projects/123"
|
|
3236
|
+
```
|
|
3237
|
+
|
|
3238
|
+
**Mejores prácticas**:
|
|
3239
|
+
1. Implementar backoff exponencial (1s, 2s, 4s, 8s, 16s)
|
|
3240
|
+
2. Verificar headers `RateLimit-*` en respuestas
|
|
3241
|
+
3. Usar keyset pagination en lugar de offset para grandes datasets
|
|
3242
|
+
4. Batch operations cuando sea posible (GraphQL multiplex)
|
|
3243
|
+
5. Cachear respuestas cuando sea apropiado
|
|
3244
|
+
|
|
3245
|
+
---
|
|
3246
|
+
|
|
3247
|
+
#### 5xx - Errores del Servidor
|
|
3248
|
+
|
|
3249
|
+
**500 Internal Server Error**
|
|
3250
|
+
- **Significado**: Error interno del servidor
|
|
3251
|
+
- **Acción**: Reportar a administrador, reintentar más tarde
|
|
3252
|
+
- **Ejemplo**:
|
|
3253
|
+
|
|
3254
|
+
```bash
|
|
3255
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
3256
|
+
--url "https://gitlab.example.com/api/v4/projects/123"
|
|
3257
|
+
# Response: 500 {"message":"500 Internal Server Error"}
|
|
3258
|
+
```
|
|
3259
|
+
|
|
3260
|
+
**Resolución**:
|
|
3261
|
+
1. Verificar logs del servidor (si tienes acceso)
|
|
3262
|
+
2. Reintentar después de unos minutos
|
|
3263
|
+
3. Reportar a administrador con detalles del request
|
|
3264
|
+
4. Verificar status de GitLab.com (si aplica)
|
|
3265
|
+
|
|
3266
|
+
---
|
|
3267
|
+
|
|
3268
|
+
**503 Service Unavailable**
|
|
3269
|
+
- **Significado**: Servidor temporalmente sobrecargado
|
|
3270
|
+
- **Acción**: Reintentar con backoff
|
|
3271
|
+
- **Ejemplo**:
|
|
3272
|
+
|
|
3273
|
+
```bash
|
|
3274
|
+
curl --header "PRIVATE-TOKEN: <token>" \
|
|
3275
|
+
--url "https://gitlab.example.com/api/v4/projects/123"
|
|
3276
|
+
# Response: 503 {"message":"503 Service Unavailable"}
|
|
3277
|
+
```
|
|
3278
|
+
|
|
3279
|
+
**Resolución**:
|
|
3280
|
+
1. Esperar y reintentar (similar a 429)
|
|
3281
|
+
2. Verificar header `Retry-After` si está presente
|
|
3282
|
+
3. Reducir frecuencia de requests
|
|
3283
|
+
4. Verificar status de GitLab.com
|
|
3284
|
+
|
|
3285
|
+
---
|
|
3286
|
+
|
|
3287
|
+
### Debugging Avanzado
|
|
3288
|
+
|
|
3289
|
+
#### Capturar información completa del request:
|
|
3290
|
+
|
|
3291
|
+
```bash
|
|
3292
|
+
#!/bin/bash
|
|
3293
|
+
|
|
3294
|
+
debug_api_call() {
|
|
3295
|
+
local method="${1:-GET}"
|
|
3296
|
+
local url="$2"
|
|
3297
|
+
local data="$3"
|
|
3298
|
+
|
|
3299
|
+
echo "=== API Debug Info ==="
|
|
3300
|
+
echo "Method: $method"
|
|
3301
|
+
echo "URL: $url"
|
|
3302
|
+
echo "Timestamp: $(date -Iseconds)"
|
|
3303
|
+
echo ""
|
|
3304
|
+
|
|
3305
|
+
if [ -n "$data" ]; then
|
|
3306
|
+
curl --request "$method" \
|
|
3307
|
+
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
|
|
3308
|
+
--header "Content-Type: application/json" \
|
|
3309
|
+
--data "$data" \
|
|
3310
|
+
--verbose \
|
|
3311
|
+
--include \
|
|
3312
|
+
--output response_body.txt \
|
|
3313
|
+
--write-out "\n=== Response Info ===\nHTTP Code: %{http_code}\nTotal Time: %{time_total}s\nSize: %{size_download} bytes\n" \
|
|
3314
|
+
"$url" 2> request_debug.txt
|
|
3315
|
+
else
|
|
3316
|
+
curl --request "$method" \
|
|
3317
|
+
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
|
|
3318
|
+
--verbose \
|
|
3319
|
+
--include \
|
|
3320
|
+
--output response_body.txt \
|
|
3321
|
+
--write-out "\n=== Response Info ===\nHTTP Code: %{http_code}\nTotal Time: %{time_total}s\nSize: %{size_download} bytes\n" \
|
|
3322
|
+
"$url" 2> request_debug.txt
|
|
3323
|
+
fi
|
|
3324
|
+
|
|
3325
|
+
echo ""
|
|
3326
|
+
echo "=== Request Debug ==="
|
|
3327
|
+
cat request_debug.txt
|
|
3328
|
+
echo ""
|
|
3329
|
+
echo "=== Response Body ==="
|
|
3330
|
+
cat response_body.txt | jq '.' 2>/dev/null || cat response_body.txt
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
# Uso
|
|
3334
|
+
debug_api_call GET "https://gitlab.example.com/api/v4/projects/123"
|
|
3335
|
+
debug_api_call POST "https://gitlab.example.com/api/v4/projects/123/issues" \
|
|
3336
|
+
'{"title": "Test issue"}'
|
|
3337
|
+
```
|
|
3338
|
+
|
|
3339
|
+
#### Verificar headers de respuesta:
|
|
3340
|
+
|
|
3341
|
+
```bash
|
|
3342
|
+
# Capturar solo headers
|
|
3343
|
+
curl --head \
|
|
3344
|
+
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
|
|
3345
|
+
--url "https://gitlab.example.com/api/v4/projects/123"
|
|
3346
|
+
|
|
3347
|
+
# Headers importantes:
|
|
3348
|
+
# - RateLimit-Limit: Límite de requests
|
|
3349
|
+
# - RateLimit-Remaining: Requests restantes
|
|
3350
|
+
# - RateLimit-Reset: Timestamp de reset
|
|
3351
|
+
# - X-Total: Total de items (paginación)
|
|
3352
|
+
# - X-Total-Pages: Total de páginas
|
|
3353
|
+
# - X-Page: Página actual
|
|
3354
|
+
# - Link: Links de paginación
|
|
3355
|
+
```
|
|
3356
|
+
|
|
3357
|
+
#### Verificar conectividad y DNS:
|
|
3358
|
+
|
|
3359
|
+
```bash
|
|
3360
|
+
# Verificar resolución DNS
|
|
3361
|
+
nslookup gitlab.example.com
|
|
3362
|
+
|
|
3363
|
+
# Verificar conectividad
|
|
3364
|
+
ping -c 4 gitlab.example.com
|
|
3365
|
+
|
|
3366
|
+
# Verificar SSL/TLS
|
|
3367
|
+
openssl s_client -connect gitlab.example.com:443 -servername gitlab.example.com
|
|
3368
|
+
|
|
3369
|
+
# Trace completo
|
|
3370
|
+
curl --trace-ascii trace.txt \
|
|
3371
|
+
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
|
|
3372
|
+
--url "https://gitlab.example.com/api/v4/projects/123"
|
|
3373
|
+
```
|
|
3374
|
+
|
|
3375
|
+
---
|
|
3376
|
+
|
|
3377
|
+
### Errores Específicos de GraphQL
|
|
3378
|
+
|
|
3379
|
+
GraphQL siempre retorna HTTP 200, pero puede contener errores en la respuesta:
|
|
3380
|
+
|
|
3381
|
+
#### Error de autenticación:
|
|
3382
|
+
```json
|
|
3383
|
+
{
|
|
3384
|
+
"errors": [
|
|
3385
|
+
{
|
|
3386
|
+
"message": "Invalid token"
|
|
3387
|
+
}
|
|
3388
|
+
]
|
|
3389
|
+
}
|
|
3390
|
+
```
|
|
3391
|
+
|
|
3392
|
+
#### Error de query inválida:
|
|
3393
|
+
```json
|
|
3394
|
+
{
|
|
3395
|
+
"errors": [
|
|
3396
|
+
{
|
|
3397
|
+
"message": "Field 'invalidField' doesn't exist on type 'Project'",
|
|
3398
|
+
"locations": [{"line": 3, "column": 5}],
|
|
3399
|
+
"path": ["project", "invalidField"]
|
|
3400
|
+
}
|
|
3401
|
+
]
|
|
3402
|
+
}
|
|
3403
|
+
```
|
|
3404
|
+
|
|
3405
|
+
#### Error de complejidad excedida:
|
|
3406
|
+
```json
|
|
3407
|
+
{
|
|
3408
|
+
"errors": [
|
|
3409
|
+
{
|
|
3410
|
+
"message": "Query has complexity of 251, which exceeds max complexity of 250"
|
|
3411
|
+
}
|
|
3412
|
+
]
|
|
3413
|
+
}
|
|
3414
|
+
```
|
|
3415
|
+
|
|
3416
|
+
**Resolución**:
|
|
3417
|
+
1. Verificar sintaxis de query
|
|
3418
|
+
2. Verificar campos existen en schema
|
|
3419
|
+
3. Reducir complejidad (menos campos, menos niveles de anidamiento)
|
|
3420
|
+
4. Usar paginación para limitar resultados
|
|
3421
|
+
|
|
3422
|
+
#### Verificar errores en GraphQL:
|
|
3423
|
+
|
|
3424
|
+
```bash
|
|
3425
|
+
#!/bin/bash
|
|
3426
|
+
|
|
3427
|
+
graphql_query() {
|
|
3428
|
+
local query="$1"
|
|
3429
|
+
|
|
3430
|
+
RESPONSE=$(curl --request POST \
|
|
3431
|
+
--silent \
|
|
3432
|
+
--header "Authorization: Bearer $GITLAB_TOKEN" \
|
|
3433
|
+
--header "Content-Type: application/json" \
|
|
3434
|
+
--data "{\"query\": \"$query\"}" \
|
|
3435
|
+
--url "https://gitlab.com/api/graphql")
|
|
3436
|
+
|
|
3437
|
+
# Verificar si hay errores
|
|
3438
|
+
if echo "$RESPONSE" | jq -e '.errors' > /dev/null 2>&1; then
|
|
3439
|
+
echo "❌ GraphQL Errors:"
|
|
3440
|
+
echo "$RESPONSE" | jq '.errors'
|
|
3441
|
+
return 1
|
|
3442
|
+
else
|
|
3443
|
+
echo "✅ Success:"
|
|
3444
|
+
echo "$RESPONSE" | jq '.data'
|
|
3445
|
+
return 0
|
|
3446
|
+
fi
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
# Uso
|
|
3450
|
+
graphql_query 'query {currentUser {name}}'
|
|
3451
|
+
```
|
|
3452
|
+
|
|
3453
|
+
---
|
|
3454
|
+
|
|
3455
|
+
### Spam Detection
|
|
3456
|
+
|
|
3457
|
+
#### REST API - Sin CAPTCHA:
|
|
3458
|
+
|
|
3459
|
+
```json
|
|
3460
|
+
{
|
|
3461
|
+
"message": {
|
|
3462
|
+
"error": "Your snippet has been recognized as spam and has been discarded."
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
```
|
|
3466
|
+
|
|
3467
|
+
**Resolución**: Modificar contenido para que no sea detectado como spam.
|
|
3468
|
+
|
|
3469
|
+
#### REST API - Con CAPTCHA:
|
|
3470
|
+
|
|
3471
|
+
```json
|
|
3472
|
+
{
|
|
3473
|
+
"needs_captcha_response": true,
|
|
3474
|
+
"spam_log_id": 42,
|
|
3475
|
+
"captcha_site_key": "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI",
|
|
3476
|
+
"message": {
|
|
3477
|
+
"error": "Your snippet has been recognized as spam. Please, change the content or solve the reCAPTCHA to proceed."
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
```
|
|
3481
|
+
|
|
3482
|
+
**Resolución**:
|
|
3483
|
+
|
|
3484
|
+
```bash
|
|
3485
|
+
#!/bin/bash
|
|
3486
|
+
|
|
3487
|
+
# 1. Obtener CAPTCHA response (Google reCAPTCHA v2)
|
|
3488
|
+
# Esto requiere interacción humana en browser
|
|
3489
|
+
# https://developers.google.com/recaptcha/docs/display
|
|
3490
|
+
|
|
3491
|
+
# 2. Reenviar con headers de CAPTCHA
|
|
3492
|
+
CAPTCHA_RESPONSE="<response-from-recaptcha>"
|
|
3493
|
+
SPAM_LOG_ID="42"
|
|
3494
|
+
|
|
3495
|
+
curl --request POST \
|
|
3496
|
+
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
|
|
3497
|
+
--header "X-GitLab-Captcha-Response: $CAPTCHA_RESPONSE" \
|
|
3498
|
+
--header "X-GitLab-Spam-Log-Id: $SPAM_LOG_ID" \
|
|
3499
|
+
--header "Content-Type: application/json" \
|
|
3500
|
+
--data '{"title": "My snippet", "content": "Content"}' \
|
|
3501
|
+
--url "https://gitlab.example.com/api/v4/snippets"
|
|
3502
|
+
```
|
|
3503
|
+
|
|
3504
|
+
#### GraphQL - Con CAPTCHA:
|
|
3505
|
+
|
|
3506
|
+
```json
|
|
3507
|
+
{
|
|
3508
|
+
"errors": [
|
|
3509
|
+
{
|
|
3510
|
+
"message": "Request denied. Solve CAPTCHA challenge and retry",
|
|
3511
|
+
"locations": [{"line": 6, "column": 7}],
|
|
3512
|
+
"path": ["updateSnippet"],
|
|
3513
|
+
"extensions": {
|
|
3514
|
+
"needsCaptchaResponse": true,
|
|
3515
|
+
"captchaSiteKey": "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI",
|
|
3516
|
+
"spamLogId": 67
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
],
|
|
3520
|
+
"data": {
|
|
3521
|
+
"updateSnippet": {
|
|
3522
|
+
"snippet": null
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
```
|
|
3527
|
+
|
|
3528
|
+
**Resolución**:
|
|
3529
|
+
|
|
3530
|
+
```bash
|
|
3531
|
+
CAPTCHA_RESPONSE="<response-from-recaptcha>"
|
|
3532
|
+
SPAM_LOG_ID="67"
|
|
3533
|
+
|
|
3534
|
+
curl --request POST \
|
|
3535
|
+
--header "Authorization: Bearer $GITLAB_TOKEN" \
|
|
3536
|
+
--header "Content-Type: application/json" \
|
|
3537
|
+
--header "X-GitLab-Captcha-Response: $CAPTCHA_RESPONSE" \
|
|
3538
|
+
--header "X-GitLab-Spam-Log-Id: $SPAM_LOG_ID" \
|
|
3539
|
+
--data-binary '{"query": "mutation {updateSnippet(input: {id: \"gid://gitlab/Snippet/123\", title: \"Updated\"}) {snippet {id} errors}}"}' \
|
|
3540
|
+
--url "https://gitlab.com/api/graphql"
|
|
3541
|
+
```
|
|
3542
|
+
|
|
3543
|
+
---
|
|
3544
|
+
|
|
3545
|
+
### Error 404 con Reverse Proxy
|
|
3546
|
+
|
|
3547
|
+
**Problema**: URLs con caracteres especiales (/, ?, @) causan 404 cuando hay reverse proxy.
|
|
3548
|
+
|
|
3549
|
+
**Causa**: Proxy decodifica URL-encoded characters antes de pasar a GitLab.
|
|
3550
|
+
|
|
3551
|
+
**Síntomas**:
|
|
3552
|
+
- Editor extensions no funcionan
|
|
3553
|
+
- glab CLI falla con paths
|
|
3554
|
+
- API calls con URL-encoded params retornan 404
|
|
3555
|
+
|
|
3556
|
+
**Solución Apache**:
|
|
3557
|
+
|
|
3558
|
+
```apache
|
|
3559
|
+
<VirtualHost *:443>
|
|
3560
|
+
ServerName git.example.com
|
|
3561
|
+
|
|
3562
|
+
# CRÍTICO: Agregar esta línea
|
|
3563
|
+
AllowEncodedSlashes NoDecode
|
|
3564
|
+
|
|
3565
|
+
<Location />
|
|
3566
|
+
# CRÍTICO: Agregar flag nocanon
|
|
3567
|
+
ProxyPass http://127.0.0.1:8080/ nocanon
|
|
3568
|
+
ProxyPassReverse http://127.0.0.1:8080/
|
|
3569
|
+
</Location>
|
|
3570
|
+
</VirtualHost>
|
|
3571
|
+
```
|
|
3572
|
+
|
|
3573
|
+
**Solución NGINX**:
|
|
3574
|
+
|
|
3575
|
+
```nginx
|
|
3576
|
+
server {
|
|
3577
|
+
server_name git.example.com;
|
|
3578
|
+
|
|
3579
|
+
location / {
|
|
3580
|
+
# CRÍTICO: No decodificar automáticamente
|
|
3581
|
+
proxy_pass http://127.0.0.1:8080;
|
|
3582
|
+
proxy_set_header Host $host;
|
|
3583
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
3584
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
3585
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
```
|
|
3589
|
+
|
|
3590
|
+
**Verificación**:
|
|
3591
|
+
|
|
3592
|
+
```bash
|
|
3593
|
+
# Debe funcionar sin 404
|
|
3594
|
+
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
|
|
3595
|
+
--url "https://git.example.com/api/v4/projects/grupo%2Fproyecto"
|
|
3596
|
+
|
|
3597
|
+
# También debe funcionar
|
|
3598
|
+
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
|
|
3599
|
+
--url "https://git.example.com/api/v4/projects/123/repository/files/src%2Fapp.js?ref=main"
|
|
3600
|
+
```
|
|
3601
|
+
|
|
3602
|
+
---
|
|
3603
|
+
|
|
3604
|
+
## Estructura Modular Recomendada
|
|
3605
|
+
|
|
3606
|
+
Para organizar una skill operativa de GitLab API, se recomienda la siguiente estructura modular:
|
|
3607
|
+
|
|
3608
|
+
```
|
|
3609
|
+
gitlab-api-skill/
|
|
3610
|
+
├── README.md # Índice principal y guía de uso
|
|
3611
|
+
├── AUTHENTICATION.md # Guía de autenticación (tokens, scopes, headers)
|
|
3612
|
+
├── COMMON_PATTERNS.md # Patrones comunes (paginación, filtrado, errores)
|
|
3613
|
+
├── TROUBLESHOOTING.md # Guía de troubleshooting y códigos HTTP
|
|
3614
|
+
│
|
|
3615
|
+
├── rest/
|
|
3616
|
+
│ ├── README.md # Introducción a REST API
|
|
3617
|
+
│ ├── projects.md # Operaciones con proyectos
|
|
3618
|
+
│ ├── groups.md # Operaciones con grupos
|
|
3619
|
+
│ ├── issues.md # Operaciones con issues
|
|
3620
|
+
│ ├── merge-requests.md # Operaciones con merge requests
|
|
3621
|
+
│ ├── pipelines.md # Operaciones con pipelines
|
|
3622
|
+
│ ├── jobs.md # Operaciones con jobs
|
|
3623
|
+
│ ├── repository.md # Operaciones con repositorio
|
|
3624
|
+
│ ├── files.md # Operaciones con archivos
|
|
3625
|
+
│ ├── releases.md # Operaciones con releases
|
|
3626
|
+
│ ├── webhooks.md # Operaciones con webhooks
|
|
3627
|
+
│ ├── variables.md # Operaciones con variables CI/CD
|
|
3628
|
+
│ ├── users.md # Operaciones con usuarios
|
|
3629
|
+
│ ├── labels.md # Operaciones con labels
|
|
3630
|
+
│ ├── milestones.md # Operaciones con milestones
|
|
3631
|
+
│ ├── runners.md # Operaciones con runners
|
|
3632
|
+
│ ├── registry.md # Operaciones con container registry
|
|
3633
|
+
│ └── deployments.md # Operaciones con deployments
|
|
3634
|
+
│
|
|
3635
|
+
├── graphql/
|
|
3636
|
+
│ ├── README.md # Introducción a GraphQL API
|
|
3637
|
+
│ ├── queries/
|
|
3638
|
+
│ │ ├── projects.md # Queries de proyectos
|
|
3639
|
+
│ │ ├── issues.md # Queries de issues
|
|
3640
|
+
│ │ ├── merge-requests.md # Queries de merge requests
|
|
3641
|
+
│ │ ├── pipelines.md # Queries de pipelines
|
|
3642
|
+
│ │ ├── users.md # Queries de usuarios
|
|
3643
|
+
│ │ └── advanced.md # Queries avanzadas (multiplex, complexity)
|
|
3644
|
+
│ └── mutations/
|
|
3645
|
+
│ ├── projects.md # Mutations de proyectos
|
|
3646
|
+
│ ├── issues.md # Mutations de issues
|
|
3647
|
+
│ ├── merge-requests.md # Mutations de merge requests
|
|
3648
|
+
│ ├── pipelines.md # Mutations de pipelines
|
|
3649
|
+
│ └── advanced.md # Mutations avanzadas
|
|
3650
|
+
│
|
|
3651
|
+
├── glab/
|
|
3652
|
+
│ ├── README.md # Introducción a glab CLI
|
|
3653
|
+
│ ├── setup.md # Instalación y configuración
|
|
3654
|
+
│ ├── projects.md # Comandos de proyectos
|
|
3655
|
+
│ ├── issues.md # Comandos de issues
|
|
3656
|
+
│ ├── merge-requests.md # Comandos de merge requests
|
|
3657
|
+
│ ├── pipelines.md # Comandos de pipelines
|
|
3658
|
+
│ ├── api.md # Uso de glab api
|
|
3659
|
+
│ └── scripting.md # Scripts y automatización
|
|
3660
|
+
│
|
|
3661
|
+
├── examples/
|
|
3662
|
+
│ ├── bash/
|
|
3663
|
+
│ │ ├── create-issue.sh # Script completo: crear issue
|
|
3664
|
+
│ │ ├── create-mr.sh # Script completo: crear MR
|
|
3665
|
+
│ │ ├── trigger-pipeline.sh # Script completo: ejecutar pipeline
|
|
3666
|
+
│ │ ├── bulk-operations.sh # Script: operaciones en bulk
|
|
3667
|
+
│ │ └── monitoring.sh # Script: monitoreo de pipelines
|
|
3668
|
+
│ ├── python/
|
|
3669
|
+
│ │ ├── gitlab_client.py # Cliente Python para GitLab API
|
|
3670
|
+
│ │ ├── create_issue.py # Ejemplo: crear issue
|
|
3671
|
+
│ │ ├── create_mr.py # Ejemplo: crear MR
|
|
3672
|
+
│ │ └── pipeline_monitor.py # Ejemplo: monitorear pipelines
|
|
3673
|
+
│ └── graphql/
|
|
3674
|
+
│ ├── queries.graphql # Colección de queries útiles
|
|
3675
|
+
│ └── mutations.graphql # Colección de mutations útiles
|
|
3676
|
+
│
|
|
3677
|
+
└── reference/
|
|
3678
|
+
├── status-codes.md # Referencia completa de códigos HTTP
|
|
3679
|
+
├── rate-limits.md # Información sobre rate limits
|
|
3680
|
+
├── pagination.md # Guía detallada de paginación
|
|
3681
|
+
└── global-ids.md # Guía de Global IDs en GraphQL
|
|
3682
|
+
```
|
|
3683
|
+
|
|
3684
|
+
### Contenido de cada archivo
|
|
3685
|
+
|
|
3686
|
+
#### README.md (Principal)
|
|
3687
|
+
|
|
3688
|
+
```markdown
|
|
3689
|
+
# GitLab API & CLI - Skill Operativa
|
|
3690
|
+
|
|
3691
|
+
Guía completa para interactuar con GitLab mediante API REST, GraphQL y CLI (glab).
|
|
3692
|
+
|
|
3693
|
+
## Índice
|
|
3694
|
+
|
|
3695
|
+
### Guías Generales
|
|
3696
|
+
- [Autenticación](AUTHENTICATION.md) - Tokens, scopes, headers
|
|
3697
|
+
- [Patrones Comunes](COMMON_PATTERNS.md) - Paginación, filtrado, errores
|
|
3698
|
+
- [Troubleshooting](TROUBLESHOOTING.md) - Códigos HTTP y resolución
|
|
3699
|
+
|
|
3700
|
+
### REST API
|
|
3701
|
+
- [Introducción](rest/README.md)
|
|
3702
|
+
- [Projects](rest/projects.md) - Crear, listar, actualizar proyectos
|
|
3703
|
+
- [Issues](rest/issues.md) - Gestión de issues
|
|
3704
|
+
- [Merge Requests](rest/merge-requests.md) - Gestión de MRs
|
|
3705
|
+
- [Pipelines](rest/pipelines.md) - CI/CD pipelines
|
|
3706
|
+
- [Jobs](rest/jobs.md) - CI/CD jobs
|
|
3707
|
+
- [Repository](rest/repository.md) - Branches, tags, commits
|
|
3708
|
+
- [Files](rest/files.md) - Operaciones con archivos
|
|
3709
|
+
- [Y más...](rest/)
|
|
3710
|
+
|
|
3711
|
+
### GraphQL API
|
|
3712
|
+
- [Introducción](graphql/README.md)
|
|
3713
|
+
- [Queries](graphql/queries/) - Consultas de datos
|
|
3714
|
+
- [Mutations](graphql/mutations/) - Modificaciones de datos
|
|
3715
|
+
|
|
3716
|
+
### glab CLI
|
|
3717
|
+
- [Introducción](glab/README.md)
|
|
3718
|
+
- [Setup](glab/setup.md) - Instalación y configuración
|
|
3719
|
+
- [Comandos por feature](glab/)
|
|
3720
|
+
|
|
3721
|
+
### Ejemplos
|
|
3722
|
+
- [Bash Scripts](examples/bash/)
|
|
3723
|
+
- [Python](examples/python/)
|
|
3724
|
+
- [GraphQL](examples/graphql/)
|
|
3725
|
+
|
|
3726
|
+
### Referencia
|
|
3727
|
+
- [Códigos de Estado HTTP](reference/status-codes.md)
|
|
3728
|
+
- [Rate Limits](reference/rate-limits.md)
|
|
3729
|
+
- [Paginación](reference/pagination.md)
|
|
3730
|
+
- [Global IDs](reference/global-ids.md)
|
|
3731
|
+
|
|
3732
|
+
## Inicio Rápido
|
|
3733
|
+
|
|
3734
|
+
### REST API
|
|
3735
|
+
\`\`\`bash
|
|
3736
|
+
# Listar proyectos
|
|
3737
|
+
curl --header "PRIVATE-TOKEN: <token>" \\
|
|
3738
|
+
--url "https://gitlab.example.com/api/v4/projects"
|
|
3739
|
+
\`\`\`
|
|
3740
|
+
|
|
3741
|
+
### GraphQL API
|
|
3742
|
+
\`\`\`bash
|
|
3743
|
+
curl --request POST \\
|
|
3744
|
+
--header "Authorization: Bearer <token>" \\
|
|
3745
|
+
--header "Content-Type: application/json" \\
|
|
3746
|
+
--data '{"query": "query {currentUser {name}}"}' \\
|
|
3747
|
+
--url "https://gitlab.com/api/graphql"
|
|
3748
|
+
\`\`\`
|
|
3749
|
+
|
|
3750
|
+
### glab CLI
|
|
3751
|
+
\`\`\`bash
|
|
3752
|
+
# Autenticación
|
|
3753
|
+
glab auth login
|
|
3754
|
+
|
|
3755
|
+
# Listar issues
|
|
3756
|
+
glab issue list
|
|
3757
|
+
\`\`\`
|
|
3758
|
+
|
|
3759
|
+
## Recursos Oficiales
|
|
3760
|
+
|
|
3761
|
+
- [GitLab API Docs](https://docs.gitlab.com/api/)
|
|
3762
|
+
- [GraphQL API Reference](https://docs.gitlab.com/api/graphql/reference/)
|
|
3763
|
+
- [glab CLI Docs](https://docs.gitlab.com/cli/)
|
|
3764
|
+
```
|
|
3765
|
+
|
|
3766
|
+
#### rest/projects.md (Ejemplo)
|
|
3767
|
+
|
|
3768
|
+
```markdown
|
|
3769
|
+
# Projects API - REST
|
|
3770
|
+
|
|
3771
|
+
Operaciones con proyectos de GitLab mediante REST API.
|
|
3772
|
+
|
|
3773
|
+
## Endpoints
|
|
3774
|
+
|
|
3775
|
+
- `GET /projects` - Listar proyectos
|
|
3776
|
+
- `GET /projects/:id` - Obtener proyecto
|
|
3777
|
+
- `POST /projects` - Crear proyecto
|
|
3778
|
+
- `PUT /projects/:id` - Actualizar proyecto
|
|
3779
|
+
- `DELETE /projects/:id` - Eliminar proyecto
|
|
3780
|
+
- `POST /projects/:id/archive` - Archivar proyecto
|
|
3781
|
+
- `POST /projects/:id/unarchive` - Desarchivar proyecto
|
|
3782
|
+
|
|
3783
|
+
## Ejemplos
|
|
3784
|
+
|
|
3785
|
+
### Listar proyectos
|
|
3786
|
+
|
|
3787
|
+
\`\`\`bash
|
|
3788
|
+
# Todos los proyectos
|
|
3789
|
+
curl --header "PRIVATE-TOKEN: <token>" \\
|
|
3790
|
+
--url "https://gitlab.example.com/api/v4/projects"
|
|
3791
|
+
|
|
3792
|
+
# Proyectos propios
|
|
3793
|
+
curl --header "PRIVATE-TOKEN: <token>" \\
|
|
3794
|
+
--url "https://gitlab.example.com/api/v4/projects?owned=true"
|
|
3795
|
+
|
|
3796
|
+
# Con paginación
|
|
3797
|
+
curl --header "PRIVATE-TOKEN: <token>" \\
|
|
3798
|
+
--url "https://gitlab.example.com/api/v4/projects?page=2&per_page=50"
|
|
3799
|
+
\`\`\`
|
|
3800
|
+
|
|
3801
|
+
### Obtener proyecto
|
|
3802
|
+
|
|
3803
|
+
\`\`\`bash
|
|
3804
|
+
# Por ID
|
|
3805
|
+
curl --header "PRIVATE-TOKEN: <token>" \\
|
|
3806
|
+
--url "https://gitlab.example.com/api/v4/projects/123"
|
|
3807
|
+
|
|
3808
|
+
# Por path (URL-encoded)
|
|
3809
|
+
curl --header "PRIVATE-TOKEN: <token>" \\
|
|
3810
|
+
--url "https://gitlab.example.com/api/v4/projects/grupo%2Fproyecto"
|
|
3811
|
+
\`\`\`
|
|
3812
|
+
|
|
3813
|
+
### Crear proyecto
|
|
3814
|
+
|
|
3815
|
+
\`\`\`bash
|
|
3816
|
+
curl --request POST \\
|
|
3817
|
+
--header "PRIVATE-TOKEN: <token>" \\
|
|
3818
|
+
--header "Content-Type: application/json" \\
|
|
3819
|
+
--data '{
|
|
3820
|
+
"name": "Mi Proyecto",
|
|
3821
|
+
"path": "mi-proyecto",
|
|
3822
|
+
"description": "Descripción",
|
|
3823
|
+
"visibility": "private",
|
|
3824
|
+
"initialize_with_readme": true
|
|
3825
|
+
}' \\
|
|
3826
|
+
--url "https://gitlab.example.com/api/v4/projects"
|
|
3827
|
+
\`\`\`
|
|
3828
|
+
|
|
3829
|
+
## Ver también
|
|
3830
|
+
|
|
3831
|
+
- [Groups](groups.md)
|
|
3832
|
+
- [Repository](repository.md)
|
|
3833
|
+
- [Troubleshooting](../TROUBLESHOOTING.md)
|
|
3834
|
+
```
|
|
3835
|
+
|
|
3836
|
+
### Ventajas de esta estructura:
|
|
3837
|
+
|
|
3838
|
+
1. **Modularidad**: Cada feature en su propio archivo
|
|
3839
|
+
2. **Navegación fácil**: Índice claro en README principal
|
|
3840
|
+
3. **Búsqueda rápida**: Archivos organizados por tipo de API
|
|
3841
|
+
4. **Ejemplos prácticos**: Scripts completos en carpeta examples/
|
|
3842
|
+
5. **Referencia rápida**: Información común en archivos dedicados
|
|
3843
|
+
6. **Escalabilidad**: Fácil agregar nuevos endpoints/features
|
|
3844
|
+
7. **Mantenimiento**: Actualizar un feature no afecta otros
|
|
3845
|
+
8. **Cross-referencing**: Links entre archivos relacionados
|
|
3846
|
+
|
|
3847
|
+
### Uso de la estructura:
|
|
3848
|
+
|
|
3849
|
+
```bash
|
|
3850
|
+
# Buscar cómo crear un issue
|
|
3851
|
+
cat rest/issues.md | grep -A 10 "Crear issue"
|
|
3852
|
+
|
|
3853
|
+
# Ver todos los ejemplos de bash
|
|
3854
|
+
ls examples/bash/
|
|
3855
|
+
|
|
3856
|
+
# Buscar información sobre rate limits
|
|
3857
|
+
cat reference/rate-limits.md
|
|
3858
|
+
|
|
3859
|
+
# Ver troubleshooting de código 404
|
|
3860
|
+
cat TROUBLESHOOTING.md | grep -A 20 "404 Not Found"
|
|
3861
|
+
```
|
|
3862
|
+
|
|
3863
|
+
---
|
|
3864
|
+
|
|
3865
|
+
## Conclusión
|
|
3866
|
+
|
|
3867
|
+
Esta guía proporciona:
|
|
3868
|
+
|
|
3869
|
+
✅ **Resumen completo** de 7 URLs oficiales de GitLab Docs
|
|
3870
|
+
✅ **Patrones de uso** para autenticación, paginación, errores y rate limits
|
|
3871
|
+
✅ **25+ ejemplos concretos** distribuidos por features (projects, issues, MRs, pipelines, jobs, repository, files, releases, webhooks, variables, GraphQL queries/mutations, glab api)
|
|
3872
|
+
✅ **Troubleshooting detallado** con códigos HTTP y resoluciones
|
|
3873
|
+
✅ **Estructura modular** recomendada para organizar skill operativa
|
|
3874
|
+
|
|
3875
|
+
**Próximos pasos sugeridos:**
|
|
3876
|
+
1. Implementar scripts de ejemplo en `examples/bash/`
|
|
3877
|
+
2. Crear cliente Python reutilizable en `examples/python/`
|
|
3878
|
+
3. Documentar casos de uso específicos por equipo/proyecto
|
|
3879
|
+
4. Agregar ejemplos de integración CI/CD
|
|
3880
|
+
5. Crear cheatsheet de comandos más usados
|
|
3881
|
+
|
|
3882
|
+
---
|
|
3883
|
+
|
|
3884
|
+
**Fecha de última actualización**: 18 de febrero de 2026
|
|
3885
|
+
**Fuentes**: docs.gitlab.com/api/
|