Sviluppare, versionare e rilasciare in automatico: una soluzione basata su Subversion, Apache e SSH

      

In ambiente enterprise vi è sempre più sovente l’esigenza di automatizzare qualsiasi processo sistemistico; uno dei processi che toglie tempo al lavoro di amministrazione delle macchine è quello dei rilasci (soprattutto in ambiente di test).

In questo articolo verrà spiegato come poter automatizzare rilasci di siti attraverso SubVersion, Apache e SSH.

Il nostro obiettivo e’ quello di rendere il più possibile automatica tutta l’operazione di rilascio in ambiente di test di alcuni siti statici e di permettere estrema flessibilita’ agli sviluppatori di tali siti, in maniera tale da renderli autonomi per modifiche, aggiornamenti ed eventuali roll-back. Gioca un ruolo molto importante il fatto che, sfruttando SubVersion (e quindi un sistema di versioning), e’ possibile per lo sviluppatore riportare il sito ad una revision specifica in ogni momento.

Ambiente operativo

Prendiamo come case test un ambiente cosi’ configurato:

Distribuzione: Ubuntu 10.04 LTS
Repository Utilizzati: default dell’installazione

– Server A:  Server con Apache: configurazione standard per poter gestire piu’ VirtualHost, dove ogni VirtualHost sara’ un diverso sito da erogare (configurazione VirtualHost Apache2);

– Server B:  Server  con Apache + Subversion configurati  (ci sono veramente molti HOWTO disponibili in rete su come configurare i servizi, ecco un esempio);

Configurazioni preliminari

Una volta installato Apache2, Ubuntu imposta il servizio in modo che venga avviato mediante l’utenza www-data. Questa utenza nel file /etc/passwd si presenta in questa modo:

www-data:x:33:33:www-data:/var/www:/bin/sh

Sia sul Server A che sul Server B è necessario modificare quindi alcuni parametri dell’utenza www-data al fine di avere una home dell’utente “coerente” col sistema, dove sarà possibile posizionare le chiavi pubbliche/private SSH necessarie a far funzionare il tutto: modifichiamo quindi l’utente:

# usermod www-data -d /home/www-data

e creiamone la HOME

<

# mkdir /home/www-data

dando i permessi all’utente

# chown -R www-data:www-data /home/www-data

Per testare il tutto proviamo ad entrare con l’utenza appena creata

# su - www-data

Ora che su entrambi i Server abbiamo modificato l’utenza www-data è possibile iniziare a creare le chiavi necessarie.
In questo case test i fornitori che useranno subversion con relativi progetti saranno:

  • fornitore1
    • progetto1
    • progetto2
  • fornitore2
    • progetto3
    • progetto4

Configurazione

Il percorso logico che dovranno fare i Server è il seguente:

Server B -> COMMIT SVN (post-commit hook) -> SSH FORCE COMMAND (svn up) -> Server A

E’ necessario quindi creare sul Server B le chiavi SSH, una per ciascun fornitore/progetto, per l’autenticazione automatizzata.

Le fasi sono le seguenti:

Creazione directory base:

[root@ServerB]# su - www-data
[www-data@ServerB]$ mkdir .ssh
[www-data@ServerB]$ chmod 700 .ssh
[www-data@ServerB]$ cd .ssh
[www-data@ServerB]$ mkdir fornitore1 fornitore2

Creazione chiavi fornitore1 (per progetto1 e progetto2):

[www-data@ServerB]$ cd fornitore1
[www-data@ServerB]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/www-data/.ssh/fornitore1/id_rsa): /home/www-data/.ssh/fornitore1/id_rsa_progetto1
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/www-data/.ssh/fornitore1/id_rsa_progetto1.
Your public key has been saved in /home/www-data/.ssh/fornitore1/id_rsa_progetto1.pub.
The key fingerprint is:
x:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx www-data@ServerB
[www-data@ServerB]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/www-data/.ssh/fornitore1/id_rsa): /home/www-data/.ssh/fornitore1/id_rsa_progetto2
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/www-data/.ssh/fornitore1/id_rsa_progetto2.
Your public key has been saved in /home/www-data/.ssh/fornitore1/id_rsa_progetto2.pub.
The key fingerprint is:
xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx www-data@ServerB

Creazione chiavi fornitore2 (per progetto3 e progetto4):

[www-data@ServerB]$ cd ../fornitore2
[www-data@ServerB]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/www-data/.ssh/fornitore2/id_rsa): /home/www-data/.ssh/fornitore2/id_rsa_progetto3
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/www-data/.ssh/fornitore2/id_rsa_progetto3.
Your public key has been saved in /home/www-data/.ssh/fornitore2/id_rsa_progetto3.pub.
The key fingerprint is:
xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx www-data@ServerB
[www-data@ServerB]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/www-data/.ssh/fornitore2/id_rsa): /home/www-data/.ssh/fornitore2/id_rsa_progetto4
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/www-data/.ssh/fornitore2/id_rsa_progetto4.
Your public key has been saved in /home/www-data/.ssh/fornitore2/id_rsa_progetto4.pub.
The key fingerprint is:
xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx www-data@ServerB

