Python in pillole: esercizi su cicli annidati.

Python programming languagre
Python: un esercizio coi cicli

Oggi comincerò a pubblicare alcuni esercizi di Python sui cicli. Si assomigliano tutti, ognuno presenta una difficoltà leggeremente diversa, e sono basati sulla geometria. Fondamentalmente l’esercizio è indipendente dal linguaggio usato, è più un esercizio di algoritmi, ma per concisione e immediatezza d’uso ho preferito il Python,

Lo scopo, banale se volete, è quello di disegnare linee e triangoli in modalità testo, ma è un’esercizio che, a fronte di un risultato ludico – il vedere comparire un disegno -, permette di impratichirsi con la corretta valorizzazione delle variabili di ciclo.

È una pratica che fa anche un po’ anni ’80 (per chi ci è passato, e per chi non ci è passato potrà avere un’idea di quanto poco ci bastava per divertirci :-)).

Parte I – disegnare diagonali

I primi esercizi sono abbastanza semplici: tutti quanti hanno in comune la stessa struttura: due cicli for annidati e un test per stabiire se stampare un asterisco * o uno spazio. Per ognuno di questi ho riscritto più volte il codice in modo da approdare ad una forma il più simmetrica e concisa possibile (senza fare uso della funzione abs() che qui tornerebbe utile).

Esercizio 1 – disegnare una diagonale TLBR (Top Left Bottom Right)

Per prima cosa aggiungiamo ad ogni file l’intestazione

#!/usr/bin/python

N = 10

che dichiara l’interprete da usare per poter invocare il programma come un script da linea di comando:

$ ./diagonals.py

e inizializza a 10 l’ampiezza del pattern da visualizzare.

"""
print TLBR diagonal
*  
  *  
    *  
      *  
        * 
"""
print("TLBR")
# remember: range(N) goes form 0 to N-1
for i in range(N):
    for j in range(i + 1):
        if j == i:
            print('*'),
        else:
            print(" "),
    print(" ")  # a capo

Questo è abbastanza semplice: occorre un ciclo esterno per disegnare le righe (uso la variabile i) e uno per scandire le colonne (uso la variabile j)

La condizione che discrimina se stampare uno spazio o un asterisco è che ascisssa i e ordinata j siano uguali.

Finito. Una piccola ottimizzazione: la scansione delle colonne non viene fatta per tutta la riga ma solo fino al raggiungimento della condizione di plot (chiamiamola così).

L’ouput è il seguente

TLBR
*  
  *  
    *  
      *  
        *  
          *  
            *  
              *  
                *  
                  *  

Esercizio 2: – disegnare una diagonale TRBL (Top Right Bottom Left)

Si tratta semplicemente di disegnare l’atra diagonale del rettangolo:

"""
print TRBL diagonal
        *  
      *  
    *  
  *  
* 
"""
print("TRBL")
for i in range(N):
    for j in range(N - i):
        if j == N - 1 - i:
            print("*"),
        else:
            print(" "),
    print(" ")  # a capo

Il risultato è questo

TRBL
                  *  
                *  
              *  
            *  
          *  
        *  
      *  
    *  
  *  
*  

La condizione di plot è che l’indice di colonna sia uguale a N-1 (che è il massimo valore a cui arriva l’indice di riga) meno l’indice di colonna; è il numero di colonne che mancano da quella più a destra. È il duale del precedente in cui l’indice di riga era uguale all’indice di colonna (che è ovviamente il numero di colonne da quella più a sinistra). Quindi scritta così è evidente la simmetria che ci permette per sostituzione di passare da un programma all’altro. Ultima cosa, la scansione della riga non si estende a tutte le colonne ma solo fino alla condizione di plot, come nell’esercizio 1.

Dal punto di vista algebrico, nell’esecizio 1 si implementa il plot della funzione y=x (i = j nel nsotro programma), con il vincolo che l’asse delle y crescenti sia diretto verso il basso.

Nell’esercizio 2 si disegna la funzione y=N-x (i = N-j nel nostro codice), con la medesima convenzione.

Vi lascio anche i riferimenti per le puntate successive:

Fine! 🙂

Link utili Python

AdSense di Google: gli acronimi

Google AdSense
Google AdSense

