BlueThinking

think in blue

En los últimos años Microsoft, en sus sistemas para servidores, parece haberse dado cuenta que encadenar todo la administración a una interfaz gráfica plagada de opciones no es la mejor solución para automatizar las operaciones más comunes.

Uno de los últimos intentos ha sido la inclusión de una interfaz de comandos llamada powershell. Para aquellos que vengan del mundo Unix no será nada revolucionado, e incluso puede ser frustrante cuando intentan usarla porque ha sido hecha al mejor estilo Microsoft: Un tipo de letra horrible, un autocompletar demente y una integración entre las diversas partes un tanto dudosas.

Para terminar de potenciar la interfaz han incluido un lenguaje de programación, de nuevo al estilo de los sistemas Unix, que permita automatizar las operaciones más comunes. Hemos visto en esta página algunos ejemplos sobre como usarlo.

El problema es que Microsoft tiene una capacidad innata para convertir en basura casi todas las buenas ideas que pasan por sus manos, y esto no es más que el siguiente hito en el camino. El sistema no es compatible entre diferentes versiones, hay demasiadas formas de hacer las cosas, y dependiendo desde donde lo ejecutes puedes obtener resultados distintos.

Creación automatizada de directorios y asignación de permisos desde línea de comandos para una lista de usuarios:

Un ejemplo típico de la utilidad de las herramientas de administración mediante consola es la siguiente: Imaginemos que nos han pasado una lista con cien usuarios, y ha sido redactada de la siguiente forma:

    Usuario1
    Usuario2
    Usuario3

Y que queremos, en uno de nuestros servidores, crear un directorio a modo de repositorio con el nombre de casa usuario en el que sólo tenga permiso de acceso dicho usuario.

Desde la interfaz gráfica deberíamos situarnos en el directorio, crear un directorio, ir a las propiedades del mismo y asignar los permisos. Son cien usuarios, pues eso mismo cien veces.

Los permisos se pueden asignar desde la interfaz gráfica, como hemos dicho en el punto anterior, o bien recurrir a la herramienta icacls desde la línea de comandos:

#Para quitar el acceso al directorio para todos los usuarios del dominio:
PS C:\> icacls directorio /remove \"Usuarios del dominio"
#Damos permiso total (full control) al usuario:
PS C:\> icacls directorio /grant usuario:F

Sin embargo, aun teniendo la herramienta icacls, el crear los directorios uno por uno y asignar los permisos uno a uno es una tarea farragosa e ingrata. Lo ideal sería poder recorrer la lista anterior y, para cada usuario, crear un directorio, borrar los permisos para todo el mundo y agregar los del usuario.

Para hacerlo con el lenguaje de programación de powershell necesitaríamos una manera de ejecutar un comando externo, algo que seguramente exista, pero de todas las opciones probadas no he logrado que funcione ninguna (seguramente por culpa mía, claro)

Al final la opción más sencilla resulto hacer un programa en python y ejecutar los comandos desde allí.

import sys
import os
from subprocess import call

def ensure_dir( d ):

    if not os.path.exists(d):
    print ("creando directorio %s") % ( d )
        os.makedirs(d)

#------------------------------------

if(len(sys.argv) > 1):
    fichero = sys.argv[1]
    print "Abriendo " + fichero
    f = open( fichero, "r" )
   
    for line in f:
        datos = line.strip ()
    if ( len ( datos )> 3 ):
            print ("---%s--") % (datos)
        dir = ".\\" + datos
        print dir
        ensure_dir ( dir )

        cmd = "icacls " +  datos + " /remove \"Usuarios del dominio\""
        os.system( cmd )

        cmd = "icacls " +  datos + " /remove Everyone"
        os.system ( cmd )

        cmd = "icacls " +  datos + " /grant " + datos + ":F"
        os.system ( cmd )
    f.close()    
else:
    print "Debes indicar el nombre del archivo"

El programa se encarga de abrir el fichero, recorrer cada línea, crear un directorio con ese nombre y asignar los permisos correspondientes. EL truco es que el nombre de usuario se llama exactamente igual que el directorio.

Muchas veces nos toca mandar alguna fotografía por correo y descubrimos que ocupan demasiado para poder ser enviadas. Algunos programas permiten automatizar de alguna forma ese problema, aunque no siempre los tenemos instalados o requieren demasiados pasos.

El comando convert, del que ya hemos hablado en otras ocasiones, permite reducir el tamaño de una fotografía a un porcentaje de la original o bien a un tamaño predefinido. Si tenemos un directorio con las fotos y otro llamado thumbs donde queremos guardar las imágenes en un tamaño más reducido (15% del tamaño inicial), podemos escribir lo siguiente en un terminal:

#Recorremos todos los ficheros con la extensión jpg y
#los convertimos en la carpeta thumb:
for f in *.jpg ; do convert -resize 15% $f thumb/$f; done

Si, además, somos de los que no nos gusta dar pistas sobre como hemos hecho las fotos siempre podemos hacer un procesado para eliminar toda la información Exif (además ahorramos algo de tamaño). En esta ocasión lo haremos directamente sobre las fotos, sin salvar el original, y con el comando find (por variar):

#Vamos al directorio donde hemos dejado las fotos
cd thumb
#Y eliminamos la información Exif del fichero:
find *.jpg  | xargs mogrify -strip

mogrify es una navaja suiza similar a convert, y también cuenta con numerosas posibilidades que se pueden consultar con el man.

