Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Publicaciones sobre sysadmin

Con iterpipes, python puede reemplacar a bash para scripting. En serio.

O al me­nos así era has­ta que hoy me en­contré con iter­pi­pes en py­tho­n.­re­ddi­t.­com

Iter­pi­pes es "u­na bi­blio­te­ca pa­ra usar pi­pe­li­nes de she­ll usan­do sin­ta­xis pa­re­ci­da al she­ll" y ¿sa­bés qué? Es bri­llan­te.

Acá hay un ejem­plo de su pá­gi­na de PY­PI:

# Total lines in *.py files under /path/to/dir,
# use safe shell parameters formatting:

>>> total = cmd(
...     'find {} -name {} -print0 | xargs -0 wc -l | tail -1 | awk {}',
...     '/path/to/dir', '\*.py', '{print $1}')
>>> run(total | strip() | join | int)
315

Así se­ría en she­ll:

find /path/to/dir -name '*.py' -print0 | xargs -0 wc -l | tail -1 | awk '{print $1}'

Tal vez di­gas que la ver­sión de she­ll es más sen­ci­lla. Eso es una ilu­sión cau­sa­da por la den­si­dad del cam­po ma­li­co del she­ll: esa ver­sión tie­ne bugs.

Porqué tiene bugs? Porque si controlo qué hay adentro de /path/to/dir puedo hacer que falle 1, pero en python puedo manejar el error.

Tam­bién en la ma­yo­ría de los in­ten­tos de es­cri­bir ese co­man­do se­ría in­se­gu­ro por­que las re­glas de co­mi­llas y ca­rac­te­res de es­ca­pe de she­ll son de­men­tes.

La ver­sión de iter­pi­pes usa el equi­va­len­te de pre­pa­red sta­te­men­ts de SQL que de­be­ría ser más se­gu­ro.

Es ca­si im­po­si­ble ha­cer ese co­man­do en pu­ro she­ll y es­tar se­gu­ro de que es se­gu­ro.

Ade­más la ver­sión en she­ll pro­du­ce un string en vez de un en­te­ro, que es una ba­zo­fia si lo ne­ce­si­tás usar pa­ra al­go.

Y el be­ne­fi­cio más im­por­tan­te es, por su­pues­to, no cuan­do tra­tás de ha­cer que py­thon fun­cio­ne co­mo un she­ll, sino cuan­do po­dés de­jar de ha­cer co­mo que el she­ll es un len­gua­je de ver­da­d.

Comsideremos esta gema del script /etc/rc.shutdown en Arch Linux. DAEMONS es una lista de cosas que se arrancaron al inicio, y este script intenta detenerlas en orden inverso, a menos que el nombre empiece con "!":

