Migraciones

Migraciones en Drupal (IV): depurar migraciones I

Migraciones en Drupal (IV): depurar migraciones I

Las migraciones en Drupal, aunque sus definiciones sean lineales, contienen mucha complejidad heredada. La razón es intuitiva: Migrate API ofrece una interfaz de interacción relativamente sencilla a quien desarrolla migraciones, pero durante el proceso colaboran numerosos subsistemas: entidades, base de datos, plugins… Incluso en la migración más sencilla intervienen muchas clases. Si añadimos que una migración suele producir errores hasta que la afinamos, está claro que una de nuestras primeras necesidades será aprender a depurarlas.


Foto de Unsplash, de Krzysztof Niewolny, @epan5


Índice

1. Introducción

Siguiendo los últimos artículos, quiero ampliar la información sobre migraciones en Drupal. He pensado en una pequeña subserie sobre depuración dentro de esta serie principal. Esta es la primera parte: un conjunto de pasos básicos para obtener toda la información disponible de un proceso de migración. Los ejemplos proceden del módulo migration_google_sheet de mi cuenta de GitLab.

2. Depuración básica: revisa tus archivos

Empezaremos por una aproximación muy elemental a la detección de errores. Es fundamental reducir las posibles causas abordando la migración de forma iterativa e incremental: avanzaremos paso a paso y ampliaremos progresivamente los datos migrados.

2.1. Revisar el archivo de definición de la migración

Comenzamos con el paso más intuitivo. Hay errores que están a la vista y, por habituales que sean, no siempre resultan evidentes.

Antes de nada, revisaremos dos cuestiones que aparecen con frecuencia:

Espacios en blanco: un espacio adicional puede causar problemas en el archivo de definición. Recorre sus líneas para localizar espacios sobrantes.

Errores de sangrado: el formato YAML organiza los datos mediante claves y valores, con niveles padre e hijo y sangrado de dos espacios en cada nivel del ejemplo. Un sangrado incorrecto puede provocar un error al procesar el archivo. Revisa todos los niveles, igual que has revisado los espacios.

Puedes apoyarte en un validador de sintaxis YAML como www.yamllint.com, aunque también tendrás que comprobar el resultado.

2.2. Revisar los registros de la base de datos

En la instalación básica utilizada en el ejemplo, con el perfil estándar, tenemos setenta y tres tablas. Tras activar migrate, migrate_plus, migrate_tools y el módulo personalizado migration_google_sheet_wrong, pasan a ser setenta y cinco. Se han generado dos tablas nuevas:

cache_migrate
cache_discovery_migration

Después de ejecutar la migración con identificador taxonomy_google_sheet_wrong, incluida en el módulo personalizado, aparecen otras dos tablas relacionadas con la migración:

  • migrate_map_taxonomy_google_sheet. Contiene la información sobre el recorrido de cada fila de datos: las migraciones trabajan fila a fila. Migrate API almacena el identificador de origen, el de destino y un hash relacionado con la fila. Estas correspondencias permiten seguir cambios, actualizar información progresivamente y resolver dependencias entre migraciones. Las búsquedas o lookups se apoyan en estos datos. Por ejemplo, para cargar un término de taxonomía puede ser necesario buscar antes su término padre y mantener así la jerarquía. Si, después de lanzar la migración, no encontramos resultados registrados, no se han almacenado los datos y tendremos que depurar el proceso.
  • migrate_message_taxonomy_google_sheet. Guarda los mensajes asociados a la ejecución, también organizados por fila. Cada mensaje tiene su propio identificador y está asociado al id_hash de la fila de origen:

Columnas de la tabla de mensajes de migración

Podemos obtener esta información con Drush: el contenido de la tabla es lo que aparece al ejecutar:

drush migrate:messages id-migration

Es una forma útil de empezar a recopilar información sobre los errores de la migración.

2.4. Recargar los objetos de configuración

Durante la depuración también necesitamos actualizar los cambios realizados en el objeto de configuración creado a partir del archivo de definición de config/install.

Cada vez que instalamos el módulo se genera un objeto de configuración disponible en Drupal. Mientras depuramos tendremos que modificar el archivo y recargarlo para comprobar el efecto de los cambios. ¿Cómo podemos facilitar este ciclo?

Primero vincularemos el ciclo de vida del objeto de configuración de migración a la instalación de nuestro módulo. Como vimos al tratar las migraciones como configuración, declararemos el módulo personalizado como dependencia forzada:

dependencies:
   enforced:
      module:
          - migration_google_sheet

Podemos realizar importaciones específicas con Drush o Drupal Console, aprovechando las opciones de importación individual.

Con Drupal Console:

drupal config:import:single --directory="/modules/custom/migration_google_sheet/config/install" --file="migrate_plus.migration.taxonomy_google_sheet.yml"

Con Drush:

drush cim --partial --source=/folder/

También podemos eliminar objetos de la configuración activa con ambas herramientas:

drush config-delete "migrate_plus.migration.taxonomy_google_sheet"
drupal config:delete active "migrate_plus.migration.taxonomy_google_sheet"