In questo modo sono state create le chiavi necessarie per far sì che il ServerB possa autenticarsi con chiave SSH su ServerA. Ora è necessario modificare il file authorized_keys su ServerA:

# su - www-data
[www-data@ServerA]$ mkdir .ssh
[www-data@ServerA]$ chmod 700 .ssh
[www-data@ServerA]$ cd .ssh
[www-data@ServerA]$ touch authorized_keys
[www-data@ServerA]$ chmod 644 authorized_keys

Da ServerB è necessario prendere le chiavi pubbliche precedentemente create per poi inserirle nel file authorized_keys di ServerA:

[www-data@ServerA]$ cat .ssh/fornitore1/id_rsa_progetto1.pub
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAomhotTKNhOwyWFLF0BctOHG+yg0MVN5DREszJlMz4/UEAOfk1MTftEAHVyqhX/oj7n+0ITB2GTrLSR1n5Yx7WZrBc+4fdZMf6gRUZYeZjDyqj4/Odbuy1XPkXAARcNlSw8tOHi3GzyR3ghUGKtUzcSrbZnSegViVji5Yyvs6tdlToofKjt/r542eWWqj7syRAtDqqmQQNBQxQqMcDIOGaSjIluvg60qkEXnYiqQ+J43xbP8w6YK5Z1trOMuTRb4ocK4aJ0zLMiLnjwLIU+gkl8LlzxZwtE97dURPbVQRxPastFKnTWy0v9jguX7NHZmTN4q7z8OQRH4oagSV1lbdSw== www-data@ServerB

La chiave deve essere copiata e poi incollata nel file authorized_keys di ServerA aggiungendo:

ssh-rsa <strong>command="/usr/local/bin/svnup.sh fornitore1 progetto1"</strong> AAAAB3NzaC1yc2EAAAABIwAAAQEAomhotTKNhOwyWFLF0BctOHG+yg0MVN5DREszJlMz4/UEAOfk1MTftEAHVyqhX/oj7n+0ITB2GTrLSR1n5Yx7WZrBc+4fdZMf6gRUZYeZjDyqj4/Odbuy1XPkXAARcNlSw8tOHi3GzyR3ghUGKtUzcSrbZnSegViVji5Yyvs6tdlToofKjt/r542eWWqj7syRAtDqqmQQNBQxQqMcDIOGaSjIluvg60qkEXnYiqQ+J43xbP8w6YK5Z1trOMuTRb4ocK4aJ0zLMiLnjwLIU+gkl8LlzxZwtE97dURPbVQRxPastFKnTWy0v9jguX7NHZmTN4q7z8OQRH4oagSV1lbdSw== www-data@ServerB

Ovviamente tutte le chiavi pubbliche precedentemente create devono essere aggiunte, cambiando, all’accorrenza fornitore1 progetto1.

Nel case test preso in esame , il file authorized_keys, alla fine, si presenterà in questo modo:

[www-data@ServerA]$ cat .ssh/authorized_keys
ssh-rsa <strong>command="/usr/local/bin/svnup.sh fornitore1 progetto1"</strong> [... key data ..] www-data@ServerB
ssh-rsa <strong>command="/usr/local/bin/svnup.sh fornitore1 progetto2"</strong> [... key data ..] www-data@ServerB
ssh-rsa <strong>command="/usr/local/bin/svnup.sh fornitore2 progetto3"</strong> [... key data ..] www-data@ServerB
ssh-rsa <strong>command="/usr/local/bin/svnup.sh fornitore2 progetto4"</strong> [... key data ..] www-data@ServerB

La parte relativa alla configurazione SSH è terminata; rimangono da configurare  i repository SVN, gli scripts di hook e lo script presente nel force command SSH.

Creare i repository SVN ed i relativi script

Su ServerB I repository SVN dovranno essere cosi configurati:

rootSVN/fornitoreX/

Nel caso in oggetto, su ServerB la situazione sarà:

[root@ServerB]# cd /store/subversion
[root@ServerB]# svnadmin create fornitore1
[root@ServerB]# svnadmin create fornitore2
[root@ServerB]# chown -R www-data:www-data fornitore1 fornitore2

Nota: In questo modo ogni fornitore avrà un proprio repository: ciò fa sì che ogni progetto nuovo creato dovrà essere un commit: le REV gestite da SVN, quindi, non saranno a livello di singolo progetto.