Riporto per comodità mia ed eventualmente per piacere vostro, il significato degli acronimi utilizzzati in Google AdSense.

CTR pagina

Click-Through Rate = numero di click sui banner di una pagina / numero di visualizzazioni di quella pagina. Espresso in percentuale

Es: +4,02% significa che se la pagina è stata visualizzata 100 volte ha collezionato 4 click sui banner.

CPC

Cost Per Click = quanto si viene mediamente remunerati per un click su un Ad. Espresso in Euro.

Es: 0,13 € : ci sono iserzionisti che pagano pochi centesimi al click e chi anche 20 centesimi

RPM

Revenue Per Mile = Ricavo ogni 1000 impressioni per pagina. Espresso in Euro

Es: 5,20€ per 1000 impressioni/pagina

Impressioni

1 Impressione = il download di un banner sul dispositivo del navigante. Dipende da quanto il navigante si sofferma nella pagine. Se c’è un’alta frequenza di rimbalzo vuol dire che il navigante si ferma poco in un singola pagina e quindi non tutte gli Ad vengono scaricati e ho poche impressioni per pagina.

Link utili

Pillole Mirthconnect/NextGen: un semplice canale di echo

Costruiamo un semplice web service con MirthConnect che acquisice la request e ne ripropone il contenuto nella response (il comportamento del comando Unix echo)

Configuriamo per ingresso e uscita il tipo di file XML (oppure raw):

Impostazione dei tipi di dato del canale MirthConnect
Impostazione dei tipi di dato del canale MirthConnect

Definiamo come Source il componente Web Service Listener:

Source connector del canale Mirth HelloWorld
Source connector del canale Mirth HelloWorld

Ho scelto una porta 7100 per poi poterla collegare ad internet con un Reverse Proxy Apache.

È importante impostare la Response: si può fare anche dopo aver scritto la destination (anzi, così ci ritroviamo la viariabile che contine il valore da ritornare già nella combo, in questo caso response).

Per i rimanenti parametri lasciamo default. In particolare il nome del servizio da chiamare sarà MirthConnect. Quindi l’url di invocazione sarà qualcosa del tipo

https://betaingegneria.it/services/Mirth

Definiamo quandi una destination di tipo Javascript Writer e scriviamo questo programma:

Source connector del canale Mirth HelloWorld
Source connector del canale Mirth HelloWorld

Qui carichiamo una variabile javascript locale “text” con il contenuto grezzo del messaggio in entrata; lo scriviamo quindi nel logger (per controllare il traffico) e resitutiamo lo stesso valore alla mappa del canale nella variabile response.

Dopo aver scritto questo frammento potremo ritrovare la variabile response nella combo box di configurazione della Source – come visto prima.

A questo punto deployamo il canale e testiamo con SoapUI:

Test del canale Mirth HelloWorld con SoapUI
Test del canale Mirth HelloWorld con SoapUI

Ecco realizzato un webservice di echo ocn MirthConnect.

Link utili

Pillole Apache: configurazione di un virtualhost

Virtualhost con Apache: come evitare l'error 404
Apache httpd Virtualhost su localhost

Problema: definisco un virtualhost su un nuovo file di configurazione sotto la directory:

/etc/apache2/sites-available/newsite.conf

che contiene queste informazioni

<VirtualHost *:80>
        ProxyPreserveHost On
        ProxyPass /beta http://betaingegneria.it
        ProxyPassReverse /beta http://betaingegneria.it
        ServerName localhost
        ErrorLog ${APACHE_LOG_DIR}/error_newsite.log
        CustomLog ${APACHE_LOG_DIR}/access_newsite.log combined
</VirtualHost>

Lo abilito:

# a2ensite newsite
Enabling site newsite.
To activate the new configuration, you need to run:
systemctl reload apache2

Questo comando crea un link simbolico nella directory

/etc/apache2/sites-enabled/newsite.conf

Faccio ricaricare la configurazione ad Apache:

# systemctl reload apache2

Apache virtualhost: error 404

Il risultato è deludente: puntando a http://localhost/beta ho sempre un error 404 anziché vedere sul browser il sito betaingegneria.it.

In questo esempio non considero la gestione della crittografia, parliamo di http sulla porta standard 80 senza invio del certificato di chiave pubblica (server).

