Un maño entre gaúchos

Archive for the ‘ruby’ tag

Gráficas de uso de memoria de passenger con Munin

with one comment

Estaba instalando algunos plugins adicionales en el munin para monitorizar el estado de las peticiones que entran por Apache.
He encontrado uno para monitorizar el uso de memoria pero se ha quedado anticuado con respecto a ciertos cambios que ha sufrido la salida del comando passenger-memory-stats. Por lo tanto ya no sirve.
Basándome en dicho plugin y en este otro, he hecho algunos cambios y el código resultante es el siguiente:


#!/usr/bin/env ruby

def output_config
  puts <<-END
graph_category Passenger
graph_title passenger memory stats
graph_vlabel megabytes

apache_memory.label apache_memory
passenger_memory.label passenger_memory
END
  exit 0
end

def output_values
  memory_stats_command = '/usr/bin/passenger-memory-stats'
  memory = {}

  apache_header = "Apache processes"
  passenger_header = "Passenger processes"
  nginx_header = "Nginx processes"
  section = nil

  `#{memory_stats_command}`.each_line do |line|
    if line.include?(apache_header)
      section = "apache"
    elsif line.include?(nginx_header)
      section = "nginx"
    elsif line.include?(passenger_header)
      section = "passenger"
    elsif /### Total private dirty RSS: (\d+\.\d+) MB/.match(line)
      memory[section] = $1
    else
      next
    end
  end

  puts "apache_memory.value #{memory['apache']}"
  puts "passenger_memory.value #{memory['passenger']}"
  #puts "nginx_memory.value #{memory['nginx']}"
end

if ARGV[0] == "config"
  output_config
else
  output_values
end

El plugin parsea los totales de memoria tanto de apache, como de nginx, como de apache passenger. En mi caso solo muestro los valores de apache y de passenger, pero si se quisiera mostrar los de nginx, bastaría con descomentar la linea:


puts "nginx_memory.value #{memory['nginx']}"

Written by luis

July 29th, 2009 at 12:19 pm

Posted in Sistemas, ruby

Tagged with , , , ,

Oferta de becario para el área de sistemas de The Cocktail

without comments

En The Cocktail Experience, empresa dedicada a la consultoría web, estamos interesados en contratar becarios para el área de sistemas. Gente con muchas ganas de trabajar y aprender.

El perfil que estamos buscando es el siguiente:

  • Experiencia con sistemas GNU/Linux.
  • Conocimientos básicos de scripting
  • Conocimientos básicos de servidores web y servidores de bases de datos (por ejemplo MySQL, Apache….)
  • Posibilidad de realizar convenio de prácticas

Si estás interesado, envía tu CV a: andrea.hidalgo ARROBA the-cocktail PUNTO com

Written by luis

June 18th, 2009 at 3:54 pm

Exportar passwords de revelation a texto plano con ruby

without comments

Revelation tiene la opción de exportar el fichero de contraseñas a texto plano. El problema es que la organización de las entradas no es demasiado buena, porque las pone en una sola columna sin indentación, por lo que resulta dificil ver el anidación de las contraseñas en caso de que se usen carpetas.

Necesitaba tener las contraseñas en texto plano de forma que pudiese ver a simple vista esta información y fuese más facil de mantener. Esto se puede hacer fácilmente con ruby y xml-simple:

require 'rubygems'
require 'xmlsimple'

PASSWORD_FILE="./passwords.xml"

config = XmlSimple.xml_in(PASSWORD_FILE)

ce = config

indent = ""
INDENT_SPACES = "  "

def stepin(ce, indent)
  if !ce.nil?
    indent = indent + INDENT_SPACES
  end

  ce['entry'].each { |item|
    puts indent + item['name'].to_s
    if !item['entry'].nil?
      stepin(item, indent)
    elsif item['entry'].nil? && item['type'] != 'folder'
      if item['description'] && !item['description'].to_s.empty?
        puts indent + INDENT_SPACES + "description: " + item['description'].to_s
      end
      item['field'].each { |field_type|
        if !field_type['content'].nil?
          puts indent +
               INDENT_SPACES +
               field_type.values[0].gsub("generic-", "") +
               ": " +
               field_type.values[1]
        end
      }
    end
  }
