Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Formato
run: test -z "$(gofmt -l .)" || (gofmt -l . && exit 1)

- name: Vet
run: go vet ./...

- name: Tests
run: go test -race ./...

- name: Build
run: go build ./...
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23.x'
go-version-file: go.mod

- name: Build binaries
run: make build VERSION=${{ github.ref_name }}
Expand Down
23 changes: 17 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,30 @@ VERSION ?= dev

DIST_DIR = dist

.PHONY: all build clean
LDFLAGS = -s -w -X azuredevops/azdevops.Version=$(VERSION)

.PHONY: all build clean test vet install

all: build

build: clean
@echo "🔧 Compilando binarios para múltiples plataformas..."
GOOS=linux GOARCH=amd64 go build -o $(DIST_DIR)/$(VERSION)/linux-amd64/$(APP_NAME)
GOOS=linux GOARCH=arm64 go build -o $(DIST_DIR)/$(VERSION)/linux-arm64/$(APP_NAME)
GOOS=darwin GOARCH=amd64 go build -o $(DIST_DIR)/$(VERSION)/darwin-amd64/$(APP_NAME)
GOOS=darwin GOARCH=arm64 go build -o $(DIST_DIR)/$(VERSION)/darwin-arm64/$(APP_NAME)
GOOS=windows GOARCH=amd64 go build -o $(DIST_DIR)/$(VERSION)/windows-amd64/$(APP_NAME).exe
GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o $(DIST_DIR)/$(VERSION)/linux-amd64/$(APP_NAME)
GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o $(DIST_DIR)/$(VERSION)/linux-arm64/$(APP_NAME)
GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o $(DIST_DIR)/$(VERSION)/darwin-amd64/$(APP_NAME)
GOOS=darwin GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o $(DIST_DIR)/$(VERSION)/darwin-arm64/$(APP_NAME)
GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o $(DIST_DIR)/$(VERSION)/windows-amd64/$(APP_NAME).exe
@echo "✅ Binarios generados en la carpeta $(DIST_DIR)"

install:
go install -ldflags "$(LDFLAGS)" .

test:
go test ./...

vet:
go vet ./...

