Migraciones

Migraciones en Drupal (II): ejemplos

Migraciones en Drupal (II): ejemplos

Como comentaba en el artículo anterior, durante estos meses estaré experimentando con migraciones y preparando algunos casos para un futuro libro, eso espero. Durante estos días de confinamiento quiero seguir publicando pequeños artículos para compartir experiencias relacionadas con las migraciones.

En la entrega anterior abordé las migraciones en Drupal desde el punto de vista de una caja de herramientas: un conjunto de recursos básicos que nos ayudan a plantear una migración.

Hay mucha información que procesar y, por supuesto, intervienen más conceptos, técnicas y tácticas a la hora de resolver una migración. Por eso, este mes quiero escribir algo que me permita experimentar con ellas de una forma más práctica.


Foto de Unsplash, de Émile Séguin, @emileseguin

Índice

1. Introducción

La API de migraciones de Drupal puede ser una de las más interesantes, pero también una de las más complejas: sus operaciones suelen estar relacionadas con clases y métodos de otras APIs de Drupal, algo que se hace especialmente evidente al depurar. En cualquier caso, como la cantidad de conceptos puede resultar abrumadora, creo que podemos practicar la mecánica de las migraciones mediante un par de ejercicios.

Para este artículo me había propuesto modelar dos procesos de migración diferentes siguiendo una idea que podría resumirse como «primum vivere, deinde philosophari»: primero experimentar y después teorizar. Por eso he decidido organizarlo de una manera particular:

  • Los dos procesos se dividen en apartados comunes. En lugar de terminar uno y empezar el siguiente, avanzaremos con ambos en paralelo: tú eliges tu propia aventura.
  • Solo al final del artículo encontrarás algunos de los conceptos clave que utilizaremos. Primero vamos a jugar con las estructuras y después las entenderemos.

En los siguientes pasos trabajaremos con dos experiencias concretas:

  1. Migrar datos desde un formato embebido, quizá el ejemplo más sencillo de una migración en Drupal.
  2. Migrar datos desde un archivo CSV, un caso algo más complejo que el anterior.

Ambos son, probablemente, de los escenarios más básicos de migración. Por eso recomiendo este artículo a quien quiera iniciarse en su mecánica, como complemento práctico al estudio de las migraciones en Drupal.

2. Preparativos

Primer caso: migrar datos embebidos

Para el primer caso necesitaremos, por una parte, activar el módulo Migrate del core de Drupal y, por otra, descargar e instalar un módulo contribuido con el que gestionar las migraciones.

Entre las opciones disponibles elegiremos migrate_run, que ya mencionamos en la entrega anterior. Podemos entenderlo como una versión ligera de migrate_tools, aunque en realidad es un fork del proyecto. Ambos proporcionan comandos Drush para ejecutar migraciones, por lo que, si tienes migrate_tools instalado, debes desinstalarlo para evitar conflictos con migrate_run.

La primera lección, como detalle curioso, es que ni migrate_plus ni migrate_tools son dependencias obligatorias para ejecutar migraciones en Drupal: podemos implementar migraciones sin tener estos módulos activados en nuestra instalación.

Conviene saber, además, que migrate_run está optimizado para Drush 9 y posteriores. Si utilizas Drush 8, tendrás que recurrir a una versión adaptada, como la Alpha 4, que todavía estaba preparada para Drush 8.

Con Composer y Drush:

composer require drupal/migrate_run
drush pmu migrate_tools # If you need
drush en migrate migrate_run -y
drush cr

Con Drupal Console:

composer require drupal/migrate_run
drupal mou migrate_tools # If you need
drupal moi migrate migrate_run

En la ruta /admin/modules verás lo siguiente:

Activación de los módulos Migrate y Migrate Run

Construcción de los recursos

Crearemos un módulo personalizado para nuestra primera migración:

cd project/web/modules/custom
mkdir migration_basic_module

Después, el archivo migration_basic_module.info.yml, con este contenido:

name: 'Migration Basic Module'
type: module
description: 'Just a basic example of basic migration process.'
package: 'Migrations Examples 2000'
core: 8.x
dependencies:
  - drupal:migrate

Crea el archivo de definición de la migración en la ruta /migration_basic_module/migrations/basic_migration_one.yml.

Nuestro archivo declarativo basic_migration_one.yml describe la migración mediante una lista de parámetros y valores en un archivo estático de tipo YAML. Incluiremos los datos embebidos de dos nodos del tipo de contenido «página básica», cargando únicamente dos valores:

  1. Un título: una cadena de texto.
  2. Un cuerpo: un texto basado en el generador ChiquitoIpsum*, http://www.chiquitoipsum.com.

*Chiquito de la Calzada fue una figura popular en España, un humorista legendario.

basic_migration_one.yml

id: basic_migration_one
label: 'Custom Basic Migration 2000'
source:
  plugin: embedded_data
  data_rows:
    -
      unique_id: 1
      page_title: 'Title for migrated node - One'
      page_content: 'Lorem fistrum mamaar se calle ustée tiene musho pelo.'
    -
      unique_id: 2
      page_title: 'Title for migrated node - Two'
      page_content: 'Se calle ustée caballo blanco caballo negroorl.'
  ids:
    unique_id:
      type: integer
process:
  title: article_title
  body: article_content
destination:
  plugin: 'entity:node'
  default_bundle: page

Esta será la estructura del nuevo módulo personalizado del ejemplo de migración básica:

/project/web/modules/custom/
                     \__migration_basic_module/
                         \__migration_basic_module.info.yml
                             \__migrations/
                                 \__basic_migration_one.yml

Activación de todos los módulos necesarios con Drush:

drush pm:enable -y migrate migrate_run migration_basic_module
drush cr

O con Drupal Console:

drupal moi migrate migrate_run migration_basic_module

Segundo caso: migrar desde archivos CSV

Para el segundo caso desactivaremos migrate_run, si procede, y activaremos el conjunto de módulos migrate, migrate_plus y migrate_tools. Para tratar los archivos CSV utilizaremos un plugin de origen incluido en el módulo contribuido Migrate Source CSV, migrate_source_csv. En su versión 3.x, este módulo utiliza league/csv para procesar los archivos CSV. Vamos allá, con Composer y Drush:

composer require drupal/migrate_plus drupal/migrate_tools drupal/migrate_source_csv
drush pmu migrate_run # If you need
drush en migrate migrate_plus migrate_tools migrate_source_csv -y
drush cr

Ahora, en la ruta /admin/modules/:

Activación de Migrate, Migrate Plus y Migrate Tools

Construcción de los recursos

Crearemos otro módulo personalizado para la segunda migración:

cd project/web/modules/custom
mkdir migration_csv_module

Con un nuevo archivo migration_csv_module.info.yml:

name: 'Migration CSV Module'
type: module
description: 'Just a basic example of basic migration process with a CSV source.'
package: 'Migrations Examples 2000'
core: 8.x
dependencies:
  - drupal:migrate
  - drupal:migrate_tools
  - drupal:migrate_plus

En este ejemplo también necesitaremos un archivo declarativo de la migración, como en el caso anterior, pero lo ubicaremos en otro lugar: la ruta /migration_csv_module/config/install/.

La estructura tendrá este aspecto:

/project/web/modules/custom/
                     \__migration_csv_module/
                         \__migration_csv_module.info.yml
                          \__csv/
                               \_migration_csv_articles.csv
                           \__config/
                                \__install/
                                     \__migrate_plus.migration.article_csv_import.yml

Necesitamos un CSV con los datos originales que vamos a migrar. Podemos generarlo fácilmente con herramientas web como Mockaroo, un generador de datos aleatorios bastante bueno. He creado un CSV con los campos id, title, body, tags e image. Los recursos relacionados se mantienen ahora en mis snippets de GitLab.

Este archivo será la fuente de datos del proceso de migración. De momento, crea los directorios del módulo y coloca el CSV en la ruta /csv:

Estructura del módulo de migración CSV