# Shutdown daemons in reverse order
let i=${#DAEMONS[@]}-1
while [ $i -ge 0 ]; do
        if [ "${DAEMONS[$i]:0:1}" != '!' ]; then
                ck_daemon ${DAEMONS[$i]#@} || stop_daemon ${DAEMONS[$i]#@}
        fi
        let i=i-1
done

¿No es lin­do?

¿Y có­mo se ve­ría en py­tho­n? (tal vez ha­ya in­ver­ti­do el sig­ni­fi­ca­do de ck_­dae­mo­n):

# Shutdown daemons in reverse order
for daemon in reversed(DAEMONS):
    if daemon[0]=='!':
        continue
    if ck_daemon(daemon):
        stop_daemon(daemon)

Mien­tras que sto­p_­dae­mon so­lía ser es­to:

stop_daemon() {
    /etc/rc.d/$1 stop
}

Aho­ra se­rá es­to:

.. code-block:: python
def stop_daemon(daemon):

run(­cm­d('/e­tc/r­c.­d/{} sto­p',­dae­mo­n))

Va­mos gen­te, es­ta­mos en pleno si­glo 21, y el she­ll scrip­ting ya era feo en el 20.

1

Ejer­ci­cio pa­ra el lec­to­r.

Migrando de Haloscan a Disqus (si podés comentar, funcionó ;-)

Si sos usua­rio de Ha­los­can, y es­tás em­pe­zan­do a pre­gun­tar­te que ha­ce­r... es­ta pá­gi­na te va a ex­pli­car una for­ma de lle­var­te tus co­men­ta­rios a Dis­qus, otro ser­vi­cio de co­men­ta­rios pa­ra blogs gra­tui­to.

Ha­ce unos po­cos días Ha­los­can anun­ció el fin de su ser­vi­cio de co­men­ta­rios gra­tui­tos pa­ra blogs. Adi­vi­ná en qué ser­vi­cio ten­go 9 años de co­men­ta­rio­s? Sí, en Ha­los­can.

Ofre­cen una mi­gra­ción sen­ci­lla a su pla­ta­for­ma Echo, que es un ser­vi­cio pa­go. Si bien Echo pa­re­ce una pla­ta­for­ma de co­men­ta­rios per­fec­ta­men­te dig­na, no ten­go ga­nas de gas­tar di­ne­ro en es­te bloh si pue­do evi­tar­lo. Ya bas­tan­te le doy tiem­po!

Por suer­te, los mu­cha­chos de Ha­los­can per­mi­ten ex­por­tar los co­men­ta­rios (an­tes ha­bía que pa­gar pa­ra eso­), así que gra­cias Ha­los­can, fué un gus­to!

En­ton­ces em­pe­cé a in­ves­ti­gar a don­de me po­día es­ca­pa­r. Pa­re­ce ha­ber dos sis­te­mas gran­des y gra­tui­tos de co­men­ta­rio­s:

Tén­ga­se en men­te que prin­ci­pal in­te­rés es no per­der mis co­men­ta­rio­s, no la ca­li­dad del ser­vi­cio. Ha­bien­do di­cho eso, los dos pa­re­cen ofre­cer más o me­nos las mis­mas ca­rac­te­rís­ti­ca­s.

Con­si­de­re­mos co­mo im­por­tar co­men­ta­rios en ca­da ser­vi­cio:

  • Dis­­qus: Pue­­de im­­po­r­­tar de blo­­­gger y al­­gún otro se­r­­vi­­cio ho­s­­tea­­do. No de Ha­­lo­s­­can.

  • In­­ten­­se De­­ba­­te: Pue­­de im­­po­r­­tar al­­gu­­nos se­r­­vi­­cios ho­s­­tea­­dos y al­­gu­­nos ar­­chi­­vo­­s. No el que Ha­­lo­s­­can me dió.

En­ton­ce que ha­go? Es­cri­bir un pro­gra­ma en Py­tho­n, por su­pues­to! Ahí ga­nó Dis­qus: tie­nen un API pú­bli­ca pa­ra los co­men­ta­rio­s.

En­ton­ce­s, to­do lo que hay que ha­cer es:

  1. En­­ten­­der el API de Dis­­qus

  2. En­­ten­­der los co­­­men­­ta­­rios de Ha­­lo­s­­can (es XM­­L)

  3. Crear los hi­­los ne­­ce­s­a­­rios en Dis­­qus

  4. Po­s­­tear los co­­­men­­ta­­rios de Ha­­lo­s­­can a Dis­­qus

  5. Arre­­glar el blog pa­­ra que los li­nks a Ha­­lo­s­­can fun­­cio­­­nen con Dis­­qus

Pan co­mi­do. Me lle­vó me­dio día, lo que a mi ta­ri­fa ac­tual es el equi­va­len­te de 3 años de Echo, pe­ro que gra­cia ten­dría pa­gar?

Así que, va­mos pa­so por pa­so.

1. Entender el API de Disqus

Por suer­te hay una bi­blio­te­ca ra­zo­na­ble: Dis­qus Py­thon Client li­bra­ry y do­cs pa­ra la API así que es­to no fué tan di­fí­ci­l.

Ins­ta­le la bi­blio­te­ca:

hg clone https://IanLewis@bitbucket.org/IanLewis/disqus-python-client/
cd disqus-python-client
python setup.py install

El uso que va­mos a dar­le al API es sen­ci­llo, así que si ha­ce fal­ta, lee la do­cu­men­ta­ción 15 mi­nu­to­s. Yo sa­qué to­do lo que ne­ce­si­ta­ba de es­te script pa­ra im­por­tar py­blo­x­som

Bá­si­ca­men­te:

  1. Ob­­te­r­­ner un key pa­­ra la API

  2. Lo­­­guea­r­­se

  3. Ob­­te­­ner el "fo­­­ro" co­­­rre­c­­to (Se pue­­de usar una cuen­­ta Dis­­qus pa­­ra más de un blo­­­g)

  4. Po­s­­tear en el hi­­lo ade­­cua­­do

2. Entender el archivo de comentarios de Haloscan

No só­lo es XM­L, ¡ Es XML fá­ci­l!

Es más o me­nos así:

<?xml version="1.0" encoding="iso-8859-1" ?>
<comments>
    <thread id="BB546">
      <comment>
        <datetime>2007-04-07T10:21:54-05:00</datetime>
        <name>superstoned</name>
        <email>josje@isblond.nl</email>
        <uri></uri>
        <ip>86.92.111.236</ip>
        <text><![CDATA[that is one hell of a cool website ;-)]]></text>
      </comment>
      <comment>
        <datetime>2007-04-07T16:14:53-05:00</datetime>
        <name>Remi Villatel</name>
        <email>maxilys@tele2.fr</email>
        <uri></uri>
        <ip>77.216.206.65</ip>
        <text><![CDATA[Thank you for these rare minutes of sweetness in this rough world...]]></text>
      </comment>
    </thread>
</comments>

En­ton­ce­s: un tag co­m­men­ts, que con­tie­ne una o más tags th­rea­d, que con­tie­nen uno o mas tags co­m­men­t. ¡Pan co­mi­do con Ele­men­tTree!

Hay una ob­via co­rres­pon­den­cia en­tre co­men­ta­rios y th­rea­ds en Ha­los­can y Dis­qus. Bien.

3. Crear los hilos necesarios en Disqus

Es­ta es la par­te com­pli­ca­da, por­que re­quie­re co­sas de tu blo­g.

  • Hay que te­­ner un pe­r­­ma­­li­nk por post

  • Ca­­da pe­r­­ma­­li­nk de­­be ser una pá­­gi­­na se­­pa­­ra­­da. No si­r­­ve un pe­r­­ma­­li­nk con # en la UR­­L.

  • Pa­­ra ca­­da po­s­­t, hay que sa­­ber el tí­­tu­­lo, el pe­r­­ma­­li­nk, y có­­­mo iden­­ti­­fi­­car los co­­­men­­ta­­rios en Ha­­lo­s­­can.

Por ejem­plo, su­pon­ga­mos que hay un post en //­ral­si­na.­me/we­blo­g/­pos­ts/A­D­V0.ht­ml con un li­nk de Ha­los­can co­mo és­te:

<a hre­f="ja­vas­crip­t:Ha­loS­can('A­D­V0');" tar­ge­t="_sel­f"> <s­cript ty­pe="­tex­t/­ja­vas­crip­t">­pos­tCoun­t('A­D­V0');</s­crip­t></a>

¿A­dón­de más sa­le ese 'A­D­VO­'? En el ar­chi­vo XML de Ha­los­can, por su­pues­to. Es el atri­bu­to "i­d" de un th­rea­d.

Ade­má­s, el tí­tu­lo de es­te post es "A­d­vo­ga­to post for 2000-01-17 17:19:57" (Es mi blog por su­pues­to ;-)

¿Te­nés esos da­to­s?

En­ton­ces va­mos a crear un th­read en Dis­qus con exac­ta­men­te los mis­mos da­tos:

  • URL

  • Th­­read ID

  • Ti­­tu­­lo

La ma­la no­ti­cia es... vas a ne­ce­si­tar te­ner es­ta in­for­ma­ción pa­ra to­do tu blog y guar­dar­la en al­gún la­do. Si te­nés suer­te, tal vez la pue­das sa­car de una ba­se de da­to­s, co­mo hi­ce yo. Si no­... bue­no, va a ser bas­tan­te tra­ba­jo :-(

Pa­ra los pro­pó­si­tos de es­ta ex­pli­ca­ción voy a asu­mir que ese da­to es­tá en un bo­ni­to dic­cio­na­rio in­de­xa­do por th­read id:

{
  id1: (url, title),
  id2: (url, title)
}

4. Postear los comentarios de Haloscan a Disqus

Aquí es­tá el có­di­go. No es­tá real­men­te pro­ba­do, por­que tu­ve que ha­cer va­rios in­ten­tos y arre­glos par­cia­le­s, pe­ro de­be­ría es­tar cer­ca de lo co­rrec­to. (do­wn­load):

#!/usr/bin/python
# -*- coding: utf-8 -*-

# Read all comments from a CAIF file, the XML haloscan exports

from disqus import DisqusService
from xml.etree import ElementTree
from datetime import datetime
import time


# Obviously these should be YOUR comment threads ;-)
threads={
    'ADV0': ('//ralsina.me/weblog/posts/ADV0.html','My first post'),
    'ADV1': ('//ralsina.me/weblog/posts/ADV1.html','My second post'),
    }

key='USE YOUR API KEY HERE'
ds=DisqusService()
ds.login(key)
forum=ds.get_forum_list()[0]

def importThread(node):
    t_id=node.attrib['id']

    # Your haloscan thread data
    thr_data=threads[t_id]

    # A Disqus thread: it will be created if needed
    thread=ds.thread_by_identifier(forum,t_id,t_id)['thread']

    # Set the disqus thread data to match your blog
    ds.update_thread(forum, thread, url=thr_data[0], title=thr_data[1])


    # Now post all the comments in this thread
    for node in node.findall('comment'):
        dt=datetime.strptime(node.find('datetime').text[:19],'%Y-%m-%dT%H:%M:%S')
        name=node.find('name').text or 'Anonymous'
        email=node.find('email').text or ''
        uri=node.find('uri').text or ''
        text=node.find('text').text or 'No text'

        print '-'*80
        print 'Name:', name
        print 'Email:', email
        print 'Date:', dt
        print 'URL:', uri
        print
        print 'Text:'
        print text

        print ds.create_post(forum, thread, text, name, email,
                                   created_at=dt, author_url=uri)
        time.sleep(1)

def importComments(fname):
    tree=ElementTree.parse(fname)
    for node in tree.findall('thread'):
        importThread(node)


# Replace comments.xml with the file you downloaded from Haloscan
importComments('comments.xml')

Aho­ra, si tu­vi­mos suer­te, ya te­nés una lin­da y fun­cio­nal co­lec­ción de co­men­ta­rios en tu cuen­ta de Dis­qus, y la tran­qui­li­dad de que no se per­die­ron los da­to­s. Lis­to pa­ra el pa­so fi­na­l?

Por qué sigo usando Arch Linux

Que­ría pro­bar rs­t2­pdf contra re­por­tlab de SVN, wor­da­xe de SVN y do­cu­tils de SVN, y que­ría que fue­ra fá­ci­l.

So­lu­ció­n: Los em­pa­que­té en AUR!

Ahora, cada vez que quiero probar rst2pdf contra wordaxe de trunk SVN, hago un yaourt -S py­tho­n-wor­da­xe-s­vn y para volver a wordaxe estable hago yaourt -S py­tho­n-wor­da­xe.

El pa­que­te SVN siem­pre es trunk ac­tua­li­za­do sin mo­di­fi­ca­cio­nes, y pue­do ir y vol­ver en unos 45 se­gun­do­s, sin rom­per pa­que­tes del sis­te­ma.

También puedo mantener mis paquetes SVN instalados al día con un yaourt -Su --­de­vel cada tanto.

Co­mo lo hu­bie­ra he­cho usan­do De­bian o al­go ba­sa­do en RPM? Su­pon­go que por atrás del sis­te­ma de pa­que­tes (que odio ha­cer­lo) o ha­cien­do un re­po pri­va­do (que es tris­te) o con un re­po pú­bli­co (que es tra­ba­jo­!).

La ver­dad si uno pro­gra­ma, no se me ocu­rre una dis­tro que te ha­ga la vi­da más fá­cil que Ar­ch. Ca­si to­do es­tá ahí (12K pa­que­tes en un­su­pporte­d!) y si no es­tá son 5 mi­nu­tos pa­ra me­ter­lo en AUR y ayu­dar a la co­mu­ni­da­d.

Su­po­né que es­tás ha­cien­do una apli­ca­ción KDE. En la ma­yo­ría de las dis­tros te­nés que ins­ta­lar­te tu pro­pia co­pia de kde­libs de los fuen­tes pa­ra te­ner la úl­ti­ma ver­sión y ase­gu­rar­te que no es­tá arrui­na­da por par­ches es­pe­cí­fi­cos de la dis­tro.

En ar­ch? Em­par­char es­tá mal vis­to. No te­ner la úl­ti­ma ver­sión es­tá mal vis­to. Así que es más o me­nos el am­bien­te ideal pa­ra de­sa­rro­llar con KDE, GNO­ME, Py­Qt o lo que sea.

Si mi tiem­po no es­tu­vie­ra ocu­pao un 150% in­ten­ta­ría ser de­sa­rro­lla­dor Ar­ch, o por lo me­nos un TU (Trus­ted Use­r).

Ca­paz la pr­óxi­ma reen­car­na­ción :-)

Outlook, IMAP y Exchange

  • Al in­s­­ta­­lar Ou­­tlook 2000, se pue­­de ele­­gir "Só­­­lo grou­­pwa­­re", "Só­­­lo In­­te­r­­ne­­t" o "A­m­­bo­­s".

  • Só­­­lo In­­te­r­­net ha­­bi­­li­­ta cuen­­tas IMAP y PO­­­P, pe­­ro no Ex­­chan­­ge.

  • Só­­­lo Grou­­pwa­­re ha­­bi­­li­­ta Ex­­chan­­ge, pe­­ro no IMAP ni PO­­­P.

  • Am­­bos ha­­bi­­li­­ta Ex­­chan­­ge y PO­­­P... pe­­ro no IMA­­P!

Así se ha­ce pa­ra te­ner Ex­chan­ge y IMAP al mis­mo tiem­po:

  1. Ce­­rrar Ou­­tlook

  2. Ir al pa­­nel de co­n­­trol

  3. Ahí hay un icono "Co­­­rreo" que se creó cuan­­do in­s­­ta­­la­­mos OU­­tlook.

  4. Use ese icono pa­­ra crear la cuen­­ta IMA­­P.

Y hay quien se atre­ve a de­cir que win­do­ws es fá­cil de ad­mi­nis­trar :-P

Sí, ya sé, es so­ftwa­re vie­jo, lo que sea. No es mi cul­pa que ac­tua­li­zar el mal­di­to clien­te de co­rreo cues­te tan ca­ro que no lo quie­ran ha­cer por­que im­pli­ca ac­tua­li­zar to­do el re­ve­ren­do offi­ce.

PD: Gra­cias al que me con­tó co­mo se ha­ce ;-)

