gwift-book/asciidoc-to-tex.tex

1132 lines
46 KiB
TeX
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{(}\ExtensionTok{gwift{-}env}\KeywordTok{)} \ExtensionTok{fred@aerys}\NormalTok{:\textasciitilde{}/Sources/gwift$ tree .}
\ExtensionTok{.}
\NormalTok{├── }\ExtensionTok{gwift}
\NormalTok{│   ├── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{│   ├── }\ExtensionTok{asgi.py}
\NormalTok{│   ├── }\ExtensionTok{settings.py}
\NormalTok{│   ├── }\ExtensionTok{urls.py}
\NormalTok{│   ├── }\ExtensionTok{wish}
\NormalTok{│   │   ├── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{│   │   ├── }\ExtensionTok{admin.py}
\NormalTok{│   │   ├── }\ExtensionTok{apps.py}
\NormalTok{│   │   ├── }\ExtensionTok{migrations}
\NormalTok{│   │   │   └── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{│   │   ├── }\ExtensionTok{models.py}
\NormalTok{│   │   ├── }\ExtensionTok{tests.py}
\NormalTok{│   │   └── }\ExtensionTok{views.py}
\NormalTok{│   └── }\ExtensionTok{wsgi.py}
\NormalTok{├── }\ExtensionTok{Makefile}
\NormalTok{├── }\ExtensionTok{manage.py}
\NormalTok{├── }\ExtensionTok{README.md}
\NormalTok{├── }\ExtensionTok{requirements}
\NormalTok{│   ├── }\ExtensionTok{base.txt}
\NormalTok{│   ├── }\ExtensionTok{dev.txt}
\NormalTok{│   └── }\ExtensionTok{prod.txt}
\NormalTok{├── }\ExtensionTok{setup.cfg}
\NormalTok{└── }\ExtensionTok{tox.ini}
\ExtensionTok{5}\NormalTok{ directories, 22 files}
\end{Highlighting}
\end{Shaded}
\hypertarget{_structure_finale_de_notre_environnement}{%
\subsection{Structure finale de notre
environnement}\label{_structure_finale_de_notre_environnement}}
\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{(}\ExtensionTok{gwift{-}env}\KeywordTok{)} \ExtensionTok{fred@aerys}\NormalTok{:\textasciitilde{}/Sources/gwift$ tree .}
\ExtensionTok{.}
\NormalTok{├── }\ExtensionTok{gwift}
\NormalTok{│   ├── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{│   ├── }\ExtensionTok{asgi.py}
\NormalTok{│   ├── }\ExtensionTok{settings.py}
\NormalTok{│   ├── }\ExtensionTok{urls.py}
\NormalTok{│   ├── }\ExtensionTok{wish}
\NormalTok{│   │   ├── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{│   │   ├── }\ExtensionTok{admin.py}
\NormalTok{│   │   ├── }\ExtensionTok{apps.py}
\NormalTok{│   │   ├── }\ExtensionTok{migrations}
\NormalTok{│   │   │   └── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{│   │   ├── }\ExtensionTok{models.py}
\NormalTok{│   │   ├── }\ExtensionTok{tests.py}
\NormalTok{│   │   └── }\ExtensionTok{views.py}
\NormalTok{│   └── }\ExtensionTok{wsgi.py}
\NormalTok{├── }\ExtensionTok{Makefile}
\NormalTok{├── }\ExtensionTok{manage.py}
\NormalTok{├── }\ExtensionTok{README.md}
\NormalTok{├── }\ExtensionTok{requirements}
\NormalTok{│   ├── }\ExtensionTok{base.txt}
\NormalTok{│   ├── }\ExtensionTok{dev.txt}
\NormalTok{│   └── }\ExtensionTok{prod.txt}
\NormalTok{├── }\ExtensionTok{setup.cfg}
\NormalTok{└── }\ExtensionTok{tox.ini}
\end{Highlighting}
\end{Shaded}
\hypertarget{_authentification}{%
\section{Authentification}\label{_authentification}}
\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ datetime }\ImportTok{import}\NormalTok{ datetime}
\ImportTok{from}\NormalTok{ django.contrib.auth }\ImportTok{import}\NormalTok{ backends, get\_user\_model}
\ImportTok{from}\NormalTok{ django.db.models }\ImportTok{import}\NormalTok{ Q}
\ImportTok{from}\NormalTok{ accounts.models }\ImportTok{import}\NormalTok{ Token }
\NormalTok{UserModel }\OperatorTok{=}\NormalTok{ get\_user\_model()}
\KeywordTok{class}\NormalTok{ TokenBackend(backends.ModelBackend):}
\KeywordTok{def}\NormalTok{ authenticate(}\VariableTok{self}\NormalTok{, request, username}\OperatorTok{=}\VariableTok{None}\NormalTok{, password}\OperatorTok{=}\VariableTok{None}\NormalTok{, }\OperatorTok{**}\NormalTok{kwargs):}
\CommentTok{"""Authentifie l\textquotesingle{}utilisateur sur base d\textquotesingle{}un jeton qu\textquotesingle{}il a reçu.}
\CommentTok{ On regarde la date de validité de chaque jeton avant d\textquotesingle{}autoriser l\textquotesingle{}accès.}
\CommentTok{ """}
\NormalTok{ token }\OperatorTok{=}\NormalTok{ kwargs.get(}\StringTok{"token"}\NormalTok{, }\VariableTok{None}\NormalTok{)}
\NormalTok{ current\_token }\OperatorTok{=}\NormalTok{ Token.objects.}\BuiltInTok{filter}\NormalTok{(token}\OperatorTok{=}\NormalTok{token, validity\_date\_\_gte}\OperatorTok{=}\NormalTok{datetime.now()).first()}
\ControlFlowTok{if}\NormalTok{ current\_token:}
\NormalTok{ user }\OperatorTok{=}\NormalTok{ current\_token.user}
\NormalTok{ current\_token.last\_used\_date }\OperatorTok{=}\NormalTok{ datetime.now()}
\NormalTok{ current\_token.save()}
\ControlFlowTok{return}\NormalTok{ user}
\ControlFlowTok{return} \VariableTok{None}
\end{Highlighting}
\end{Shaded}
\begin{itemize}
\item
Sous-entend qu'on a bien une classe qui permet d'accéder à ces jetons
;-)
\end{itemize}
\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ django.contrib.auth }\ImportTok{import}\NormalTok{ backends, get\_user\_model}
\ImportTok{from}\NormalTok{ ldap3 }\ImportTok{import}\NormalTok{ Server, Connection, ALL}
\ImportTok{from}\NormalTok{ ldap3.core.exceptions }\ImportTok{import}\NormalTok{ LDAPPasswordIsMandatoryError}
\ImportTok{from}\NormalTok{ config }\ImportTok{import}\NormalTok{ settings}
\NormalTok{UserModel }\OperatorTok{=}\NormalTok{ get\_user\_model()}
\KeywordTok{class}\NormalTok{ LdapBackend(backends.ModelBackend):}
\CommentTok{"""Implémentation du backend LDAP pour la connexion des utilisateurs à l\textquotesingle{}Active Directory.}
\CommentTok{ """}
\KeywordTok{def}\NormalTok{ authenticate(}\VariableTok{self}\NormalTok{, request, username}\OperatorTok{=}\VariableTok{None}\NormalTok{, password}\OperatorTok{=}\VariableTok{None}\NormalTok{, }\OperatorTok{**}\NormalTok{kwargs):}
\CommentTok{"""Authentifie l\textquotesingle{}utilisateur au travers du serveur LDAP.}
\CommentTok{ """}
\NormalTok{ ldap\_server }\OperatorTok{=}\NormalTok{ Server(settings.LDAP\_SERVER, get\_info}\OperatorTok{=}\NormalTok{ALL)}
\NormalTok{ ldap\_connection }\OperatorTok{=}\NormalTok{ Connection(ldap\_server, user}\OperatorTok{=}\NormalTok{username, password}\OperatorTok{=}\NormalTok{password)}
\ControlFlowTok{try}\NormalTok{:}
\ControlFlowTok{if} \KeywordTok{not}\NormalTok{ ldap\_connection.bind():}
\ControlFlowTok{raise} \PreprocessorTok{ValueError}\NormalTok{(}\StringTok{"Login ou mot de passe incorrect"}\NormalTok{)}
\ControlFlowTok{except}\NormalTok{ (LDAPPasswordIsMandatoryError, }\PreprocessorTok{ValueError}\NormalTok{) }\ImportTok{as}\NormalTok{ ldap\_exception:}
\ControlFlowTok{raise}\NormalTok{ ldap\_exception}
\NormalTok{ user, \_ }\OperatorTok{=}\NormalTok{ UserModel.objects.get\_or\_create(username}\OperatorTok{=}\NormalTok{username)}
\end{Highlighting}
\end{Shaded}
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}
\hypertarget{_modification_du_moduxe8le}{%
\subsection{Modification du modèle}\label{_modification_du_moduxe8le}}
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.
\hypertarget{_extension_du_moduxe8le_existant}{%
\subsection{Extension du modèle
existant}\label{_extension_du_moduxe8le_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.
\hypertarget{_substitution}{%
\subsection{Substitution}\label{_substitution}}
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{Shaded}
\begin{Highlighting}[]
\NormalTok{AUTH\_USER\_MODEL }\OperatorTok{=} \StringTok{\textquotesingle{}myapp.MyUser\textquotesingle{}}
\end{Highlighting}
\end{Shaded}
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{}}.
\hypertarget{_backend}{%
\subsubsection{Backend}\label{_backend}}
\hypertarget{_templates}{%
\subsubsection{Templates}\label{_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}
\hypertarget{_social_authentification}{%
\subsubsection{Social-Authentification}\label{_social_authentification}}
Voir ici : \href{https://github.com/omab/python-social-auth}{python
social auth}
\hypertarget{_un_petit_mot_sur_oauth}{%
\subsubsection{Un petit mot sur OAuth}\label{_un_petit_mot_sur_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.
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{INSTALLED\_APPS }\OperatorTok{=}\NormalTok{ [}
\StringTok{"django.contrib..."}
\NormalTok{]}
\end{Highlighting}
\end{Shaded}
peut être splitté en plusieurs parties:
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{INSTALLED\_APPS }\OperatorTok{=}\NormalTok{ [}
\NormalTok{]}
\NormalTok{THIRD\_PARTIES }\OperatorTok{=}\NormalTok{ [}
\NormalTok{]}
\NormalTok{MY\_APPS }\OperatorTok{=}\NormalTok{ [}
\hypertarget{_logging_2}{%
\subsection{Logging}\label{_logging_2}}
\hypertarget{_muxe9thode_de_duxe9ploiement}{%
\section{Méthode de déploiement}\label{_muxe9thode_de_duxe9ploiement}}
\hypertarget{_duxe9ploiement_sur_debian}{%
\section{Déploiement sur Debian}\label{_duxe9ploiement_sur_debian}}
\begin{Shaded}
\begin{Highlighting}[]
\ExtensionTok{apt}\NormalTok{ update}
\ExtensionTok{groupadd}\NormalTok{ {-}{-}system webapps }
\ExtensionTok{groupadd}\NormalTok{ {-}{-}system gunicorn\_sockets }
\ExtensionTok{useradd}\NormalTok{ {-}{-}system {-}{-}gid webapps {-}{-}shell /bin/bash {-}{-}home /home/gwift gwift }
\FunctionTok{mkdir}\NormalTok{ {-}p /home/gwift }
\FunctionTok{chown}\NormalTok{ gwift:webapps /home/gwift }
\end{Highlighting}
\end{Shaded}
\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}
\hypertarget{_installation_des_duxe9pendances_systuxe8mes}{%
\subsection{Installation des dépendances
systèmes}\label{_installation_des_duxe9pendances_systuxe8mes}}
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{Shaded}
\begin{Highlighting}[]
\ExtensionTok{yum}\NormalTok{ install python36 {-}y}
\end{Highlighting}
\end{Shaded}
Ou passer par une installation alternative:
\begin{Shaded}
\begin{Highlighting}[]
\FunctionTok{sudo}\NormalTok{ yum {-}y groupinstall }\StringTok{"Development Tools"}
\FunctionTok{sudo}\NormalTok{ yum {-}y install openssl{-}devel bzip2{-}devel libffi{-}devel}
\FunctionTok{wget}\NormalTok{ https://www.python.org/ftp/python/3.8.2/Python{-}3.8.2.tgz}
\BuiltInTok{cd}\NormalTok{ Python{-}3.8*/}
\ExtensionTok{./configure}\NormalTok{ {-}{-}enable{-}optimizations}
\FunctionTok{sudo}\NormalTok{ make altinstall }
\end{Highlighting}
\end{Shaded}
\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}
\hypertarget{_configuration_de_lapplication}{%
\subsection{Configuration de
l'application}\label{_configuration_de_lapplication}}
\begin{Shaded}
\begin{Highlighting}[]
\VariableTok{SECRET\_KEY=}\OperatorTok{\textless{}}\KeywordTok{set} \ExtensionTok{your}\NormalTok{ secret key here}\OperatorTok{\textgreater{}}
\VariableTok{ALLOWED\_HOSTS=}\ExtensionTok{*}
\VariableTok{STATIC\_ROOT=}\NormalTok{/var/www/gwift/static}
\VariableTok{DATABASE=}
\end{Highlighting}
\end{Shaded}
\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}
\hypertarget{_cruxe9ation_des_ruxe9pertoires_de_logs}{%
\subsection{Création des répertoires de
logs}\label{_cruxe9ation_des_ruxe9pertoires_de_logs}}
\begin{verbatim}
mkdir -p /var/www/gwift/static
\end{verbatim}
\hypertarget{_cruxe9ation_du_ruxe9pertoire_pour_le_socket}{%
\subsection{Création du répertoire pour le
socket}\label{_cruxe9ation_du_ruxe9pertoire_pour_le_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}
\hypertarget{_gunicorn}{%
\subsection{Gunicorn}\label{_gunicorn}}
\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\#!/bin/bash}
\CommentTok{\# defines settings for gunicorn}
\VariableTok{NAME=}\StringTok{"gwift"}
\VariableTok{DJANGODIR=}\NormalTok{/home/gwift/webapps/gwift}
\VariableTok{SOCKFILE=}\NormalTok{/var/run/webapps/gunicorn\_gwift.sock}
\VariableTok{USER=}\NormalTok{gwift}
\VariableTok{GROUP=}\NormalTok{gunicorn\_sockets}
\VariableTok{NUM\_WORKERS=}\NormalTok{5}
\VariableTok{DJANGO\_SETTINGS\_MODULE=}\NormalTok{config.settings\_production}
\VariableTok{DJANGO\_WSGI\_MODULE=}\NormalTok{config.wsgi}
\BuiltInTok{echo} \StringTok{"Starting }\VariableTok{$NAME}\StringTok{ as }\KeywordTok{\textasciigrave{}}\FunctionTok{whoami}\KeywordTok{\textasciigrave{}}\StringTok{"}
\BuiltInTok{source}\NormalTok{ /home/gwift/.venvs/gwift/bin/activate}
\BuiltInTok{cd} \VariableTok{$DJANGODIR}
\BuiltInTok{export} \VariableTok{DJANGO\_SETTINGS\_MODULE=$DJANGO\_SETTINGS\_MODULE}
\BuiltInTok{export} \VariableTok{PYTHONPATH=$DJANGODIR}\NormalTok{:}\VariableTok{$PYTHONPATH}
\BuiltInTok{exec}\NormalTok{ gunicorn }\VariableTok{$\{DJANGO\_WSGI\_MODULE\}}\NormalTok{:application }\KeywordTok{\textbackslash{}}
\ExtensionTok{{-}{-}name} \VariableTok{$NAME} \KeywordTok{\textbackslash{}}
\ExtensionTok{{-}{-}workers} \VariableTok{$NUM\_WORKERS} \KeywordTok{\textbackslash{}}
\ExtensionTok{{-}{-}user} \VariableTok{$USER} \KeywordTok{\textbackslash{}}
\ExtensionTok{{-}{-}bind}\NormalTok{=unix:}\VariableTok{$SOCKFILE} \KeywordTok{\textbackslash{}}
\ExtensionTok{{-}{-}log{-}level}\NormalTok{=debug }\KeywordTok{\textbackslash{}}
\ExtensionTok{{-}{-}log{-}file}\NormalTok{={-}}
\end{Highlighting}
\end{Shaded}
\hypertarget{_supervision_keep_alive_et_autoreload}{%
\subsection{Supervision, keep-alive et
autoreload}\label{_supervision_keep_alive_et_autoreload}}
Pour la supervision, on passe par Supervisor. Il existe d'autres
superviseurs,
\begin{Shaded}
\begin{Highlighting}[]
\ExtensionTok{yum}\NormalTok{ install supervisor {-}y}
\end{Highlighting}
\end{Shaded}
On crée ensuite le fichier \texttt{/etc/supervisord.d/gwift.ini}:
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{[}\ExtensionTok{program}\NormalTok{:gwift]}
\VariableTok{command=}\NormalTok{/home/gwift/bin/start\_gunicorn.sh}
\VariableTok{user=}\NormalTok{gwift}
\VariableTok{stdout\_logfile=}\NormalTok{/var/log/gwift/gwift.log}
\VariableTok{autostart=}\NormalTok{true}
\VariableTok{autorestart=}\NormalTok{unexpected}
\VariableTok{redirect\_stdout=}\NormalTok{true}
\VariableTok{redirect\_stderr=}\NormalTok{true}
\end{Highlighting}
\end{Shaded}
Et on crée les répertoires de logs, on démarre supervisord et on vérifie
qu'il tourne correctement:
\begin{Shaded}
\begin{Highlighting}[]
\FunctionTok{mkdir}\NormalTok{ /var/log/gwift}
\FunctionTok{chown}\NormalTok{ gwift:nagios /var/log/gwift}
\ExtensionTok{systemctl}\NormalTok{ enable supervisord}
\ExtensionTok{systemctl}\NormalTok{ start supervisord.service}
\ExtensionTok{systemctl}\NormalTok{ status supervisord.service}
\NormalTok{}\ExtensionTok{supervisord.service}\NormalTok{ {-} Process Monitoring and Control Daemon}
\ExtensionTok{Loaded}\NormalTok{: loaded (/usr/lib/systemd/system/supervisord.service}\KeywordTok{;} \ExtensionTok{enabled}\KeywordTok{;} \ExtensionTok{vendor}\NormalTok{ preset: disabled)}
\ExtensionTok{Active}\NormalTok{: active (running) }\ExtensionTok{since}\NormalTok{ Tue 2019{-}12{-}24 10:08:09 CET}\KeywordTok{;} \ExtensionTok{10s}\NormalTok{ ago}
\ExtensionTok{Process}\NormalTok{: 2304 ExecStart=/usr/bin/supervisord {-}c /etc/supervisord.conf (code=exited, status=0/SUCCESS)}
\ExtensionTok{Main}\NormalTok{ PID: 2310 (supervisord)}
\ExtensionTok{CGroup}\NormalTok{: /system.slice/supervisord.service}
\NormalTok{ ├─}\ExtensionTok{2310}\NormalTok{ /usr/bin/python /usr/bin/supervisord {-}c /etc/supervisord.conf}
\NormalTok{ ├─}\ExtensionTok{2313}\NormalTok{ /home/gwift/.venvs/gwift/bin/python3 /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...}
\NormalTok{ ├─}\ExtensionTok{2317}\NormalTok{ /home/gwift/.venvs/gwift/bin/python3 /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...}
\NormalTok{ ├─}\ExtensionTok{2318}\NormalTok{ /home/gwift/.venvs/gwift/bin/python3 /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...}
\NormalTok{ ├─}\ExtensionTok{2321}\NormalTok{ /home/gwift/.venvs/gwift/bin/python3 /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...}
\NormalTok{ ├─}\ExtensionTok{2322}\NormalTok{ /home/gwift/.venvs/gwift/bin/python3 /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...}
\NormalTok{ └─}\ExtensionTok{2323}\NormalTok{ /home/gwift/.venvs/gwift/bin/python3 /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...}
\FunctionTok{ls}\NormalTok{ /var/run/webapps/}
\end{Highlighting}
\end{Shaded}
On peut aussi vérifier que l'application est en train de tourner, à
l'aide de la commande \texttt{supervisorctl}:
\begin{Shaded}
\begin{Highlighting}[]
\VariableTok{$$}\NormalTok{$ }\ExtensionTok{supervisorctl}\NormalTok{ status gwift}
\ExtensionTok{gwift}\NormalTok{ RUNNING pid 31983, uptime 0:01:00}
\end{Highlighting}
\end{Shaded}
Et pour gérer le démarrage ou l'arrêt, on peut passer par les commandes
suivantes:
\begin{Shaded}
\begin{Highlighting}[]
\VariableTok{$$}\NormalTok{$ }\ExtensionTok{supervisorctl}\NormalTok{ stop gwift}
\ExtensionTok{gwift}\NormalTok{: stopped}
\ExtensionTok{root@ks3353535}\NormalTok{:/etc/supervisor/conf.d\# supervisorctl start gwift}
\ExtensionTok{gwift}\NormalTok{: started}
\ExtensionTok{root@ks3353535}\NormalTok{:/etc/supervisor/conf.d\# supervisorctl restart gwift}
\ExtensionTok{gwift}\NormalTok{: stopped}
\ExtensionTok{gwift}\NormalTok{: started}
\end{Highlighting}
\end{Shaded}
\hypertarget{_configuration_du_firewall_et_ouverture_des_ports}{%
\subsection{Configuration du firewall et ouverture des
ports}\label{_configuration_du_firewall_et_ouverture_des_ports}}
\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}
\hypertarget{_installation_dnginx}{%
\subsection{Installation d'Nginx}\label{_installation_dnginx}}
\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}
\hypertarget{_mise_uxe0_jour}{%
\subsection{Mise à jour}\label{_mise_uxe0_jour}}
Script de mise à jour.
\begin{Shaded}
\begin{Highlighting}[]
\FunctionTok{su}\NormalTok{ {-} }\OperatorTok{\textless{}}\NormalTok{user}\OperatorTok{\textgreater{}}
\BuiltInTok{source}\NormalTok{ \textasciitilde{}/.venvs/}\OperatorTok{\textless{}}\NormalTok{app}\OperatorTok{\textgreater{}}\NormalTok{/bin/activate}
\BuiltInTok{cd}\NormalTok{ \textasciitilde{}/webapps/}\OperatorTok{\textless{}}\NormalTok{app}\OperatorTok{\textgreater{}}
\FunctionTok{git}\NormalTok{ fetch}
\FunctionTok{git}\NormalTok{ checkout vX.Y.Z}
\ExtensionTok{pip}\NormalTok{ install {-}U requirements/prod.txt}
\ExtensionTok{python}\NormalTok{ manage.py migrate}
\ExtensionTok{python}\NormalTok{ manage.py collectstatic}
\BuiltInTok{kill}\NormalTok{ {-}HUP }\KeywordTok{\textasciigrave{}}\FunctionTok{ps}\NormalTok{ {-}C gunicorn fch {-}o pid }\KeywordTok{|} \FunctionTok{head}\NormalTok{ {-}n 1}\KeywordTok{\textasciigrave{}}
\end{Highlighting}
\end{Shaded}
\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}
\hypertarget{_configuration_des_sauvegardes}{%
\subsection{Configuration des
sauvegardes}\label{_configuration_des_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}
\hypertarget{_rotation_des_jounaux}{%
\subsection{Rotation des jounaux}\label{_rotation_des_jounaux}}
\begin{Shaded}
\begin{Highlighting}[]
\ExtensionTok{/var/log/gwift/*}\NormalTok{ \{}
\ExtensionTok{weekly}
\ExtensionTok{rotate}\NormalTok{ 3}
\FunctionTok{size}\NormalTok{ 10M}
\ExtensionTok{compress}
\ExtensionTok{delaycompress}
\NormalTok{\}}
\end{Highlighting}
\end{Shaded}
Puis on démarre logrotate avec \# logrotate -d /etc/logrotate.d/gwift
pour vérifier que cela fonctionne correctement.
\hypertarget{_ansible}{%
\subsection{Ansible}\label{_ansible}}
TODO
\hypertarget{_duxe9ploiement_sur_heroku}{%
\section{Déploiement sur Heroku}\label{_duxe9ploiement_sur_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,
\ldots\hspace{0pt}), 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{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{heroku}\NormalTok{ create}
\ExtensionTok{Creating}\NormalTok{ app... done, ⬢ young{-}temple{-}86098}
\ExtensionTok{https}\NormalTok{://young{-}temple{-}86098.herokuapp.com/ }\KeywordTok{|} \ExtensionTok{https}\NormalTok{://git.heroku.com/young{-}temple{-}86098.git}
\NormalTok{$ }\FunctionTok{cat}\NormalTok{ .git/config}
\NormalTok{[}\ExtensionTok{core}\NormalTok{]}
\ExtensionTok{repositoryformatversion}\NormalTok{ = 0}
\ExtensionTok{filemode}\NormalTok{ = false}
\ExtensionTok{bare}\NormalTok{ = false}
\ExtensionTok{logallrefupdates}\NormalTok{ = true}
\ExtensionTok{symlinks}\NormalTok{ = false}
\ExtensionTok{ignorecase}\NormalTok{ = true}
\NormalTok{[}\ExtensionTok{remote} \StringTok{"heroku"}\NormalTok{]}
\ExtensionTok{url}\NormalTok{ = https://git.heroku.com/still{-}thicket{-}66406.git}
\ExtensionTok{fetch}\NormalTok{ = +refs/heads/*:refs/remotes/heroku/*}
\end{Highlighting}
\end{Shaded}
IMPORTANT:
\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}.
\hypertarget{_configuration_du_compte_heroku}{%
\subsection{Configuration du compte
Heroku}\label{_configuration_du_compte_heroku}}
+ 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}
\def\labelenumi{\arabic{enumi}.}
\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{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{heroku}\NormalTok{ login}
\end{Highlighting}
\end{Shaded}
Et créer votre application:
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{heroku}\NormalTok{ create}
\ExtensionTok{Creating}\NormalTok{ app... done, ⬢ young{-}temple{-}86098}
\ExtensionTok{https}\NormalTok{://young{-}temple{-}86098.herokuapp.com/ }\KeywordTok{|} \ExtensionTok{https}\NormalTok{://git.heroku.com/young{-}temple{-}86098.git}
\end{Highlighting}
\end{Shaded}
\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{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{heroku}\NormalTok{ addons:create heroku{-}postgresql:hobby{-}dev}
\ExtensionTok{Creating}\NormalTok{ heroku{-}postgresql:hobby{-}dev on ⬢ still{-}thicket{-}66406... free}
\ExtensionTok{Database}\NormalTok{ has been created and is available}
\NormalTok{ ! }\ExtensionTok{This}\NormalTok{ database is empty. If upgrading, you can transfer}
\NormalTok{ ! }\ExtensionTok{data}\NormalTok{ from another database with pg:copy}
\ExtensionTok{Created}\NormalTok{ postgresql{-}clear{-}39693 as DATABASE\_URL}
\ExtensionTok{Use}\NormalTok{ heroku addons:docs heroku{-}postgresql to view documentation}
\NormalTok{$ }\ExtensionTok{heroku}\NormalTok{ pg:backups schedule {-}{-}at }\StringTok{\textquotesingle{}14:00 Europe/Brussels\textquotesingle{}}\NormalTok{ DATABASE\_URL}
\ExtensionTok{Scheduling}\NormalTok{ automatic daily backups of postgresql{-}clear{-}39693 at 14:00 Europe/Brussels... done}
\end{Highlighting}
\end{Shaded}
TODO: voir comment récupérer le backup de la db :-p
\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# Copié/collé de https://cookiecutter{-}django.readthedocs.io/en/latest/deployment{-}on{-}heroku.html}
\ExtensionTok{heroku}\NormalTok{ create {-}{-}buildpack https://github.com/heroku/heroku{-}buildpack{-}python}
\ExtensionTok{heroku}\NormalTok{ addons:create heroku{-}redis:hobby{-}dev}
\ExtensionTok{heroku}\NormalTok{ addons:create mailgun:starter}
\ExtensionTok{heroku}\NormalTok{ config:set PYTHONHASHSEED=random}
\ExtensionTok{heroku}\NormalTok{ config:set WEB\_CONCURRENCY=4}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_DEBUG=False}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_SETTINGS\_MODULE=config.settings.production}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_SECRET\_KEY=}\StringTok{"}\VariableTok{$(}\ExtensionTok{openssl}\NormalTok{ rand {-}base64 64}\VariableTok{)}\StringTok{"}
\CommentTok{\# Generating a 32 character{-}long random string without any of the visually similar characters "IOl01":}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_ADMIN\_URL=}\StringTok{"}\VariableTok{$(}\ExtensionTok{openssl}\NormalTok{ rand {-}base64 4096 }\KeywordTok{|} \FunctionTok{tr}\NormalTok{ {-}dc }\StringTok{\textquotesingle{}A{-}HJ{-}NP{-}Za{-}km{-}z2{-}9\textquotesingle{}} \KeywordTok{|} \FunctionTok{head}\NormalTok{ {-}c 32}\VariableTok{)}\StringTok{/"}
\CommentTok{\# Set this to your Heroku app url, e.g. \textquotesingle{}bionic{-}beaver{-}28392.herokuapp.com\textquotesingle{}}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_ALLOWED\_HOSTS=}
\CommentTok{\# Assign with AWS\_ACCESS\_KEY\_ID}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_AWS\_ACCESS\_KEY\_ID=}
\CommentTok{\# Assign with AWS\_SECRET\_ACCESS\_KEY}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_AWS\_SECRET\_ACCESS\_KEY=}
\CommentTok{\# Assign with AWS\_STORAGE\_BUCKET\_NAME}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_AWS\_STORAGE\_BUCKET\_NAME=}
\FunctionTok{git}\NormalTok{ push heroku master}
\ExtensionTok{heroku}\NormalTok{ run python manage.py createsuperuser}
\ExtensionTok{heroku}\NormalTok{ run python manage.py check {-}{-}deploy}
\ExtensionTok{heroku}\NormalTok{ open}
\end{Highlighting}
\end{Shaded}
\hypertarget{_configuration}{%
\subsection{Configuration}\label{_configuration}}
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}
\def\labelenumi{\arabic{enumi}.}
\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{Shaded}
\begin{Highlighting}[]
\CommentTok{\# Procfile}
\ExtensionTok{release}\NormalTok{: python3 manage.py migrate}
\ExtensionTok{web}\NormalTok{: gunicorn gwift.wsgi}
\end{Highlighting}
\end{Shaded}
\hypertarget{_huxe9bergement_s3}{%
\subsection{Hébergement S3}\label{_huxe9bergement_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{Shaded}
\begin{Highlighting}[]
\NormalTok{AWS\_ACCESS\_KEY\_ID }\OperatorTok{=}\NormalTok{ os.getenv(}\StringTok{\textquotesingle{}ACCESS\_KEY\_ID\textquotesingle{}}\NormalTok{)}
\NormalTok{AWS\_SECRET\_ACCESS\_KEY }\OperatorTok{=}\NormalTok{ os.getenv(}\StringTok{\textquotesingle{}SECRET\_ACCESS\_KEY\textquotesingle{}}\NormalTok{)}
\NormalTok{AWS\_STORAGE\_BUCKET\_NAME }\OperatorTok{=}\NormalTok{ os.getenv(}\StringTok{\textquotesingle{}AWS\_STORAGE\_BUCKET\_NAME\textquotesingle{}}\NormalTok{)}
\NormalTok{AWS\_S3\_REGION\_NAME }\OperatorTok{=}\NormalTok{ os.getenv(}\StringTok{\textquotesingle{}AWS\_S3\_REGION\_NAME\textquotesingle{}}\NormalTok{)}
\NormalTok{AWS\_DEFAULT\_ACL }\OperatorTok{=} \StringTok{\textquotesingle{}public{-}read\textquotesingle{}}
\NormalTok{AWS\_LOCATION }\OperatorTok{=} \StringTok{\textquotesingle{}static\textquotesingle{}}
\NormalTok{AWS\_S3\_SIGNATURE\_VERSION }\OperatorTok{=} \StringTok{\textquotesingle{}s3v4\textquotesingle{}}
\NormalTok{AWS\_S3\_HOST }\OperatorTok{=} \StringTok{\textquotesingle{}s3.}\SpecialCharTok{\%s}\StringTok{.scw.cloud\textquotesingle{}} \OperatorTok{\%}\NormalTok{ (AWS\_S3\_REGION\_NAME,)}
\NormalTok{AWS\_S3\_ENDPOINT\_URL }\OperatorTok{=} \StringTok{\textquotesingle{}https://}\SpecialCharTok{\%s}\StringTok{\textquotesingle{}} \OperatorTok{\%}\NormalTok{ (AWS\_S3\_HOST, )}
\NormalTok{DEFAULT\_FILE\_STORAGE }\OperatorTok{=} \StringTok{\textquotesingle{}storages.backends.s3boto3.S3Boto3Storage\textquotesingle{}}
\NormalTok{STATICFILES\_STORAGE }\OperatorTok{=} \StringTok{\textquotesingle{}storages.backends.s3boto3.S3ManifestStaticStorage\textquotesingle{}}
\NormalTok{STATIC\_URL }\OperatorTok{=} \StringTok{\textquotesingle{}}\SpecialCharTok{\%s}\StringTok{/}\SpecialCharTok{\%s}\StringTok{/\textquotesingle{}} \OperatorTok{\%}\NormalTok{ (AWS\_S3\_ENDPOINT\_URL, AWS\_LOCATION)}
\CommentTok{\# General optimization for faster delivery}
\NormalTok{AWS\_IS\_GZIPPED }\OperatorTok{=} \VariableTok{True}
\NormalTok{AWS\_S3\_OBJECT\_PARAMETERS }\OperatorTok{=}\NormalTok{ \{}
\StringTok{\textquotesingle{}CacheControl\textquotesingle{}}\NormalTok{: }\StringTok{\textquotesingle{}max{-}age=86400\textquotesingle{}}\NormalTok{,}
\NormalTok{\}}
\end{Highlighting}
\end{Shaded}
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{Shaded}
\begin{Highlighting}[]
\ExtensionTok{remote}\NormalTok{: {-}{-}{-}{-}{-}}\OperatorTok{\textgreater{}}\NormalTok{ $ python manage.py collectstatic {-}{-}noinput}
\ExtensionTok{remote}\NormalTok{: 128 static files copied, 156 post{-}processed.}
\end{Highlighting}
\end{Shaded}
\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}
\hypertarget{_autres_outils}{%
\section{Autres outils}\label{_autres_outils}}
\hypertarget{_arborescences}{%
\subsection{Arborescences}\label{_arborescences}}
\begin{Shaded}
\begin{Highlighting}[]
\end{Highlighting}
\end{Shaded}
\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# \textless{}app\textgreater{}/management/commands/rebuild.py}
\CommentTok{"""This command manages Closure Tables implementation}
\CommentTok{It adds new levels and cleans links between entities.}
\CommentTok{This way, it\textquotesingle{}s relatively easy to fetch an entire tree with just one tiny request.}
\CommentTok{"""}
\ImportTok{from}\NormalTok{ django.core.management.base }\ImportTok{import}\NormalTok{ BaseCommand}
\ImportTok{from}\NormalTok{ rps.structure.models }\ImportTok{import}\NormalTok{ Entity, EntityTreePath}
\KeywordTok{class}\NormalTok{ Command(BaseCommand):}
\KeywordTok{def}\NormalTok{ handle(}\VariableTok{self}\NormalTok{, }\OperatorTok{*}\NormalTok{args, }\OperatorTok{**}\NormalTok{options):}
\NormalTok{ entities }\OperatorTok{=}\NormalTok{ Entity.objects.}\BuiltInTok{all}\NormalTok{()}
\ControlFlowTok{for}\NormalTok{ entity }\KeywordTok{in}\NormalTok{ entities:}
\NormalTok{ breadcrumb }\OperatorTok{=}\NormalTok{ [node }\ControlFlowTok{for}\NormalTok{ node }\KeywordTok{in}\NormalTok{ entity.breadcrumb()]}
\NormalTok{ tree }\OperatorTok{=} \BuiltInTok{set}\NormalTok{(EntityTreePath.objects.}\BuiltInTok{filter}\NormalTok{(descendant}\OperatorTok{=}\NormalTok{entity))}
\ControlFlowTok{for}\NormalTok{ idx, node }\KeywordTok{in} \BuiltInTok{enumerate}\NormalTok{(breadcrumb):}
\NormalTok{ tree\_path, \_ }\OperatorTok{=}\NormalTok{ EntityTreePath.objects.get\_or\_create(}
\NormalTok{ ancestor}\OperatorTok{=}\NormalTok{node, descendant}\OperatorTok{=}\NormalTok{entity, weight}\OperatorTok{=}\NormalTok{idx }\OperatorTok{+} \DecValTok{1}
\NormalTok{ )}
\ControlFlowTok{if}\NormalTok{ tree\_path }\KeywordTok{in}\NormalTok{ tree:}
\NormalTok{ tree.remove(tree\_path)}
\ControlFlowTok{for}\NormalTok{ tree\_path }\KeywordTok{in}\NormalTok{ tree:}
\NormalTok{ tree\_path.delete()}
\end{Highlighting}
\end{Shaded}
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}