Y ahora, nuestro archivo migrate_plus.migration.article_csv_import.yml. En apartados posteriores explicaremos su construcción y sus secciones:

uuid: 1bcec3e7-0a49-4473-87a2-6dca09b91aba
langcode: en
status: true
dependencies: {  }
id: article_csv_import
label: 'Migrating articles'
source:
  plugin: csv
  path: modules/custom/migration_csv_module/csv/migration_csv_articles.csv
  delimiter: ','
  enclosure: '"'
  header_offset: 0
  ids:
    - id
  fields:
    -
      name: id
      label: 'Unique Id'
    -
      name: title
      label: Title
    -
      name: body
      label: 'Post Body'
    -
      name: tags
      label: 'Taxonomy Tag'
    -
      name: image
      label: 'Image Field'
process:
  title: title
  body: body
  tags: field_tags
  image: field_image
  type:
    plugin: default_value
    default_value: article
destination:
  plugin: 'entity:node'

Ya tenemos todos los recursos necesarios para crear la nueva migración. Veamos cómo vamos a abordar el proceso.

3. Enfoques

Describiremos los diferentes enfoques que aplicaremos a nuestros ejemplos para entenderlos mejor.

Primer caso: migrar datos embebidos

En el primer caso hemos planteado una migración tan ligera como sea posible: solo dos nodos con dos campos básicos cada uno y los datos embebidos. Lo más sencillo posible.

Además, en las tres fases ETL de la migración —extracción, transformación y carga— utilizaremos plugins que Drupal ya proporciona. No desarrollaremos ningún plugin personalizado. Si todavía no conoces el concepto de plugin de migración, detente un momento y lee la introducción del primer artículo de esta serie.

Para aligerar el ejemplo utilizaremos Migrate Run, esa versión «lite» de Migration Tools. Emplearemos únicamente los comandos básicos, sin opciones ni parámetros complementarios, con el identificador del archivo de migración como argumento.

Segundo caso: migrar desde archivos CSV

En esta ejecución quiero probar algo interesante. Como ejecutaremos la segunda migración como configuración, me parece divertido recorrer el camino inverso. Propongo no instalar ni activar el módulo personalizado del CSV: lo dejaremos únicamente como ubicación del archivo de datos.

Vamos a llevar la migración a otro lugar y ejecutarla desde allí. Sorpresa. Visita la ruta /admin/config/development/configuration/single/import de tu instalación Drupal. ¡Nos vemos allí!

4. Migraciones

Primer caso: migrar datos embebidos

Consultar las migraciones disponibles

drush migrate:status
drush ms

Output from console:
----------------- -------- ------- ---------- ------------- ---------------------
  Migration ID      Status   Total   Imported   Unprocessed   Last Imported
----------------- -------- ------- ---------- ------------- ---------------------
basic_migration_one   Idle     2       0          2
----------------- -------- ------- ---------- ------------- ---------------------

Ejecutar las migraciones

drush migrate:import basic_migration_one
drush mi basic_migration_one

Output from console:
----------------- -------- ------- ---------- ------------- -------------------
  Migration ID      Status   Total   Imported   Unprocessed  Last Imported
----------------- -------- ------- ---------- ------------- -------------------
basic_migration_one   Idle     2       2 (100%)   0            2020-03-17 23:19:36
----------------- -------- ------- ---------- ------------- -------------------

Al visitar /admin/content verás los dos nodos nuevos:

Migración básica de datos embebidos en Drupal

Revertir las migraciones: deshacer la importación

drush migrate:rollback basic_migration_one
drush mr basic_migration_one

Output from console:

[notice] Rolled back 2 items - done with 'basic_migration_one'

Comandos de la migración básica de Drupal

Segundo caso: migrar desde archivos CSV

En la ruta /admin/config/development/configuration/single/import importaremos el archivo de definición de nuestra migración personalizada.

Cargar la configuración de la migración

Ve a Importar → Elemento individual, selecciona «Migration» como tipo de configuración y pega el contenido del archivo original de la migración:

Carga de la definición de migración mediante configuración