Però, se invece di creare un nuovo file scrivo la configurazione del proxy dentro al file 000-default.conf, rileggendo (reload) la configurazione nel browser, all’indirizzo http://localhost/beta vedo il mio sito. Questo era l’effetto che volevo ma creando un nuovo file di configurazione separato.

Per non farvela lunga (io invece un po’ di tempo ce l’ho perso) il problema è il “conflitto” tra il file 000.default.conf e il nuovo newsite.conf: infatti in questa directory ci dovrebbe esser un solo file per ogni nome di dominio; il file che avevo creato usava come ServerName il nome localhost, esattamente quello di default a cui si riferisce Apache quando ha esaurito i nomi di dominio definiti, gestito in 000.default.conf.

Quindi o disabilito 000-default.conf e scrivo la nuova configurazione nel nuovo file, oppure viceversa.

Disabilitare 000-default.conf a lungo andare non è la scelta più oculata perché in caso dismettessi il nuovo sito, se non mi ricordo questa cosa, lascio Apache senza un sito di default per localhost.

Link utili

Laravel bugfix: Errore 404 – not found ma la rotta esiste

Laravel bugfix  per un errore HTTP 404 su una rotta esistente
Laravel bugfix per un errore HTTP 404 su una rotta esistente

Laravel bugfix: sistemiamo questo bug: ho definito una rotta nel file routes/web.php:

Route::get('/activities/start', 'ActivityController@start');

e ho definito il metodo start() nell’ActivityController

    /**
     * start - start the day first activity
     * @return bool
     */
    public function start()
    {
        //
        $activity = new Activity;
        $activity->order_id = Config::get('constants.START');
        $activity->description = Config::get('constants.START_TEXT');
        $activity->notes = '';

        $activity->save();

        return true;
    }

Il risultato è deludente e alquanto irritante, visto che ho seguito questa prassi – che ritengo corretta – fin dall’inizio:

La soluzione è semplice: il metodo resource() nella definzione delle rotte va collocato dopo le definizioni delle singole rotte nel file routes/web.php:

...
Route::get('/activities/start', 'ActivityController@start');
Route::get('/activities/report', 'ActivityController@reportForm'); 
// at the end...
Route::resource('/activities', 'ActivityController');

In sostanza devo riorganizzare i metodi perché c’è qualche conflitto con le rotte RESTful generate dal resource()e quelle da me definite “a mano”.

Questa breve attività di Laravel bugfix è stata molto istruttiva.

A volte i file crescono senza troppi controlli, soprattutto mentre si impara un nuovo strumento coem Laravel o Grails, ma è bene fare spesso il punto dell’ordine e della pulizia all’interno dei file.

Altri articoli su Laravel bugfix

Problemi con la libreria Ixudra / Curl

#bugfix: Call to undefined function Ixudra\Curl\curl_init()

Ixudra curl - Cosa fare quando si verifica l'errore Call to undefined function Ixudra\Curl\curl_init()?
Cosa fare quando si verifica l’errore Call to undefined function Ixudra\Curl\curl_init()?

cURL è una nota libreria per trattare gli url e gestire chiamate http.

Utilizzo una libreria per Laravel che mi permette di fare delle richieste HTTP da server a sever che si chiama ixudra/curl.

La libreria funziona a meraviglia, se non che, per un problema di configurazione del mio SO, ogni volta che aggiorno la libreria, cURL smette di funzionare.

Il sintomo (undefined function Ixudra \ Curl\Curl_init())

Il sintomo del problema che Laravel esibisce è questo

Symfony\Component\Debug\Exception\FatalThrowableError
Call to undefined function Ixudra\Curl\curl_init()

In realtà il problema risiede ancora più in basso poiché nemmeno il client PHP è immune dagli effetti di questa errata configurazione; invocando semplicemente il client da riga di comando ho:

