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.
@@ -0,0 +1,1351 @@
1
+ # GitLab API - Scripts Prácticos
2
+
3
+ > Colección de scripts completos y listos para usar
4
+
5
+ ---
6
+
7
+ ## Tabla de Contenidos
8
+
9
+ 1. [Configuración Inicial](#configuración-inicial)
10
+ 2. [Scripts de Proyectos](#scripts-de-proyectos)
11
+ 3. [Scripts de Issues](#scripts-de-issues)
12
+ 4. [Scripts de Merge Requests](#scripts-de-merge-requests)
13
+ 5. [Scripts de Pipelines](#scripts-de-pipelines)
14
+ 6. [Scripts de Repository](#scripts-de-repository)
15
+ 7. [Scripts de Monitoreo](#scripts-de-monitoreo)
16
+ 8. [Scripts de Automatización](#scripts-de-automatización)
17
+
18
+ ---
19
+
20
+ ## Configuración Inicial
21
+
22
+ ### Script: setup-gitlab-env.sh
23
+
24
+ ```bash
25
+ #!/bin/bash
26
+
27
+ # setup-gitlab-env.sh
28
+ # Configura variables de entorno para GitLab API
29
+
30
+ set -e
31
+
32
+ echo "=== GitLab API Setup ==="
33
+ echo ""
34
+
35
+ # Solicitar información
36
+ read -p "GitLab Host (default: https://gitlab.com): " GITLAB_HOST
37
+ GITLAB_HOST=${GITLAB_HOST:-https://gitlab.com}
38
+
39
+ read -sp "GitLab Token: " GITLAB_TOKEN
40
+ echo ""
41
+
42
+ # Validar token
43
+ echo "Validating token..."
44
+ RESPONSE=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" \
45
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
46
+ --url "$GITLAB_HOST/api/v4/user")
47
+
48
+ HTTP_STATUS=$(echo "$RESPONSE" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
49
+ BODY=$(echo "$RESPONSE" | sed -e 's/HTTPSTATUS\:.*//g')
50
+
51
+ if [ "$HTTP_STATUS" -eq 200 ]; then
52
+ USERNAME=$(echo "$BODY" | jq -r '.username')
53
+ echo "✅ Token válido. Usuario: $USERNAME"
54
+
55
+ # Guardar en archivo .env
56
+ cat > .gitlab-env << EOF
57
+ export GITLAB_HOST="$GITLAB_HOST"
58
+ export GITLAB_TOKEN="$GITLAB_TOKEN"
59
+ export GITLAB_API_URL="$GITLAB_HOST/api/v4"
60
+ EOF
61
+
62
+ echo ""
63
+ echo "✅ Configuración guardada en .gitlab-env"
64
+ echo "Para usar: source .gitlab-env"
65
+ else
66
+ echo "❌ Token inválido o error de conexión"
67
+ exit 1
68
+ fi
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Scripts de Proyectos
74
+
75
+ ### Script: list-all-projects.sh
76
+
77
+ ```bash
78
+ #!/bin/bash
79
+
80
+ # list-all-projects.sh
81
+ # Lista todos los proyectos con paginación automática
82
+
83
+ source .gitlab-env
84
+
85
+ OUTPUT_FILE="projects.json"
86
+ page=1
87
+ all_projects="[]"
88
+
89
+ echo "Obteniendo proyectos..."
90
+
91
+ while true; do
92
+ echo "Página $page..."
93
+
94
+ response=$(curl --silent \
95
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
96
+ --url "$GITLAB_API_URL/projects?page=$page&per_page=100&simple=true")
97
+
98
+ # Verificar si hay proyectos
99
+ count=$(echo "$response" | jq '. | length')
100
+
101
+ if [ "$count" -eq 0 ]; then
102
+ break
103
+ fi
104
+
105
+ # Agregar a la lista
106
+ all_projects=$(echo "$all_projects" | jq ". + $response")
107
+
108
+ page=$((page + 1))
109
+ done
110
+
111
+ # Guardar resultados
112
+ echo "$all_projects" > "$OUTPUT_FILE"
113
+
114
+ total=$(echo "$all_projects" | jq '. | length')
115
+ echo ""
116
+ echo "✅ Total de proyectos: $total"
117
+ echo "📄 Guardado en: $OUTPUT_FILE"
118
+
119
+ # Mostrar resumen
120
+ echo ""
121
+ echo "Top 10 proyectos:"
122
+ echo "$all_projects" | jq -r '.[:10] | .[] | "\(.id) - \(.name_with_namespace)"'
123
+ ```
124
+
125
+ ### Script: create-project.sh
126
+
127
+ ```bash
128
+ #!/bin/bash
129
+
130
+ # create-project.sh
131
+ # Crea un nuevo proyecto con configuración completa
132
+
133
+ source .gitlab-env
134
+
135
+ # Parámetros
136
+ PROJECT_NAME="${1:-}"
137
+ PROJECT_PATH="${2:-}"
138
+ DESCRIPTION="${3:-}"
139
+ VISIBILITY="${4:-private}"
140
+
141
+ if [ -z "$PROJECT_NAME" ]; then
142
+ read -p "Nombre del proyecto: " PROJECT_NAME
143
+ fi
144
+
145
+ if [ -z "$PROJECT_PATH" ]; then
146
+ PROJECT_PATH=$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
147
+ fi
148
+
149
+ if [ -z "$DESCRIPTION" ]; then
150
+ read -p "Descripción: " DESCRIPTION
151
+ fi
152
+
153
+ echo "Creando proyecto..."
154
+ echo " Nombre: $PROJECT_NAME"
155
+ echo " Path: $PROJECT_PATH"
156
+ echo " Visibilidad: $VISIBILITY"
157
+
158
+ response=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" \
159
+ --request POST \
160
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
161
+ --header "Content-Type: application/json" \
162
+ --data "{
163
+ \"name\": \"$PROJECT_NAME\",
164
+ \"path\": \"$PROJECT_PATH\",
165
+ \"description\": \"$DESCRIPTION\",
166
+ \"visibility\": \"$VISIBILITY\",
167
+ \"initialize_with_readme\": true,
168
+ \"issues_enabled\": true,
169
+ \"merge_requests_enabled\": true,
170
+ \"wiki_enabled\": true,
171
+ \"snippets_enabled\": true,
172
+ \"builds_enabled\": true
173
+ }" \
174
+ --url "$GITLAB_API_URL/projects")
175
+
176
+ HTTP_STATUS=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
177
+ BODY=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g')
178
+
179
+ if [ "$HTTP_STATUS" -eq 201 ]; then
180
+ PROJECT_ID=$(echo "$BODY" | jq -r '.id')
181
+ WEB_URL=$(echo "$BODY" | jq -r '.web_url')
182
+
183
+ echo ""
184
+ echo "✅ Proyecto creado exitosamente"
185
+ echo " ID: $PROJECT_ID"
186
+ echo " URL: $WEB_URL"
187
+ else
188
+ echo ""
189
+ echo "❌ Error al crear proyecto"
190
+ echo "$BODY" | jq '.'
191
+ exit 1
192
+ fi
193
+ ```
194
+
195
+ ### Script: clone-project-settings.sh
196
+
197
+ ```bash
198
+ #!/bin/bash
199
+
200
+ # clone-project-settings.sh
201
+ # Clona configuración de un proyecto a otro
202
+
203
+ source .gitlab-env
204
+
205
+ SOURCE_PROJECT_ID="$1"
206
+ TARGET_PROJECT_ID="$2"
207
+
208
+ if [ -z "$SOURCE_PROJECT_ID" ] || [ -z "$TARGET_PROJECT_ID" ]; then
209
+ echo "Uso: $0 <source_project_id> <target_project_id>"
210
+ exit 1
211
+ fi
212
+
213
+ echo "Clonando configuración..."
214
+ echo " Origen: $SOURCE_PROJECT_ID"
215
+ echo " Destino: $TARGET_PROJECT_ID"
216
+
217
+ # Obtener configuración del proyecto origen
218
+ source_config=$(curl --silent \
219
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
220
+ --url "$GITLAB_API_URL/projects/$SOURCE_PROJECT_ID")
221
+
222
+ # Extraer configuración relevante
223
+ visibility=$(echo "$source_config" | jq -r '.visibility')
224
+ issues_enabled=$(echo "$source_config" | jq -r '.issues_enabled')
225
+ merge_requests_enabled=$(echo "$source_config" | jq -r '.merge_requests_enabled')
226
+ wiki_enabled=$(echo "$source_config" | jq -r '.wiki_enabled')
227
+ builds_enabled=$(echo "$source_config" | jq -r '.builds_enabled')
228
+ auto_devops_enabled=$(echo "$source_config" | jq -r '.auto_devops_enabled')
229
+
230
+ # Aplicar configuración al proyecto destino
231
+ curl --request PUT \
232
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
233
+ --header "Content-Type: application/json" \
234
+ --data "{
235
+ \"visibility\": \"$visibility\",
236
+ \"issues_enabled\": $issues_enabled,
237
+ \"merge_requests_enabled\": $merge_requests_enabled,
238
+ \"wiki_enabled\": $wiki_enabled,
239
+ \"builds_enabled\": $builds_enabled,
240
+ \"auto_devops_enabled\": $auto_devops_enabled
241
+ }" \
242
+ --url "$GITLAB_API_URL/projects/$TARGET_PROJECT_ID"
243
+
244
+ echo ""
245
+ echo "✅ Configuración clonada exitosamente"
246
+ ```
247
+
248
+ ---
249
+
250
+ ## Scripts de Issues
251
+
252
+ ### Script: create-issue-from-template.sh
253
+
254
+ ```bash
255
+ #!/bin/bash
256
+
257
+ # create-issue-from-template.sh
258
+ # Crea issues desde un template CSV
259
+
260
+ source .gitlab-env
261
+
262
+ PROJECT_ID="$1"
263
+ CSV_FILE="$2"
264
+
265
+ if [ -z "$PROJECT_ID" ] || [ -z "$CSV_FILE" ]; then
266
+ echo "Uso: $0 <project_id> <csv_file>"
267
+ echo ""
268
+ echo "Formato CSV: title,description,labels,assignee_username"
269
+ exit 1
270
+ fi
271
+
272
+ if [ ! -f "$CSV_FILE" ]; then
273
+ echo "❌ Archivo no encontrado: $CSV_FILE"
274
+ exit 1
275
+ fi
276
+
277
+ echo "Creando issues desde $CSV_FILE..."
278
+ echo ""
279
+
280
+ created=0
281
+ failed=0
282
+
283
+ # Leer CSV (skip header)
284
+ tail -n +2 "$CSV_FILE" | while IFS=',' read -r title description labels assignee_username; do
285
+ echo "Creando: $title"
286
+
287
+ # Obtener assignee ID si se especificó
288
+ assignee_id=""
289
+ if [ -n "$assignee_username" ]; then
290
+ assignee_id=$(curl --silent \
291
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
292
+ --url "$GITLAB_API_URL/users?username=$assignee_username" \
293
+ | jq -r '.[0].id')
294
+ fi
295
+
296
+ # Crear issue
297
+ response=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" \
298
+ --request POST \
299
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
300
+ --header "Content-Type: application/json" \
301
+ --data "{
302
+ \"title\": \"$title\",
303
+ \"description\": \"$description\",
304
+ \"labels\": \"$labels\"
305
+ $([ -n "$assignee_id" ] && echo ", \"assignee_ids\": [$assignee_id]")
306
+ }" \
307
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/issues")
308
+
309
+ HTTP_STATUS=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
310
+
311
+ if [ "$HTTP_STATUS" -eq 201 ]; then
312
+ created=$((created + 1))
313
+ echo " ✅ Creado"
314
+ else
315
+ failed=$((failed + 1))
316
+ echo " ❌ Error"
317
+ fi
318
+
319
+ # Rate limiting
320
+ sleep 0.5
321
+ done
322
+
323
+ echo ""
324
+ echo "Resumen:"
325
+ echo " ✅ Creados: $created"
326
+ echo " ❌ Fallidos: $failed"
327
+ ```
328
+
329
+ ### Script: export-issues.sh
330
+
331
+ ```bash
332
+ #!/bin/bash
333
+
334
+ # export-issues.sh
335
+ # Exporta todas las issues de un proyecto a JSON/CSV
336
+
337
+ source .gitlab-env
338
+
339
+ PROJECT_ID="$1"
340
+ FORMAT="${2:-json}" # json o csv
341
+
342
+ if [ -z "$PROJECT_ID" ]; then
343
+ echo "Uso: $0 <project_id> [format]"
344
+ echo " format: json (default) o csv"
345
+ exit 1
346
+ fi
347
+
348
+ OUTPUT_FILE="issues-$PROJECT_ID.$FORMAT"
349
+ page=1
350
+ all_issues="[]"
351
+
352
+ echo "Exportando issues del proyecto $PROJECT_ID..."
353
+
354
+ while true; do
355
+ echo "Página $page..."
356
+
357
+ response=$(curl --silent \
358
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
359
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/issues?page=$page&per_page=100")
360
+
361
+ count=$(echo "$response" | jq '. | length')
362
+
363
+ if [ "$count" -eq 0 ]; then
364
+ break
365
+ fi
366
+
367
+ all_issues=$(echo "$all_issues" | jq ". + $response")
368
+ page=$((page + 1))
369
+ done
370
+
371
+ total=$(echo "$all_issues" | jq '. | length')
372
+
373
+ if [ "$FORMAT" = "json" ]; then
374
+ echo "$all_issues" > "$OUTPUT_FILE"
375
+ elif [ "$FORMAT" = "csv" ]; then
376
+ echo "iid,title,state,labels,assignee,created_at,updated_at,web_url" > "$OUTPUT_FILE"
377
+ echo "$all_issues" | jq -r '.[] | [
378
+ .iid,
379
+ .title,
380
+ .state,
381
+ (.labels | join(";")),
382
+ (.assignees[0].username // ""),
383
+ .created_at,
384
+ .updated_at,
385
+ .web_url
386
+ ] | @csv' >> "$OUTPUT_FILE"
387
+ fi
388
+
389
+ echo ""
390
+ echo "✅ Exportadas $total issues"
391
+ echo "📄 Guardado en: $OUTPUT_FILE"
392
+ ```
393
+
394
+ ### Script: bulk-close-issues.sh
395
+
396
+ ```bash
397
+ #!/bin/bash
398
+
399
+ # bulk-close-issues.sh
400
+ # Cierra issues en bulk por label o milestone
401
+
402
+ source .gitlab-env
403
+
404
+ PROJECT_ID="$1"
405
+ FILTER_TYPE="$2" # label o milestone
406
+ FILTER_VALUE="$3"
407
+
408
+ if [ -z "$PROJECT_ID" ] || [ -z "$FILTER_TYPE" ] || [ -z "$FILTER_VALUE" ]; then
409
+ echo "Uso: $0 <project_id> <filter_type> <filter_value>"
410
+ echo " filter_type: label o milestone"
411
+ echo ""
412
+ echo "Ejemplos:"
413
+ echo " $0 123 label bug"
414
+ echo " $0 123 milestone v1.0"
415
+ exit 1
416
+ fi
417
+
418
+ # Construir URL según filtro
419
+ if [ "$FILTER_TYPE" = "label" ]; then
420
+ FILTER_PARAM="labels=$FILTER_VALUE"
421
+ elif [ "$FILTER_TYPE" = "milestone" ]; then
422
+ FILTER_PARAM="milestone=$FILTER_VALUE"
423
+ else
424
+ echo "❌ Tipo de filtro inválido: $FILTER_TYPE"
425
+ exit 1
426
+ fi
427
+
428
+ echo "Obteniendo issues con $FILTER_TYPE=$FILTER_VALUE..."
429
+
430
+ issues=$(curl --silent \
431
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
432
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/issues?state=opened&$FILTER_PARAM&per_page=100")
433
+
434
+ count=$(echo "$issues" | jq '. | length')
435
+
436
+ if [ "$count" -eq 0 ]; then
437
+ echo "No se encontraron issues abiertas"
438
+ exit 0
439
+ fi
440
+
441
+ echo "Encontradas $count issues abiertas"
442
+ echo ""
443
+ read -p "¿Cerrar todas? (y/n): " confirm
444
+
445
+ if [ "$confirm" != "y" ]; then
446
+ echo "Cancelado"
447
+ exit 0
448
+ fi
449
+
450
+ closed=0
451
+ failed=0
452
+
453
+ echo "$issues" | jq -r '.[].iid' | while read -r iid; do
454
+ echo "Cerrando issue #$iid..."
455
+
456
+ response=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" \
457
+ --request PUT \
458
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
459
+ --header "Content-Type: application/json" \
460
+ --data '{"state_event": "close"}' \
461
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/issues/$iid")
462
+
463
+ HTTP_STATUS=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
464
+
465
+ if [ "$HTTP_STATUS" -eq 200 ]; then
466
+ closed=$((closed + 1))
467
+ else
468
+ failed=$((failed + 1))
469
+ fi
470
+
471
+ sleep 0.3
472
+ done
473
+
474
+ echo ""
475
+ echo "Resumen:"
476
+ echo " ✅ Cerradas: $closed"
477
+ echo " ❌ Fallidas: $failed"
478
+ ```
479
+
480
+ ---
481
+
482
+ ## Scripts de Merge Requests
483
+
484
+ ### Script: create-mr-from-branch.sh
485
+
486
+ ```bash
487
+ #!/bin/bash
488
+
489
+ # create-mr-from-branch.sh
490
+ # Crea MR automáticamente desde branch actual
491
+
492
+ source .gitlab-env
493
+
494
+ # Detectar branch actual
495
+ CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
496
+
497
+ if [ "$CURRENT_BRANCH" = "main" ] || [ "$CURRENT_BRANCH" = "master" ]; then
498
+ echo "❌ No puedes crear MR desde la rama principal"
499
+ exit 1
500
+ fi
501
+
502
+ # Detectar proyecto desde remote
503
+ REMOTE_URL=$(git config --get remote.origin.url)
504
+ PROJECT_PATH=$(echo "$REMOTE_URL" | sed -E 's|.*[:/]([^/]+/[^/]+)\.git|\1|')
505
+
506
+ echo "Creando MR..."
507
+ echo " Branch: $CURRENT_BRANCH"
508
+ echo " Proyecto: $PROJECT_PATH"
509
+
510
+ # Obtener project ID
511
+ PROJECT_ID=$(curl --silent \
512
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
513
+ --url "$GITLAB_API_URL/projects/$(echo "$PROJECT_PATH" | sed 's|/|%2F|g')" \
514
+ | jq -r '.id')
515
+
516
+ if [ "$PROJECT_ID" = "null" ]; then
517
+ echo "❌ No se pudo obtener ID del proyecto"
518
+ exit 1
519
+ fi
520
+
521
+ # Generar título desde nombre de branch
522
+ TITLE=$(echo "$CURRENT_BRANCH" | sed 's|-| |g' | sed 's|_| |g')
523
+ TITLE=$(echo "$TITLE" | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')
524
+
525
+ read -p "Título del MR [$TITLE]: " INPUT_TITLE
526
+ TITLE=${INPUT_TITLE:-$TITLE}
527
+
528
+ read -p "Target branch [main]: " TARGET_BRANCH
529
+ TARGET_BRANCH=${TARGET_BRANCH:-main}
530
+
531
+ # Crear MR
532
+ response=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" \
533
+ --request POST \
534
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
535
+ --header "Content-Type: application/json" \
536
+ --data "{
537
+ \"source_branch\": \"$CURRENT_BRANCH\",
538
+ \"target_branch\": \"$TARGET_BRANCH\",
539
+ \"title\": \"$TITLE\",
540
+ \"remove_source_branch\": true
541
+ }" \
542
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/merge_requests")
543
+
544
+ HTTP_STATUS=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
545
+ BODY=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g')
546
+
547
+ if [ "$HTTP_STATUS" -eq 201 ]; then
548
+ MR_IID=$(echo "$BODY" | jq -r '.iid')
549
+ WEB_URL=$(echo "$BODY" | jq -r '.web_url')
550
+
551
+ echo ""
552
+ echo "✅ MR creado exitosamente"
553
+ echo " IID: !$MR_IID"
554
+ echo " URL: $WEB_URL"
555
+ else
556
+ echo ""
557
+ echo "❌ Error al crear MR"
558
+ echo "$BODY" | jq '.'
559
+ exit 1
560
+ fi
561
+ ```
562
+
563
+ ### Script: auto-approve-mrs.sh
564
+
565
+ ```bash
566
+ #!/bin/bash
567
+
568
+ # auto-approve-mrs.sh
569
+ # Aprueba automáticamente MRs que cumplen criterios
570
+
571
+ source .gitlab-env
572
+
573
+ PROJECT_ID="$1"
574
+
575
+ if [ -z "$PROJECT_ID" ]; then
576
+ echo "Uso: $0 <project_id>"
577
+ exit 1
578
+ fi
579
+
580
+ echo "Buscando MRs para aprobar en proyecto $PROJECT_ID..."
581
+
582
+ # Obtener MRs abiertos
583
+ mrs=$(curl --silent \
584
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
585
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/merge_requests?state=opened")
586
+
587
+ approved=0
588
+ skipped=0
589
+
590
+ echo "$mrs" | jq -c '.[]' | while read -r mr; do
591
+ iid=$(echo "$mr" | jq -r '.iid')
592
+ title=$(echo "$mr" | jq -r '.title')
593
+ pipeline_status=$(echo "$mr" | jq -r '.pipeline.status // "none"')
594
+ has_conflicts=$(echo "$mr" | jq -r '.has_conflicts')
595
+
596
+ echo ""
597
+ echo "MR !$iid: $title"
598
+ echo " Pipeline: $pipeline_status"
599
+ echo " Conflicts: $has_conflicts"
600
+
601
+ # Criterios de auto-aprobación
602
+ if [ "$pipeline_status" = "success" ] && [ "$has_conflicts" = "false" ]; then
603
+ echo " ✅ Aprobando..."
604
+
605
+ curl --silent \
606
+ --request POST \
607
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
608
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/merge_requests/$iid/approve" \
609
+ > /dev/null
610
+
611
+ approved=$((approved + 1))
612
+ else
613
+ echo " ⏭️ Omitiendo (no cumple criterios)"
614
+ skipped=$((skipped + 1))
615
+ fi
616
+ done
617
+
618
+ echo ""
619
+ echo "Resumen:"
620
+ echo " ✅ Aprobados: $approved"
621
+ echo " ⏭️ Omitidos: $skipped"
622
+ ```
623
+
624
+ ---
625
+
626
+ ## Scripts de Pipelines
627
+
628
+ ### Script: monitor-pipeline.sh
629
+
630
+ ```bash
631
+ #!/bin/bash
632
+
633
+ # monitor-pipeline.sh
634
+ # Monitorea pipeline en tiempo real
635
+
636
+ source .gitlab-env
637
+
638
+ PROJECT_ID="$1"
639
+ PIPELINE_ID="$2"
640
+
641
+ if [ -z "$PROJECT_ID" ] || [ -z "$PIPELINE_ID" ]; then
642
+ echo "Uso: $0 <project_id> <pipeline_id>"
643
+ exit 1
644
+ fi
645
+
646
+ echo "Monitoreando pipeline $PIPELINE_ID..."
647
+ echo ""
648
+
649
+ previous_status=""
650
+
651
+ while true; do
652
+ response=$(curl --silent \
653
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
654
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/pipelines/$PIPELINE_ID")
655
+
656
+ status=$(echo "$response" | jq -r '.status')
657
+ duration=$(echo "$response" | jq -r '.duration // 0')
658
+
659
+ # Solo mostrar si cambió el estado
660
+ if [ "$status" != "$previous_status" ]; then
661
+ timestamp=$(date '+%Y-%m-%d %H:%M:%S')
662
+ echo "[$timestamp] Status: $status (Duration: ${duration}s)"
663
+
664
+ # Mostrar jobs
665
+ jobs=$(curl --silent \
666
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
667
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/pipelines/$PIPELINE_ID/jobs")
668
+
669
+ echo "$jobs" | jq -r '.[] | " - \(.name): \(.status)"'
670
+ echo ""
671
+
672
+ previous_status="$status"
673
+ fi
674
+
675
+ # Verificar si terminó
676
+ if [[ "$status" == "success" || "$status" == "failed" || "$status" == "canceled" ]]; then
677
+ echo "Pipeline finalizado con estado: $status"
678
+
679
+ if [ "$status" = "success" ]; then
680
+ exit 0
681
+ else
682
+ exit 1
683
+ fi
684
+ fi
685
+
686
+ sleep 10
687
+ done
688
+ ```
689
+
690
+ ### Script: trigger-pipeline-with-vars.sh
691
+
692
+ ```bash
693
+ #!/bin/bash
694
+
695
+ # trigger-pipeline-with-vars.sh
696
+ # Ejecuta pipeline con variables personalizadas
697
+
698
+ source .gitlab-env
699
+
700
+ PROJECT_ID="$1"
701
+ REF="${2:-main}"
702
+
703
+ if [ -z "$PROJECT_ID" ]; then
704
+ echo "Uso: $0 <project_id> [ref]"
705
+ exit 1
706
+ fi
707
+
708
+ echo "Configurar variables para pipeline..."
709
+ echo ""
710
+
711
+ variables="[]"
712
+
713
+ while true; do
714
+ read -p "Variable key (Enter para terminar): " key
715
+
716
+ if [ -z "$key" ]; then
717
+ break
718
+ fi
719
+
720
+ read -p "Variable value: " value
721
+
722
+ variables=$(echo "$variables" | jq ". + [{\"key\": \"$key\", \"value\": \"$value\"}]")
723
+ echo " ✅ Agregada: $key"
724
+ done
725
+
726
+ if [ "$(echo "$variables" | jq '. | length')" -eq 0 ]; then
727
+ echo "No se agregaron variables"
728
+ variables_json=""
729
+ else
730
+ variables_json=", \"variables\": $variables"
731
+ fi
732
+
733
+ echo ""
734
+ echo "Ejecutando pipeline en $REF..."
735
+
736
+ response=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" \
737
+ --request POST \
738
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
739
+ --header "Content-Type: application/json" \
740
+ --data "{\"ref\": \"$REF\"$variables_json}" \
741
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/pipeline")
742
+
743
+ HTTP_STATUS=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
744
+ BODY=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g')
745
+
746
+ if [ "$HTTP_STATUS" -eq 201 ]; then
747
+ PIPELINE_ID=$(echo "$BODY" | jq -r '.id')
748
+ WEB_URL=$(echo "$BODY" | jq -r '.web_url')
749
+
750
+ echo ""
751
+ echo "✅ Pipeline creado"
752
+ echo " ID: $PIPELINE_ID"
753
+ echo " URL: $WEB_URL"
754
+ echo ""
755
+
756
+ read -p "¿Monitorear pipeline? (y/n): " monitor
757
+
758
+ if [ "$monitor" = "y" ]; then
759
+ exec "$0/../monitor-pipeline.sh" "$PROJECT_ID" "$PIPELINE_ID"
760
+ fi
761
+ else
762
+ echo ""
763
+ echo "❌ Error al crear pipeline"
764
+ echo "$BODY" | jq '.'
765
+ exit 1
766
+ fi
767
+ ```
768
+
769
+ ### Script: retry-failed-jobs.sh
770
+
771
+ ```bash
772
+ #!/bin/bash
773
+
774
+ # retry-failed-jobs.sh
775
+ # Reintenta todos los jobs fallidos de un pipeline
776
+
777
+ source .gitlab-env
778
+
779
+ PROJECT_ID="$1"
780
+ PIPELINE_ID="$2"
781
+
782
+ if [ -z "$PROJECT_ID" ] || [ -z "$PIPELINE_ID" ]; then
783
+ echo "Uso: $0 <project_id> <pipeline_id>"
784
+ exit 1
785
+ fi
786
+
787
+ echo "Obteniendo jobs fallidos del pipeline $PIPELINE_ID..."
788
+
789
+ jobs=$(curl --silent \
790
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
791
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/pipelines/$PIPELINE_ID/jobs?scope[]=failed")
792
+
793
+ count=$(echo "$jobs" | jq '. | length')
794
+
795
+ if [ "$count" -eq 0 ]; then
796
+ echo "No hay jobs fallidos"
797
+ exit 0
798
+ fi
799
+
800
+ echo "Encontrados $count jobs fallidos:"
801
+ echo "$jobs" | jq -r '.[] | " - \(.id): \(.name)"'
802
+ echo ""
803
+
804
+ read -p "¿Reintentar todos? (y/n): " confirm
805
+
806
+ if [ "$confirm" != "y" ]; then
807
+ echo "Cancelado"
808
+ exit 0
809
+ fi
810
+
811
+ retried=0
812
+ failed=0
813
+
814
+ echo "$jobs" | jq -r '.[].id' | while read -r job_id; do
815
+ echo "Reintentando job $job_id..."
816
+
817
+ response=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" \
818
+ --request POST \
819
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
820
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/jobs/$job_id/retry")
821
+
822
+ HTTP_STATUS=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
823
+
824
+ if [ "$HTTP_STATUS" -eq 201 ]; then
825
+ retried=$((retried + 1))
826
+ else
827
+ failed=$((failed + 1))
828
+ fi
829
+
830
+ sleep 0.5
831
+ done
832
+
833
+ echo ""
834
+ echo "Resumen:"
835
+ echo " ✅ Reintentados: $retried"
836
+ echo " ❌ Fallidos: $failed"
837
+ ```
838
+
839
+ ---
840
+
841
+ ## Scripts de Repository
842
+
843
+ ### Script: backup-repository.sh
844
+
845
+ ```bash
846
+ #!/bin/bash
847
+
848
+ # backup-repository.sh
849
+ # Crea backup completo de un repositorio
850
+
851
+ source .gitlab-env
852
+
853
+ PROJECT_ID="$1"
854
+ BACKUP_DIR="${2:-./backups}"
855
+
856
+ if [ -z "$PROJECT_ID" ]; then
857
+ echo "Uso: $0 <project_id> [backup_dir]"
858
+ exit 1
859
+ fi
860
+
861
+ mkdir -p "$BACKUP_DIR"
862
+
863
+ echo "Creando backup del proyecto $PROJECT_ID..."
864
+
865
+ # Obtener información del proyecto
866
+ project=$(curl --silent \
867
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
868
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID")
869
+
870
+ project_name=$(echo "$project" | jq -r '.path_with_namespace' | tr '/' '-')
871
+ timestamp=$(date '+%Y%m%d-%H%M%S')
872
+ backup_name="$project_name-$timestamp"
873
+ backup_path="$BACKUP_DIR/$backup_name"
874
+
875
+ mkdir -p "$backup_path"
876
+
877
+ echo " Nombre: $project_name"
878
+ echo " Backup: $backup_path"
879
+ echo ""
880
+
881
+ # 1. Clonar repositorio
882
+ echo "1. Clonando repositorio..."
883
+ git_url=$(echo "$project" | jq -r '.http_url_to_repo')
884
+ git clone --mirror "$git_url" "$backup_path/repository.git"
885
+
886
+ # 2. Exportar issues
887
+ echo "2. Exportando issues..."
888
+ page=1
889
+ all_issues="[]"
890
+ while true; do
891
+ issues=$(curl --silent \
892
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
893
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/issues?page=$page&per_page=100")
894
+
895
+ count=$(echo "$issues" | jq '. | length')
896
+ if [ "$count" -eq 0 ]; then break; fi
897
+
898
+ all_issues=$(echo "$all_issues" | jq ". + $issues")
899
+ page=$((page + 1))
900
+ done
901
+ echo "$all_issues" > "$backup_path/issues.json"
902
+
903
+ # 3. Exportar merge requests
904
+ echo "3. Exportando merge requests..."
905
+ page=1
906
+ all_mrs="[]"
907
+ while true; do
908
+ mrs=$(curl --silent \
909
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
910
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/merge_requests?page=$page&per_page=100")
911
+
912
+ count=$(echo "$mrs" | jq '. | length')
913
+ if [ "$count" -eq 0 ]; then break; fi
914
+
915
+ all_mrs=$(echo "$all_mrs" | jq ". + $mrs")
916
+ page=$((page + 1))
917
+ done
918
+ echo "$all_mrs" > "$backup_path/merge_requests.json"
919
+
920
+ # 4. Exportar pipelines (últimos 100)
921
+ echo "4. Exportando pipelines..."
922
+ pipelines=$(curl --silent \
923
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
924
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/pipelines?per_page=100")
925
+ echo "$pipelines" > "$backup_path/pipelines.json"
926
+
927
+ # 5. Exportar configuración del proyecto
928
+ echo "5. Exportando configuración..."
929
+ echo "$project" > "$backup_path/project.json"
930
+
931
+ # 6. Crear archivo comprimido
932
+ echo "6. Comprimiendo backup..."
933
+ cd "$BACKUP_DIR"
934
+ tar -czf "$backup_name.tar.gz" "$backup_name"
935
+ rm -rf "$backup_name"
936
+
937
+ echo ""
938
+ echo "✅ Backup completado"
939
+ echo " Archivo: $BACKUP_DIR/$backup_name.tar.gz"
940
+ ```
941
+
942
+ ### Script: sync-files-to-repo.sh
943
+
944
+ ```bash
945
+ #!/bin/bash
946
+
947
+ # sync-files-to-repo.sh
948
+ # Sincroniza archivos locales con repositorio GitLab
949
+
950
+ source .gitlab-env
951
+
952
+ PROJECT_ID="$1"
953
+ LOCAL_DIR="$2"
954
+ BRANCH="${3:-main}"
955
+
956
+ if [ -z "$PROJECT_ID" ] || [ -z "$LOCAL_DIR" ]; then
957
+ echo "Uso: $0 <project_id> <local_dir> [branch]"
958
+ exit 1
959
+ fi
960
+
961
+ if [ ! -d "$LOCAL_DIR" ]; then
962
+ echo "❌ Directorio no encontrado: $LOCAL_DIR"
963
+ exit 1
964
+ fi
965
+
966
+ echo "Sincronizando archivos de $LOCAL_DIR a proyecto $PROJECT_ID..."
967
+ echo ""
968
+
969
+ # Crear commit con múltiples archivos
970
+ actions="[]"
971
+
972
+ find "$LOCAL_DIR" -type f | while read -r file; do
973
+ relative_path="${file#$LOCAL_DIR/}"
974
+ content=$(cat "$file" | base64)
975
+
976
+ echo " + $relative_path"
977
+
978
+ actions=$(echo "$actions" | jq ". + [{
979
+ \"action\": \"create\",
980
+ \"file_path\": \"$relative_path\",
981
+ \"content\": \"$content\",
982
+ \"encoding\": \"base64\"
983
+ }]")
984
+ done
985
+
986
+ echo ""
987
+ echo "Creando commit..."
988
+
989
+ response=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" \
990
+ --request POST \
991
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
992
+ --header "Content-Type: application/json" \
993
+ --data "{
994
+ \"branch\": \"$BRANCH\",
995
+ \"commit_message\": \"Sync files from local directory\",
996
+ \"actions\": $actions
997
+ }" \
998
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/repository/commits")
999
+
1000
+ HTTP_STATUS=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
1001
+ BODY=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g')
1002
+
1003
+ if [ "$HTTP_STATUS" -eq 201 ]; then
1004
+ COMMIT_ID=$(echo "$BODY" | jq -r '.id')
1005
+ echo ""
1006
+ echo "✅ Archivos sincronizados"
1007
+ echo " Commit: $COMMIT_ID"
1008
+ else
1009
+ echo ""
1010
+ echo "❌ Error al sincronizar"
1011
+ echo "$BODY" | jq '.'
1012
+ exit 1
1013
+ fi
1014
+ ```
1015
+
1016
+ ---
1017
+
1018
+ ## Scripts de Monitoreo
1019
+
1020
+ ### Script: monitor-ci-status.sh
1021
+
1022
+ ```bash
1023
+ #!/bin/bash
1024
+
1025
+ # monitor-ci-status.sh
1026
+ # Dashboard de estado de CI/CD para múltiples proyectos
1027
+
1028
+ source .gitlab-env
1029
+
1030
+ PROJECTS_FILE="${1:-projects.txt}"
1031
+
1032
+ if [ ! -f "$PROJECTS_FILE" ]; then
1033
+ echo "❌ Archivo no encontrado: $PROJECTS_FILE"
1034
+ echo ""
1035
+ echo "Crear archivo con IDs de proyectos (uno por línea):"
1036
+ echo " 123"
1037
+ echo " 456"
1038
+ echo " 789"
1039
+ exit 1
1040
+ fi
1041
+
1042
+ while true; do
1043
+ clear
1044
+ echo "=== GitLab CI/CD Dashboard ==="
1045
+ echo "Actualizado: $(date '+%Y-%m-%d %H:%M:%S')"
1046
+ echo ""
1047
+
1048
+ printf "%-40s %-15s %-10s %-10s\n" "PROYECTO" "ÚLTIMO PIPELINE" "ESTADO" "DURACIÓN"
1049
+ printf "%-40s %-15s %-10s %-10s\n" "========" "===============" "======" "========"
1050
+
1051
+ while read -r project_id; do
1052
+ # Obtener proyecto
1053
+ project=$(curl --silent \
1054
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
1055
+ --url "$GITLAB_API_URL/projects/$project_id")
1056
+
1057
+ project_name=$(echo "$project" | jq -r '.name')
1058
+
1059
+ # Obtener último pipeline
1060
+ pipeline=$(curl --silent \
1061
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
1062
+ --url "$GITLAB_API_URL/projects/$project_id/pipelines?per_page=1" \
1063
+ | jq '.[0]')
1064
+
1065
+ if [ "$pipeline" != "null" ]; then
1066
+ pipeline_id=$(echo "$pipeline" | jq -r '.id')
1067
+ status=$(echo "$pipeline" | jq -r '.status')
1068
+ duration=$(echo "$pipeline" | jq -r '.duration // 0')
1069
+
1070
+ # Colorear estado
1071
+ case "$status" in
1072
+ success)
1073
+ status_display="\033[32m$status\033[0m"
1074
+ ;;
1075
+ failed)
1076
+ status_display="\033[31m$status\033[0m"
1077
+ ;;
1078
+ running)
1079
+ status_display="\033[33m$status\033[0m"
1080
+ ;;
1081
+ *)
1082
+ status_display="$status"
1083
+ ;;
1084
+ esac
1085
+
1086
+ printf "%-40s %-15s %-20s %-10s\n" \
1087
+ "$project_name" \
1088
+ "#$pipeline_id" \
1089
+ "$(echo -e "$status_display")" \
1090
+ "${duration}s"
1091
+ else
1092
+ printf "%-40s %-15s %-10s %-10s\n" \
1093
+ "$project_name" \
1094
+ "N/A" \
1095
+ "N/A" \
1096
+ "N/A"
1097
+ fi
1098
+ done < "$PROJECTS_FILE"
1099
+
1100
+ echo ""
1101
+ echo "Presiona Ctrl+C para salir"
1102
+ sleep 30
1103
+ done
1104
+ ```
1105
+
1106
+ ### Script: check-pipeline-health.sh
1107
+
1108
+ ```bash
1109
+ #!/bin/bash
1110
+
1111
+ # check-pipeline-health.sh
1112
+ # Analiza salud de pipelines de un proyecto
1113
+
1114
+ source .gitlab-env
1115
+
1116
+ PROJECT_ID="$1"
1117
+ DAYS="${2:-7}"
1118
+
1119
+ if [ -z "$PROJECT_ID" ]; then
1120
+ echo "Uso: $0 <project_id> [days]"
1121
+ exit 1
1122
+ fi
1123
+
1124
+ echo "Analizando pipelines de los últimos $DAYS días..."
1125
+ echo ""
1126
+
1127
+ # Calcular fecha
1128
+ date_from=$(date -d "$DAYS days ago" '+%Y-%m-%dT00:00:00Z')
1129
+
1130
+ # Obtener pipelines
1131
+ pipelines=$(curl --silent \
1132
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
1133
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/pipelines?updated_after=$date_from&per_page=100")
1134
+
1135
+ total=$(echo "$pipelines" | jq '. | length')
1136
+ success=$(echo "$pipelines" | jq '[.[] | select(.status == "success")] | length')
1137
+ failed=$(echo "$pipelines" | jq '[.[] | select(.status == "failed")] | length')
1138
+ canceled=$(echo "$pipelines" | jq '[.[] | select(.status == "canceled")] | length')
1139
+
1140
+ success_rate=0
1141
+ if [ "$total" -gt 0 ]; then
1142
+ success_rate=$(echo "scale=2; $success * 100 / $total" | bc)
1143
+ fi
1144
+
1145
+ echo "📊 Estadísticas de Pipelines"
1146
+ echo " Total: $total"
1147
+ echo " ✅ Exitosos: $success"
1148
+ echo " ❌ Fallidos: $failed"
1149
+ echo " ⏹️ Cancelados: $canceled"
1150
+ echo " 📈 Tasa de éxito: $success_rate%"
1151
+ echo ""
1152
+
1153
+ # Duración promedio
1154
+ avg_duration=$(echo "$pipelines" | jq '[.[] | select(.duration != null) | .duration] | add / length')
1155
+ echo "⏱️ Duración promedio: ${avg_duration}s"
1156
+ echo ""
1157
+
1158
+ # Pipelines más lentos
1159
+ echo "🐌 Top 5 pipelines más lentos:"
1160
+ echo "$pipelines" | jq -r '
1161
+ sort_by(.duration) | reverse | .[0:5] |
1162
+ .[] |
1163
+ " #\(.id): \(.duration)s - \(.ref)"
1164
+ '
1165
+ ```
1166
+
1167
+ ---
1168
+
1169
+ ## Scripts de Automatización
1170
+
1171
+ ### Script: auto-merge-approved-mrs.sh
1172
+
1173
+ ```bash
1174
+ #!/bin/bash
1175
+
1176
+ # auto-merge-approved-mrs.sh
1177
+ # Merge automático de MRs aprobados
1178
+
1179
+ source .gitlab-env
1180
+
1181
+ PROJECT_ID="$1"
1182
+ MIN_APPROVALS="${2:-2}"
1183
+
1184
+ if [ -z "$PROJECT_ID" ]; then
1185
+ echo "Uso: $0 <project_id> [min_approvals]"
1186
+ exit 1
1187
+ fi
1188
+
1189
+ echo "Buscando MRs listos para merge..."
1190
+ echo " Aprobaciones mínimas: $MIN_APPROVALS"
1191
+ echo ""
1192
+
1193
+ # Obtener MRs abiertos
1194
+ mrs=$(curl --silent \
1195
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
1196
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/merge_requests?state=opened")
1197
+
1198
+ merged=0
1199
+ skipped=0
1200
+
1201
+ echo "$mrs" | jq -c '.[]' | while read -r mr; do
1202
+ iid=$(echo "$mr" | jq -r '.iid')
1203
+ title=$(echo "$mr" | jq -r '.title')
1204
+
1205
+ # Obtener detalles de aprobación
1206
+ approvals=$(curl --silent \
1207
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
1208
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/merge_requests/$iid/approvals")
1209
+
1210
+ approved_count=$(echo "$approvals" | jq -r '.approved_by | length')
1211
+ pipeline_status=$(echo "$mr" | jq -r '.pipeline.status // "none"')
1212
+ has_conflicts=$(echo "$mr" | jq -r '.has_conflicts')
1213
+
1214
+ echo "MR !$iid: $title"
1215
+ echo " Aprobaciones: $approved_count/$MIN_APPROVALS"
1216
+ echo " Pipeline: $pipeline_status"
1217
+ echo " Conflicts: $has_conflicts"
1218
+
1219
+ # Verificar criterios
1220
+ if [ "$approved_count" -ge "$MIN_APPROVALS" ] && \
1221
+ [ "$pipeline_status" = "success" ] && \
1222
+ [ "$has_conflicts" = "false" ]; then
1223
+
1224
+ echo " ✅ Mergeando..."
1225
+
1226
+ curl --silent \
1227
+ --request PUT \
1228
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
1229
+ --header "Content-Type: application/json" \
1230
+ --data '{"should_remove_source_branch": true}' \
1231
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/merge_requests/$iid/merge" \
1232
+ > /dev/null
1233
+
1234
+ merged=$((merged + 1))
1235
+ else
1236
+ echo " ⏭️ Omitiendo"
1237
+ skipped=$((skipped + 1))
1238
+ fi
1239
+
1240
+ echo ""
1241
+ done
1242
+
1243
+ echo "Resumen:"
1244
+ echo " ✅ Mergeados: $merged"
1245
+ echo " ⏭️ Omitidos: $skipped"
1246
+ ```
1247
+
1248
+ ### Script: cleanup-old-branches.sh
1249
+
1250
+ ```bash
1251
+ #!/bin/bash
1252
+
1253
+ # cleanup-old-branches.sh
1254
+ # Limpia branches viejos y mergeados
1255
+
1256
+ source .gitlab-env
1257
+
1258
+ PROJECT_ID="$1"
1259
+ DAYS_OLD="${2:-30}"
1260
+
1261
+ if [ -z "$PROJECT_ID" ]; then
1262
+ echo "Uso: $0 <project_id> [days_old]"
1263
+ exit 1
1264
+ fi
1265
+
1266
+ echo "Buscando branches antiguos (>$DAYS_OLD días)..."
1267
+ echo ""
1268
+
1269
+ # Obtener branches
1270
+ branches=$(curl --silent \
1271
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
1272
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/repository/branches")
1273
+
1274
+ # Fecha límite
1275
+ cutoff_date=$(date -d "$DAYS_OLD days ago" '+%s')
1276
+
1277
+ to_delete=()
1278
+
1279
+ echo "$branches" | jq -c '.[]' | while read -r branch; do
1280
+ name=$(echo "$branch" | jq -r '.name')
1281
+ merged=$(echo "$branch" | jq -r '.merged')
1282
+ commit_date=$(echo "$branch" | jq -r '.commit.committed_date')
1283
+
1284
+ # Skip protected branches
1285
+ if [ "$name" = "main" ] || [ "$name" = "master" ] || [ "$name" = "develop" ]; then
1286
+ continue
1287
+ fi
1288
+
1289
+ # Convertir fecha
1290
+ commit_timestamp=$(date -d "$commit_date" '+%s')
1291
+
1292
+ # Verificar si es antiguo y mergeado
1293
+ if [ "$commit_timestamp" -lt "$cutoff_date" ] && [ "$merged" = "true" ]; then
1294
+ echo " 🗑️ $name (último commit: $commit_date)"
1295
+ to_delete+=("$name")
1296
+ fi
1297
+ done
1298
+
1299
+ if [ ${#to_delete[@]} -eq 0 ]; then
1300
+ echo "No hay branches para eliminar"
1301
+ exit 0
1302
+ fi
1303
+
1304
+ echo ""
1305
+ echo "Se eliminarán ${#to_delete[@]} branches"
1306
+ read -p "¿Continuar? (y/n): " confirm
1307
+
1308
+ if [ "$confirm" != "y" ]; then
1309
+ echo "Cancelado"
1310
+ exit 0
1311
+ fi
1312
+
1313
+ deleted=0
1314
+ for branch in "${to_delete[@]}"; do
1315
+ echo "Eliminando $branch..."
1316
+
1317
+ curl --silent \
1318
+ --request DELETE \
1319
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
1320
+ --url "$GITLAB_API_URL/projects/$PROJECT_ID/repository/branches/$(echo "$branch" | sed 's|/|%2F|g')" \
1321
+ > /dev/null
1322
+
1323
+ deleted=$((deleted + 1))
1324
+ sleep 0.3
1325
+ done
1326
+
1327
+ echo ""
1328
+ echo "✅ Eliminados $deleted branches"
1329
+ ```
1330
+
1331
+ ---
1332
+
1333
+ **Nota**: Todos estos scripts requieren:
1334
+ 1. Archivo `.gitlab-env` con variables de entorno
1335
+ 2. Permisos adecuados en GitLab
1336
+ 3. `jq` instalado para procesamiento JSON
1337
+ 4. `curl` para llamadas API
1338
+
1339
+ Para usar los scripts:
1340
+ ```bash
1341
+ # 1. Configurar entorno
1342
+ ./setup-gitlab-env.sh
1343
+
1344
+ # 2. Cargar variables
1345
+ source .gitlab-env
1346
+
1347
+ # 3. Ejecutar scripts
1348
+ ./list-all-projects.sh
1349
+ ./create-issue-from-template.sh 123 issues.csv
1350
+ ./monitor-pipeline.sh 123 456
1351
+ ```