Personal tools
Come gestire e distribuire i vostri prodotti Plone?

Gestire il proprio codice in... relax!

Feb 08, 2013

Come gestire e distribuire i vostri prodotti Plone?

Un'esplorazione di tutti i (bizzarri) metodi con cui ho visto gestire il codice Plone (ma non solo), alla ricerca di una soluzione per ogni problema

Come gestire e distribuire i vostri prodotti Plone?

Sarà perché nella mia esperienza ho affrontato vari corsi di formazione per sviluppatori Plone, sarà perché non tutto il codice è sempre pronto per essere rilasciato, una cosa è certa: il rapporto tra il neofita e il codice da lui sviluppato è piuttosto combattuto.

Partiamo con una carrellata di quello che potete fare (ma in gran parte non dovete) fino ad arrivare alla soluzione dei casi più delicati.

Sviluppo old-style

Nell'uso quotidiano di Plone, il nostro amico buildout ci ha abituati per anni all'uso della directory di develop (di solito, la directory src):

[buildout]
...
develop =
src/my.package
...

PapyrusSebbene questo metodo oggi sia largamente superato (vedi mr.developer), la comunità Plone lo ha utilizzato per anni e c'è ancora tanta documentazione in giro che ne parla.

Per molti utenti che non hanno dimestichezza con l'installazione di mr.developer (prima che questo diventasse parte dei template di buildout Plone rilasciati) questo modo è quello "più semplice".

Semplicemente: funziona!

Versiona-che?

Ammettiamo che vi troviate nella situazione di cui sopra: qual è la cosa peggiore che potreste trovare in un buildout?
Semplice! L'assenza del versionamento del codice.

Mi è capitato diverse volte di trovarmi in questa situazione: il Cliente X chiama per mettere mano a un buildout in cui erano presenti prodotti legacy proprio all'interno della cartella src.
Nei casi peggiori, si tratta di codice non versionato. Eppure ci viene chiesto di mettergli mano e di apportare un qualche tipo di modifica!

A volte può capitare di accorgersi che nemmeno la configurazione del buildout sia sotto controllo di versione...

Spesso il cliente si sente sufficientemente al sicuro sapendo che ha "una copia da qualche parte" (quando va bene), eppure è difficile fargli capire che la soluzione "backup" non è tutto.

Probabilmente siamo passati tutti per questa fase (detta anche fase del "ero giovane, avevo bisogno di lavorare"), ma spero per voi l'abbiate abbandonata abbastanza in fretta.

Externals SVN

Prima dell'esplosione di popolarità di Git/Github, il software di versionamento del codice più in voga tra gli sviluppatori Plone è stato SVN.
Ne è riprova il fatto che, ancora oggi, la collective SVN è un repository piuttosto utilizzato, anche se la nuova collective, ospitata su GitHub, sta rapidamente prendendo piede.

Una delle caratteristiche di SVN era la possibilità di definire il contenuto di una directory sotto controllo di versione tramite la definizione di externals (svn:externals), ossia permettere di caricare nella directory una serie di altri sorgenti provenienti (volendo) anche da altri repository.

Una volta compresa l'importanza del versionamento, il Cliente X potrebbe davvero apprezzare il fatto di sapere che il suo buildout è stato versionato, e che il contenuto della directory src contenente il suo codice è controllato da svn:externals, così che il sorgente di ogni prodotto venga scaricato dal repository stesso del prodotto.

Un esempio del formato che questa proprietà dovrebbe avere per due ipotetici prodotti Plone:

my.package https://my.repository/svn/my.package/trunk
your.package https://your.repository/svn/your.package/trunk

Il tutto ora è piuttosto robusto. L'unico problema riguarda l'utilizzo del trunk dei prodotti.
Questo tipo di configurazione può andare bene per un ambiente di sviluppo, ma come possiamo configurare l'ambiente di produzione?

Il trunk è per definizione una versione instabile o comunque sempre aggiornata di un prodotto; la possibilità che, aggiornando l'installazione o effettuandone una replica su un nuovo server (magari in seguito ad un disaster-recovery del sistema), il codice possa cambiare o aggiornarsi in qualche modo, è piuttosto pericolosa.

Per mantenere le cose stabili, si dovrebbe quindi riferirsi ai tag dei vari prodotti:

my.package https://my.repository/svn/my.package/tags/1.2
your.package https://your.repository/svn/your.package/tags/1.4.1

Il problema ora diventa degli sviluppatori, che sarebbero obbligati a gestire manualmente il cambio di repository da tag a trunk.

Come soluzione, per un certo periodo, abbiamo sperimentato l'uso di un doppia directory dei sorgenti, una per i tag di produzione e una per lo sviluppo.

Diciamo quindi di avere un buildout con un production.cfg in questa forma:

[buildout]
...
develop =
prod-src/my.package
prod-src/your.package
...

...e un development.cfg così:

[buildout]