Pulsa «Importar» y se creará el nuevo objeto en el sistema de configuración.

¿Y ahora?

Ejecutar la migración

Con el archivo de migración bajo la gestión de configuración, puedes ejecutar el proceso con las mismas herramientas del caso anterior. Ahora tenemos una nueva migración disponible desde la consola. Compruébalo con drush migrate:status:

Estado de las migraciones con Drush

Puedes ejecutar la migración con drush migrate-import article_csv_import. Se crearán los nuevos nodos. ¿La limitación? No se migrarán las etiquetas ni las imágenes: una etiqueta es una referencia a entidad y una imagen no es simplemente un enlace, sino un archivo. Ambos tipos necesitan otros plugins; lo veremos en futuras entregas.

Drush cex / Drush cim

Al estar la migración en el sistema de configuración, podemos editarla, importarla y exportarla con las herramientas básicas de Drush. Por ejemplo, probando drush cex:

Exportación de configuración con Drush cex

Como puedes ver, el sistema de configuración ha puesto directamente el nuevo archivo bajo la gestión de Migrate Plus y ha realizado algunas operaciones: renombrarlo con el prefijo migrate_plus.migration o añadir un archivo para el grupo, que sirve para agrupar procesos de migración.

¿Recuerdas el nombre del archivo? Es el mismo que utilizábamos en /config/install: migrate_plus.migration.article_csv_import.yml. Hemos realizado el mismo proceso, pero desde otra dirección. ¿Te impresiona? ¿No? ¿Al menos te parece interesante?

Recuerda también que puedes utilizar drush cim con este archivo de configuración para cargar la migración en otra instalación Drupal, siempre que tenga acceso al CSV como fuente de datos, claro.

Así hemos migrado unos 102 nodos nuevos utilizando dos enfoques y metodologías diferentes. No está mal.

5. Conceptos clave

Plugins de migración

Esto es importante, así que toca repetir la misma canción: hay que conocer el formato de los plugins y el diverso mundo de los plugins de migración existentes.

Cada plugin responde a un tipo de dato, un formato o una fuente concreta. Conviene conocer bien los principales e investigar los que puedas necesitar, porque las migraciones los utilizan intensivamente. Por eso, por ejemplo, no hemos podido migrar los términos de taxonomía ni las imágenes del segundo caso directamente desde el CSV.

Vamos a revisar los plugins que intervienen en estas dos migraciones a partir de sus archivos de definición.

Migración básica con datos embebidos

source:
  plugin: embedded_data
  data_rows:
         ...
process:
  title: creative_title
  body: engaging_content
destination:
  plugin: 'entity:node'
  default_bundle: page

Para extraer los datos utilizamos el plugin Embedded Data, una clase PHP disponible en /web/core/modules/migrate/src/Plugin/migrate/source/EmbeddedDataSource.php. En su bloque de documentación puedes ver algunas claves de configuración que puedes utilizar en la definición:

 *
 * Available configuration keys
 * - data_rows: The source data array.
 * - ids: The unique ID field of the data.
 *

Precisamente data_rows e ids son las claves que utilizamos: el array de datos de origen y su identificador único. Puedes leer más sobre la clase EmbeddedDataSource en la documentación de la API de Drupal.org.

Ahora fíjate en el bloque process y busca… ¿dónde está el plugin de procesamiento? Esto es interesante. Normalmente cada correspondencia de campos necesita un plugin de proceso. Con un poco de «azúcar sintáctico», Migrate API permite simplificar la definición: cuando un campo no necesita un tratamiento específico, el mismo plugin puede encargarse de obtener su valor. Además, ese plugin predeterminado puede quedar implícito. Si no lo declaramos, Drupal aplica el plugin de proceso por defecto.

Ese plugin implícito es la clase Get, la solución básica para procesar campos. La encontrarás en /web/core/modules/migrate/src/Plugin/migrate/process/Get.php. Más información sobre Get.php en la API de Drupal.org. En otras palabras, escribir esto:

process:
  title: page_title

