Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add traefik stack support #7

Open
wants to merge 47 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
2788ddd
Add viewer and logger
dnclain Oct 7, 2021
bb609a1
merge with latest change
dnclain Oct 7, 2021
74495ea
Add .env management for local start
dnclain Oct 7, 2021
3c16bea
make env not git repository
dnclain Oct 7, 2021
b245a34
ignore .env
dnclain Oct 7, 2021
b8842b5
quick laïus on .env in documentation
dnclain Oct 7, 2021
d99b40f
Add LIMIT_FEATURE options
dnclain Oct 8, 2021
343bb7c
fix viewer margin and database healthcheck
dnclain Oct 8, 2021
48a9734
refactoring to take in account some remarks from esgn
dnclain Nov 2, 2021
6271e5a
use constants
dnclain Nov 2, 2021
997aef3
remove review
dnclain Nov 4, 2021
6186807
print changes in dataset importer
dnclain Nov 4, 2021
cbc8fce
Null field management
dnclain Nov 4, 2021
b8b5029
Add viewer
dnclain Nov 4, 2021
db404a9
add constant api_key
dnclain Nov 5, 2021
7d83752
update README for viewer_url
dnclain Nov 5, 2021
bb5cfa6
Merge branch 'add-viewer' into add-apikey
dnclain Nov 5, 2021
33edfeb
API_KEY support
dnclain Nov 5, 2021
eb2f116
Full support for an apiKey
dnclain Nov 5, 2021
955a786
Add instructions for k8s
dnclain Nov 5, 2021
ac8570d
Fix documentation for k8s
dnclain Nov 5, 2021
67bb2a8
fix json structure
dnclain Nov 8, 2021
22ada57
Add instructions for k8s
dnclain Nov 5, 2021
dedb647
Fix documentation for k8s
dnclain Nov 5, 2021
ecaf95a
Merge branch 'add-k8s-info' of github.com:dnclain/api-parcellaire-exp…
dnclain Nov 8, 2021
ec1ab76
lighter images
dnclain Nov 9, 2021
827876d
progress bar
dnclain Nov 9, 2021
aaea130
removes unused env from docker-compose
dnclain Nov 10, 2021
7d007e3
example of traefik file
Nov 19, 2021
7ae12c2
Separating dev compose from prod stack
dnclain Dec 1, 2021
3b517da
information about docker stack
dnclain Dec 1, 2021
529e7b2
Make STACK input conf configurable
dnclain Dec 1, 2021
781dc8d
Mini comment fix
dnclain Dec 1, 2021
68991e5
Fix network name
dnclain Dec 1, 2021
614d8be
Minifix documentation
dnclain Dec 1, 2021
04868da
fix documentation
dnclain Dec 2, 2021
32792a7
minifix doc
dnclain Dec 2, 2021
d6fb4e5
mini fix in documentation
dnclain Dec 2, 2021
9ce88c9
fix image ref
dnclain Dec 2, 2021
afc173b
Add info about api key usage
dnclain Dec 2, 2021
fdd6f4c
Fix traefik exposition in swarm mode
dnclain Dec 5, 2021
a5e8898
move traefik port exposition in traefik service
dnclain Dec 5, 2021
8fcd575
reactivate loadbalancer
dnclain Dec 6, 2021
0e960e1
fix loadbalancer : service name is swarm deploy name
dnclain Dec 6, 2021
60cfd60
Fix traefik syntax v2
dnclain Dec 6, 2021
5f36c5e
Fix copy paste hell 🙀 topo=> parcellaire
dnclain Dec 6, 2021
ad50b8c
fix use of OPTIONS preflight
dnclain Oct 12, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions .env

This file was deleted.

45 changes: 45 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
##
# MANDATORY
##

# Valeurs de configuration de l'importer
DOWNLOAD_URL=https://files.opendatarchives.fr/professionnels.ign.fr/parcellaire-express/PCI-par-DEPT_2021-04/
MAX_PARALLEL_DL=4
TEST_IMPORTER=0

