Working climate-dt deployment
This commit is contained in:
parent
b679402a1b
commit
01729a323a
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ config.yaml
|
|||||||
*.json
|
*.json
|
||||||
raw_list
|
raw_list
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
|
deps/
|
@ -1,4 +1,4 @@
|
|||||||
{{- if .Values.stacServer.ingress.enabled }}
|
{{- if .Values.ingress.enabled }}
|
||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
@ -6,20 +6,31 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
ingressClassName: nginx
|
ingressClassName: nginx
|
||||||
rules:
|
rules:
|
||||||
- host: {{ .Values.stacServer.ingress.hostname }}
|
- host: {{ .Values.ingress.hostname }}
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- path: /
|
{{- if .Values.stacServer.enabled }}
|
||||||
|
- path: /api
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
name: stac-server
|
name: stac-server
|
||||||
port:
|
port:
|
||||||
number: {{ .Values.stacServer.servicePort }}
|
number: {{ .Values.stacServer.servicePort }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.webQueryBuilder.enabled }}
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: web-query-builder
|
||||||
|
port:
|
||||||
|
number: {{ .Values.webQueryBuilder.servicePort }}
|
||||||
|
{{- end }}
|
||||||
tls:
|
tls:
|
||||||
- hosts:
|
- hosts:
|
||||||
- {{ .Values.stacServer.ingress.hostname }}
|
- {{ .Values.ingress.hostname }}
|
||||||
secretName: lumi-wildcard-tls
|
secretName: {{ .Values.ingress.tlsSecretName }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
|
|
||||||
|
11
chart/templates/stac-server-configmap.yaml
Normal file
11
chart/templates/stac-server-configmap.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# apiVersion: v1
|
||||||
|
# kind: ConfigMap
|
||||||
|
# metadata:
|
||||||
|
# name: stack-server
|
||||||
|
# data:
|
||||||
|
# file1.txt: |-
|
||||||
|
# {{ .Files.Get "files/file1.txt" | nindent 2 }}
|
||||||
|
# file2.txt: |-
|
||||||
|
# {{ .Files.Get "files/file2.txt" | nindent 2 }}
|
||||||
|
# file3.txt: |-
|
||||||
|
# {{ .Files.Get "files/file3.txt" | nindent 2 }}
|
37
chart/templates/web-query-builder-deployment.yaml
Normal file
37
chart/templates/web-query-builder-deployment.yaml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: web-query-builder
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.webQueryBuilder.replicas }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: web-query-builder
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: web-query-builder
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: web-query-builder
|
||||||
|
image: "{{ .Values.webQueryBuilder.image.repository }}:{{ .Values.webQueryBuilder.image.tag }}"
|
||||||
|
imagePullPolicy: {{ .Values.webQueryBuilder.image.pullPolicy }}
|
||||||
|
env:
|
||||||
|
- name: API_HOST
|
||||||
|
value: stac-server
|
||||||
|
ports:
|
||||||
|
- containerPort: {{ .Values.webQueryBuilder.servicePort }}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: web-query-builder
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: web-query-builder
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ .Values.webQueryBuilder.servicePort }}
|
||||||
|
targetPort: {{ .Values.webQueryBuilder.servicePort }}
|
||||||
|
type: ClusterIP
|
@ -1,5 +1,3 @@
|
|||||||
# values.yaml
|
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
servicePort: 6379
|
servicePort: 6379
|
||||||
pvc:
|
pvc:
|
||||||
@ -11,15 +9,25 @@ redis:
|
|||||||
service:
|
service:
|
||||||
port: 6379
|
port: 6379
|
||||||
|
|
||||||
# See https://eccr.ecmwf.int/harbor/projects/258/repositories
|
|
||||||
stacServer:
|
stacServer:
|
||||||
|
enabled: true
|
||||||
image:
|
image:
|
||||||
repository: "eccr.ecmwf.int/qubed/stac_server"
|
repository: "eccr.ecmwf.int/qubed/stac_server"
|
||||||
tag: "latest"
|
tag: "latest"
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: Always
|
||||||
servicePort: 8080
|
servicePort: 80
|
||||||
environment:
|
environment:
|
||||||
REDIS_HOST: "redis"
|
REDIS_HOST: "redis"
|
||||||
ingress:
|
|
||||||
enabled: True
|
webQueryBuilder:
|
||||||
hostname: "climate-catalogue.lumi.apps.dte.destination-earth.eu"
|
enabled: true
|
||||||
|
image:
|
||||||
|
repository: "eccr.ecmwf.int/qubed/web_query_builder"
|
||||||
|
tag: "latest"
|
||||||
|
pullPolicy: Always
|
||||||
|
servicePort: 80
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: True
|
||||||
|
tlsSecretName: "lumi-wildcard-tls"
|
||||||
|
hostname: "climate-catalogue.lumi.apps.dte.destination-earth.eu"
|
1253
config/extremes-dt/language.yaml
Normal file
1253
config/extremes-dt/language.yaml
Normal file
File diff suppressed because it is too large
Load Diff
11
config/local/schema
Normal file
11
config/local/schema
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[ class=od, stream, date, time
|
||||||
|
[ domain, type, levtype, dbase, rki, rty, ty
|
||||||
|
[ step, levelist?, param ]]
|
||||||
|
]
|
||||||
|
|
||||||
|
[ class=ensemble, number, stream, date, time,
|
||||||
|
[ domain, type, levtype, dbase, rki, rty, ty
|
||||||
|
[ step, levelist?, param ]]
|
||||||
|
]
|
||||||
|
|
||||||
|
[ class, foo]
|
33
dockerfile
33
dockerfile
@ -1,4 +1,4 @@
|
|||||||
FROM python:3.12-slim AS stac_server
|
FROM python:3.12-slim AS base
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
build-essential \
|
build-essential \
|
||||||
@ -7,6 +7,9 @@ RUN apt-get update && apt-get install -y \
|
|||||||
git \
|
git \
|
||||||
&& apt-get clean
|
&& apt-get clean
|
||||||
|
|
||||||
|
RUN pip install uv
|
||||||
|
|
||||||
|
# Allows cloning private repos using RUN --mount=type=ssh git clone
|
||||||
RUN mkdir -p -m 0600 ~/.ssh && \
|
RUN mkdir -p -m 0600 ~/.ssh && \
|
||||||
ssh-keyscan -H github.com >> ~/.ssh/known_hosts
|
ssh-keyscan -H github.com >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
@ -15,20 +18,38 @@ RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
|
|||||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||||
|
|
||||||
WORKDIR /code
|
WORKDIR /code
|
||||||
|
|
||||||
|
FROM base AS stac_server
|
||||||
|
|
||||||
COPY stac_server/requirements.txt /code/requirements.txt
|
COPY stac_server/requirements.txt /code/requirements.txt
|
||||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||||
|
|
||||||
# Todo: don't embed this here, mount them at runtime
|
# Todo: don't embed this here, mount them at runtime
|
||||||
COPY config/destinE/schema /config/schema
|
# ENV CONFIG_DIR=/config/
|
||||||
COPY config/destinE/language.yaml /config/language.yaml
|
# COPY config/destinE/config.yaml /config/config.yaml
|
||||||
|
# COPY config/destinE/schema /config/schema
|
||||||
|
# COPY config/destinE/language.yaml /config/language.yaml
|
||||||
|
|
||||||
COPY ./tree_compresser /code/tree_compresser
|
COPY ./tree_compresser /code/tree_compresser
|
||||||
|
|
||||||
# Clone the rsfdb and rsfindlibs repos manually because they're private
|
# Clone the rsfdb and rsfindlibs repos manually because they're private
|
||||||
RUN --mount=type=ssh git clone ssh://git@github.com/ecmwf/rsfdb.git
|
|
||||||
RUN --mount=type=ssh git clone ssh://git@github.com/ecmwf/rsfindlibs.git
|
# RUN --mount=type=ssh git clone ssh://git@github.com/ecmwf/rsfdb.git
|
||||||
|
# RUN --mount=type=ssh git clone ssh://git@github.com/ecmwf/rsfindlibs.git
|
||||||
|
COPY stac_server/deps/rsfdb /code/rsfdb
|
||||||
|
COPY stac_server/deps/rsfindlibs /code/rsfindlibs
|
||||||
|
|
||||||
RUN pip install --no-cache-dir -e /code/tree_compresser
|
RUN pip install --no-cache-dir -e /code/tree_compresser
|
||||||
COPY ./stac_server /code/stac_server
|
COPY ./stac_server /code/stac_server
|
||||||
|
|
||||||
WORKDIR /code/stac_server
|
WORKDIR /code/stac_server
|
||||||
CMD ["fastapi", "dev", "main.py", "--proxy-headers", "--port", "8080", "--host", "0.0.0.0"]
|
CMD ["fastapi", "dev", "main.py", "--proxy-headers", "--port", "80", "--host", "0.0.0.0"]
|
||||||
|
|
||||||
|
FROM base AS web_query_builder
|
||||||
|
|
||||||
|
COPY web_query_builder/requirements.txt /code/requirements.txt
|
||||||
|
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||||
|
|
||||||
|
COPY web_query_builder /code/web_query_builder
|
||||||
|
WORKDIR /code/web_query_builder
|
||||||
|
CMD ["flask", "run", "--host", "0.0.0.0", "--port", "80"]
|
||||||
|
@ -2,12 +2,14 @@ set -e
|
|||||||
|
|
||||||
sudo docker login eccr.ecmwf.int
|
sudo docker login eccr.ecmwf.int
|
||||||
|
|
||||||
# Uses ssh agent to check out private repos
|
sudo docker build \
|
||||||
# Make sure that ssh agent is running, your key is added
|
|
||||||
# and potentially that you're using ssh-forwarding if building on a remote machine
|
|
||||||
sudo DOCKER_BUILDKIT=1 docker build \
|
|
||||||
--ssh default=${SSH_AUTH_SOCK} \
|
|
||||||
--tag=eccr.ecmwf.int/qubed/stac_server:latest \
|
--tag=eccr.ecmwf.int/qubed/stac_server:latest \
|
||||||
--target=stac_server \
|
--target=stac_server \
|
||||||
.
|
.
|
||||||
sudo docker --debug push eccr.ecmwf.int/qubed/stac_server:latest
|
sudo docker push eccr.ecmwf.int/qubed/stac_server:latest
|
||||||
|
|
||||||
|
sudo docker build \
|
||||||
|
--tag=eccr.ecmwf.int/qubed/web_query_builder:latest \
|
||||||
|
--target=web_query_builder \
|
||||||
|
.
|
||||||
|
sudo docker push eccr.ecmwf.int/qubed/web_query_builder:latest
|
19
scripts/load_redis.py
Executable file
19
scripts/load_redis.py
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#! .venv/bin/python
|
||||||
|
|
||||||
|
import redis
|
||||||
|
import yaml
|
||||||
|
import json
|
||||||
|
|
||||||
|
print("Opening redis connection")
|
||||||
|
r = redis.Redis(host="redis", port=6379, db=0)
|
||||||
|
|
||||||
|
print("Loading data from local files")
|
||||||
|
with open("config/climate-dt/compressed_tree.json") as f:
|
||||||
|
compressed_catalog = json.load(f)
|
||||||
|
|
||||||
|
with open("config/climate-dt/language.yaml") as f:
|
||||||
|
mars_language = yaml.safe_load(f)["_field"]
|
||||||
|
|
||||||
|
print("Storing data in redis")
|
||||||
|
r.set('compressed_catalog', json.dumps(compressed_catalog))
|
||||||
|
r.set('mars_language', json.dumps(mars_language))
|
3
scripts/setup.sh
Normal file
3
scripts/setup.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install pyyaml redis
|
@ -2,6 +2,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import redis
|
import redis
|
||||||
import yaml
|
import yaml
|
||||||
@ -23,40 +24,33 @@ app.add_middleware(
|
|||||||
async def favicon():
|
async def favicon():
|
||||||
return FileResponse("favicon.ico")
|
return FileResponse("favicon.ico")
|
||||||
|
|
||||||
with open(os.environ.get("CONFIG_DIR", ".") + "/config.yaml", "r") as f:
|
|
||||||
config = yaml.safe_load(f)
|
|
||||||
|
|
||||||
if "local_cache" in config:
|
if "LOCAL_CACHE" in os.environ:
|
||||||
print("Getting cache from local file")
|
print("Getting data from local file")
|
||||||
with open(config["local_cache"], "r") as f:
|
|
||||||
json_data = f.read()
|
base = Path(os.environ["LOCAL_CACHE"])
|
||||||
print("Found compressed catalog in local file")
|
with open(base / "compressed_tree.json", "r") as f:
|
||||||
|
json_tree = f.read()
|
||||||
|
|
||||||
|
|
||||||
|
with open(base / "language.yaml", "r") as f:
|
||||||
|
mars_language = yaml.safe_load(f)["_field"]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("Getting cache from redis")
|
print("Getting cache from redis")
|
||||||
r = redis.Redis(host=os.environ.get("REDIS_HOST", "localhost"), port=6379, db=0)
|
r = redis.Redis(host="redis", port=6379, db=0)
|
||||||
json_data = r.get('compressed_catalog')
|
json_tree = r.get('compressed_catalog')
|
||||||
|
assert json_tree, "No compressed tree found in redis"
|
||||||
|
mars_language = json.loads(r.get('mars_language'))
|
||||||
|
|
||||||
print("Loading tree to json")
|
print("Loading tree from json")
|
||||||
if not json_data:
|
c_tree = CompressedTree.from_json(json.loads(json_tree))
|
||||||
c_tree = CompressedTree.from_json({})
|
|
||||||
else:
|
|
||||||
compressed_tree_json = json.loads(json_data)
|
|
||||||
c_tree = CompressedTree.from_json(compressed_tree_json)
|
|
||||||
|
|
||||||
print("Partialy decompressing tree, shoud be able to skip this step in future.")
|
print("Partialy decompressing tree, shoud be able to skip this step in future.")
|
||||||
tree = c_tree.reconstruct_compressed_ecmwf_style()
|
tree = c_tree.reconstruct_compressed_ecmwf_style()
|
||||||
|
|
||||||
print("Ready to serve requests!")
|
print("Ready to serve requests!")
|
||||||
|
|
||||||
base = os.environ.get("CONFIG_DIR", ".")
|
|
||||||
config = {
|
|
||||||
"fdb_schema": f"{base}/schema",
|
|
||||||
"mars_language": f"{base}/language.yaml",
|
|
||||||
}
|
|
||||||
|
|
||||||
with open(config["mars_language"], "r") as f:
|
|
||||||
mars_language = yaml.safe_load(f)["_field"]
|
|
||||||
|
|
||||||
def request_to_dict(request: Request) -> Dict[str, Any]:
|
def request_to_dict(request: Request) -> Dict[str, Any]:
|
||||||
# Convert query parameters to dictionary format
|
# Convert query parameters to dictionary format
|
||||||
request_dict = dict(request.query_params)
|
request_dict = dict(request.query_params)
|
||||||
@ -114,7 +108,7 @@ def get_leaves(tree):
|
|||||||
for leaf in get_leaves(v):
|
for leaf in get_leaves(v):
|
||||||
yield leaf
|
yield leaf
|
||||||
|
|
||||||
@app.get("/match")
|
@app.get("/api/match")
|
||||||
async def get_match(request: Request):
|
async def get_match(request: Request):
|
||||||
# Convert query parameters to dictionary format
|
# Convert query parameters to dictionary format
|
||||||
request_dict = request_to_dict(request)
|
request_dict = request_to_dict(request)
|
||||||
@ -130,7 +124,7 @@ async def get_match(request: Request):
|
|||||||
|
|
||||||
return match_tree
|
return match_tree
|
||||||
|
|
||||||
@app.get("/paths")
|
@app.get("/api/paths")
|
||||||
async def api_paths(request: Request):
|
async def api_paths(request: Request):
|
||||||
request_dict = request_to_dict(request)
|
request_dict = request_to_dict(request)
|
||||||
match_tree = match_against_cache(request_dict, tree)
|
match_tree = match_against_cache(request_dict, tree)
|
||||||
@ -155,7 +149,7 @@ async def api_paths(request: Request):
|
|||||||
"values": sorted(v["values"], reverse=True),
|
"values": sorted(v["values"], reverse=True),
|
||||||
} for key, v in by_path.items()]
|
} for key, v in by_path.items()]
|
||||||
|
|
||||||
@app.get("/stac")
|
@app.get("/api/stac")
|
||||||
async def get_STAC(request: Request):
|
async def get_STAC(request: Request):
|
||||||
request_dict = request_to_dict(request)
|
request_dict = request_to_dict(request)
|
||||||
paths = await api_paths(request)
|
paths = await api_paths(request)
|
||||||
|
@ -5,3 +5,4 @@ python-dotenv
|
|||||||
flask-login
|
flask-login
|
||||||
flask-cors
|
flask-cors
|
||||||
cachetools
|
cachetools
|
||||||
|
uvicorn
|
@ -6,7 +6,7 @@ function getSTACUrlFromQuery() {
|
|||||||
|
|
||||||
// get current window url and remove path part
|
// get current window url and remove path part
|
||||||
let api_url = new URL(window.location.href);
|
let api_url = new URL(window.location.href);
|
||||||
api_url.pathname = "/stac";
|
api_url.pathname = "/api/stac";
|
||||||
|
|
||||||
for (const [key, value] of params.entries()) {
|
for (const [key, value] of params.entries()) {
|
||||||
api_url.searchParams.set(key, value);
|
api_url.searchParams.set(key, value);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user