Mondi su mondi, sistemi di sistemi.

Coda & Subversion

Uso Coda ormai da qual­che tempo. È un buon pro­gramma per lo svi­luppo dei siti, pia­ce­vole da usare e affi­da­bile, non ho avuto biso­gno di molto altro per diverso tempo.

I limiti più evi­denti di Coda

Dopo qual­che tempo, però, ho comin­ciato a sen­tire la man­canza di Emacs e così ho con­fi­gu­rato Coda in modo da usarlo come edi­tor esterno. A quel punto ho sco­perto con fasti­dio che la lista dei file da cari­care sul sito non veniva sem­pre aggior­nata con le modi­fi­che da Emacs, nono­stante fos­sero cor­ret­ta­mente indi­vi­duati come file modi­fi­cati rispetto alla repo­si­tory nel VCS (Version Control Software; attual­mente è Subversion), costrin­gen­domi ogni volta a mar­carli a mano. No fun.

Inoltre non sem­pre abbiamo a che fare con un deploy­ment diretto. Spesso dob­biamo prima cari­care il sito su un ser­ver di sta­ging e poi, veri­fi­cato che tutto vada bene, pas­sarlo in pro­du­zione. Teoricamente potremmo “ripun­tare” Coda fra i due ser­ver ma, in pra­tica, sarebbe un delirio.

Sfruttiamo Subversion!

Il modo ovvio di risol­vere que­sti pro­blemi è usare i punti di aggan­cio che Subversion mette a dispo­si­zione, ovvero, la pos­si­bi­lità di ese­guire delle ope­ra­zioni arbi­tra­rie in con­co­mi­tanza di alcuni eventi:

  • post-commit
  • post-lock
  • post-revprop-change
  • post-unlock
  • pre-commit
  • pre-lock
  • pre-revprop-change
  • pre-unlock
  • start-commit

In que­sto caso, lo script aggan­ciato sarà aggan­ciato in post-commit ed effet­tuerà il deployment.

I det­ta­gli importanti

Una cosa piut­to­sto sem­plice, insomma, anche se ci sono diverse cosine di cui tener conto. In primo luogo, è sem­pre pre­fe­ri­bile disim­pe­gnare Subversion il prima pos­si­bile1 e quindi dob­biamo posporre le varie ope­ra­zioni; inol­tre, dato che lo script è con­di­viso per tutta la repo­si­tory, dob­biamo avere l’accortezza di fare le azioni giu­ste per il pro­getto giu­sto; infine, visto che ogni pro­getto fa sto­ria a sé, dob­biamo tro­vare un buona fles­si­bi­lità2 per poter spe­ci­fi­care le azioni che vogliamo, nel modo che rite­niamo più oppor­tuno per quel progetto.

Disimpegnare velo­ce­mente la repository

Ad ogni com­mit viene creato uno script di shell (a par­tire da un tem­plate descritto più avanti) chia­mato con il numero di release. La crea­zione è pra­ti­ca­mente istantanea.

Questo script va a finire in una spe­cie di coda sul file­sy­stem. Il fatto di usare una direc­tory invece di un file sin­golo evita pro­blemi di accesso con­tem­po­ra­neo, lasciando com­pleta libertà nel suo con­te­nuto e mi con­sente di sfrut­tare laun­chd per fare in modo che il pro­ces­sa­mento degli script nella coda venga lan­ciato solo al momento oppor­tuno. Questo è il file di configurazione:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>it.refactor.deployment-queue</string>
<key>OnDemand</key>
<true/>
<key>Program</key>
<string>/usr/local/bin/projects-auto-deployment</string>
<key>QueueDirectories</key>
<array>
<string>/var/tmp/deployment-queue</string>
</array>
</dict>
</plist>

Il det­ta­glio impor­tante è la diret­tiva QueueDirectories che, a dif­fe­renza di WatchPaths, lan­cia il pro­cesso alla modi­fica del con­te­nuto della direc­tory, ma solo quando in pre­ce­denza era vuota.

