Skip to content

Commit

Permalink
Merge pull request #450 from open-source-uc/dev
Browse files Browse the repository at this point in the history
Actualizar main
  • Loading branch information
kovaxis authored Jul 3, 2024
2 parents d769d47 + 83fcf65 commit f738109
Show file tree
Hide file tree
Showing 60 changed files with 2,174 additions and 2,027 deletions.
3 changes: 1 addition & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@
"charliermarsh.ruff",
"kokakiwi.vscode-just",
"redhat.ansible",
"github.vscode-github-actions",
"ms-python.black-formatter"
"github.vscode-github-actions"
],
"settings": {
"terminal.integrated.env.linux": {
Expand Down
14 changes: 5 additions & 9 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,12 @@ jobs:
poetry install
poetry run prisma generate
- name: Run black
uses: wearerequired/lint-action@v2
- name: Run ruff formatter
uses: chartboost/ruff-action@v1
with:
github_token: ${{ secrets.github_token }}
auto_fix: ${{ github.event_name == 'pull_request' }}
black: true
black_dir: backend
black_command_prefix: poetry run
black_auto_fix: true

args: "format --diff"
src: "./backend"

- name: Run ruff
uses: chartboost/ruff-action@v1
with:
Expand Down
1 change: 0 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"ms-vscode-remote.remote-containers",
"kokakiwi.vscode-just",
"ms-python.python",
"ms-python.black-formatter",
"charliermarsh.ruff",
"prisma.prisma",
"bradlc.vscode-tailwindcss",
Expand Down
6 changes: 2 additions & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,12 @@
// Black + Ruff
"python.formatting.provider": "none",
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit"
},
// Prevents ruff-vscode#128
"source.organizeImports.isort": true,
}
},
"ruff.args": [
"--config=/workspaces/planner/backend/pyproject.toml"
Expand Down
27 changes: 16 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ Sigue en la sección [Desarrollo general](#desarrollo-general).

Una vez listo, podrás entrar a la app en [http://localhost:3000](http://localhost:3000) 🎉


Necesitaras un nombre de usuario para acceder a CAS. Puedes acceder con `testuser` o con otros usuarios definidos en `cas-mock-users.json`.

Necesitaras un nombre de usuario para acceder a CAS. Puedes acceder con `testuser` o con otros usuarios definidos en `cas-mock-users.json`.

Para realizar acciones sobre el repositorio (migraciones, generación de código, etc) puedes usar:

- el task runner de VSCode (<kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> -> _"Tasks: Run Task"_).
- `just` en la linea de comandos. Para ver comandos disponibles, corre `just` desde cualquier carpeta.

Es importante que cuando:

- Cambias la estructura de la API, corras la tarea _"Generate client"_ (también disponible en modo watch).
- Cambies el esquema de la base de datos, corras la tarea _"Create/apply migrations"_ para que los cambios se reflejen en la base de datos.

Expand All @@ -89,6 +89,7 @@ El proyecto se integra con dos servicios externos: SIDING (para acceder a mallas
## Deployment

### Deployment automático

Para deployments a producción, se provee un playbook de [Ansible](https://docs.ansible.com/ansible/latest/index.html)
que permite levantar la app completa de principio a fin, pensado para su uso en un entorno de producción, en particular
en un servidor corriendo [Rocky Linux 9](https://rockylinux.org/).
Expand All @@ -102,12 +103,12 @@ $ ansible-playbook infra/playbook.yml -i <IP>, -u <USUARIO>
Asumiendo que ya se tenga una llave SSH al servidor. El playbook es completamente idempotente, por lo que
también puede ser utilizado para corregir configuration drift en un servidor de producción.

El playbook opcionalmente provee prompts para entregar credenciales de Tailscale, Netdata y SIDING. Por defecto
El playbook opcionalmente provee prompts para entregar credenciales de Tailscale, Netdata y SIDING. Por defecto
el playbook no levanta un mock de CAS, que es necesario para instalaciones sin acceso al SSO UC.

También se provee un script más ligero en [just](./justfile), que principalmente está pensado para su uso en
junto al sistema de [Continuous Deployment](./.github/workflows/deploy.yml) del repositorio. Sin embargo,
dado un entorno ya configurado de Docker Compose, este puede ser utilizado para levantar un servidor independiente,
dado un entorno ya configurado de Docker Compose, este puede ser utilizado para levantar un servidor independiente,
incluyendo uno que utilice un SSO mock.

Abajo se entregan instrucciones manuales de deploy.
Expand All @@ -117,17 +118,20 @@ Abajo se entregan instrucciones manuales de deploy.
El ambiente de staging está diseñado para testear las nuevas versiones del planner en un ambiente real antes de pasar a producción.

En primer lugar, es necesario generar manualmente los archivos `.env` y reemplazar los valores según corresponda para cada servicio utilizando los ejemplos ubicados en cada carpeta:

- _API_`backend/.env.staging.template`
- _servidor web_`frontend/.env.staging.template`
- _base de datos_`database/.env.staging.template`

Luego, se debe crear el volumen de Caddy con `docker volume create caddy_data`.

Ahora, para correr la aplicación utilizando un servidor mock de **CAS externo** se debe:

1. Definir las variables `CAS_SERVER_URL` y `CAS_LOGIN_REDIRECTION_URL` en `backend/.env` con la URL del servidor externo.
2. Levantar los contenedores con `docker compose up planner -d --build` desde la raíz del repositorio.

Alternativamente, para correr la aplicación utilizando un servidor mock de **CAS local**:

1. Dejar las variables `CAS_SERVER_URL` y `CAS_LOGIN_REDIRECTION_URL` en `backend/.env` con los valores predeterminados del archivo de ejemplo `.env.staging.template`.
2. Luego, es necesario generar el archivo `cas-mock-users.json` en `cas-mock/data` a partir del ejemplo `cas-mock-users.json.example`.
3. Levantar los contenedores con `docker compose up -d --build` desde la raíz del repositorio.
Expand All @@ -137,22 +141,24 @@ Finalmente, se puede detener la app con `docker compose down` desde la raíz del
### Deployment manual: Producción

El ambiente de producción es manejado por la universidad de forma interna, por lo que aquí se detallan las **instrucciones para desplegar el planner** de forma manual:
1. Se deben crear tres archivos `.env`, uno por cada servicio y dentro de su respectiva carpeta:
- `backend/.env` a partir del ejemplo `backend/.env.production.template` (_API_)
- `frontend/.env` a partir del ejemplo `frontend/.env.production.template` (_servidor web_).
- `database/.env` a partir del ejemplo `database/.env.production.template` (_base de datos_).

1. Se debe crear el archivo `.env` para la API. En particular, crear `backend/.env` a partir del ejemplo `backend/.env.production.template`.

2. Reemplazar los valores de las variables de entorno según corresponda en todos los archivos `.env` creados. **IMPORTANTE:** no olvidar modificar la variable `JWT_SECRET` en `backend/.env` y otras variables que puedan contener secretos para evitar vulnerabilidades de seguridad.

- Para generar una clave `JWT_SECRET` segura y aleatoria se puede utilizar el comando `openssl rand -base64 32`.

3. Se debe crear el volumen donde Caddy guardará los certificados SSL con `docker volume create caddy_data`.
4. Levantar los contenedores con `docker compose up planner -d --build` desde la raíz del repositorio. Requiere _Docker_ y _Docker Compose_ instalados en la máquina.
5. Revisar el estado de los contenedores con `docker ps` o `docker container ls`.
6. Finalmente, se puede detener la app con `docker compose down` desde la misma ubicación.

Nota: los comandos podrían variar ligeramente dependiendo del sistema operativo y versión de *Docker Compose*. En particular, podría ser necesario utilizar `docker-compose` en vez de `docker compose` y `sudo docker compose` en vez de `docker compose`.
Nota: los comandos podrían variar ligeramente dependiendo del sistema operativo y versión de _Docker Compose_. En particular, podría ser necesario utilizar `docker-compose` en vez de `docker compose` y `sudo docker compose` en vez de `docker compose`.

---

## Equipo

<table>
<tbody>
<tr>
Expand All @@ -165,7 +171,6 @@ Nota: los comandos podrían variar ligeramente dependiendo del sistema operativo
</tbody>
</table>


Además del equipo núcleo, nos apoyan los contribuidores al proyecto. Puedes ver [la lista completa de contribuidores aquí.](contributors.md)

## Licencia
Expand Down
15 changes: 15 additions & 0 deletions backend/.env.default
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
################################################################################################
# No es necesario modificar este archivo durante el proceso de deploy. #
# #
# Nota para los desarrolladores: #
# Aquí van las variables de entorno para producción que NO contienen secretos pero requieren #
# ser definidas antes de levantar el contenedor. Si una variable se utiliza en el código, #
# entonces debe ser definida en el archivo `settings.py` en vez de aquí. #
# Para desarrollo, se sobreescriben estas variables en el archivo `.env`. #
################################################################################################

# Variable requerida para configurar Prisma.
DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres?schema=public

# Variable requerida para definir el ambiente de ejecución.
PYTHON_ENV=production
9 changes: 9 additions & 0 deletions backend/.env.development.template
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

# In development mode, use a local server instead of the production URL.
PLANNER_URL="http://localhost:3000"

# URL to the CAS server endpoint.
Expand All @@ -7,11 +9,15 @@ PLANNER_URL="http://localhost:3000"
# - The user's browser is redirected here when they request to log in.
# If the URL the user is redirected to is different to the URL used to verify the
# tokens, the `CAS_LOGIN_REDIRECTION_URL` variable should be set to override it.
#
# In development, use a local CAS mock server.
CAS_SERVER_URL="http://localhost:3004/"
CAS_LOGIN_REDIRECTION_URL=""

# Admin RUT. Is always the only admin and has the power of adding/removing mods.
# There will be no admins if this string is left empty.
#
# In development, make an invalid test RUT be the admin.
ADMIN_RUT="012345678-K"

# JWT secret string. If this secret is leaked, anyone can forge JWT tokens for
Expand All @@ -31,6 +37,9 @@ SIDING_PASSWORD=""
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres?schema=public"

# In development, connect with redis through localhost instead of through the docker network.
REDIS_URI="redis://localhost:6379"

# Avoid updating data as much as possible in development mode.
AUTOSYNC_COURSES="false"
AUTOSYNC_CURRICULUMS="false"
Expand Down
36 changes: 12 additions & 24 deletions backend/.env.production.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,21 @@
# Este archivo está pensado para ser copiado con el nombre `.env` y ser usado en producción. #
# Es necesario entregar los valores reales a cada una de las variables definidas abajo #
# antes de levantar los contenedores de la aplicación para que funcione correctamente. #
# #
# Nota para los desarrolladores: #
# La idea es que este archivo sea completamente estático, ya que será manejado por la #
# universidad directamente en la máquina de producción. Por esta razón, solo debe ser usado #
# para definir secretos. Cualquier otra variable (e.g. URL de la bbdd) se ingresa a nivel de #
# código para poder ser modificada con un commit al repo. #
# En particular, las variables del backend usadas en código se definen como predeterminadas #
# en el archivo `settings.py`, mientras que Las variables necesarias para levantar los #
# contenedores van en el archivo `.env.default`. #
###############################################################################################

# URL que apunta al planner.
# --> ejemplo: "https://plan.ing.puc.cl"
#
# NOTA: Incluir `https://` en este URL, de otra manera el token se mandará por HTTP desencriptado.
# PLANNER_URL=""

# URL que apunta al servidor de autenticacion CAS.
# --> ejemplo: "https://sso.uc.cl/cas"
# CAS_SERVER_URL=""

# RUT del administrador.
# Es único y tiene el poder de añadir y remover moderadores.
# Si se deja vacío no existirá administrador.
# --> ejemplo: "012345678-K"
# --> ejemplo: "12345678-K"
# ADMIN_RUT=""

# Secreto para generar y verificar tokens JWT.
Expand All @@ -26,16 +25,5 @@
# JWT_SECRET="mal secreto, REEMPLAZAR ESTO por un buen secreto."

# Credenciales para utilizar el webservice de Siding.
# SIDING_USERNAME="cambiar_esta_variable"
# SIDING_PASSWORD="cambiar_esta_variable"

# No debería ser necesario modificar esta variable, a menos que se modifiquen las credenciales
# de la base de datos. En tal caso considerar la siguiente estructura:
# "postgresql://USER:PASSWORD@HOST:PORT/DATABASE"
DATABASE_URL="postgresql://postgres:postgres@db:5432/postgres?schema=public"

# No debería ser necesario modificar esta variable.
REDIS_URI="redis://redis:6379/0"

# Don't remove
PYTHON_ENV="production"
# SIDING_USERNAME="<usuario de siding>"
# SIDING_PASSWORD="<contrasena de siding>"
2 changes: 1 addition & 1 deletion backend/.env.staging.template
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# URL que apunta al planner.
PLANNER_URL="http://localhost"
PLANNER_URL="https://mallastest.ing.uc.cl"

# URL que apunta al servidor de autenticacion CAS.
CAS_SERVER_URL="http://cas_mock_server:3004/"
Expand Down
8 changes: 3 additions & 5 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,15 @@ COPY --from=requirements-stage /tmp/requirements.txt /app/requirements.txt
# Install deps from the requirements-stage
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt

# Copy the application code
# TODO: copy only the necessary files
# Copy the application code, including config and env files
COPY ./ /app
WORKDIR /app

# Copy the .env file
COPY .env /app/.env

# Generate the Prisma client
RUN prisma generate

ENV PRE_START_PATH /app/scripts/prestart.sh

# Monitor the app
HEALTHCHECK --start-period=5m CMD curl -f http://localhost:80/health || exit 1
HEALTHCHECK --start-period=5m CMD curl -f http://localhost:80/health || exit 1
17 changes: 6 additions & 11 deletions backend/app/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import asyncio
import logging
import random
from typing import TYPE_CHECKING, Annotated, Literal
from typing import Literal

import sentry_sdk
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.params import Depends
from fastapi.routing import APIRoute
from pydantic import BaseModel, Field
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
Expand All @@ -21,11 +20,6 @@
from app.sync.siding.client import client as siding_soap_client
from app.sync.siding.client import get_titles

if TYPE_CHECKING:
from typing import Any

from redis.asyncio import Redis


# Set up operation IDs for OpenAPI
def custom_generate_unique_id(route: APIRoute):
Expand Down Expand Up @@ -128,7 +122,7 @@ class HealthResponse(BaseModel):


@app.get("/health")
async def health(redis: Annotated["Redis[Any]", Depends(get_redis)]) -> HealthResponse:
async def health() -> HealthResponse:
response = HealthResponse()

try:
Expand All @@ -139,9 +133,10 @@ async def health(redis: Annotated["Redis[Any]", Depends(get_redis)]) -> HealthRe
logging.error(f"Database error detected: {e}")

try:
# Check Redis connection
await redis.ping()
response.detail["redis"] = "healthy"
async with get_redis() as redis:
# Check Redis connection
await redis.ping()
response.detail["redis"] = "healthy"
except Exception as e: # noqa: BLE001
logging.error(f"Redis error detected: {e}")

Expand Down
12 changes: 10 additions & 2 deletions backend/app/plan/courseinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
from app.plan.validation.courses.logic import Expr


class ExprRedefine(BaseModel):
"""
Type adapter. When we update to Pydantic 2, use `TypeAdapter` instead.
"""

__root__: Expr


class CourseDetails(BaseModel):
# The unique code identifying this course.
code: str
Expand Down Expand Up @@ -51,12 +59,12 @@ class CourseDetails(BaseModel):
@staticmethod
def from_db(db: Course) -> "CourseDetails":
# Parse and validate dep json
deps = pydantic.parse_raw_as(Expr, db.deps)
deps = pydantic.parse_raw_as(ExprRedefine, db.deps)
return CourseDetails(
code=db.code,
name=db.name,
credits=db.credits,
deps=deps,
deps=deps.__root__,
banner_equivs=db.banner_equivs,
canonical_equiv=db.canonical_equiv,
program=db.program,
Expand Down
Loading

0 comments on commit f738109

Please sign in to comment.