extends = production.cfg
...
develop +=
src/my.package
src/your.package
...

Le due directory prod-src e src venivano quindi configurate con due svn:externals differenti: uno che puntava ai tag (versioni stabili e di produzione) e l'altro alle versioni di sviluppo.

La situazione è solida, ma in presenza di molti prodotti personalizzati (10, 20, ...) questo sistema risente di lentezza in fase di aggiornamento.

Ci sono modi migliori: andiamo avanti.

infrae.subversion

L'approccio dell'uso di svn:externals aveva anche un altro aspetto negativo: i puristi (diciamo pure: i maniaci) di buildout amano controllare tutto nei propri file .cfg; nell'esempio al caso precedente viene lasciata un'importante parte della configurazione (quali sorgenti scaricare) a SVN.
In pratica, aprendo il file di configurazione buildout.cfg non siamo in grado di vedere dove e quali versioni dei prodotti stiamo utilizzando.

La recipe infrae.subversion faceva esattamente questo e per un breve periodo ha risentito di una certa popolarità: che cosa scaricare usando SVN veniva configurato in un file .cfg!

[svneggs]
recipe = infrae.subversion
location = src
urls =
https://my.repository/svn/my.package/tags/1.2 my.repository
...

La cosa non è andata avanti per molto, per vari aspetti negativi dell'approccio tra cui la lentezza (maggiore che usando svn:externals) e la fragilità (spesso gli aggiornamenti non andavano a buon fine).

Dimentichiamoci quindi anche di questo approccio.

Habemus egg

Torniamo al principio:
L'uso della sezione develop del nostro buildout è stata prevista per lo sviluppo.

Rendiamoci conto che tutti questi problemi si hanno solo in presenza di codice non pubblicamente rilasciato e rilasciabile: un qualunque prodotto pubblico viene scaricato automaticamente dal Python Package Index e ci dobbiamo preoccupare del suo sorgente solo se intendiamo mettergli mano per svilupparlo.