In alter­na­tiva avremmo potuto usare un qual­che dae­mon. Non un gran pro­blema ma mi sem­brava un po’ meno robusto.

Progetti diversi, copioni diversi

Come dicevo prima, lo scopo di usare degli script di shell è quello di lasciare la mas­sima libertà.

Ogni voce della con­fi­gu­ra­zione letta dallo script è simile a questa:

    <regexp> {
        env {
            REL=$rev
        }
        preflight {...}
        deploy {...}
        postflight {...}
    }

Le quat­tro voci: env, preflight, deploy e postflight rag­grup­pano e ordi­nano i diversi fram­menti che vanno a com­porre lo script di deploy­ment, che viene atti­vato in base all’eventuale match dell’espressione regolare.

L’unica varia­bile cono­sciuta è il numero della release, che è pas­sato da Subversion, il resto è tutto libero. Più gene­rico di così!

Di solito, dopo aver defi­nito le varia­bili ambien­tali in env, il preflight si occupa di fare il chec­kout dal VCS; il deploy fa quello che si può imma­gi­nare; il postflight si occupa di fare un po’ di puli­zia ecc. Ribadisco però il fatto che quelle descritte prima sono solo sezioni: se per assurdo voglio met­tere tutto nella sezione env lo posso fare.

Ma non è finita qui…

Tutto bene? Quasi. Così com’è pen­sato, il sistema non per­mette di fare alcune cose molto importanti.

Percorsi varia­bili

Se vogliamo usarlo anche per il deploy­ment delle release “tag­gate”3, dob­biamo aggan­ciare lo script al per­corso dei tag. Tuttavia, dato che l’url com­pleta cam­bia ad ogni nuovo tag — ad esem­pio avremo …/tags/REL1, …/tags/REL2 — come fac­ciamo a sapere in post-commit quale prendere?

Per ora non ho tro­vato una solu­zione gene­rica. Mi sono affi­dato al fatto che il nome della ver­sione in tags è uguale al numero pre­ce­dente della release di post-commit4. Ad esem­pio, se l’operazione è stata ese­guita alla release 3753, cer­che­remo la direc­tory r3752. Una volta otte­nuta l’URL rien­triamo nel caso standard.

Azioni spe­ci­fi­che per le sin­gole release

Un’altra cosa abba­stanza fre­quente è l’esecuzione di ope­ra­zioni spe­ci­fi­che per una data ver­sione. Esempio tipico: delle modi­fi­che allo schema del data­base, che vanno fatte una volta sola e non devono essere riapplicate.

Per risol­vere que­sto pro­blema, all’interno del pro­getto, in una direc­tory nota, creiamo uno script che verrà cer­cato dal sistema di deploy­ment per essere ese­guito5.

roll-up delle modifiche

Infine, nello sce­na­rio sta­ging & deploy­ment è desi­de­ra­bile che una serie di ope­ra­zioni fatte in sta­ging pos­sano venire appli­cate in una volta sola in deployment.

Dato che lo script spe­ci­fico per una data release è anch’esso sotto con­trollo di ver­sione, abbiamo accesso a tutta la sua sto­ria. Bisognerà andare a pren­dere quella por­zione di modi­fi­che inter­corse fra l’ultimo deploy­ment e quello attuale e appen­derle ad un unico file, con un comando del genere, ad esem­pio:
svn log -r3778:3783 -q URL | egrep ^r | awk '{ print $1 }' | sort | while read r; do svn cat -$r URL; done

Come ottengo il range da pas­sare al comando appena visto? Partiamo da un sce­na­rio sem­pli­fi­cato all’osso, sup­po­nendo di par­tire da una repo­si­tory nuova di zecca.

Dalla r1 alla r10, lavoro solo nel trunk; quando creo un nuovo tag, in base alle con­ven­zioni adot­tate prima, il suo nome sarà r10 e il suo numero di release sarà r11. Proseguendo, dalla r12 alla r20, lavoro sem­pre nel trunk; come prima, creando un nuovo tag, il nome sarà r20 e il suo numero di release sarà r21.