$ php -v
PHP Warning:  PHP Startup: Unable to load dynamic library 'curl.so' (tried: /usr/lib/php/20190902/curl.so (/usr/lib/php/20190902/curl.so: symbol curl_mime_addpart version CURL_OPENSSL_4 not defined in file libcurl.so.4 with link time reference), /usr/lib/php/20190902/curl.so.so (/usr/lib/php/20190902/curl.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0
PHP 7.4.6 (cli) (built: May 14 2020 10:02:44) ( NTS )
...

Il problema

La radice del problema si trova nella directory /usr/local/lib

$ cd /usr/local/lib/
$ ll
totale 12924
drwxr-xr-x  8 root root     4096 mag 27 08:48 ./
drwxr-xr-x 13 root root     4096 ott 17  2018 ../
-rw-r--r--  1 root root  1006444 mag 23  2017 libcurl.a
-rwxr-xr-x  1 root root     1042 mag 23  2017 libcurl.la*
lrwxrwxrwx  1 root root       16 mag 21 19:12 libcurl.so.4 -> libcurl.so.4.4.0*
-rwxr-xr-x  1 root root   542272 mag 23  2017 libcurl.so.4.4.0*
...

La “sconfigurazione” è dovuta alla presenza del link simbolico libcurl.so.4, la soluzione proposta è quella di interromperlo perché prenda il sopravvento la versione di sistema. Il fatto è che ogni volta che viene eseguito un aggiornamento di cURL, viene ripristinato anche questo link simbolico reintroducendo l’errore.

Una soluzione

Nell’attesa di escogitare una soluzione più definitiva ho seguito il consiglio che si dava nel forum:

$ sudo mv libcurl.so.4.4.0 libcurl.so.4.4.0.OLD

Così facendo non ho più il problema al client:

$ php -v
PHP 7.4.6 (cli) (built: May 14 2020 10:02:44) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.6, Copyright (c), by Zend Technologies
    with Xdebug v2.9.5, Copyright (c) 2002-2020, by Derick Rethans

Come si vede, l’errore è scomparso. Affinche anche Apache possa beneficiare della soluzione del problema occorre riavviarlo:

$ sudo service apache2 restart

A questo punto anche l’applicazione Laravel torna a funzionare.

Altri articoli Laravel

Laravel avanzato: l’utilizzo di cron

Lanciare in cron un metodo di un controller Laravel
Lanciare in cron un metodo di un controller Laravel

Ho bisogno di eseguire un determinato metodo di un controller di un’applicazione sviluppata in Laravel regolarmente una volta al giorno.

Avevo fatto un programma bash e l’avevo inserito nella crontab, ci dovevo fare un aggiornamento quotidiano del mio DB da una sorgente dati.

Questo database mi serve per una applicazione live, per cui devo rendermi autonomo da operazioni manuali.

Avrei potuto replicare la modalità anche in remoto, ma ho preferito farlo attraverso Laravel, tirando dentro all’applicazione tutta la logica eseguita nello script di shell.

Un primo livello è stato quello di scrivere dei programmi PHP che leggevano la sorgente e aggiornavano il db attraverso un nuovo metodo dei miei controller dedicati a quelle entità relazionali.

Quindi potevo lanciare manualmente invocando un URL la funzionalità e tutto filava meravigliosamente.

L’ultimo passo è rendere autonoma l’applicazione Laravel perché ad una certa ora del giorno esegua il task. Qui arriva l’utilissimo Scheduler di Laravel.

L’idea di fondo è quella di invocare in modalità cron la rotta URI che scatena il metodo del controller. Questa funzionalità di Laravel si basa comunque sul clock del sistema operativo: un cron batch che viene lanciato ogni minuto e che viene preso come riferimento da Laravel per temporizzazioni diverse. Quindi è comunque obbligatorio agire su due fronti:

  • sullo scheduler di Laravel e
  • sul crontab di sistema.

Vediamo come si fa.

Estendere Laravel/Artisan creando un comando che chiama una rotta

Premetto che sto utilizzando questa versione di Laravel:

$ php artisan --version
Laravel Framework 6.18.8

Possiamo estendere le funzionalità di artisan che usiamo correntemente come ad esempio:

$ php artisan make:controller InvoiceController

per fare in modo che si possa invocare un nostro comando artisan; quindi la prima cosa da fare è creare un nuovo comando artisan che chiamo route:call. Ho bisogno inoltre che a questo comando venga affiancata una serie di parametri opzionali.

Esistono già dei comandi artisan per gestire le rotte; se provo a lanciare il mio comando ancora da definire ho questa risposta:

$ php artisan route:call
  Command "route:call" is not defined.
  Did you mean one of these?
      route:cache
      route:clear
      route:list

Per creare un nuovo comando artisan si fa così:

$ php artisan make:command RouteCall

Laravel creerà un nuovo comando che consiste in una classe che si chiamerà come il primo parametro (RouteCall) e la sintassi (signature) sarà definita all’interno del file contenente la nuova classe, che viene creata dentro la cartella app/Console/Commands ed estende la classe Command:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Request;

class RouteCall extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'route:call {--uri=}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Call route from CLI';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $request = Request::create($this->option('uri'), 'GET');
        $this->info(app()['Illuminate\Contracts\Http\Kernel']->handle($request));
    }
    
    protected function getOptions()
    {
        return [
            ['uri', null, InputOption::VALUE_REQUIRED, 'The path of the route to be called', null],
        ];
    }

}