La soluzione migliore, da tempo documentata, è:

  • poter utilizzare un server pypi privato (aziendale o del cliente)
  • rilasciare i prodotti nel formato standard python (gli egg per l'appunto).

Il primo caso è in realtà piuttosto semplice: un pypi server non è altro che una directory di file accessibile via Web/HTTP.

Ammettendo di avere una directory su un server privato e che questa directory contenga tutti i nostri egg, questo semplice comando Python lanciato sul server permetterebbe di avere un repository di egg compatibile con buildout:

$ cd pypi-archive
$ python -m SimpleHTTPServer 9000

A questo punto va indicato al buildout che esiste questa possibilità:

[buildout]
...
find-links =
http://dist.plone.org/release/4.2.4
http://dist.plone.org/thirdparty
http://the.server:9000/

Ovviamente un modo più robusto sarebbe quello di configurare un vero web server, come Apache.

Il problema in questo caso è che l'egg, una volta generato, andrebbe copiato manualmente in questa directory, mentre l'uso del Python Package Index ci ha abituati a un automatismo a cui è difficile rinunciare.
Presto detto: se volete ottenere un server in stile pypi perfettamente funzionante, potete usare Plone stesso e il prodotto PloneSoftwareCenter (che supporta le Python Package Index API).

EggOra il secondo problema: com'è fatto un egg? Come posso generarlo e rilasciarlo?

Se per anni il rilascio di egg (pubblico o privato che fosse) necessitava di conoscere almeno i rudimenti di distutils, oggi ci sono fantastiche utility che nascondono tutta la complessità. In questo stesso blog potete leggere una dettagliata descrizione del migliore di questi: zest.releaser.

Come potete leggere dall'articolo, zest.releaser si occupa di tutte quelle operazioni di versionamento del codice necessarie al momento di una nuova release (aggiornamento, tag della versione, ...) e lo fa per varie tecnologie di versionamento (SVN, Git, Mercurial, ...).

Sembrerebbe quindi che abbiamo trovato la soluzione migliore per i nostri prodotti proprietari:

  • codice versionato
  • buildout che usa mr.developer
  • server pypi riservato
  • uso di zest.releaser.

Ambienti poco amichevoli

EndorNon abbiamo però definito che cosa si intende per "server pypi riservato". Questo può essere:

  • un servizio pubblicamente accessibile ma protetto da autenticazione
  • un servizio senza autenticazione, ma posto su rete interna del cliente o dell'azienda.

Nel primo caso, l'autenticazione può essere un problema: configurare buildout/Python perché richieda autenticazione non è esattamente triviale e può dare vari grattacapi.

Nel secondo caso tutto torna ad essere molto semplice, ma cosa succede se il servizio è nella rete interna del cliente e siete voi il fornitore che deve mettere mano al codice per poi rilasciare nuove versioni?
A meno di non possedere una VPN, la cosa non sta in piedi.

In più: ci sono situazioni in cui l'accesso alla rete dall'esterno (e a volte anche verso l'esterno) è fortemente limitato da policy di sicurezza e regole di firewall che farebbero impallidire lo scudo sulla luna boscosa di Endor.

La difficoltà di poter avere un repository di egg condiviso tra voi e il cliente può non essere banale.

Ci sono però alcuni prerequisiti su cui non si transige: i sistemisti avranno dovuto accettare per forza di cose che "Plone non viene distribuito con un CD ma viene installato accedendo alla Rete" e che "Il buildout usa un sistema di versionamento a cui gli sviluppatori devono poter accedere".

In questi casi limite, noi in RedTurtle abbiamo iniziato a usare un metodo forse non troppo pulito, ma semplice e senza controindicazioni.

Pypi-local!

L'idea è semplice:

  • generare gli egg senza rilasciarli (ci piace rilasciare codice, ma non sempre si può)
  • inserire gli egg nel buildout, in una directory
  • versionare anche i sorgenti dell'egg.

"pypi-local" è stato il primo nome dato alla directory atta ad ospitare gli "egg locali", e da allora non è più cambiato (se in RedTurtle qualcuno dice "...è nella pypi-local" tutti capiscono!).

Tenendo fede a questo nome, ecco come configurare il buildout:

[buildout]
...
find-links =
./pypi-local
http://dist.plone.org/release/4.2.4
http://dist.plone.org/thirdparty

In pratica, nella radice del buildout deve esistere questa directory.

Prima di impallidire per la poca eleganza di questo approccio, sappiate che questo ci ha aiutato in molte situazioni, non ultimo il caso seguente: dare un prodotto (sempre non rilasciato pubblicamente, a volte per mere questioni di "non saprei se va bene per pypi") a un cliente con un livello tecnico minimo, limitato solo al "so lanciare un buildout".
Come? Si manda l'egg per e-mail, con due righe di istruzioni e il gioco è fatto.

rt.zestreleaser.pypilocal

Fermo restando che nessuno vuole abbandonare l'uso di zest.releaser, l'uso della directory locale richiede la perdita di un automatismo e una copia manuale dell'egg generato nella directory:

...
Register and upload to pypi (y/N)?
Register and upload to plone.org (Y/n)? n
Register and upload to redturtle (Y/n)? n
...
INFO: Finished full release.
INFO: Reminder: tag checkout is in /private/var/folders/Ka/Ka7qqP8VFb8dZJO8sohbrE+++TI/-Tmp-/...

zest.releaser esegue una serie di operazioni automatiche per rilasciare gli egg su tutti i server configurati, ma alla fine ci dice dove possiamo trovare la copia temporanea usata per queste operazioni ("Reminder: tag checkout is in...").

Ci basta andare cercare il nostro egg dentro a quella directory (nella sottodirectory dist).

Semplice? Sì, ma rimangono due considerazioni:

  • la programmazione fa l'uomo pigro
  • zest.releaser è maledettamente ben fatto, ed è pluggabile.

Per questo motivo (sì, esatto, solo per non dover copiare manualmente l'egg) abbiamo reso disponibile un plugin per zest.releaser che permette di copiare automaticamente l'egg nella directory "pypi-local" (o in qualunque directory vogliate): rt.zestreleaser.pypilocal.

La sua configurazione è molto semplice: vi basta modificare il vostro file .pypirc nel seguente modo:

[distutils]
index-servers =
pypi
plone.org
...

[rt.zestreleaser.pypilocal]
pypi-local = ../../pypi-local
global = /opt/global-pypi

Nella sezione "rt.zestreleaser.pypilocal" dovete andare a specificare la directory (volendo anche più di una) che zest.releaser cercherà prima di chiedervi se intendete copiare lì i file.

L'esempio "../../pypi-local" non è casuale: se state usando un struttura di directory standard nel vostro buildout, quando lancerete i comandi di zest.releaser vi troverete dentro alla directory del vostro prodotto (solitamente qualcosa nella forma buildout-root/src/my.package). Il percorso relativo fornito rispecchia esattamente l'idea che la directory pypi-local sia nella radice del buildout.

L'output di zest.releaser cambierà in questo modo:

...
Register and upload to pypi (y/N)?
Register and upload to plone.org (Y/n)? n
Register and upload to redturtle (Y/n)? n
Copy egg to folder ../../pypi-local (Y/n)?
Copy egg to folder /opt/global-pypi (Y/n)? n
...
INFO: Finished full release.
INFO: Reminder: tag checkout is in /private/var/folders/Ka/Ka7qqP8VFb8dZJO8sohbrE+++TI/-Tmp-/...

Conclusione

Tra tutti i metodi descritti, spero ne abbiate trovato almeno uno che si adatti alle vostre esigenze.

Gli strumenti per fare le cose nel modo giusto e per evitare il caos nella gestione dei vostri add-on Plone ci sono tutti, e sono semplici da usare!

L'immagine in testata è di fifthconspiracy.

comments powered by Disqus