# Valeurs de configuration de la base de données
POSTGRES_VERSION=13
POSTGIS_VERSION=3
POSTGRES_HOST=parcellaire-postgis
POSTGRES_DB=parcellaire-express
POSTGRES_PORT=5432
POSTGRES_USER=parcellaire
POSTGRES_PASSWORD=password
POSTGRES_SCHEMA=edition211

# Valeurs de configuration de l'API
API_PORT=8010
# A positive number. '0' means disabled.
MAX_FEATURE=1000

##
# Stack deployment
##

# Please adapt to your own registry
STACK_FRONTEND_DNS=ign-parcellaire.yoursite.org
STACK_IMAGE_IMPORTER=ghcrio.io/esgn/parcellaire-importer:latest
STACK_IMAGE_API=ghcrio.io/esgn/parcellaire-api:latest
STACK_IMAGE_POSTGIS=ghcrio.io/esgn/parcellaire-postgis:latest

##
# OPTIONAL
##

# The url path to the viewer
# Leave empty or undefined to disable
VIEWER_URL=/viewer/

# A unique secure id
# Leave empty or undefined to disable.
API_KEY=
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.swp
.env
.DS_Store
# Local configuration
docker-compose.yml
docker-stack.yml
76 changes: 73 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Il est possible de décommenter le service `adminer` dans `docker-compose.yml` p

## Préalables

0. 🚨 Copier le fichier `.env.example` vers le fichier `.env` avant toute opération. Les valeurs par défauts devraient être suffisantes, mais il vous est possible de l'adapter à votre environnement.