In /rootSVN/fornitoreX/hooks/ saranno presenti gli script di HOOK di SVN:

[root@ServerB]# su - www-data
[www-data@ServerB]$ cd /store/subversion/fornitore1/hooks
[www-data@ServerB]$ cp post-commit.tmlp post-commit
[www-data@ServerB]$ chmod +x post-commit

Il file post-commit deve essere modificato in questo modo:

#!/bin/sh
REPOS="$1"
REV="$2"
/usr/share/subversion/hook-scripts/post-commit-fornitore1.sh "$REPOS" "$REV"

Di seguito lo script /usr/share/subversion/hook-scripts/post-commit-fornitore1.sh

#!/bin/sh
REPOS=$1
REV=$2
SVNLOOK=/usr/bin/svnlook
TMPFILE=$(mktemp /tmp/svnXXX)
$SVNLOOK changed -r "$REV" "$REPOS" | cut -d '/' -f2 | sort | uniq &gt; $TMPFILE
for line in $(cat $TMPFILE)
do
ssh -i /home/www-data/.ssh/fornitore1/id_rsa_$line www-data@ServerA &gt;&amp;2
done
rm $TMPFILE

Cosa succede: lo script post-commit entra in gioco nel momento in cui viene fatto un COMMIT su SVN. Nel momento in cui il COMMIT viene effettuato l’hook prende i parametri “fornitore1 ultimaREV”  e li passa allo script /usr/share/subversion/hook-scripts/post-commit-fornitore1.sh che, tramite svnlook crea una lista (salvata in $TMPFILE ) dei progetti che vengono modificati dal commit. A seconda dei progetti che vengono modificati, lo script fa partire la connessione SSH verso ServerA utilizzando come certificato quello adeguato per far eseguire il force command giusto su ServerA.

Fatto ciò, bisogna configurare lo script /usr/local/bin/svnup.sh su ServerA e inizializzare la DocumentRoot che andrà ad accogliere il commit per essere erogato da Apache2:

[root@ServerA]# cd /usr/local/bin

Nella configurazione di test, la root dei vari repository è:

/store/www

mentre le DocumentRoot dei progetti:

/store/www/progettoX/www

Creare il file svnup.sh e modificarlo come segue:

#!/bin/bash
FORNITORE=$1
PROGETTO=$2</p>
WWWDIR="/store/www/${PROGETTO}/www"
 
(
 
cd $WWWDIR
 
svn --username ${FORNITORE} --password $(grep ${FORNITORE} /home/www-data/mapFornitori | cut -d':' -f2) up
 
)

E’ possibile notare come venga usato il file /home/www-data/mapFornitori come mappatura dei fornitori e della propria password: questo è necessario poichè SVN memorizza un solo username associato ad una sola password nei propri metadata. Il file è così composto:

fornitore1:passwordFornitore1
fornitore2:passwordFornitore2

Ora che il giro SSH + HOOKS SVN è completato, è necessario creare la DocumentRoot del progetto ed inizializzarla (e, quindi, diventerà una working copy):

[root@ServerA]# su - www-data
[www-data@ServerA]$ mkdir /store/www/progetto1
[www-data@ServerA]$ cd /store/www/progetto1
[www-data@ServerA]$ svn co --username=fornitore1 --password=progetto1 http://ServerB/fornitore1/progetto1 www

Nota: se non viene eseguita l’inizializzazione non saranno creati i metadata necessari ad SVN per far funzionare il comando svn up

Da questo momento in poi, ogni qualvolta vi sarà un commit SVN (per esempio viene committata una pagina nuova in progetto1 di fornitore1) il girò sarà il seguente:

Sviluppatore esegue il commit, dopo il quale l’hook post-commit esegue /usr/share/subversion/hook-scripts/post-commit-fornitore1.sh “progetto1” “2” che poi esegue   ssh -i /home/www-data/.ssh/fornitore1/id_rsa_progetto1 www-data@ServerA

Quest’ultimo, in virtù dell’opzione command=”/usr/local/bin/svnup.sh fornitore1 progetto1″ fa sì che su ServerA venga eseguito in sequenza:

cd /store/www/progetto1/www
svn --username fornitore1 --password passwordFornitore1 up

Aggiornando la working copy che verrà erogata da Apache2.

Conclusioni

La soluzione presentata mostra come, con un minimo di implementazione, sia possibile creare automatismi preziosi, soprattutto laddove esistono situazioni in cui molte persone sviluppano su siti diversi il cui codice risiede in un repository centrale per il versioning. Gli spunti offerti possono servire da punto di partenza per l’introduzione di varianti volte ad automatizzare il processo di sviluppo. Il limite è solo la fantasia.