end

stepin(ce, indent)

Para que esto funcione basta tener instalado rubygems y xml-simple.

Written by luis

April 27th, 2009 at 11:38 am

Una historia de capistrano, crontabs y pipes

with 2 comments

Al montar el deploy de una nueva máquina con capistrano he querido afinar un poco la carga de crontabs.
No me gusta poner las tareas de crontab en el /etc/crontab. Creo que es una muy mala práctica. En vez de eso prefiero que cada usuario tenga su propia tabla de crontabs y para ello hago uso del comando crontab.
En muchas ocasiones hace falta definir crontabs para más de un usuario. Para no andar añadiendo una linea en el script de deploy por cada fichero de crontab que se tenga doy por hecho que los nombres de todos los archivos con las tablas de crontab siguen el mismo formato, que es “crontab ..
El código pues para capistrano es:

task :app_deploy, :roles => [:app] do
  Dir["./appserver/etc/crontab.*"].each { |crontab|
    sudo "sh -c 'cat #{release_path}/#{crontab} | \
    crontab -u  #{File.extname(File.basename(crontab)).delete('.')} -"
  }
end

En este código hay varias cosas que explicar.
Primero se presupone pues que si quiero, por ejemplo, añadir el crontab para el usuario www-data simplemente lo crearé y lo guardaré en appserver/etc/crontab.www-data
En cuanto al código, por un lado el bloque lo que hace es cargar en un array los ficheros con un nombre que coincida con el patrón comentado anteriormente. Hay que tener en cuenta que el código en ruby se ejecuta en la máquina desde la que se lanza el deploy y lo que se le pasa al comando run o sudo es un comando unix que se va a ejecutar en la máquina en la que se quiera hacer el deploy.

Por otro lado está el hecho de que cuando se ejecutan con sudo dos comandos unidos por una tubería, el sudo se va a aplicar únicamente al primero.
Si se hace:


$ sudo echo '* * * * * date > /tmp/date' | crontab -u root -

el sudo se va a aplicar únicamente al comando echo y no al comando crontab por lo que eso no funcionará ya que no tenemos privilegios suficientes.

Por lo tanto para no tener que repetir el comando sudo a los dos lados de la tubería y también para no complicar el comando en capistrano, lo que se puede hacer es englobar toda la sentencia en una subshell de la siguiente forma:


$ sudo sh -c 'echo "* * * * * date > /tmp/date" | crontab -u root -'

Written by luis

April 15th, 2009 at 4:37 pm

Error al instalar passenger (fcgi)

without comments

Al intentar instalar passenger a partir de la gema, me he encontrado con un error rarísimo. He estado un rato intentando ver que podía ser.
Al final me he cansado y he probado a cambiar la versión de rubygems de la 1.0.1 a la 1.3.1 y se ha instalado correctamente.
El error era el siguiente:

# gem install --no-ri --no-rdoc passenger
Bulk updating Gem source index for: http://gems.rubyforge.org
Building native extensions. This could take a while...
ERROR: Error installing passenger:
ERROR: Failed to build gem native extension.

/usr/bin/ruby1.8 extconf.rb install --no-ri --no-rdoc passenger
checking for fcgiapp.h... no
checking for fastcgi/fcgiapp.h... no
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of
necessary libraries and/or headers. Check the mkmf.log file for more
details. You may need configuration options.

Provided configuration options:
--with-opt-dir
--without-opt-dir
--with-opt-include
--without-opt-include=${opt-dir}/include
--with-opt-lib
--without-opt-lib=${opt-dir}/lib
--with-make-prog
--without-make-prog
--srcdir=.
--curdir
--ruby=/usr/bin/ruby1.8
--with-fcgi-dir
--without-fcgi-dir
--with-fcgi-include
--without-fcgi-include=${fcgi-dir}/include
--with-fcgi-lib
--without-fcgi-lib=${fcgi-dir}/lib

Gem files will remain installed in /usr/lib/ruby/gems/1.8/gems/fcgi-0.8.7 for inspection.
Results logged to /usr/lib/ruby/gems/1.8/gems/fcgi-0.8.7/ext/fcgi/gem_make.out