PD2: Co­mo ha­cer que Ou­tlook mues­tre más de 100 re­sul­ta­dos de LDA­P: http://­su­ppor­t.­mi­cro­so­ft.­co­m/k­b/262848

A hard-to-block spammer: I need help.

Many of my clients have been spammed by La Cap­i­tana Re­al Es­tate late­ly. And I mean many. Hun­dred­s.

How­ev­er, they seem to have found a way to spam that work­s. And that suck­s.

They have cre­at­ed a Google Group, added all their vic­tims there, and let google do the dirty work.

What's the prob­lem?

  1. Google group mails are not block­­able at SMT­P-lev­el be­­cause their senders con­­tain a sort of hash and the re­­cip­i­ent ad­­dress, and no group name. That's in­­­cred­i­bly stupid in google's part.

  2. The mes­sages they send are huge (6MB and up) so spa­­mas­sas­sin can not process them. The SA docs say this will not hap­pen be­­cause of "the eco­nom­ics of spam". Well, it hap­pens when you make google do it!

  3. I don't want to go back to the old days of keep­­ing a lo­­cal queue-lev­el ad­­dress black­­list. That's aw­­ful!

I have com­plained to google, I have com­plained to the spam­mer­s, even by phone. They use the stan­dard de­fense of "we are just invit­ing peo­ple". "They can un­sub­scribe if they want to". "This is not spam"

Noone does any­thing.

What's the next step? I can't black­list google group­s!