Si miramos la ayuda de convert veremos que permite, además, hacer un sinfín de operaciones con las imágenes, que pueden ser procesadas usando el comando anterior.

en el ejemplo anterior vimos como crear un módulo avanzado con drupal, que tuviese navegación entre varios formularios y crease un esquema en la base de datos.

Sin embargo ese ejemplo era un módulo que se encontraba disponible para todo el mundo, tanto usuarios registrados como usuarios públicos.

Si queremos añadir algún tipo de control de acceso debemos retocar algo el código anterior, en concreto en el hook de menú:

function MiPrimerModulo_menu() {

    //global $user;

    //$user->uid == 0 son los usuarios no registrados en el sistema
    //if ($user->uid != 0) {
        $items['MiPrimerModulo/principal'] = array(
                'title' => 'Crear proyecto',
                'page callback' => 'drupal_get_form',
                'page arguments' => array('MiPrimerModulo_principal_form'),
                'access callback' => 'custom_module_access',
                 //llamamos a la funcion custom_modulo_access con el parametro gestorproyectos.
                'access arguments' => array ('gestorproyectos'),
                'type' => MENU_NORMAL_ITEM
            );

        return $items;
    //}
}

Es decir, cada vez que se presenta en pantalla el módulo se llama a una función llamada custom_module_access con el parámetro del rol gestor_de proyectos. El rol gestor_de_proyectos, es un rol que hemos creado en la administración de Drupal y en el que hemos incluido al usuario con el que estamos haciendo las pruebas-

La función custom_module_access sería algo así:

function custom_module_access ( $rol ) {
    global $user;
   
        if  ( (in_array( $rol, array_values($user->roles))) ||  (in_array('administrator', array_values($user->roles))) ){
            return (true );
        }
}

Vale, la comparación es bastante mala y se podría programar mucho mejor, pero es una forma sencilla de definir que tanto los administradores como los que pertenezcan al grupo gestor_de_proyectos podrán usar el módulo.

El código completo MiPrimerModuloControlAcceso.

No debemos olvidar que Drupal puede integrarse con un servidor LDAP, por lo que podemos integrar los grupos de acceso a este módulo como grupos de LDAP o, como en este ejemplo, con grupos locales.

La ventaja de los gestores de contenido es que nos ofrecen ya resueltas operaciones complejas como la gestión de usuarios y de contenidos, además de darnos una serie de herramientas para poder ampliar esas funcionalidades.

Muchas veces sólo es necesaria la instalación de un módulo que, una vez configurado, nos solucione el problema. A veces, sin embargo, es necesario llegar un poco más lejos y debemos ser nosotros quien desarrollemos esos módulos.

Para Drupal tenemos una mini guía sobre el desarrollo de módulos que cubre bastantes aspectos básicos, pero no sobre algunos más complejos.

En el caso que nos ocupa queremos desarrollar un módulo desde cero que permita navegar a través de varios formularios (la mayoría de módulos han sido pensados para hacer operaciones sencillas que no necesitan formularios, ya que muestran la información en la barra de opciones).

Toda la complejidad del asunto gira en torno al hook del menú:

function MiPrimerModulo_menu() {
      global $user;
      //$user->uid == 0 son los usuarios no registrados en el sistema
      if ($user->uid != 0) {
            $items['MiPrimerModulo/principal'] = array(
                  'title' => 'Crear proyecto',
                  'page callback' => 'drupal_get_form',
                  'page arguments' => array('MiPrimerModulo_principal_form'),
                  'access callback' => TRUE,
                  'type' => MENU_NORMAL_ITEM
            );
      return $items;
      }
}

Y la función a la que se llama desde el menú, en este caso de nombre MiPrimerModulo_principal_form, que en vez de limitarse a mostrar el formulario, se encarga de hacer un uso ingenioso de las variables:

$form_state['page_num']
//Valores de los campos de la página 1
$form_state['page_values'][1]
//Valores de los campos de la página 2
$form_state['page_values'][2]
//Guardamos los valores de la página 2
$form_state['page_values'][2] = $form_state['values']

Esas variables nos indican en todo momento en que punto de la navegación nos encontramos y, lo que es mejor, los valores que se han ido guardando (para poder volver hacia atrás sin perder el contenido, y también para recopilar la información al final del proceso).

En esta función, que es el punto al que volvemos después de cada formulario, debemos definir que formulario presentamos:

function MiPrimerModulo_principal_form ($form, &$form_state) {

      // Display page 2 if $form_state['page_num'] == 1
      if (!empty($form_state['page_num']) && $form_state['page_num'] == 2) {
            return MiPrimerModulo_opcion1_form ($form, $form_state);
      }

      // En otro caso estamos en la página 1.

Además, el módulo funcionando crearía una estructura sencilla en la base de datos (que no se usa pero he puesto como ejemplo de uso), y un entrada en el menú principal disponible para todos los usuarios:

Se puede descargar el código completo aquí. Ese código, y otros ejemplos, se pueden encontrar en este enlace, pero no son ejemplos completos como este que se ha desarrollado aquí.

En el código adjunto también se encuentra el fichero module.install que proporciona la manera de crear el esquema de la base de datos al instalar el módulo. Si en algunas de las pruebas no se genera bien el esquema, debemos desinstalar (no deshabilitar) el módulo para que Drupal entienda que lo hemos borrado del todo y volver a instalarlo (para que Drupal vuelva a leer el module.install). Si sólo se deshabilita, Drupal no vuelve a leer el module.install.

Otro ejemplo sobre la creación de módulos puede verse aquí.