Si preferimos la interfaz de Drupal, el módulo contribuido Config Delete incorpora opciones al menú de sincronización para eliminar elementos de configuración. Basta con descargarlo con Composer y activarlo con Drush o Drupal Console:

composer require drupal/config_delete
drush en config_delete -y

Acciones del módulo Config Delete

Así podemos volver a importar objetos sin chocar con versiones ya existentes en la base de datos. Si prefieres actualizar y comparar versiones, puedes valorar Configuration Update Manager.

3. Depuración intermedia con Migrate Devel

Hemos revisado los datos detenidamente y, aun así, la migración de términos desde Google Spreadsheet no parece funcionar.

Necesitamos técnicas intermedias para obtener más información. En esta fase nos ayudarán módulos y plugins que permiten observar mejor lo que ocurre durante la migración.

3.1. Migrate Devel

Migrate Devel es un módulo contribuido que añade funcionalidades mediante opciones nuevas de Drush. Funciona con migrate_tools y migrate_run.

Actualización del 03/07/2020: versión 8.x-2.0-alpha2

Justo cuando publiqué este artículo, Andrew Macpherson, nuevo mantenedor de Migrate Devel y uno de los mantenedores de accesibilidad del core de Drupal, dejó un comentario al pie del original con novedades importantes. Desde que empecé el borrador se había publicado una versión nueva, el 28 de junio, compatible con Drush 9, ¡y no lo sabía! Esa versión evita instalar el parche que se describe más abajo.

Para instalarla, la descargamos con Composer y la activamos con Drush: Migrate Devel 8.x-2.0-alpha2.

composer require drupal/migrate_devel
# To install the 2.0 branch of the module:
composer require drupal/migrate_devel:^2.0
drush en migrate_devel -y
Para versiones anteriores a 8.x-2.0-alpha2

Si trabajas con versiones anteriores, hay algunas particularidades. Estaban preparadas para Drush 8 y la adaptación a Drush 9 y posteriores aún no parecía cerrada.

En la rama 8.x-1.x había una etiqueta publicada dos semanas antes: migrate_devel/tree/8.x-1.4.

Para utilizar esas versiones con Drush 9 o superior, el parche de esta incidencia resultaba prácticamente imprescindible. Aunque tampoco parecía definitivo, permitía ejecutar de forma controlada algunas funciones del módulo. Veremos sus aportaciones.

Podemos descargar el parche con wget y aplicarlo con git apply:

cd /web/modules/contrib/migrate_devel/
wget https://www.drupal.org/files/issues/2018-10-08/migrate_devel-drush9-2938677-6.patch
git apply migrate_devel-drush9-2938677-6.patch

Otra posibilidad es añadirlo a la sección de parches de composer.json, si tenemos activada la extensión composer-patches.

La instalamos con:

composer require cweagans/composer-patches

Y añadimos el parche a la sección extra de composer.json:

Incorporación del parche a Composer

Cómo funciona

Al ejecutar una migración con las opciones de Migrate Devel obtenemos valores por consola que podemos inspeccionar. Por ejemplo, con –migrate-debug:

Primera parte de la salida de Migrate Devel Segunda parte de la salida de Migrate Devel

Es una vista parcial del procesamiento de una fila: muestra la fuente, los valores asociados y el identificador final de destino, almacenado en la tabla de correspondencias para seguir el proceso:

Salida de Migrate Devel y registro en la base de datos

Vemos que al valor 1 de origen, en el primer array, se le asignó el identificador 117 en destino. También será el identificador interno de la nueva entidad de Drupal, aquí un término de taxonomía. Así relacionamos el registro de migración con la entidad creada y almacenada.

¿Y los suscriptores de eventos? Migrate Devel crea una clase que implementa EventSubscriberInterface y escucha eventos de Migrate API, disponibles en el módulo migrate del core:

Called from +56
/var/www/html/web/modules/contrib/migrate_devel/src/EventSubscriber/MigrationEventSubscriber.php

La llamada procede de la clase que escucha estos eventos. En modules/migrate/src/Event se definen muchos, pero Migrate Devel presta atención especialmente a dos:

  1. MigratePostRowSaveEvent.php.
  2. MigratePreRowSaveEvent.php.

Estos corresponden a las dos opciones Drush del módulo, que originalmente acababan llamando a dump() de Kint, proporcionado por Devel, para imprimir los mensajes. En la versión 8.x-2.0-alpha2 se sustituyen esas llamadas por Symfony VarDumper. Donde antes teníamos:

  /**
   * Pre Row Save Function for --migrate-debug-pre.
   *
   * @param \Drupal\migrate\Event\MigratePreRowSaveEvent $event
   *    Pre-Row-Save Migrate Event.
   */
  public function debugRowPreSave(MigratePreRowSaveEvent $event) {
    $row = $event->getRow();

    $using_drush = function_exists('drush_get_option');
    if ($using_drush && drush_get_option('migrate-debug-pre')) {
      // Start with capital letter for variables since this is actually a label.
      $Source = $row->getSource();
      $Destination = $row->getDestination();

      // We use kint directly here since we want to support variable naming.
      kint_require();
      \Kint::dump($Source, $Destination);
    }
  }