A que­sto punto, se cerco tutte le modi­fi­che in tags, esclu­dendo quella rela­tiva alla crea­zione della direc­tory tags, avrò r11 ed r21. Come prima, in base al numero di release pas­sato in post-commit, che al secondo tag sarà 20, posso rica­vare il range da con­si­de­rare, cal­co­la­bile come (release immediatamente precedente):(numero di release - 1), ovvero, r11:r19.

There’s more than one way to do it

Per chi non avesse voglia di rein­ven­tare la ruota, ci sono delle alter­na­tive. In par­ti­co­lare, una delle pos­si­bi­lità che ho preso in con­si­de­ra­zione è quella di instal­lare il soft­ware di VCS diret­ta­mente sul server.

In effetti è una solu­zione piut­to­sto comoda, dato che basta fare un svn up per aggior­nare l’installazione e con VCS distri­buiti le pos­si­bi­lità sono ancora più ampie.

Ci sono però diversi punti dolenti. Come visto prima è raro che basti un sem­plice aggior­na­mento del codice; in secondo luogo non mi piace mol­tis­simo l’idea che si crei un’ulteriore dipen­denza fra il sistema che pub­blica gli aggior­na­menti e quello che li riceve.

Tolta que­sta pos­si­bi­lità, ci sono i soft­ware di SCM veri e pro­pri, da Puppet a Capistrano, pas­sando per Fabric. Il primo pro­pone un modello in pull, dove i cliente con­tat­tano il ser­ver per sta­bi­lire cosa c’è da fare; il secondo e il terzo sono più vicini ai sistemi in push, dove l’azione è ini­ziata dal server.

Infine, non dimen­ti­chia­moci di Hudson che, pur essendo un ser­ver di inte­gra­zione, può auto­ma­tiz­zare pra­ti­ca­mente qual­siasi cosa e quindi anche pro­cessi come questi.

Hudson è quello più adatto, essendo un vero e pro­prio ser­ver di con­ti­nous inte­gra­tion. Anzi, non escludo di comin­ciare ad usarlo a breve, ma per ora mi bastano le due righe che ho scritto in Tcl.

Per quanto riguarda gli SCM, più vicini al mondo dei sysad­min, Puppet mi piace di più, anche per­ché ci ho gio­cato un paio di volte, men­tre ho visto usare Capistrano in situa­zioni simili a quelle descritte.

Come si può notare, alcune delle parti più con­torte sono dovute a Subversion. Dato che ho in pro­getto di pas­sare a Git, sono curioso di sapere se e come il pro­ce­di­mento si semplificherà.

  1. In post-commit Subversion ignora l’esito delle ope­ra­zioni, dato che il com­mit è già stato fatto; tut­ta­via non mi è chiaro se lo ese­gua in asin­crono
  2. avete mai sen­tito di un pro­getto che non deve essere fles­si­bile?
  3. Per chi non lo sapesse, un tag è un’istantanea del codice sor­gente ad un dato momento. In Subversion, è di prassi che le ver­sioni uffi­ciali stiano in tags e le ver­sioni di svi­luppo che deviano da quella prin­ci­pale stiano in branches.
  4. l’uso dei numeri di release come nome del tag non mi risulta sia la cosa più con­si­glia­bile, lo so, tut­ta­via nelle appli­ca­zioni con rila­sci con­ti­nui la cosa non mi sem­bra così grave
  5. Qualcuno stor­cerà il naso nel vedere una com­mi­stione fra deploy­ment e svi­luppo, all’interno del sor­gente. Non è sicu­ra­mente la solu­zione più pulita anche se ha l’indubbio van­tag­gio di tenere sotto con­trollo di ver­sione anche cose che spesso ven­gono lasciate un po’ a se stesse. Ad oggi credo che il bilan­cio costi/benefici sia posi­tivo.

Per proseguire

Commenti e trackback sono disabilitati.