2022-03-23 09:06:22 +01:00
\begin { Shaded}
\begin { Highlighting} []
2022-04-25 19:12:16 +02:00
\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}
2022-03-23 09:06:22 +01:00
2022-04-25 19:12:16 +02:00
\ExtensionTok { 5} \NormalTok { directories, 22 files}
2022-03-23 09:06:22 +01:00
\end { Highlighting}
\end { Shaded}
2022-04-25 19:12:16 +02:00
\hypertarget { _ structure_ finale_ de_ notre_ environnement} { %
\subsection { Structure finale de notre
environnement} \label { _ structure_ finale_ de_ notre_ environnement} }
2022-03-23 09:06:22 +01:00
\begin { Shaded}
\begin { Highlighting} []
2022-04-25 19:12:16 +02:00
\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}
2022-03-23 09:06:22 +01:00
\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\ l’ application\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 { - } base 64 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 { - } base 64 4096 } \KeywordTok { | } \FunctionTok { tr } \NormalTok { { - } dc } \StringTok { \textquotesingle { } A { - } HJ { - } NP { - } Za { - } km { - } z 2 { - } 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} }
2022-04-27 19:33:47 +02:00
\hypertarget { _ arborescences} { %
\subsection { Arborescences} \label { _ arborescences} }
2022-03-23 09:06:22 +01:00
\begin { Shaded}
\begin { Highlighting} []
\end { Highlighting}
\end { Shaded}
\begin { Shaded}
\begin { Highlighting} []
2022-04-27 19:33:47 +02:00
\CommentTok { \# \textless { } app\textgreater { } /management/commands/rebuild.py}
2022-03-23 09:06:22 +01:00
2022-04-27 19:33:47 +02:00
\CommentTok { """This command manages Closure Tables implementation}
2022-03-23 09:06:22 +01:00
2022-04-27 19:33:47 +02:00
\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.}
2022-03-23 09:06:22 +01:00
2022-04-27 19:33:47 +02:00
\CommentTok { """}
2022-03-23 09:06:22 +01:00
2022-04-27 19:33:47 +02:00
\ImportTok { from} \NormalTok { django.core.management.base } \ImportTok { import} \NormalTok { BaseCommand}
2022-03-23 09:06:22 +01:00
2022-04-27 19:33:47 +02:00
\ImportTok { from} \NormalTok { rps.structure.models } \ImportTok { import} \NormalTok { Entity, EntityTreePath}
2022-03-23 09:06:22 +01:00
2022-04-27 19:33:47 +02:00
\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 { ()}
2022-03-23 09:06:22 +01:00
2022-04-27 19:33:47 +02:00
\ControlFlowTok { for} \NormalTok { entity } \KeywordTok { in} \NormalTok { entities:}
\NormalTok { breadcrumb } \OperatorTok { =} \NormalTok { [node } \ControlFlowTok { for} \NormalTok { node } \KeywordTok { in} \NormalTok { entity.breadcrumb()]}
2022-03-23 09:06:22 +01:00
2022-04-27 19:33:47 +02:00
\NormalTok { tree } \OperatorTok { =} \BuiltInTok { set} \NormalTok { (EntityTreePath.objects.} \BuiltInTok { filter} \NormalTok { (descendant} \OperatorTok { =} \NormalTok { entity))}
2022-03-23 09:06:22 +01:00
2022-04-27 19:33:47 +02:00
\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 { )}
2022-03-23 09:06:22 +01:00
2022-04-27 19:33:47 +02:00
\ControlFlowTok { if} \NormalTok { tree\_ path } \KeywordTok { in} \NormalTok { tree:}
\NormalTok { tree.remove(tree\_ path)}
2022-03-23 09:06:22 +01:00
2022-04-27 19:33:47 +02:00
\ControlFlowTok { for} \NormalTok { tree\_ path } \KeywordTok { in} \NormalTok { tree:}
\NormalTok { tree\_ path.delete()}
2022-03-23 09:06:22 +01:00
\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}