Trayendo datos sanitizados de producción a desarrollo cuando no hay rsync
El entorno de desarrollo de blog-manager siempre corrió con casi nada: un usuario semilla, dos blogs de fixture. Eso está bien para la suite de pruebas automatizada, que de todas formas usa fixtures, pero hace que las pruebas manuales (bin/dev, hacer clic por ahí) sean casi inútiles, ya que no hay historial real de publicaciones, ni imágenes destacadas reales, nada que se parezca al sitio real que administro. Quería una forma de traer una instantánea real de producción a desarrollo sin armarla a mano cada vez.
La app usa SQLite, no Postgres, desplegada vía Kamal con el archivo de base de datos viviendo en un volumen montado por bind (/data/storage en el host, /rails/storage en el contenedor). Eso en realidad hace esto más simple que un dump/restore de Postgres, ya que toda la base de datos es solo un archivo que se puede copiar.
Lo que construí
Una tarea rake, bin/rails data:pull_from_prod:
- scp del archivo SQLite de producción hacia un directorio temporal descartable
- Sanitizarlo en el lugar, antes de que toque nada local: anular cada columna de token/clave de API cifrada, restablecer todas las contraseñas de usuario a la misma que ya usa
db/seeds.rb, vaciar la tabla de sesiones - Pedir confirmación, ya que está a punto de sobrescribir
storage/development.sqlite3 - Traer también los archivos de Active Storage, para que las imágenes destacadas realmente se resuelvan localmente
- Correr las migraciones pendientes, ya que el esquema de producción puede ir atrasado respecto a lo que hay en el checkout local
La parte de sanitización importa más de lo que parece. Esta app usa Active Record Encryption en cosas como tokens de GitHub y claves de API, y desarrollo no comparte las claves de cifrado de producción, así que aunque se dejaran esas columnas intactas, serían solo basura en desarrollo, ilegibles y propensas a lanzar un error al descifrar la primera vez que algo las tocara. Anularlas es la única opción sensata, no solo la más segura.
Lo que me sorprendió
La primera corrida explotó de inmediato: no such column: unsplash_access_key. Producción corría un esquema de antes de que esa migración se integrara localmente, lo cual tiene sentido al pensarlo (los deploys de producción van atrasados respecto a main), pero el SQL de sanitización tenía nombres de columna fijos y asumía que el esquema local coincidía con el de producción. Se corrigió revisando PRAGMA table_info antes de tocar cada tabla y anulando solo las columnas que en verdad existían en esa instantánea en particular.
Segunda sorpresa: intenté usar rsync para traer los archivos de Active Storage y obtuve rsync: command not found, desde el shell remoto, retransmitido de vuelta por la conexión SSH, lo cual hizo que el error local (unexpected end of file) resultara bastante confuso hasta que en verdad me conecté por SSH y revisé. El aprovisionamiento del host de Kamal instala lo que Docker/Kamal necesita y nada más; rsync nunca formó parte de eso. Se cambió a un simple pipe de tar sobre SSH en su lugar, tar -cf - | tar -xf -, ya que tar es de lo más cercano a garantizado que existe, y funcionó al primer intento.
Probándolo de verdad
Lo corrí contra el host real de producción en vez de confiar en que el código estuviera bien. Traje la base de datos real, confirmé que las columnas sanitizadas en verdad quedaron en nil, levanté el servidor de desarrollo, e inicié sesión por HTTP con la contraseña restablecida contra el usuario real de producción, obtuve un 302 y una cookie de sesión, y después confirmé que el panel mostraba ambos blogs reales. Es agradable tener una tarea que resulta aburrida de correr porque simplemente funciona.
Qué sigue
Esto cubre la mitad de “sin datos realistas” del problema. Hay una pregunta abierta aparte: si vale la pena mantener el entorno de staging, dado que se usa poco y tiene exactamente este mismo problema de datos escasos de forma independiente. Eso se rastrea por separado; este mecanismo de sincronización funciona sin importar hacia qué lado se resuelva eso.
Lecturas relacionadas
Retirando el entorno de staging
Un segundo contenedor, un monitor aparte, una tasa de fallos del 11% en el workflow, y cero evidencia de que alguna vez detectara algo que los deploys de producción no detectaran. La auditoría que terminó en un borrado.
El bug de normalización que solo aparece con etiquetas hechas de nada
Un normalizador basado en strip se topa con una etiqueta de puro signo de puntuación: string vacío como clave de hash, sustitución de etiqueta equivocada, y un autocompletado que hace match con todo. Tres síntomas, una sola causa raíz.
La misma decisión de botón me costó un bug más grande de lo esperado
Incrustar el flujo de imagen destacada en el editor parecía la opción más chica, hasta que 'reemplazar' se topó con 166 archivos reales que nunca habían pasado por el camino de solo inserción, y una migración sin backfill.