In particolare osserviamo la sintassi del comando contenuta nella proprietà $signature che è quella che abbiamo definito nel creare la class nel comando sopra a cui però ho aggiunto il parametro {--uri} che è l’URI del metodo da chiamare. All’interno della classe interessata aggiungerò anche una gestione del parametro in modo tale da renderlo opzionale: in pratica il parametro è una data in formato YYYY-MM-DD: se non la passo nell’URI, sottointendo data odierna.

Il metodo chiave è handle() che trasforma un comando Linux in una request HTTP. Come si vedrà adesso il controllo passa al modulo Kernel.php.

Schedulazione del comando

Il secondo passaggio è quello di definire la temporizzazione del comando:

<?php
// file app/Console/Kernel.php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        'App\Console\Commands\CallRoute',
    ];

    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        $schedule->command('call:route')->dailyAt('19:00');
    }

    /**
     * Register the commands for the application.
     *
     * @return void
     */
    protected function commands()
    {
        $this->load(__DIR__.'/Commands');

        require base_path('routes/console.php');
    }
}

Il nodo centrale è il metodo schedule() che contiene uno o più comandi, ognuno con la temporizzazione desiderata, in questo caso voglio che il Kernel dello scheduler avvii il comando route:call ogni giorno alle 19:00.

Avvio dello scheduler

La terza operazione è quella di creare una nuova voce nella crontab che avvia lo scheduler:

$ crontab -e
...
# For more information see the manual pages of crontab(5) and cron(8)
# 
# m h  dom mon dow   command
* * * * * cd path/to/project/ && php artisan schedule:run >> /dev/null 2>&1

questo job è chiamato con la risoluzione massimo di 1 volta al minuto, poi la logica di Laravel farà in modo che i singolo job vengano eseguiti su temporizzazioni diverse – come sono state definite nel metodo Kernel->schedule()– ma sempre con il clock di riferimento dettato dalla crontab.

Quindi riassumendo, a partire dalla causa primaria, fino all’effetto ultimo:

  • crontab invoca lo scheduler di Laravel ogni minuto;
  • lo scheduler controlla se c’è quacosa da eseguire in base alle diretive everyMinute(), dailyAt(), daily() eccetera definite per i comandi invocati nel metodo schedule() del Kernel;
  • il Kernel avvia tutti i comandi registrati nel metodo schedule();
  • ognuno di questi metodi è invocato con un comando artisan(es. route:call) ma in generale potrebbe anche essere una chamata a Eloquent per scrivere qualcosa nel database;
  • il singolo comando artisan è definito in una classe specifica (es. RouteCall) contenuta nella directory app/Console/Commands/
  • il metodo che esegue quanto si desidera (nel mio caso la trasformazione di un comando shell come php artisan route:call in una request HTTP) è il metodo handle()

È tutto.

Link utili

Sito ufficiale Laravel

Creare ed eliminare una singola tabella database con Laravel

Modificare una singola tabella database con Laravel
Modificare una singola tabella database con Laravel

Al volte ci si trova a dover ridisegnare una tabella database (ad esempio per dover modificare il tipo dei campi o la dimensione, o aggiungere / togliere campi) in fase di progetto, o ancora più spesso in fase di prototipazione.

E spesso non è nemmeno richiesto di conservare una storia di queste modifiche perché semplicemente si sta modellando una versione finale non ancora soddisfacente.

Quindi invece di procedere con modifiche puntuali sul campo, ad esempio con un equivalente di