Equivale a escribir esto otro:

process:
  title:
    plugin: get
    source: page_title

Así la vida resulta un poco más sencilla, ¿verdad? Recuerda: si no declaras un plugin de procesamiento para un campo, Drupal aplicará Get por defecto.

Por último, en el destino utilizamos el plugin de entidad con el parámetro node para crear nodos del tipo page. Esto recurre al plugin de destino Entity.php, una clase abstracta ubicada en web/core/modules/migrate/src/Plugin/migrate/destination/Entity.php, y a su plugin derivado. Puedes leer más sobre los plugins derivados en Drupal, el plugin de destino Entity.php y la clase derivadora de migraciones.

Migración con un CSV como fuente de datos

Creo que en este caso revisar los plugins resulta más sencillo e intuitivo:

source:
  plugin: csv
 ...
 process:
 ...
   type:
     plugin: default_value
     default_value: article
 destination:
   plugin: 'entity:node'

Para el origen utilizamos el plugin CSV del módulo contribuido migrate_source_csv. En el procesamiento usamos Get por defecto y Default Value para el tipo de contenido. En el destino repetimos el plugin del ejemplo anterior: nuevas entidades.

Migraciones como código o como configuración

Como has visto, hemos tratado cada proceso de una manera distinta. El primero, con datos embebidos, forma parte del código, sin más particularidades. El segundo se trata como un elemento de configuración del sistema: al situarlo en config/install, la instalación puede crear un objeto de configuración.

En ambos casos escribimos una definición YAML y colocamos el archivo en una ubicación u otra. Pero hay más diferencias. Resumamos las claves:

  • Las migraciones «como código» están disponibles de serie. El módulo Migrate Plus permite tratar la definición como un objeto de configuración.
  • Según el enfoque, cambian la ubicación de los archivos y el flujo de trabajo.
  • Como código, necesitas acceso al sistema de archivos para modificar la definición y gestionarla como cualquier otro archivo de código. Es un enfoque orientado a desarrolladores.
  • Como configuración, puedes modificar la definición desde la interfaz de sincronización de Drupal, en /admin/config/development/configuration, y utilizar las herramientas de exportación e importación drush cex y drush cim. La migración se sincroniza y su definición se guarda en la base de datos. Esto permite trabajar con las definiciones desde la interfaz de usuario. Toda una sorpresa.
  • Como objeto de configuración, la migración crea un registro en el sistema de configuración que puede permanecer cuando se desinstale el módulo de migración. Para eliminarlo con el módulo, añade tu módulo personalizado como dependencia forzada en el YAML. Así se eliminará de la configuración activa al desinstalarlo:
  dependencies:
    enforced:
      module:
        - my_own_migration_custom_module
  • Otra diferencia del enfoque como configuración es el uso de un UUID: un identificador global del sistema de gestión de configuración de Drupal. Añade un UUID propio y único al principio del archivo. Su formato consta de 32 dígitos hexadecimales repartidos en cinco grupos con el patrón 8-4-4-4-12. ¡Genera el tuyo! El original utiliza como ejemplo ilustrativo uuid: cacafuti-1a23-2b45-3c67-4d567890a1b2; contiene caracteres no hexadecimales, por lo que debes sustituirlo por un UUID válido.

6. Recursos

Descarga, prueba y experimenta con los recursos utilizados en el artículo. Los recursos relacionados se mantienen ahora en mis snippets de GitLab.

  1. Archivo de migración básica.
  2. Archivo de migración CSV.
  3. CSV de origen con datos aleatorios.
  4. Código de los dos módulos de migración, básico y CSV, referenciado desde mi perfil de GitLab. Este será un repositorio central para los módulos de la serie, con los dos ejemplos:
    • Primera migración básica con datos embebidos.
    • Segunda migración básica con un CSV como fuente de datos.
  5. En paralelo a estos artículos publico snippets en GitLab sobre migraciones, con un formato más breve. Desde el primero puedes acceder al resto de la serie: Drupal Migrations Tips (I): Creating a new basic migration structure.

7. :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.