Written by luis

January 7th, 2009 at 6:11 pm

Posted in Sistemas, ruby

Tagged with , , , ,

Oferta de becario para el área de sistemas de The Cocktail

without comments

En The Cocktail Experience, empresa dedicada a la consultoría web, estamos interesados en contratar becarios para el área de sistemas. Gente con muchas ganas de trabajar y aprender.

El perfil que estamos buscando es el siguiente:

  • Experiencia con sistemas GNU/Linux, especialmente Debian
  • Conocimientos básicos de scripting (en algún interprete tipo bash, ruby, perl, python, ….)
  • Conocimientos básicos de servidores web y servidores de bases de datos (por ejemplo MySQL, Apache….)
  • Interés y compromiso por el software libre
  • Estudios relacionados (Informática, Telecomunicaciones….)

Si estás interesado, envía tu CV a: andrea.hidalgo ARROBA the-cocktail PUNTO com

Written by luis

September 30th, 2008 at 5:46 pm

ActiveLDAP, validaciones y callbacks

without comments

Mientras intentaba implementar el soporte de vacaciones con gnarwl y postfix en una aplicación Rails de gestión de usuarios en LDAP me he encontrado con una situación que no me esperaba.

Primero he de comentar por encima en que consiste gnarwl. Se trata de un script al que se le pasa por la entrada standard un correo en texto plano, con su remitente y destinatario. Después el script busca el destinatario del correo en el árbol LDAP donde está guardada toda su información y comprueba si el atributo vacationActive está activado. Si lo está, le envía al remitente del correo un mail con el texto indicado en el atributo vacationInfo.

Al grano. En el modelo del usuario, gracias a ActiveLDAP, tenía indicado que uno de los objectClass que definen a todo usuario en esta aplicación en concreto es el objectClass Vacation.

ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=Usuarios',
             :classes => ['top', 'person', 'qmailUser', 'inetOrgPerson', 'Vacation']

Como no quería sobrecargar el formulario de creación de usuario con un campo/checkbox “Vacaciones”, he metido un callback before_create en el modelo del usuario, de la siguiente forma:

before_create :set_vacation

def set_vacation
  self.vacationActive = false
end

De esta forma, la propia definición de objectClass Vacation del usuario se iba a encargar de asignarle ese objectClass y el before_create se iba a encargar de crear el atributo vacationActive y de ponerlo a false.

Cual ha sido mi sorpresa al ir a crear un usuario nuevo y recibir un mensaje típico de validación que no esperaba:

1 error prohibited this user from being saved

There were problems with the following fields:

    * vacationActive is required attribute by objectClass 'Vacation'

Al principio me ha despistado un poco, pero al final he visto cual era el problema.
Obviamente sabía que cuando una entrada en LDAP tiene el objectClass Vacation, se exige que como mínimo tenga también el atributo vacationActive definido. Lo que no sabía era que ActiveLDAP genera las validaciones en tiempo real consultando primero los objectClass de esa entrada y generandose su lista con los atributos de los que dependen esos objectClass.

Por lo tanto el before_create añadido anteriormente no sirve, ya que es necesario crear ese atributo antes de la validación. Lo correcto sería:

before_validation_on_create :set_vacation

Además de este detalle se había juntado otro problema que hacía que me costase un poco con la solución. Este problema es que ActiveLDAP no acepta una definición de un booleano de la forma clásica:
vacationActive = false

ActiveLDAP acepta valores booleanos de la siguiente en forma de cadenas de la forma “TRUE” o “FALSE”.
Mirando un poco las tripas de ActiveLDAP he visto que tiene una función para normalizar los valores en el caso de que se le pase true o false. No obstante parece que no aplica esa función a nivel interno, por lo que resulta totalmente inutil en caso de desconocimiento del programador, como en este caso me ha pasado a mi.

Por lo tanto, el callback correcto en esta situación no sería el que he indicado anteriormente, sino que sería este:

before_validation_on_create :set_vacation

def set_vacation
  self.vacationActive = "FALSE"
end

Written by luis

August 5th, 2008 at 11:25 am

Caracteres inválidos en el parametro page de will_paginate