ALTER TABLE RENAME COLUMN myField AS Field1;

è più immediato fare un drop e ricreare la tabella, eventualmente salvando i dati o preparando uno script di alimentazione da lanciare all’occorrenza.

Sebbene si possano effettuare entrambe le operazioni (più soft con l’alter table o più drastica con il drop/create) vediamo come si fa a “droppare” e ricreare la tabella con i comandi di migrazione di Laravel.

Lo script di creazione tabella contiene già per default i due metodi per costruire e distruggere la tabella, rispettivamente up() e donw():

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateDatiRegioniTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('dati_regioni', function (Blueprint $table) {
            $table->bigIncrements('id');
            ...
         });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('dati_regioni');
    }
}

Il metodo up() viene invocato quando si esegue la migrazione:

$ php artisan migrate

ed il file ad essere eseguito è l’ultimo (il più recente) che si trova nella directory database/migrations. Per droppare la tabella si esegue:

$ php artisan migrate:rollback

e successivamente, per ricrearla:

$ php artisan migrate

Per eseguire operazioni su una determinata tabella, individuare il file contente le migrazioni per quella tabella e modificarne il nome in modo tale che la stringa sia in senso lessicografico la più grande di tutte

2020_04_17_154056_create_regioni_table.php 
2020_04_17_155500_create_dati_regioni_table.php

Link utili

Deploy with git: Impiego della tecnica push-to-deploy con Git

Deploy with git: ovvero, come usare Git come strumento per gestire i deploy oltre che per versionare il codice. Questo articolo molto vecchio l’ho trovato originariamente nel blog di Kris Jordan ma mi è tornato utile adesso. Lo accomodo per il caso di mio interesse.

Ho un progetto in un host locale, un ambiente di produzione e un ambiente di staging al quale vorrei applicare in tempo reale le modifiche appena consegnate (commit) alla versione di sviluppo.

Questo aticolo, per arrivare a questo obiettivo, propone di creare un repository “bare” in remoto che serve solo per fare da tramite tra cartella di sviluppo in localhost e la cartella di deploy/stage in remoto, come illustrato nella figura 1

deploy with git: Illustrazione della tecnica push - post - receive
Fig. 1 deploy with git: Illustrazione della tecnica push – post – receive

Questa metodologia è stata inizalmente sviluppata da Heroku.

Deploy with git: preparazione dei repository

In questa versione personalizzata, a differenza dell’articolo di riferimento (nel quale si mette tutto in localhost):

  • lasceremo il repository git “myProject” in localhost;
  • creeremo il repository git “push” in mydomain.com
  • la directory “stage” ovviamente in mydomain.com

come illustrato in Fig. 1.

me@localhost:$ cd IdeaProjects/myProject

Ora dobbiamo spostarci in remoto e qui è indispensabile avere l’accesso shell al server.

Qui inizializziamo un repository detto “bare” (nudo) che avrà solo la funzione di passamano tra il Git locale e la directory di deploy in remoto

me@localhost:$ ssh myUser@mydomain.com
Password:
myUser@remotehost:$ git init --bare myProjectPush.git

Questo repository myProjectPush.git non conterrà il codice sorgente, bensì farà da passamano tra il client Git a bordo del nostro server di sviluppo e la cartella di staging “stage” nel server di staging

Ora nel Git locale dobbiamo creare un riferimento al repository remoto di push che chiameremo “push” (magari non è la scelta più felice… ma basta fare attenzione):

me@localhost:$  git remote add push myUser@mydomain.com:myProjectPush.git

Il nuovo repository remoto lo troviamo scritto nel file .git/config:

...
[remote "push"]
        url = myUser@mydomain.com:public_html/myProjectPush.git
        fetch = +refs/heads/*:refs/remotes/push/*

A questo dobbiamo istruire il Git remoto perché copi i file nella directory stage

Deploy with git: impostazione Push-to-Stage

Dopo aver creato il repository Git remoto dobbiamo scrivere un programma/script che istruisce git su come trattare i file che arrivano dal Git locale. Nella directory hooks della cartella myProjectPush.git ci sono gli script che Git lancia al verificarsi di un particolare evento. Ce ne sono già parecchi di esempio scritti in linguaggio bash, ma per questo scopo si può usare un qualsiasi linguaggio, nell’esempio che ho trovato è scritto in Ruby. Ma prima di tutto creiamo il file:

myUser@remotehost:$ cd myProjectPush.git/.git/hooks
myUser@remotehost:$ touch post-receive
myUser@remotehost:$ chmod +x post-receive

Il “gancio” che ci serve è quello che ci collega all’evento post-receive che è l’evento che accade quando il repository remoto riceve e accetta un push da parte di un repository di sviluppo. Quindi è necessario che il sistema operativo veda il file come uno script eseguibile.

Poi apriamo con l’editor che vogliamo il file post-receive e copiamo incolliamo questo programma Ruby (accertiamoci che l’interprete ruby sia installato con un comando “which ruby”)

#!/usr/bin/env ruby
# post-receive

# 1. Read STDIN (Format: "from_commit to_commit branch_name")
from, to, branch = ARGF.read.split " "

# 2. Only deploy if master branch was pushed
if (branch =~ /master$/) == nil
    puts "Received branch #{branch}, not deploying."
    exit
end

# 3. Copy files to deploy directory
deploy_to_dir = File.expand_path('../stage')
`GIT_WORK_TREE="#{deploy_to_dir}" git checkout -f master`
puts "DEPLOY: master(#{to}) copied to '#{deploy_to_dir}'"

# 4.TODO: Deployment Tasks
# i.e.: Run Puppet Apply, Restart Daemons, etc

Vediamo cosa fa questo script:

  1. Quando viene attivato lo script post-receive, Git gli passa in STDIN tre parametri: ID commit HEAD precedente, ID commit HEAD presente e il nome del branch da trasferire. Assegnamo questi tre valori alle variabili from, to e branch.
  2. Facciamo in modo che vengano pushati solo commit sul master branch (quello principale); questo sarebbe ragionevole per un push finale verso un server di produzione, ma io adotterò questa tecnica solo per lo staging, quindi rilasso questa parte (posso fare anche push di branch di bugfix per esempio).
  3. La prossima cosa da fare è fare un checkout dal branch attuale alla cartelle “stage”, cioè una copia dei file fisici nella cartella stage.
  4. Il lavoro è finito; sarebbe possibile completare lo script con comandi che dovessero rendersi necessari, come un restart del web server (se ne abbiamo la facoltà) o cancellare file di cache. Ma intanto mi fermo qui.

Salviamo il file e vediamo cosa succede.

Deploy with git: test con Pushing

Possiamo testare facendo un commit e un push verso il remote “push”:

me@localhost:$ git commit -m 'Test push-to-stage'
me@localhost:$ git push push master
muser@mydomain.com's password: 
Counting objects: 510, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (477/477), done.
Writing objects: 100% (510/510), 18.16 MiB | 148.00 KiB/s, done.
Total 510 (delta 260), reused 0 (delta 0)
remote: Already on 'master'
remote: DEPLOY: master(915c38ce6cd49905ccff2abf4d9fea9a6ad61a3d) copied to '/home/myUser/domains/mydomain.com/public_html/stage'
To mydomain.com:public_html/myProjectPush.git
 * [new branch]      master -> master

Nell’output, le righe che iniziano con “remote:” sono quelle generate del nostro script! In particolare vediamo che è stata fatta la copia nella cartella stage.

That’s all folks!!

Monitoraggio COVID-19

Ho realizzato questo semplice cruscotto che riporta sia in forma tabellare che grafica (per solo alcuni indicatori) i dati sull’epidemia di COVID-19.

La situazione viene aggiornata verso le 18 di ogni giorno con i nuovi dati in arrivo dal Dipartimento della Protezione Civile.

Note sull’andamento del Covid-19

Si noti il grafico della derivata seconda: questo da’ una stima dell’inversione di tendenza: quando la derivata seconda è negativa, la curva tende a trivolgere la concavità verso il basso (o la “pancia” verso l’alto), cioè il contagio rallenta (pur continuando ad aumentare).

L’inversione di tendenza come si vede si è verificata più volte in modo anche più pronunciato di questi giorni però ha avuto carattere occasionale. In questi giorni invece la derivata secondna si mantiene negativa per il terzo giorno, anche se di poco, ad indivduare forse una tendenza più stabile.

Link utili