1. Le fichier `.env` regroupe l'ensemble des valeurs de configuration. On liste ci-dessous les options les plus utiles :
* Configuration de l'importer
* `MAX_PARALLEL_DL` : Nombre de téléchargement d'archives de données simultanés. Fixé à `4` par défaut.
Expand All @@ -36,7 +38,10 @@ Il est possible de décommenter le service `adminer` dans `docker-compose.yml` p
* `POSTGRES_PASSWORD` : Mot de passe de la base de données. **A modifier**.
* Configuration de l'API
* `API_PORT` : Port d'écoute de l'API. Fixé à `8010` par défaut.
* `MAX_FEATURE` : Nombre maximal d'objets retournés par l'API. Fixé à `1000` par défaut.
* `MAX_FEATURE` : Nombre maximal d'objets retournés par l'API. Fixé à `1000` par défaut. `0` pour désactiver la limite.
* `API_KEY` : (Optionnel) Bearer Authentication. Laisser vide ou non défini pour désactiver.
* Configuration du viewer
* `VIEWER_URL` : (Optionnel) Url d'accès à une page de consultation des parcelles. Laisser vide ou non défini pour désactiver.
2. Des options de configuration de PostgreSQL sont définies dans le fichier `docker-compose.yml`. Utiliser [PGTune](https://pgtune.leopard.in.ua/#/) pour les adapter aux caractéristiques de la machine hôte.

## Déploiement
Expand All @@ -45,11 +50,18 @@ Il est possible de décommenter le service `adminer` dans `docker-compose.yml` p

`docker-compose build`

2. Lancement des containers via docker-compose
2. Eventuellement, pusher les images vers un registry (pour une potentielle mise en production)

```bash
docker login <registry>
docker-compose push
```

3. Lancement des containers via docker-compose

`docker-compose up -d`

3. Import des données en base (opérations longues pouvant être lancées dans un `screen` ou en utilisant l'option `-d` de `docker-compose run`)
4. Import des données en base (opérations longues pouvant être lancées dans un `screen` ou en utilisant l'option `-d` de `docker-compose run`)

* Téléchargement des données du produit :

Expand All @@ -59,6 +71,58 @@ Il est possible de décommenter le service `adminer` dans `docker-compose.yml` p

`docker-compose run parcellaire-importer bash /tmp/import-data.sh`

## Environnement de production Stack/traefik

Ces commandes s'appliquent pour un déploiement en production avec docker stack et traefik :

1. Installer `docker-compose` en suivant les instructions de la [documentation](https://docs.docker.com/compose/install/)

2. Compléter le fichier [`.env`] avec les informations de la production, notamment le chemin des images et le nom du réseau traefik.

- `STACK_FRONTEND_DNS` : Par exemple `ign-parcellaire.yoursite.org`. S'assurer que l'entrée DNS existe AVANT le déploiement.
- `STACK_IMAGE_IMPORTER` : Par exemple `ghcrio.io/esgn/parcellaire-importer:latest`
- `STACK_IMAGE_API` : Par exemple `ghcrio.io/esgn/parcellaire-importer:latest`
- `STACK_IMAGE_POSTGIS` : Par exmeple `ghcrio.io/esgn/parcellaire-postgis:latest`

3. Extraire la version avec les valeurs du fichier [`.env`]

`docker-compose -f docker-compose.common.yml -f docker-compose.stack config > docker-stack.yml`

Modifier le nom du réseau qui correspond à votre environnement directement dans `docker-stack.yml`.

4. S'authentifier si nécessaire avec un clé qui les droits de pull

`docker login <registry_url>`

5. Lancement

```bash
# Deploy
docker stack deploy parcellaire -c docker-stack.yml --with-registry-auth
# Check
docker stack ps parcellaire --no-trunc
# Service reference
docker service ls
```

6. Installation

Même procédure que pour `docker-compose` :

* Téléchargement des données du produit :

`docker exec parcellaire_parcellaire-importer.XXXXX python3 /tmp/download-dataset.py`

* Mise en base des données du produit :

`docker exec pacellaire_parcellaire-importer.XXXXX /bin/bash /tmp/import-data.sh`

* Enjoy !

7. Arrêt

`docker stack rm parcellaire`


## Utilisation

Expand All @@ -71,6 +135,12 @@ Il est possible de décommenter le service `adminer` dans `docker-compose.yml` p
* **GET** `/parcelle?bbox={bbox}` *ou* `/parcelle?lon_min={lon}&lat_min={lat}&lon_max={lon}&lat_max={lat}` : Recherche des parcelles intersectant une bounding box donnée en coordonnées géographiques (WGS84)
* Exemple : http://localhost:8010/parcelle?bbox=5.2135,44.5719,5.2709,44.6247

⭐️ Quand l'api est protégé par une clé d'api, merci d'ajouter à la requête le header suivant :

`Authorization: Bearer <api_key>`
OR
`Authorization: Token <api_key>`

### Formats

#### Paramètres
Expand Down
73 changes: 48 additions & 25 deletions api/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,9 @@ type App struct {
DB *sql.DB
}

func (a *App) Initialize(user, password, dbname, hostname string) {
func (a *App) Initialize(DB *sql.DB) {

log.SetOutput(os.Stderr)

connectionString :=
fmt.Sprintf("user=%s password=%s dbname=%s host=%s sslmode=disable", user, password, dbname, hostname)

var err error
a.DB, err = sql.Open("postgres", connectionString)
if err != nil {
log.Fatal(err)
}
a.DB = DB

a.Router = mux.NewRouter()
a.initializeRoutes()
Expand All @@ -54,15 +45,24 @@ func (a *App) Run(addr string) {
}

func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
response, err := json.Marshal(payload)
if err != nil {
log.Println("JSON marshalling error")
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
if _, err := w.Write(response); err != nil {
log.Println("Could not send response")

_err := json.NewEncoder(w).Encode(payload)
if _err != nil {
log.Printf("🚧 JSON encoding error : %v\n", _err)

w.WriteHeader(http.StatusInternalServerError)

_err = json.NewEncoder(w).Encode(GeneralMessage{
Message: "jsonEncodingError",
Error: true,
Literal: "Sorry, cannot output to json",
})

log.Panicf("🚨 Cannot output error message : %v\n", _err)
}

}

func respondWithError(w http.ResponseWriter, code int, message string) {
Expand Down Expand Up @@ -125,27 +125,50 @@ func (a App) healthCheckHandler(w http.ResponseWriter, r *http.Request) {
}

func (a *App) initializeRoutes() {
a.Router.HandleFunc("/parcelle/{idu:"+iduRegex+"}", a.getById).Methods("GET")

a.Router.HandleFunc("/parcelle", a.findByPosition).Queries(
theViewerUrl, isViewerUrldefined := os.LookupEnv("VIEWER_URL")
if isViewerUrldefined {
log.Printf("⭐️ Html viewer is enabled : %v", isViewerUrldefined)
// silent viewer route
a.Router.PathPrefix(theViewerUrl).Handler(http.StripPrefix(theViewerUrl, http.FileServer(http.Dir("./views")))).Methods("GET")
}

// silent route
a.Router.HandleFunc("/status", a.healthCheckHandler).Methods("GET", "OPTIONS")

_mayBeSecured := a.Router.NewRoute().Subrouter()

_mayBeSecured.Use(LogMw)

if os.Getenv(ENV_API_KEY) != "" {
log.Printf("⭐️ Api key security is enabled")
_mayBeSecured.Use(AuthMw)
}

_mayBeSecured.Use(mux.MiddlewareFunc(CorsMw("GET")))

_mayBeSecured.HandleFunc("/parcelle/{idu:"+iduRegex+"}", a.getById).Methods("GET")

_mayBeSecured.HandleFunc("/parcelle", a.findByPosition).Queries(
"pos", "{pos:"+posRegex+"}").Methods("GET")

a.Router.HandleFunc("/parcelle", a.findByPositionSplit).Queries(
_mayBeSecured.HandleFunc("/parcelle", a.findByPositionSplit).Queries(
"lon", "{lon:"+lonRegex+"}",
"lat", "{lat:"+latRegex+"}").Methods("GET")

a.Router.HandleFunc("/parcelle", a.findByBbox).Queries(
_mayBeSecured.HandleFunc("/parcelle", a.findByBbox).Queries(
"bbox", "{bbox:"+bboxRegex+"}").Methods("GET")

a.Router.HandleFunc("/parcelle", a.findByBboxSplit).Queries(
_mayBeSecured.HandleFunc("/parcelle", a.findByBboxSplit).Queries(
"lon_min", "{lon_min:"+lonRegex+"}",
"lat_min", "{lat_min:"+latRegex+"}",
"lon_max", "{lon_max:"+lonRegex+"}",
"lat_max", "{lat_max:"+latRegex+"}").Methods("GET")

a.Router.HandleFunc("/parcelle", a.error(http.StatusBadRequest, "Requête invalide"))
// handle no argument
a.Router.Handle("/parcelle", Use(LogMw).ThenFunc(a.error(http.StatusBadRequest, "Requête invalide")))

a.Router.HandleFunc("/status", a.healthCheckHandler).Methods("GET")
// handle root path
a.Router.PathPrefix("/").Handler(Use(LogMw).ThenFunc(a.error(http.StatusNotFound, "URL inconnue")))

a.Router.PathPrefix("/").HandlerFunc(a.error(http.StatusNotFound, "URL inconnue"))
}
23 changes: 23 additions & 0 deletions api/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

///
// Mandatory env vars
///

const (
ENV_POSTGRES_USER = "POSTGRES_USER"
ENV_POSTGRES_PASSWORD = "POSTGRES_PASSWORD"
ENV_POSTGRES_DB = "POSTGRES_DB"
ENV_POSTGRES_HOST = "POSTGRES_HOST"
ENV_POSTGRES_PORT = "POSTGRES_PORT"
ENV_API_PORT = "API_PORT"
ENV_POSTGRES_SCHEMA = "POSTGRES_SCHEMA"
ENV_MAX_FEATURE = "MAX_FEATURE"
)

///
// Optional env vars
///

const ENV_VIEWER_URL = "VIEWER_URL"
const ENV_API_KEY = "API_KEY"
1 change: 1 addition & 0 deletions api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.17

require (
github.com/gorilla/mux v1.8.0
github.com/joho/godotenv v1.4.0
github.com/lib/pq v1.10.3
github.com/paulmach/go.geojson v1.4.0
)
2 changes: 2 additions & 0 deletions api/go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/paulmach/go.geojson v1.4.0 h1:5x5moCkCtDo5x8af62P9IOAYGQcYHtxz2QJ3x1DoCgY=
Expand Down
Loading