««« | »»»
Coda & Subversion
Uso Coda ormai da qualche tempo. È un buon programma per lo sviluppo dei siti, piacevole da usare e affidabile, non ho avuto bisogno di molto altro per diverso tempo.
I limiti più evidenti di Coda
Dopo qualche tempo, però, ho cominciato a sentire la mancanza di Emacs e così ho configurato Coda in modo da usarlo come editor esterno. A quel punto ho scoperto con fastidio che la lista dei file da caricare sul sito non veniva sempre aggiornata con le modifiche da Emacs, nonostante fossero correttamente individuati come file modificati rispetto alla repository nel VCS (Version Control Software; attualmente è Subversion), costringendomi ogni volta a marcarli a mano. No fun.
Inoltre non sempre abbiamo a che fare con un deployment diretto. Spesso dobbiamo prima caricare il sito su un server di staging e poi, verificato che tutto vada bene, passarlo in produzione. Teoricamente potremmo “ripuntare” Coda fra i due server ma, in pratica, sarebbe un delirio.
Sfruttiamo Subversion!
Il modo ovvio di risolvere questi problemi è usare i punti di aggancio che Subversion mette a disposizione, ovvero, la possibilità di eseguire delle operazioni arbitrarie in concomitanza di alcuni eventi:
post-commitpost-lockpost-revprop-changepost-unlockpre-commitpre-lockpre-revprop-changepre-unlockstart-commit
In questo caso, lo script agganciato sarà agganciato in post-commit ed effettuerà il deployment.
I dettagli importanti
Una cosa piuttosto semplice, insomma, anche se ci sono diverse cosine di cui tener conto. In primo luogo, è sempre preferibile disimpegnare Subversion il prima possibile1 e quindi dobbiamo posporre le varie operazioni; inoltre, dato che lo script è condiviso per tutta la repository, dobbiamo avere l’accortezza di fare le azioni giuste per il progetto giusto; infine, visto che ogni progetto fa storia a sé, dobbiamo trovare un buona flessibilità2 per poter specificare le azioni che vogliamo, nel modo che riteniamo più opportuno per quel progetto.
Disimpegnare velocemente la repository
Ad ogni commit viene creato uno script di shell (a partire da un template descritto più avanti) chiamato con il numero di release. La creazione è praticamente istantanea.
Questo script va a finire in una specie di coda sul filesystem. Il fatto di usare una directory invece di un file singolo evita problemi di accesso contemporaneo, lasciando completa libertà nel suo contenuto e mi consente di sfruttare launchd per fare in modo che il processamento degli script nella coda venga lanciato solo al momento opportuno. Questo è il file di configurazione:
Il dettaglio importante è la direttiva QueueDirectories che, a differenza di WatchPaths, lancia il processo alla modifica del contenuto della directory, ma solo quando in precedenza era vuota.
In alternativa avremmo potuto usare un qualche daemon. Non un gran problema ma mi sembrava un po’ meno robusto.
Progetti diversi, copioni diversi
Come dicevo prima, lo scopo di usare degli script di shell è quello di lasciare la massima libertà.
Ogni voce della configurazione letta dallo script è simile a questa:
<regexp> {
env {
REL=$rev
}
preflight {...}
deploy {...}
postflight {...}
}
Le quattro voci: env, preflight, deploy e postflight raggruppano e ordinano i diversi frammenti che vanno a comporre lo script di deployment, che viene attivato in base all’eventuale match dell’espressione regolare.
L’unica variabile conosciuta è il numero della release, che è passato da Subversion, il resto è tutto libero. Più generico di così!
Di solito, dopo aver definito le variabili ambientali in env, il preflight si occupa di fare il checkout dal VCS; il deploy fa quello che si può immaginare; il postflight si occupa di fare un po’ di pulizia ecc. Ribadisco però il fatto che quelle descritte prima sono solo sezioni: se per assurdo voglio mettere tutto nella sezione env lo posso fare.
Ma non è finita qui…
Tutto bene? Quasi. Così com’è pensato, il sistema non permette di fare alcune cose molto importanti.
Percorsi variabili
Se vogliamo usarlo anche per il deployment delle release “taggate”3, dobbiamo agganciare lo script al percorso dei tag. Tuttavia, dato che l’url completa cambia ad ogni nuovo tag — ad esempio avremo …/tags/REL1, …/tags/REL2 — come facciamo a sapere in post-commit quale prendere?
Per ora non ho trovato una soluzione generica. Mi sono affidato al fatto che il nome della versione in tags è uguale al numero precedente della release di post-commit4. Ad esempio, se l’operazione è stata eseguita alla release 3753, cercheremo la directory r3752. Una volta ottenuta l’URL rientriamo nel caso standard.
Azioni specifiche per le singole release
Un’altra cosa abbastanza frequente è l’esecuzione di operazioni specifiche per una data versione. Esempio tipico: delle modifiche allo schema del database, che vanno fatte una volta sola e non devono essere riapplicate.
Per risolvere questo problema, all’interno del progetto, in una directory nota, creiamo uno script che verrà cercato dal sistema di deployment per essere eseguito5.
roll-up delle modifiche
Infine, nello scenario staging & deployment è desiderabile che una serie di operazioni fatte in staging possano venire applicate in una volta sola in deployment.
Dato che lo script specifico per una data release è anch’esso sotto controllo di versione, abbiamo accesso a tutta la sua storia. Bisognerà andare a prendere quella porzione di modifiche intercorse fra l’ultimo deployment e quello attuale e appenderle ad un unico file, con un comando del genere, ad esempio:
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 passare al comando appena visto? Partiamo da un scenario semplificato all’osso, supponendo di partire da una repository nuova di zecca.
Dalla r1 alla r10, lavoro solo nel trunk; quando creo un nuovo tag, in base alle convenzioni adottate prima, il suo nome sarà r10 e il suo numero di release sarà r11. Proseguendo, dalla r12 alla r20, lavoro sempre nel trunk; come prima, creando un nuovo tag, il nome sarà r20 e il suo numero di release sarà r21.
A questo punto, se cerco tutte le modifiche in tags, escludendo quella relativa alla creazione della directory tags, avrò r11 ed r21. Come prima, in base al numero di release passato in post-commit, che al secondo tag sarà 20, posso ricavare il range da considerare, calcolabile 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 reinventare la ruota, ci sono delle alternative. In particolare, una delle possibilità che ho preso in considerazione è quella di installare il software di VCS direttamente sul server.
In effetti è una soluzione piuttosto comoda, dato che basta fare un svn up per aggiornare l’installazione e con VCS distribuiti le possibilità sono ancora più ampie.
Ci sono però diversi punti dolenti. Come visto prima è raro che basti un semplice aggiornamento del codice; in secondo luogo non mi piace moltissimo l’idea che si crei un’ulteriore dipendenza fra il sistema che pubblica gli aggiornamenti e quello che li riceve.
Tolta questa possibilità, ci sono i software di SCM veri e propri, da Puppet a Capistrano, passando per Fabric. Il primo propone un modello in pull, dove i cliente contattano il server per stabilire cosa c’è da fare; il secondo e il terzo sono più vicini ai sistemi in push, dove l’azione è iniziata dal server.
Infine, non dimentichiamoci di Hudson che, pur essendo un server di integrazione, può automatizzare praticamente qualsiasi cosa e quindi anche processi come questi.
Hudson è quello più adatto, essendo un vero e proprio server di continous integration. Anzi, non escludo di cominciare 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 sysadmin, Puppet mi piace di più, anche perché ci ho giocato un paio di volte, mentre ho visto usare Capistrano in situazioni simili a quelle descritte.
Come si può notare, alcune delle parti più contorte sono dovute a Subversion. Dato che ho in progetto di passare a Git, sono curioso di sapere se e come il procedimento si semplificherà.
- In post-commit Subversion ignora l’esito delle operazioni, dato che il commit è già stato fatto; tuttavia non mi è chiaro se lo esegua in asincrono ↩
- avete mai sentito di un progetto che non deve essere flessibile? ↩
- Per chi non lo sapesse, un tag è un’istantanea del codice sorgente ad un dato momento. In Subversion, è di prassi che le versioni ufficiali stiano in
tagse le versioni di sviluppo che deviano da quella principale stiano inbranches. ↩ - l’uso dei numeri di release come nome del tag non mi risulta sia la cosa più consigliabile, lo so, tuttavia nelle applicazioni con rilasci continui la cosa non mi sembra così grave ↩
- Qualcuno storcerà il naso nel vedere una commistione fra deployment e sviluppo, all’interno del sorgente. Non è sicuramente la soluzione più pulita anche se ha l’indubbio vantaggio di tenere sotto controllo di versione anche cose che spesso vengono lasciate un po’ a se stesse. Ad oggi credo che il bilancio costi/benefici sia positivo. ↩
Per proseguire
Commenti e trackback sono disabilitati.