Finalize switch to latex
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Fred Pauchet 2022-04-30 19:05:06 +02:00
parent 6d7b03e59f
commit 50310ea9f0
10 changed files with 817 additions and 1155 deletions

File diff suppressed because it is too large Load Diff

View File

@ -86,4 +86,132 @@ class TokenBackend(backends.ModelBackend):
return user
return None
\end{minted}
\end{minted}
Ceci sous-entend qu'on a bien une classe qui permet d'accéder à ces jetons.
\begin{minted}{python}
from django.contrib.auth import backends, get_user_model
from ldap3 import Server, Connection, ALL
from ldap3.core.exceptions import LDAPPasswordIsMandatoryError
from config import settings
UserModel = get_user_model()
class LdapBackend(backends.ModelBackend):
"""Implémentation du backend LDAP pour la connexion des utilisateurs à
l'Active Directory.
"""
def authenticate(self, request, username=None, password=None, **kwargs):
"""Authentifie l'utilisateur au travers du serveur LDAP.
"""
ldap_server = Server(settings.LDAP_SERVER, get_info=ALL)
ldap_connection = Connection(
ldap_server,
user=username,
password=password
)
try:
if not ldap_connection.bind():
raise ValueError("Login ou mot de passe incorrect")
except (LDAPPasswordIsMandatoryError, ValueError) as ldap_exception:
raise ldap_exception
user, _ = UserModel.objects.get_or_create(username=username)
return user
\end{minted}
On peut résumer le mécanisme d'authentification de la manière suivante:
\begin{itemize}
\item
Si vous voulez modifier les informations liées à un utilisateur, orientez-vous vers la modification du modèle. Comme nous le verrons ci-dessous, il existe trois manières de prendre ces modifications en
compte. Voir également \href{https://docs.djangoproject.com/en/stable/topics/auth/customizing/}{ici}.
\item
Si vous souhaitez modifier la manière dont l'utilisateur se connecte, alors vous devrez modifier le \textbf{backend}.
\end{itemize}
\section{Modélisation}
Dans un premier temps, Django a besoin de manipuler \href{https://docs.djangoproject.com/en/1.9/ref/contrib/auth/\#user-model}{des instances de type \texttt{django.contrib.auth.User}}. Cette classe implémente les champs suivants:
\begin{itemize}
\item
\texttt{username}
\item
\texttt{first\_name}
\item
\texttt{last\_name}
\item
\texttt{email}
\item
\texttt{password}
\item
\texttt{date\_joined}.
\end{itemize}
D'autres champs, comme les groupes auxquels l'utilisateur est associé, ses permissions, savoir s'il est un super-utilisateur, \ldots\hspace{0pt} sont moins pertinents pour le moment. Avec les quelques champs déjà définis ci-dessus, nous avons de quoi identifier correctement nos utilisateurs. Inutile d'implémenter nos propres classes, puisqu'elles existent déjà.
Si vous souhaitez ajouter un champ, il existe trois manières de faire.
\subsection{Extension du modèle existant}
Le plus simple consiste à créer une nouvelle classe, et à faire un lien de type \texttt{OneToOne} vers la classe \texttt{django.contrib.auth.User}. De cette manière, on ne modifie rien à la manière dont Django authentife ses utlisateurs: tout ce qu'on fait, c'est un lien vers une table nouvellement créée, comme on l'a déjà vu au point {[}\ldots\hspace{0pt}voir l'héritage de modèle{]}. L'avantage de cette méthode, c'est qu'elle est extrêmement flexible, et qu'on garde les mécanismes Django standard. Le désavantage, c'est que pour avoir toutes les informations de notre utilisateur, on sera obligé d'effectuer une jointure sur le base de données, ce qui pourrait avoir des conséquences sur les performances.
\subsection{Substitution du modèle}
Avant de commencer, sachez que cette étape doit être effectuée \textbf{avant la première migration}. Le plus simple sera de définir une nouvelle classe héritant de \texttt{django.contrib.auth.User} et de spécifier la classe à utiliser dans votre fichier de paramètres. Si ce paramètre est modifié après que la première migration ait été effectuée, il ne sera pas pris en compte. Tenez-en compte au moment de modéliser votre application.
\begin{minted}{python}
AUTH_USER_MODEL = 'myapp.MyUser'
\end{minted}
Notez bien qu'il ne faut pas spécifier le package \texttt{.models} dans cette injection de dépendances: le schéma à indiquer est bien \texttt{\textless{}nom\ de\ lapplication\textgreater{}.\textless{}nom\ de\ la\ classe\textgreater{}}.
\subsection{Social-auth}
Voir ici : \href{https://github.com/omab/python-social-auth}{python
social auth}
\subsection{OAuth}
OAuth est un standard libre définissant un ensemble de méthodes à implémenter pour l'accès (l'autorisation) à une API. Son fonctionnement se base sur un système de jetons (Tokens), attribués par le possesseur de la ressource à laquelle un utilisateur souhaite accéder.
Le client initie la connexion en demandant un jeton au serveur. Ce jeton est ensuite utilisée tout au long de la connexion, pour accéder aux différentes ressources offertes par ce serveur. `wikipedia \textless{}\url{http://en.wikipedia.org/wiki/OAuth\%3E\%60_}.
Une introduction à OAuth est \href{http://hueniverse.com/oauth/guide/intro/}{disponible ici}. Elle
introduit le protocole comme étant une \texttt{valet\ key}, une clé que l'on donne à la personne qui va garer votre voiture pendant que vous profitez des mondanités. Cette clé donne un accès à votre voiture, tout
en bloquant un ensemble de fonctionnalités. Le principe du protocole est semblable en ce sens: vous vous réservez un accès total à une API, tandis que le système de jetons permet d'identifier une personne, tout
en lui donnant un accès restreint à votre application.
L'utilisation de jetons permet notamment de définir une durée d'utilisation et une portée d'utilisation. L'utilisateur d'un service A peut par exemple autoriser un service B à accéder à des ressources qu'il
possède, sans pour autant révéler son nom d'utilisateur ou son mot de passe.
L'exemple repris au niveau du \href{http://hueniverse.com/oauth/guide/workflow/}{workflow} est le suivant : un utilisateur(trice), Jane, a uploadé des photos sur le site faji.com (A). Elle souhaite les imprimer au travers du site beppa.com (B). Au moment de la commande, le site beppa.com envoie une demande au site faji.com pour accéder aux ressources partagées par Jane. Pour cela, une nouvelle page s'ouvre pour l'utilisateur, et lui demande d'introduire sa "pièce d'identité". Le site A, ayant reçu une demande de B, mais certifiée par l'utilisateur, ouvre alors les ressources et lui permet d'y accéder.
\section{Templates}
Ce qui n'existe pas par contre, ce sont les vues. Django propose donc
tout le mécanisme de gestion des utilisateurs, excepté le visuel (hors
administration). En premier lieu, ces paramètres sont fixés dans le
fichier `settings
\textless{}\url{https://docs.djangoproject.com/en/1.8/ref/settings/\#auth\%3E\%60_}.
On y trouve par exemple les paramètres suivants:
\begin{itemize}
\item
\texttt{LOGIN\_REDIRECT\_URL}: si vous ne spécifiez pas le paramètre
\texttt{next}, l'utilisateur sera automatiquement redirigé vers cette
page.
\item
\texttt{LOGIN\_URL}: l'URL de connexion à utiliser. Par défaut,
l'utilisateur doit se rendre sur la page \texttt{/accounts/login}.
\end{itemize}

View File

@ -24,9 +24,7 @@ def git_describe(request) -> str:
\end{minted}
Ceci aura pour effet d'ajouter les deux variables \texttt{git\_describe}
et \texttt{git\_date} dans tous les contextes de tous les templates de
l'application.
Ceci aura pour effet d'ajouter les deux variables \texttt{git\_describe} et \texttt{git\_date} dans tous les contextes de tous les templates de l'application.
\begin{minted}{python}
TEMPLATES = [

View File

@ -0,0 +1,5 @@
\chapter{Processus de mises à dispositions}
\section{Waffle}
\section{...}

View File

@ -17,8 +17,62 @@ Une fois que ces utilisateurs seront configurés, nous pourrons passer à l'éta
La machine hôte peut être louée chez Digital Ocean, Scaleway, OVH, Vultr, ... Il existe des dizaines d'hébergements typés VPS (\textbf{Virtual Private Server}). A vous de choisir celui qui vous convient \footnote{Personnellement, j'ai un petit faible pour Hetzner Cloud}.
\begin{verbatim}
apt update
groupadd --system webapps
groupadd --system gunicorn_sockets
useradd --system --gid webapps --shell /bin/bash --home /home/gwift gwift
mkdir -p /home/gwift
chown gwift:webapps /home/gwift
\end{verbatim}
\begin{itemize}
\item
On ajoute un groupe intitulé \texttt{webapps}
\item
On crée un groupe pour les communications via sockets
\item
On crée notre utilisateur applicatif; ses applications seront placées
dans le répertoire \texttt{/home/gwift}
\item
On crée le répertoire home/gwift
\item
On donne les droits sur le répertoire /home/gwift
\end{itemize}
\section{Dépendances systèmes}
La version 3.6 de Python se trouve dans les dépôts officiels de CentOS.
Si vous souhaitez utiliser une version ultérieure, il suffit de
l'installer en parallèle de la version officiellement supportée par
votre distribution.
Pour CentOS, vous avez donc deux possibilités :
\begin{verbatim}
yum install python36 -y
\end{verbatim}
Ou passer par une installation alternative:
\begin{verbatim}
sudo yum -y groupinstall "Development Tools"
sudo yum -y install openssl-devel bzip2-devel libffi-devel
wget https://www.python.org/ftp/python/3.8.2/Python-3.8.2.tgz
cd Python-3.8*/
./configure --enable-optimizations
sudo make altinstall
\end{verbatim}
\begin{itemize}
\item
\textbf{Attention !} Le paramètre \texttt{altinstall} est primordial.
Sans lui, vous écraserez l'interpréteur initialement supporté par la
distribution, et cela pourrait avoir des effets de bord non souhaités.
\end{itemize}
\section{Base de données}
@ -87,7 +141,8 @@ Finalement, on peut créer la DB:
Idem, installation, configuration, backup, tout ça. A copier de grimboite, je suis sûr davoir
des notes là-dessus.
\section{Environment utilisateur}
\section{Préparation de l'environment utilisateur}
\begin{verbatim}
su - gwift
@ -117,3 +172,281 @@ suivantes:
gunicorn config.wsgi:application --bind localhost:3000 --settings
=config.settings_production
\end{verbatim}
\section{Configuration de l'application}
\begin{verbatim}
SECRET_KEY=<set your secret key here>
ALLOWED_HOSTS=*
STATIC_ROOT=/var/www/gwift/static
DATABASE=
\end{verbatim}
\begin{itemize}
\item
La variable \texttt{SECRET\_KEY} est notamment utilisée pour le
chiffrement des sessions.
\item
On fait confiance à django\_environ pour traduire la chaîne de
connexion à la base de données.
\end{itemize}
\section{Création des répertoires de logs}
\begin{verbatim}
mkdir -p /var/www/gwift/static
\end{verbatim}
\section{Socket}
Dans le fichier \texttt{/etc/tmpfiles.d/gwift.conf}:
\begin{verbatim}
D /var/run/webapps 0775 gwift gunicorn_sockets -
\end{verbatim}
Suivi de la création par systemd :
\begin{verbatim}
systemd-tmpfiles --create
\end{verbatim}
\section{Gunicorn}
\begin{verbatim}
#!/bin/bash
# defines settings for gunicorn
NAME="gwift"
DJANGODIR=/home/gwift/webapps/gwift
SOCKFILE=/var/run/webapps/gunicorn_gwift.sock
USER=gwift
GROUP=gunicorn_sockets
NUM_WORKERS=5
DJANGO_SETTINGS_MODULE=config.settings_production
DJANGO_WSGI_MODULE=config.wsgi
echo "Starting $NAME as `whoami`"
source /home/gwift/.venvs/gwift/bin/activate
cd $DJANGODIR
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH
exec gunicorn ${DJANGO_WSGI_MODULE}:application \
--name $NAME \
--workers $NUM_WORKERS \
--user $USER \
--bind=unix:$SOCKFILE \
--log-level=debug \
--log-file=-
\end{verbatim}
\section{Supervsion, keepalive et autoreload}
Pour la supervision, on passe par Supervisor. Il existe d'autres
superviseurs,
\begin{verbatim}
yum install supervisor -y
\end{verbatim}
On crée ensuite le fichier \texttt{/etc/supervisord.d/gwift.ini}:
\begin{verbatim}
[program:gwift]
command=/home/gwift/bin/start_gunicorn.sh
user=gwift
stdout_logfile=/var/log/gwift/gwift.log
autostart=true
autorestart=unexpected
redirect_stdout=true
redirect_stderr=true
\end{verbatim}
Et on crée les répertoires de logs, on démarre supervisord et on vérifie
qu'il tourne correctement:
\begin{verbatim}
$ mkdir /var/log/gwift
$ chown gwift:nagios /var/log/gwift
$ systemctl enable supervisord
$ systemctl start supervisord.service
$ systemctl status supervisord.service
supervisord.service - Process Monitoring and Control Daemon
Loaded: loaded (/usr/lib/systemd/system/supervisord.service; enabled;
vendor preset: disabled)
Active: active (running) since Tue 2019-12-24 10:08:09 CET; 10s ago
Process: 2304 ExecStart=/usr/bin/supervisord -c /etc/supervisord.conf (code
=exited, status=0/SUCCESS)
Main PID: 2310 (supervisord)
CGroup: /system.slice/supervisord.service
- 2310 /usr/bin/python /usr/bin/supervisord -c
/etc/supervisord.conf
- 2313 /home/gwift/.venvs/gwift/bin/python3
/home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
- 2317 /home/gwift/.venvs/gwift/bin/python3
/home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
- 2318 /home/gwift/.venvs/gwift/bin/python3
/home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
- 2321 /home/gwift/.venvs/gwift/bin/python3
/home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
- 2322 /home/gwift/.venvs/gwift/bin/python3
/home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
- 2323 /home/gwift/.venvs/gwift/bin/python3
/home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
ls /var/run/webapps
\end{verbatim}
On peut aussi vérifier que l'application est en train de tourner, à
l'aide de la commande \texttt{supervisorctl}:
\begin{verbatim}
supervisorctl status gwift
gwift RUNNING pid 31983, uptime 0:01:00
supervisorctl stop gwift
gwift: stopped
root@ks3353535:/etc/supervisor/conf.d# supervisorctl start gwift
gwift: started
root@ks3353535:/etc/supervisor/conf.d# supervisorctl restart gwift
gwift: stopped
gwift: started
\end{verbatim}
\section{Firewall}
\begin{verbatim}
et 443 (HTTPS).
\end{verbatim}
\begin{verbatim}
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --reload
\end{verbatim}
\begin{itemize}
\item
On ouvre le port 80, uniquement pour autoriser une connexion HTTP,
mais qui sera immédiatement redirigée vers HTTPS
\item
Et le port 443 (forcément).
\end{itemize}
\section{Reverse proxy}
\begin{verbatim}
yum install nginx -y
usermod -a -G gunicorn_sockets nginx
\end{verbatim}
On configure ensuite le fichier \texttt{/etc/nginx/conf.d/gwift.conf}:
\begin{verbatim}
upstream gwift_app {
server unix:/var/run/webapps/gunicorn_gwift.sock fail_timeout=0;
}
server {
listen 80;
server_name <server_name>;
root /var/www/gwift;
error_log /var/log/nginx/gwift_error.log;
access_log /var/log/nginx/gwift_access.log;
client_max_body_size 4G;
keepalive_timeout 5;
gzip on;
gzip_comp_level 7;
gzip_proxied any;
gzip_types gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
location /static/ {
access_log off;
expires 30d;
add_header Pragma public;
add_header Cache-Control "public";
add_header Vary "Accept-Encoding";
try_files $uri $uri/ =404;
}
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://gwift_app;
}
}
\end{verbatim}
\begin{itemize}
\item
Ce répertoire sera complété par la commande \texttt{collectstatic} que
l'on verra plus tard. L'objectif est que les fichiers ne demandant
aucune intelligence soit directement servis par Nginx. Cela évite
d'avoir un processus Python (relativement lent) qui doive être
instancié pour servir un simple fichier statique.
\item
Afin d'éviter que Django ne reçoive uniquement des requêtes provenant
de 127.0.0.1
\end{itemize}
\section{Mise à jour}
\begin{verbatim}
u - <user>
source ~/.venvs/<app>/bin/activate
cd ~/webapps/<app>
git fetch
git checkout vX.Y.Z
pip install -U requirements/prod.txt
python manage.py migrate
python manage.py collectstatic
kill -HUP `ps -C gunicorn fch -o pid | head -n 1`
\end{verbatim}
\begin{itemize}
\item
\url{https://stackoverflow.com/questions/26902930/how-do-i-restart-gunicorn-hup-i-dont-know-masterpid-or-location-of-pid-file}
\end{itemize}
\section{Logrotate}
\begin{verbatim}
/var/log/gwift/* {
weekly
rotate 3
size 10M
compress
delaycompress
}
\end{verbatim}
Puis on démarre logrotate avec \# logrotate -d /etc/logrotate.d/gwift
pour vérifier que cela fonctionne correctement.
\section{Sauvegardes}
Les sauvegardes ont été configurées avec borg:
\texttt{yum\ install\ borgbackup}.
C'est l'utilisateur gwift qui s'en occupe.
\begin{verbatim}
mkdir -p /home/gwift/borg-backups/
cd /home/gwift/borg-backups/
borg init gwift.borg -e=none
borg create gwift.borg::{now} ~/bin ~/webapps
\end{verbatim}
Et dans le fichier crontab :
\begin{verbatim}
0 23 * * * /home/gwift/bin/backup.sh
\end{verbatim}
\section{Ansible}

2
chapters/graphs.tex Normal file
View File

@ -0,0 +1,2 @@
\chapter{Graphs}

View File

@ -1,2 +1,292 @@
\chapter{PaaS - Heroku}
\href{https://www.heroku.com}{Heroku} est une \emph{Plateform As A Service} paas, où vous choisissez le \emph{service} dont vous avez besoin (une base de données, un service de cache, un service applicatif, ..., vous lui envoyer les paramètres nécessaires et le tout démarre gentiment sans que vous ne deviez superviser l'hôte. Ce mode démarrage ressemble énormément aux 12 facteurs dont nous avons déjà parlé plus tôt - raison de plus pour que notre application soit directement prête à y être déployée, d'autant plus qu'il ne sera pas
possible de modifier un fichier une fois qu'elle aura démarré: si vous souhaitez modifier un paramètre, cela reviendra à couper l'actuelle et envoyer de nouveaux paramètres et recommencer le déploiement depuis le
début.
\begin{figure}
\centering
\includegraphics{images/deployment/heroku.png}
\caption{Invest in apps, not ops. Heroku handles the hard stuff ---
patching and upgrading, 24/7 ops and security, build systems, failovers,
and more --- so your developers can stay focused on building great
apps.}
\end{figure}
Pour un projet de type "hobby" et pour l'exemple de déploiement ci-dessous, il est tout à fait possible de s'en sortir sans dépenser un kopek, afin de tester nos quelques idées ou mettre rapidement un \emph{Most Valuable Product} en place. La seule contrainte consistera à pouvoir héberger des fichiers envoyés par vos utilisateurs - ceci pourra être fait en configurant un \emph{bucket compatible S3}, par exemple chez Amazon, Scaleway ou OVH.
Le fonctionnement est relativement simple: pour chaque application, Heroku crée un dépôt Git qui lui est associé. Il suffit donc d'envoyer les sources de votre application vers ce dépôt pour qu'Heroku les interprête comme étant une nouvelle version, déploie les nouvelles fonctionnalités - sous réserve que tous les tests passent correctement - et les mettent à disposition. Dans un fonctionnement plutôt manuel, chaque déploiement est initialisé par le développeur ou par un membre de l'équipe. Dans une version plus automatisée, chacun de ces déploiements peut être placé en fin de \emph{pipeline}, lorsque tous les tests
unitaires et d'intégration auront été réalisés.
Au travers de la commande \texttt{heroku\ create}, vous associez donc une nouvelle référence à votre code source, comme le montre le contenu du fichier \texttt{.git/config} ci-dessous:
\begin{verbatim}
$ heroku create
Creating app... done, -> young-temple-86098
https://young-temple-86098.herokuapp.com/ | https://git.heroku.com/young-
temple-86098.git
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
[remote "heroku"]
url = https://git.heroku.com/still-thicket-66406.git
fetch = +refs/heads/*:refs/remotes/heroku/*
\end{verbatim}
\begin{verbatim}
Pour définir de quel type d'application il s'agit, Heroku nécessite un minimum de configuration.
Celle-ci se limite aux deux fichiers suivants:
* Déclarer un fichier `Procfile` qui va simplement décrire le fichier à passer au protocole WSGI
* Déclarer un fichier `requirements.txt` (qui va éventuellement chercher ses propres dépendances dans un sous-répertoire, avec l'option `-r`)
\end{verbatim}
Après ce paramétrage, il suffit de pousser les changements vers ce
nouveau dépôt grâce à la commande \texttt{git\ push\ heroku\ master}.
Heroku propose des espaces de déploiements, mais pas d'espace de
stockage. Il est possible d'y envoyer des fichiers utilisateurs
(typiquement, des media personnalisés), mais ceux-ci seront perdus lors
du redémarrage du container. Il est donc primordial de configurer
correctement l'hébergement des fichiers média, de préférences sur un
stockage compatible S3. s3
Prêt à vous lancer ? Commencez par créer un compte:
\url{https://signup.heroku.com/python}.
\section{Configuration du compte}
+ Récupération des valeurs d'environnement pour les réutiliser ci-dessous.
Vous aurez peut-être besoin d'un coup de pouce pour démarrer votre première application; heureusement, la documentation est super bien faite:
\begin{figure}
\centering
\includegraphics{images/deployment/heroku-new-app.png}
\caption{Heroku: Commencer à travailler avec un langage}
\end{figure}
Installez ensuite la CLI (\emph{Command Line Interface}) en suivant \href{https://devcenter.heroku.com/articles/heroku-cli}{la documentation suivante}.
Au besoin, cette CLI existe pour:
\begin{enumerate}
\item
macOS, \emph{via} `brew `
\item
Windows, grâce à un
\href{https://cli-assets.heroku.com/heroku-x64.exe}{binaire x64} (la
version 32 bits existe aussi, mais il est peu probable que vous en
ayez besoin)
\item
GNU/Linux, via un script Shell
\texttt{curl\ https://cli-assets.heroku.com/install.sh\ \textbar{}\ sh}
ou sur \href{https://snapcraft.io/heroku}{SnapCraft}.
\end{enumerate}
Une fois installée, connectez-vous:
\begin{verbatim}
$ heroku login
\end{verbatim}
Et créer votre application:
\begin{verbatim}
$ heroku create
Creating app... done, -> young-temple-86098
https://young-temple-86098.herokuapp.com/ | https://git.heroku.com/young-
temple-86098.git
\end{verbatim}
\begin{figure}
\centering
\includegraphics{images/deployment/heroku-app-created.png}
\caption{Notre application est à présent configurée!}
\end{figure}
Ajoutons lui une base de données, que nous sauvegarderons à intervalle
régulier:
\begin{verbatim}
$ heroku addons:create heroku-postgresql:hobby-dev
Creating heroku-postgresql:hobby-dev on -> still-thicket-66406... free
Database has been created and is available
! This database is empty. If upgrading, you can transfer
! data from another database with pg:copy
Created postgresql-clear-39693 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation
$ heroku pg:backups schedule --at '14:00 Europe/Brussels' DATABASE_URL
Scheduling automatic daily backups of postgresql-clear-39693 at 14:00
Europe/Brussels... done
\end{verbatim}
(+ voir comment récupérer les backups)
\begin{verbatim}
# Copié/collé de https://cookiecutter- django.readthedocs.io/en/latest/deployment-on-heroku.html
heroku create --buildpack https://github.com/heroku/heroku-buildpack-python
heroku addons:create heroku-redis:hobby-dev
heroku addons:create mailgun:starter
heroku config:set PYTHONHASHSEED=random
heroku config:set WEB_CONCURRENCY=4
heroku config:set DJANGO_DEBUG=False
heroku config:set DJANGO_SETTINGS_MODULE=config.settings.production
heroku config:set DJANGO_SECRET_KEY="$(openssl rand -base64 64)"
# Generating a 32 character-long random string without any of the visually
similar characters "IOl01":
heroku config:set DJANGO_ADMIN_URL="$(openssl rand -base64 4096 | tr -dc 'A-
HJ-NP-Za-km-z2-9' | head -c 32)/"
# Set this to your Heroku app url, e.g. 'bionic-beaver-28392.herokuapp.com'
heroku config:set DJANGO_ALLOWED_HOSTS=
# Assign with AWS_ACCESS_KEY_ID
heroku config:set DJANGO_AWS_ACCESS_KEY_ID=
# Assign with AWS_SECRET_ACCESS_KEY
heroku config:set DJANGO_AWS_SECRET_ACCESS_KEY=
# Assign with AWS_STORAGE_BUCKET_NAME
heroku config:set DJANGO_AWS_STORAGE_BUCKET_NAME=
git push heroku master
heroku run python manage.py createsuperuser
heroku run python manage.py check --deploy
heroku open
\end{verbatim}
\section{Type d'application}
Pour qu'Heroku comprenne le type d'application à démarrer, ainsi que les commandes à exécuter pour que tout fonctionne correctement. Pour un projet Django, cela comprend, à placer à la racine de votre projet:
\begin{enumerate}
\item
Un fichier \texttt{requirements.txt} (qui peut éventuellement faire appel à un autre fichier, \textbf{via} l'argument \texttt{-r})
\item
Un fichier \texttt{Procfile} ({[}sans extension{]}(\url{https://devcenter.heroku.com/articles/procfile)}!),
qui expliquera la commande pour le protocole WSGI.
\end{enumerate}
Dans notre exemple:
\begin{verbatim}
# requirements.txt
django==3.2.8
gunicorn
boto3
django-storages
\end{verbatim}
\begin{verbatim}
# Procfile
release: python3 manage.py migrate
web: gunicorn gwift.wsg
\end{verbatim}
\section{Hébergement S3}
Pour cette partie, nous allons nous baser sur
l'\href{https://www.scaleway.com/en/object-storage/}{Object Storage de
Scaleway}. Ils offrent 75GB de stockage et de transfert par mois, ce qui
va nous laisser suffisament d'espace pour jouer un peu.
\includegraphics{images/deployment/scaleway-object-storage-bucket.png}
L'idée est qu'au moment de la construction des fichiers statiques,
Django aille simplement les héberger sur un espace de stockage
compatible S3. La complexité va être de configurer correctement les
différents points de terminaison. Pour héberger nos fichiers sur notre
\textbf{bucket} S3, il va falloir suivre et appliquer quelques étapes
dans l'ordre:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Configurer un bucket compatible S3 - je parlais de Scaleway, mais il y
en a - \textbf{littéralement} - des dizaines.
\item
Ajouter la librairie \texttt{boto3}, qui s'occupera de "parler" avec
ce type de protocole
\item
Ajouter la librairie \texttt{django-storage}, qui va elle s'occuper de
faire le câblage entre le fournisseur (\textbf{via} \texttt{boto3}) et
Django, qui s'attend à ce qu'on lui donne un moteur de gestion
\textbf{via} la clé
{[}\texttt{DJANGO\_STATICFILES\_STORAGE}{]}(\url{https://docs.djangoproject.com/en/3.2/ref/settings/\#std:setting-STATICFILES_STORAGE}).
\end{enumerate}
La première étape consiste à se rendre dans {[}la console
Scaleway{]}(\url{https://console.scaleway.com/project/credentials}),
pour gérer ses identifiants et créer un jeton.
\includegraphics{images/deployment/scaleway-api-key.png}
Selon la documentation de
\href{https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html\#settings}{django-storages},
de
\href{https://boto3.amazonaws.com/v1/documentation/api/latest/index.html}{boto3}
et de
\href{https://www.scaleway.com/en/docs/tutorials/deploy-saas-application/}{Scaleway},
vous aurez besoin des clés suivantes au niveau du fichier
\texttt{settings.py}:
\begin{minted}{python}
AWS_ACCESS_KEY_ID = os.getenv('ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.getenv('SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = os.getenv('AWS_STORAGE_BUCKET_NAME')
AWS_S3_REGION_NAME = os.getenv('AWS_S3_REGION_NAME')
AWS_DEFAULT_ACL = 'public-read'
AWS_LOCATION = 'static'
AWS_S3_SIGNATURE_VERSION = 's3v4'
AWS_S3_HOST = 's3.%s.scw.cloud' % (AWS_S3_REGION_NAME,)
AWS_S3_ENDPOINT_URL = 'https://%s' % (AWS_S3_HOST, )
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3ManifestStaticStorage'
STATIC_URL = '%s/%s/' % (AWS_S3_ENDPOINT_URL, AWS_LOCATION)
# General optimization for faster delivery
AWS_IS_GZIPPED = True
AWS_S3_OBJECT_PARAMETERS = {
'CacheControl': 'max-age=86400',
}
\end{minted}
Configurez-les dans la console d'administration d'Heroku:
\includegraphics{images/deployment/heroku-vars-reveal.png}
Lors de la publication, vous devriez à présent avoir la sortie suivante,
qui sera confirmée par le \textbf{bucket}:
\begin{verbatim}
remote: -----> $ python manage.py collectstatic --noinput
remote: 128 static files copied, 156 post-processed
\end{verbatim}
\includegraphics{images/deployment/gwift-cloud-s3.png}
Sources complémentaires:
\begin{itemize}
\item
{[}How to store Django static and media files on S3 in
production{]}(\url{https://coderbook.com/@marcus/how-to-store-django-static-and-media-files-on-s3-in-production/})
\item
{[}Using Django and
Boto3{]}(\url{https://www.simplecto.com/using-django-and-boto3-with-scaleway-object-storage/})
\end{itemize}

View File

@ -1,2 +1,36 @@
\chapter{Arborescences}
On a un exemple de remplissage/vidage d'une closure table, mais il faudrait en fait présenter les listes adjacentes et les autres structures de données.
Comme ça on pourra introduire les graphs juste après.
\begin{minted}{python}
# <app>/management/commands/rebuild.py
"""This command manages Closure Tables implementation
It adds new levels and cleans links between entities.
This way, it's relatively easy to fetch an entire tree with just one tiny
request.
"""
from django.core.management.base import BaseCommand
from structure.models import Entity, EntityTreePath
class Command(BaseCommand):
def handle(self, *args, **options):
entities = Entity.objects.all()
for entity in entities:
breadcrumb = [node for node in entity.breadcrumb()]
tree = set(EntityTreePath.objects.filter(descendant=entity))
for idx, node in enumerate(breadcrumb):
tree_path, _ = EntityTreePath.objects.get_or_create(
ancestor=node, descendant=entity, weight=idx + 1
)
if tree_path in tree:
tree.remove(tree_path)
for tree_path in tree:
tree_path.delete()
\end{minted}

View File

@ -59,10 +59,10 @@
\include{chapters/models.tex}
\include{chapters/migrations.tex}
\include{chapters/administration.tex}
\include{chapters/urls.tex}
\include{chapters/forms.tex}
\include{chapters/authentication.tex}
\chapter{Context Processors}
\include{chapters/context-processors.tex}
\chapter{Conclusions}
@ -72,23 +72,15 @@
\include{chapters/deployments.tex}
\include{chapters/heroku.tex}
\include{chapters/deployment-tools.tex}
\chapter{Ressources}
\include{chapters/deployment-processes.tex}
\chapter{Conclusions}
\include{parts/soa.tex}
\include{chapters/api.tex}
\include{chapters/trees.tex}
\chapter{A/B Testing}
\chapter{Modèles et relations}
\include{chapters/filters.tex}
\include{chapters/urls.tex}
\include{chapters/trees.tex}
\include{chapters/graphs.tex}
\include{chapters/i18n.tex}
\chapter{Conclusions}

View File

@ -1,10 +1,21 @@
\part{Services Oriented Applications}
Nous avons fait exprès de reprendre l'acronyme d'une \emph{Services Oriented Architecture} pour cette partie. L'objectif est de vous mettre
la puce à l'oreille quant à la finalité du développement: que
l'utilisateur soit humain, bot automatique ou client Web, l'objectif est
de fournir des applications résilientes, disponibles et accessibles.
la puce à l'oreille quant à la finalité du développement: que l'utilisateur soit humain, bot automatique ou client Web, l'objectif est de fournir des applications résilientes, disponibles et accessibles.
Dans cette partie, nous aborderons les vues, la mise en forme, la mise
en page, la définition d'une interface REST, la définition d'une
interface GraphQL et le routage d'URLs.
Dans cette partie, nous aborderons les vues, la mise en forme, la mise en page, la définition d'une interface REST, la définition d'une interface GraphQL et le routage d'URLs.
\begin{quote}
Don't make me think, or why I switched from JS SPAs to Ruby On Rails
\url{https://news.ycombinator.com/item?id=30206989\&utm_term=comment}
\end{quote}
On a parcouru les templates et le mode "monolithique de DJango".
Maintenant, on va aborder différentes options:
\begin{enumerate}
\item Le mode "intermédiaire", qui consiste à garder tous les mécanismes internes à Django, mais à ajouter une couche de dynamisme au travers d'une (légère) couche de JavaScript ou via HTMX.
\item Le mode "warrior", qui consiste lui à ajouter une API, d'abord pour vos clients et utilisateurs, mais aussi pour votre propre consommation \footnote{Aussi intitulé "Eat your own dog's food"}
\end{enumerate}