Ahora tenemos:

  /**
   * Pre Row Save Function for --migrate-debug-pre.
   *
   * @param \Drupal\migrate\Event\MigratePreRowSaveEvent $event
   *    Pre-Row-Save Migrate Event.
   */
  public function debugRowPreSave(MigratePreRowSaveEvent $event) {
    if (PHP_SAPI !== 'cli') {
      return;
    }

    $row = $event->getRow();

    if (in_array('migrate-debug-pre', \Drush\Drush::config()->get('runtime.options'))) {
      // Start with capital letter for variables since this is actually a label.
      $Source = $row->getSource();
      $Destination = $row->getDestination();

      // Uses Symfony VarDumper.
      // @todo Explore advanced usage of CLI dumper class for nicer output.
      // https://www.drupal.org/project/migrate_devel/issues/3151276
      dump(
        '---------------------------------------------------------------------',
        '|                             $Source                               |',
        '---------------------------------------------------------------------',
        $Source,
        '---------------------------------------------------------------------',
        '|                           $Destination                            |',
        '---------------------------------------------------------------------',
        $Destination
      );
    }
  }

Puedes consultar los cambios en MigrationEventSubscriber.php de 8.x-2.0-alpha2.

También encontrarás más información sobre eventos y suscriptores en el artículo de The Russian Lullaby Building Symfony events for Drupal.

3.2. El plugin de proceso Debug

Migrate Devel también incorpora un plugin de proceso llamado debug, definido en Debug.php. Lo encontrarás en /web/modules/contrib/migrate_devel/src/Plugin/migrate/process/Debug.php. Su documentación explica su responsabilidad:

/**
 * Debug the process pipeline.
 *
 * Prints the input value, assuming that you are running the migration from the
 * command line, and sends it to the next step in the pipeline unaltered.
 *
 * Available configuration keys:
 * - label: (optional) a string to print before the debug output. Include any
 *   trailing punctuation or space characters.
 * - multiple: (optional) set to TRUE to ask the next step in the process
 *   pipeline to process array values individually, like the multiple_values
 *   plugin from the Migrate Plus module.

El método transform(), heredado de la clase abstracta ProcessPluginBase, no transforma el valor: utiliza print_r de PHP para mostrar por consola tanto valores escalares como arrays.

Podemos incluir el plugin de forma autónoma en la cadena de procesamiento para que imprima resultados al procesar cada fila. En nuestro caso modificaremos el bloque process de la migración de términos para revisar los valores que se están migrando.

Primero cambiaremos la estructura. Como sabemos por las entregas anteriores, esta forma:

process:
 name: name
 description: description
 path: url
 status: published

Utiliza implícitamente el plugin Get.php y equivale a esta otra:

process:
 name:
   plugin: get
   source: name
 description:
   plugin: get
   source: description
 path:
   plugin: get
   source: url
 status:
   plugin: get
   source: published

Añadimos Debug a la cadena con una etiqueta informativa para cada campo:

process:
 name:
   plugin: debug
   label: 'Processing name field value: '
   plugin: get
   source: name
 description:
   plugin: debug
   label: 'Processing description field value: '
   plugin: get
   source: description
 path:
   plugin: debug
   label: 'Processing path field value: '
   plugin: get
   source: url
 status:
   plugin: debug
   label: 'Processing status field value:  '
   plugin: get
   source: published

Después recargamos el objeto de configuración desinstalando e instalando nuestro módulo. Como figura como dependencia, al desinstalarlo también se elimina la configuración de la migración:

drush pmu migration_google_sheet && drush en migration_google_sheet -y

Al ejecutarla de nuevo aparecerá información sobre los valores:

Información de los valores mostrada por Migrate Devel

Así obtenemos información más detallada de los datos que vamos a migrar. Para escenarios más avanzados podemos combinar argumentos y opciones. Por ejemplo, si queremos revisar un solo elemento:

 drush migrate-import --migrate-debug taxonomy_google_sheet --limit="1 items"

Esto combina la salida posterior al almacenamiento —a diferencia de --migrate-debug-pre— con la información del plugin, los valores mostrados por la herramienta de volcado y el identificador final de la única entidad procesada.

Aquí los valores son sencillos y apenas los transformamos: los extraemos y los cargamos en destino. En migraciones posteriores introduciremos tratamientos más complejos y esta información resultará aún más valiosa. Piensa en valores que hay que concatenar, recortar, ampliar o adaptar: si mostramos el resultado en cada paso podremos observar mejor toda la secuencia de transformación.

Puedes revisar el código de Debug.php y la incidencia de Drupal.org en la que surgió la propuesta de este plugin.

Con esta aproximación empezamos la subserie de depuración de migraciones. ¡Pronto, más experiencias!

4. :wq!

Canción recomendada

Únete a la newsletter

Recibe artículos sobre Drupal y desarrollo web directamente en tu bandeja de entrada.

No spam. Unsubscribe at any time.