clean:
@echo "🧹 Limpiando binarios anteriores..."
rm -rf $(DIST_DIR)/*
Expand Down
398 changes: 257 additions & 141 deletions README.md

Large diffs are not rendered by default.

82 changes: 65 additions & 17 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,73 @@
# Release v0.0.2-beta
# Release v0.1.0

Esta versión expande significativamente las capacidades de la CLI, introduciendo la ejecución de pipelines y una suite completa para la gestión de permisos y grupos de seguridad. Se ha puesto un foco especial en la optimización de las llamadas a la API para un rendimiento eficiente.
Esta versión convierte la CLI en una herramienta interactiva y mucho más completa, y reescribe su núcleo para que sea más fiable en scripts y CI.

## ✨ Funcionalidades Nuevas
## ✨ Novedades

### Ejecución de Pipelines (`pipelines run`)
### Modo interactivo
- `azdevops` sin argumentos (o `azdevops interactive`) abre un menú navegable generado a partir de los comandos.
- Todos los comandos preguntan los datos que falten: listas filtrables de Variable Groups, pipelines, ejecuciones, repositorios, conexiones de servicio y grupos de seguridad; los valores secretos se piden sin mostrarse.
- Confirmaciones antes de operaciones destructivas. Se desactiva con `--no-input`, `AZDEVOPS_NO_INPUT` o `CI`.

Se ha añadido un nuevo comando `run` al subcomando `pipelines` que permite:
- **Iniciar un pipeline** por su ID de definición.
- **Esperar su finalización** con el flag `--wait` para flujos de trabajo síncronos.
- **Pasar parámetros** de plantilla YAML usando el flag `--param`.
- **Inyectar variables** en tiempo de ejecución con el flag `--var`, con soporte para variables secretas usando el prefijo `secret:`.
### Perfiles y autenticación
- `azdevops login` valida las credenciales (incluida la identidad, para no aceptar un PAT inválido en organizaciones con proyectos públicos) y las guarda en un perfil.
- El PAT se guarda en el **llavero del sistema** (Keychain, Credential Manager, Secret Service) cuando está disponible (`--store auto|keyring|file`).
- `azdevops config view | use | delete | path` y flags globales `--org`, `--project`, `--pat`, `--profile`.

### Gestión de Seguridad y Permisos (`security`)
### Variable Groups
- `variables list` (tabla con conteo de variables y secretas).
- `variables get --id` y `-o table` con valores secretos enmascarados.
- `variables export` / `variables import` desde `.env` o JSON (`--secret`, `--create`).
- `variables copy`: copia un grupo (también a otro proyecto) pidiendo los valores secretos o con `--secret-value`; `--merge` para fusionar en uno existente.
- `variables diff`: compara dos grupos (dev vs prod, o entre proyectos) con `--exit-code` para CI.

Se introduce un nuevo conjunto de comandos de alto nivel para gestionar la seguridad:
- **`security list-groups`**: Lista todos los grupos de seguridad. Incluye un flag `--search` para filtrar los resultados y maneja la paginación automáticamente para obtener la lista completa.
- **`security search-group`**: Busca un grupo específico por nombre de forma eficiente, ideal para scripts.
- **`security add-member`**: Agrega uno o más usuarios y/o grupos a uno o más grupos de seguridad en una sola operación.
### Pipelines
- `pipelines list` con el último resultado de cada pipeline.
- `pipelines run`: `--name`, `--branch`, `--timeout`, progreso en vivo con la etapa en curso, resumen de tareas fallidas y código de salida 1 si la ejecución falla.
- `pipelines runs`, `pipelines status [--watch]`, `pipelines logs [--failed | --log-id]` y `pipelines cancel`.
- `pipelines update --branch` y `pipelines create --branch` (ahora sí se aplica la rama por defecto).

### Permisos para Variable Groups (`variables set-permissions`)
### Seguridad
- `security list-groups --search` y `--project-only`.
- `security list-members` y `security remove-member`.

Se añade un comando para gestionar los roles en los *Variable Groups*:
- **`variables set-permissions`**: Asigna los roles de **Reader**, **User**, o **Administrator** a múltiples usuarios y/o grupos en múltiples *Variable Groups* de forma masiva.
### Aprobaciones y environments
- `approvals list | approve | reject` para las aprobaciones de despliegue (con selección múltiple en modo interactivo).
- `environments list | deployments`.

### Work items (Boards)
- `workitems list | get | create | update | delete` (alias `wi`): por defecto lista tus work items abiertos; filtros por tipo, estado, texto, área o WIQL; `create --parent`, `--assigned-to @me`, `update --state --comment` y cambio de estado guiado.

### Conexiones de servicio y agentes
- `service-connections list | get | history | share` (alias `sc`).
- `agents pools | list | enable | disable`: estado de los agentes, trabajo actual y último resultado.

### Archivos seguros
- `securefiles list | get | upload | delete | authorize | set-role` (alias `sf`).
- `securefiles replace`: reemplaza el contenido conservando nombre, propiedades, pipelines autorizados, roles asignados, herencia de permisos y aprobaciones/checks, con `--dry-run`, `--keep-old` y reversión automática si algo falla.

### Extensiones de Azure DevOps
- `extension init`: crea una extensión lista para usar (tareas TypeScript, jest, ncc, azde-scripts, tfx, config dev/release, iconos de ejemplo, UUIDs y conexión de servicio opcional), en modo guiado o con flags.
- `extension add-task | bump | validate | reset-ids | uuid | pack | publish` para mantenerla, editando los JSON sin alterar su formato.

### Navegador
- `azdevops open [project|pipeline|run|variables|workitem|repo|approvals|…]`, con `--print` para solo obtener la URL.

### General
- Salida `-o table|json|yaml|tsv` y `--query` con JMESPath (como en `az`), autocompletado dinámico (nombres de Variable Groups, IDs de pipelines), `--version`, `--debug`.

## 🛠 Mejoras y correcciones
- Cliente HTTP centralizado: se valida el código de respuesta de **todas** las llamadas (antes muchas `GET` ignoraban errores), mensajes de error legibles de la API, detección de PAT inválido (respuesta 203 de Azure DevOps) y reintentos ante 429/5xx.
- Los nombres con espacios o caracteres especiales ahora se codifican correctamente en las URLs.
- `pipelines run --wait` ya no se queda esperando para siempre ni devuelve éxito si falla la consulta de estado.
- `variables set-permissions` aplica a todos los grupos que coinciden con el nombre (antes solo al primero).
- `pipelines update` ya no falla con definiciones sin `process`/`repository`.
- Se corrigieron fugas de conexiones (`defer` dentro de bucles).
- CI con `gofmt`, `go vet`, tests y build; la versión se inyecta en el binario al compilar.

## ⚠️ Cambios de comportamiento
- Los mensajes de estado (✔/✖) se escriben en *stderr*; *stdout* queda solo para datos.
- `pipelines run --wait` devuelve código 1 si la ejecución termina en `failed` o `canceled`.
- Sin terminal, las eliminaciones exigen `--yes` en lugar de leer la confirmación de stdin.
- `variables get` y `pipelines get --name` devuelven error (código 1) si no encuentran resultados.
- `pipelines get-by-id` queda obsoleto: usa `pipelines get --id`.
60 changes: 60 additions & 0 deletions azdevops/agent/agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package agent

import (
"azuredevops/azdevops"
"azuredevops/models"
"fmt"
"net/url"
"sort"
"strings"
)

// ListPools lista los agent pools de la organización.
func ListPools(client *azdevops.Client) ([]models.AgentPool, error) {
var result struct {
Value []models.AgentPool `json:"value"`
}
if err := client.Do("GET", client.OrgURL("distributedtask/pools", url.Values{"api-version": {"7.1"}}), nil, &result); err != nil {
return nil, fmt.Errorf("error al listar los agent pools: %w", err)
}
sort.Slice(result.Value, func(i, j int) bool { return result.Value[i].Name < result.Value[j].Name })
return result.Value, nil
}

// FindPool busca un pool por nombre (sin distinguir mayúsculas) o ID.
func FindPool(client *azdevops.Client, nameOrID string) (*models.AgentPool, error) {
pools, err := ListPools(client)
if err != nil {
return nil, err
}
for i, p := range pools {
if strings.EqualFold(p.Name, nameOrID) || fmt.Sprint(p.ID) == nameOrID {
return &pools[i], nil
}
}
return nil, fmt.Errorf("no se encontró el agent pool '%s'", nameOrID)
}

// ListAgents lista los agentes de un pool con su trabajo actual y el último completado.
func ListAgents(client *azdevops.Client, poolID int) ([]models.Agent, error) {
q := url.Values{"api-version": {"7.1"}, "includeAssignedRequest": {"true"}, "includeLastCompletedRequest": {"true"}}
var result struct {
Value []models.Agent `json:"value"`
}
if err := client.Do("GET", client.OrgURL(fmt.Sprintf("distributedtask/pools/%d/agents", poolID), q), nil, &result); err != nil {
return nil, fmt.Errorf("error al listar los agentes: %w", err)
}
sort.Slice(result.Value, func(i, j int) bool { return result.Value[i].Name < result.Value[j].Name })
return result.Value, nil
}

// SetAgentEnabled habilita o deshabilita un agente.
func SetAgentEnabled(client *azdevops.Client, poolID, agentID int, enabled bool) (*models.Agent, error) {
var updated models.Agent
body := map[string]any{"id": agentID, "enabled": enabled}
u := client.OrgURL(fmt.Sprintf("distributedtask/pools/%d/agents/%d", poolID, agentID), url.Values{"api-version": {"7.1"}})
if err := client.Do("PATCH", u, body, &updated); err != nil {
return nil, fmt.Errorf("error al actualizar el agente: %w", err)
}
return &updated, nil
}
Loading
Loading