Parche de idempotencia "pkgin" SmartOS para Ansible

A la hora de usar Ansible en SmartOS una molestia constante es que, a veces, al volver a ejecutar un script Ansible, nos dice que realiza de nuevo las acciones pkgin (instalación de paquetes PkgSrc) o bien salta una excepción Python. Esto no ocurre siempre, pero sí ocurre con frecuencia.

Me explico: Las acciones Ansible se diseñan para que sean idempotentes. Es decir, que lanzar una acción que ya se ha realizado en el pasado debe marcar la acción como "ya realizada", en vez de realizarla de nuevo. En el caso concreto de las acciones pkgin, si le pedimos que instale un paquete que ya está instalado, debería decirnos que ya está hecho y no, en cambio, decirnos que ha realizado la acción de nuevo o fallar sin motivo. Es decir, el resultado de la acción debería ser ok y no changed o, peor aún, que salte una excepción.

En pocas palabras, cuando ejecutamos un script Ansible dos veces, la segunda vez debería darnos ok en todas las acciones porque no ha tenido que hacer nada.

Pero Ansible no lo hace así en el caso de acciones pkgin siempre. A veces lo hace y a veces no, por motivos desconocidos. Y cuando no lo hace, puede saltar una excepción Python.

Cuando ocurre así, si lanzamos de nuevo el script Ansible termina correctamente... casi siempre.

Molesto con la situación, estudio el código fuente de Ansible, que está escrito en Python. Me encuentro lo siguiente, entre otras cosas, en ansible_collections/community/general/plugins/modules/pkgin.py:

if rc == 0:
    if re.search('^nothing to do.\n$', out):
        module.exit_json(changed=False, msg="nothing left to upgrade")
    else:
        module.fail_json(msg="could not %s packages" % cmd, stdout=out, stderr=err)

Aquí se está buscando que el comando pkgin de SmartOS devuelva la cadena "nothing to do.", tal cual. El problema es que pkgin, al menos en las versiones modernas de SmartOS, termina con esa cadena cuando no tiene nada que hacer, pero puede ir precedida por bastante más texto y eso el código de Ansible no lo contempla.

Una vez localizado el problema, el parche es trivial:

--- pkgin.py            2023-01-30 19:08:13.057554197 +0100
+++ pkgin.py.JCEA       2023-01-12 03:43:08.300412921 +0200
@@ -174,7 +174,19 @@
             #     '<' - installed but out of date
             #     '=' - installed and up to date
             #     '>' - installed but newer than the repository version
-            pkgname_with_version, raw_state = package.split(splitchar)[0:2]
+
+            if (package in ('reading local summary...',
+                           'processing local summary...',
+                           'downloading pkg_summary.xz done.')) or \
+               (package.startswith('processing remote summary (')):
+                continue
+
+            try:
+                pkgname_with_version, raw_state = package.split(splitchar)[0:2]
+            except Exception:
+                with open('/tmp/zzzzdfd', 'w') as f:
+                    print(package, file=f)
+                raise

             # Search for package, stripping version
             # (results in sth like 'gcc47-libs' or 'emacs24-nox11')
@@ -317,7 +329,7 @@
         format_pkgin_command(module, cmd))

     if rc == 0:
-        if re.search('^nothing to do.\n$', out):
+        if re.search('^(.*\n|)nothing to do.\n$', out):
             module.exit_json(changed=False, msg="nothing left to upgrade")
     else:
         module.fail_json(msg="could not %s packages" % cmd, stdout=out, stderr=err)

Las primeras líneas evitan la excepción Python precisamente ignorando las líneas informativas que puede mostrar el comando pkgin de SmartOS. Obsérvese también que si se encuentra cualquier problema, no solo damos un error Ansible sino que guardamos en mensaje en el fichero /tmp/zzzzdfd de la máquina de destino de la acción Ansible. Ojo, la máquina DESTINO el lugar donde se va a ejecutar el pkgin. Esto permite investigar qué ha pasado y mejorar este parche.

Independientemente de la gestión anterior, la sección siguiente espera que pkgin termine con "nothing to do.", pero permite que dicha línea esté precedida por texto arbitrario que simplemente ignoramos. Basta con que observemos la última línea devuelta por pkgin.

He informado de este problema a la gente de Ansible que, simplemnte, lo ha ignorado durante años. Por eso lo documento aquí.

Actualización 20240215: Abro un nuevo pull request en el proyecto Ansible: Pkgin fixes #7971. A ver si esta vez hay más suerte.

Actualización 20240229: El parche ya está integrado en la versión 9.3 de Ansible.