with 3 comments

Hoy me han llegado al correo varias notificaciones de excepciones del site del Centenario do Inter. Estos errores se han generado por no indicar el parámetro page del will_paginate, de la siguiente forma:

http://localhost:3000/fotos?page=

El código de will_paginate no contempla eso, por lo que tenía dos opciones. Una poner un filtro en mis controladores para asegurarme de que hubiese algún número de pagina. Esa opción es muy poco DRY, así que he hecho el siguiente cambio en el collection.rb del plugin de will_paginate:

Index: vendor/plugins/will_paginate/lib/will_paginate/collection.rb
===================================================================
--- vendor/plugins/will_paginate/lib/will_paginate/collection.rb        (revisión: 65)
+++ vendor/plugins/will_paginate/lib/will_paginate/collection.rb        (copia de trabajo)
@@ -16,7 +16,7 @@
     # populating the collection using the +replace+ method.
     #
     def initialize(page, per_page, total = nil)
-      @current_page = page.to_i
+      @current_page = !page.blank? && page != 0 && page.to_i != 0 ? page.to_i : 1
       @per_page     = per_page.to_i

       self.total_entries = total if total



La verdad es que no se, ni si eso está contemplado en versiones posteriores a la que estoy usando, ni si es una buena solución, pero a mi me ha servido para resolver el problema rápidamente y sin tirar apenas código.
Con ese cambio, además de asegurarme de que la aplicación no de 500 al no poner ningún número en el parámetro page, también me aseguro de que no se rompa por poner un parámetro que no sea un número, como por ejemplo:

http://localhost:3000/fotos?page=foobar

Pos eso.

Written by luis

July 23rd, 2008 at 8:44 pm

Ruby, MD5 y OpenLDAP

with 2 comments

Si lo que quieres es almacenar las contraseñas de usuarios de OpenLDAP encriptadas con el algoritmo MD5, hay que tener en cuenta tres cosas.
La primera es que muchos clientes utilizan un recurso para saber de forma automática en que algoritmo está encriptada la contraseña contra la que van a intentar autenticarse. Este recurso, es añadir el nombre del algoritmo delante de la contraseña, por ejemplo:
{MD5}OFj2IjCsPJFfMAxmQxLGPw==

Si no se le fuerza al cliente a que use un algoritmo u otro para autenticarse, mirará (si el cliente está bien implementado) si la contraseña de LDAP tiene entre llaves el nombre del algoritmo y usará en consecuencia ese algoritmo para enviar la contraseña.

Otro tema a tener en cuenta, es el tipo de encriptación MD5. Normalmente la mayoría de librerías de MD5 permiten la encriptación de una cadena en formato hexadecimal y en binario. Para OpenLDAP, no nos sirve ninguno de los dos formatos a pelo. Lo que debemos hacer para que la contraseña esté en el formato correcto en el árbol LDAP, es encriptarla en MD5 binario y codificarla después en Base64 para que sea legible. La implementación de esto en ruby, y más concretamente en un modelo de Rails sería así:

require 'digest/md5'
require 'base64'
class User < ActiveLdap::Base
  before_save :encrypt_password

  def encrypt_password
    self.userPassword = "{MD5}" + Base64.encode64(Digest::MD5.digest(self.userPassword))
  end
end

De ActiveLDAP hablaré un poco otro día.

Lo último a tener en cuenta es que el hecho de guardar contraseñas en MD5 en el árbol LDAP no quiere decir que nuestro sistema sea seguro. Sin contar con todos esos temas de desencriptación por fuerza bruta y todo eso, está el tema del envío de la contraseña por un medio no seguro. Si nuestro cliente (podría ser en este caso una web), no está en la misma máquina que el árbol LDAP, al autenticarse con el método SIMPLE, la contraseña viajará en claro por la red hasta el LDAP.
Para evitar esto tenemos dos opciones. O hacemos que las autenticaciones sean con el método SASL (con DIGEST-MD5 por ejemplo) o simplemente le damos soporte SSL/TLS al slapd, de forma que todo tipo de flujo de paquetes entre el servidor y el cliente vayan encriptados.

Written by luis

May 31st, 2008 at 8:45 am