La reproducibilidad en Go no es una decisión binaria, sino una gestión de capas de responsabilidad. Para entenderlo, debemos distinguir tres mecanismos: GOPROXY (quién te entrega el código), GOSUMDB (quién garantiza que el código no ha sido alterado) y vendor/ (dónde reside físicamente el código para el compilador).
Históricamente, vendor/ era la única forma de asegurar que un build fuera idéntico hoy y dentro de cinco años. Hoy, la arquitectura de Go es más sofisticada. El sistema de módulos utiliza GOPROXY para acelerar las descargas mediante un caché centralizado, mientras que el GOSUMDB [disponible desde Go 1.11] utiliza un árbol de Merkle (una base de datos de sumas de verificación) para asegurar que el contenido de un módulo no haya cambiado, incluso si el autor intenta reescribir un tag de Git. Esto permite que la reproducibilidad no dependa de tener el código en tu repositorio, sino de la integridad matemática del mismo.
Sin embargo, el GOSUMDB garantiza que el código es el mismo que descargaste la primera vez, pero no te protege si el proxy desaparece o si estás en un entorno sin salida a internet. Es aquí donde entra vendor/, que consiste en copiar físicamente el código fuente de las dependencias dentro de tu propio repositorio, en una carpeta llamada vendor/.
Debes usar GOPROXY y el caché local en el 99% de los casos de desarrollo estándar para mantener tus repositorios limpios y ligeros. El uso de vendor/ queda relegado a escenarios de alta criticidad: entornos de despliegue aislados (air-gapped), auditorías de seguridad donde cada línea de código de terceros debe estar en el histórico de Git, o cumplimiento normativo estricto en sectores regulados. El riesgo de usar vendor/ incorrectamente es la desincronización: si añades una dependencia a go.mod pero olvidas ejecutar go mod vendor, tu código compilará en tu máquina (usando tu caché local) pero fallará estrepitosamente en el pipeline de CI si este está configurado para usar estrictamente el directorio vendor.
// package example_test contiene pruebas para verificar el origen de los módulos.
package example_test
import (
"os/exec"
"strings"
"testing"
)
// TestModuleSource verifica si las dependencias se están cargando desde
// el directorio vendor o desde el caché de módulos del sistema.
func TestModuleSource(t *testing.T) {
// Ejecutamos 'go list' para inspeccionar la ruta real de todos los módulos
// en el contexto actual.
cmd := exec.Command("go", "list", "-m", "-f", "{{.Dir}}", "all")
output, err := cmd.Output()
if err != nil {
t.Fatalf("Error al ejecutar 'go list': %v", err)
}
lines := strings.Split(string(output), "\n")
vendorFound := false
cacheUsed := false
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
// Si la ruta contiene "/vendor/", estamos usando la estrategia de containter.
if strings.Contains(line, "/vendor/") {
vendorFound = true
}
// El caché de módulos suele residir en GOMODCACHE (ej. $GOPATH/pkg/mod).
// Comprobamos si la ruta apunta a la carpeta de módulos de Go.
if strings.Contains(line, "pkg/mod") {
cacheUsed = true
}
}
// En un entorno de desarrollo estándar, deberíamos ver rutas del caché.
// En un build con -mod=vendor, las dependencias apuntarían a 'vendor/'.
t.Logf("Análisis de origen de módulos: Vendor: %v, Cache: %v", vendorFound, cacheUsed)
}
El test anterior utiliza os/exec para inspeccionar el comportamiento del runtime de Go. Al ejecutar go list -m -f '{{.Dir}}' all, el compilador nos revela la ruta absoluta de cada dependencia. Si estuviéramos en un entorno donde se ha ejecutado go mod vendor y el build se lanza con el flag -mod=vendor, las rutas que devuelve este comando apuntarían directamente a la carpeta vendor/ de nuestro proyecto, ignorando por completo el caché global de la máquina. Si vemos rutas que apuntan a pkg/mod, el sistema está utilizando el mecanismo estándar de GOPROXY y el caché local.
El error frecuente ocurre cuando trabajamos con proxies privados (como Athens o JFrog Artifactory) y olvidamos la configuración de la integridad. Si tu empresa usa un proxy privado para alojar módulos internos, el GOSUMDB público (sum.golang.org) no conocerá los hashes de tus módulos privados. Si intentas compilar, Go intentará verificar la integridad de esos módulos contra el servidor público, recibirá un error de “sum mismatch” o un 404, y el build fallará aunque tu proxy privado funcione perfectamente. Para solucionar esto, debes configurar la variable de entorno GONOSUMDB con el patrón de tus módulos privados (ej. GONOSUMDB=github.com/mi-empresa/*) para indicarle al runtime que no intente validar la integridad de esos paquetes contra el registro público.
N° 256