Skip to content

Commit

Permalink
Add a Horreum passthrough API
Browse files Browse the repository at this point in the history
Any Horreum `GET` API path, including query parameters, can be passed to
`GET /api/v1/horreum/api/{path}`: e.g., `/api/v1/horreum/api/test?limit=10`
or `/api/v1/horreum/api/test/byName/FakeTest`. The query will be authorized
using the configured CPT project Horreum credentials.

The Horreum status, response body, and response headers will be returned to
the caller.
  • Loading branch information
dbutenhof committed May 23, 2024
1 parent e3c39d5 commit 3d4ad34
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 7 deletions.
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ port=8000
url=
personal_access_token=

[horreum]
url=
username=
password=

[airflow]
url=
username=
Expand Down Expand Up @@ -62,14 +67,31 @@ password=

Internally the API when serving the `/ocp` enpoints will use this connection. Also it is suggested to create indexes with same name in the archived instances too to avoid further complications.

The `jira` table requires a `url` key and a `personal_access_token` key. The `url` is a string value that points to the URL address of your Jira resource. The [Personal Access Token](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) is a string value that is the credential issued to authenticate and authorize this application with your Jira resource.
The `jira` configuration requires a `url` key and a `personal_access_token` key. The `url` is a string value that points to the URL address of your Jira resource. The [Personal Access Token](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) is a string value that is the credential issued to authenticate and authorize this application with your Jira resource.

```toml
[jira]
url=""
personal_access_token=""
```

The `horreum` configuration requires the `url` of a running Horreum server,
along with the `username` and `password` to be used to authenticate Horreum
queries.

All `GET` calls to `/api/v1/horreum/api/{path}` will be passed through to
Horreum, including query parameters, and Horreum's response will be returned
to the caller. For example, `GET /api/v1/horreum/api/test/{id}` will return the
same response as directly calling `GET {horreum.url}/api/test/{id}` with the
configured Horreum credentials.

```toml
[horreum]
url="http://localhost:8080"
username="user"
password="secret"
```


## Development on System

Expand Down
2 changes: 1 addition & 1 deletion backend/app/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
#
#
6 changes: 5 additions & 1 deletion backend/app/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from app.api.v1.endpoints.ocp import graph
from app.api.v1.endpoints.cpt import cptJobs
from app.api.v1.endpoints.jira import jira
from app.api.v1.endpoints.horreum import horreum
from app.api.v1.endpoints.quay import quayJobs
from app.api.v1.endpoints.quay import quayGraphs
from app.api.v1.endpoints.telco import telcoJobs
Expand All @@ -27,4 +28,7 @@
router.include_router(telcoJobs.router, tags=['telco'])

# Jira endpoints
router.include_router(jira.router, tags=['jira'])
router.include_router(jira.router, tags=['jira'])

# Horreum endpoint
router.include_router(horreum.router, tags=['horreum'])
32 changes: 32 additions & 0 deletions backend/app/api/v1/endpoints/horreum/horreum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from http import HTTPStatus
from typing import Annotated, Any

from fastapi.responses import JSONResponse

from app.services.horreum_svc import HorreumService
from fastapi import APIRouter, Path, Request, Response

router = APIRouter()


@router.get("/api/v1/horreum/api/{path:path}")
async def horreum(
request: Request, path: Annotated[str, Path(title="Horreum API path")]
) -> Response:
"""Pass GET API call to Horreum
Makes an authenticated Horreum call using the configured Horreum URL,
username, and password. It passes on the query parameters as well as the
Horreum API path, and returns the status, content, and response headers
to the caller.
Args:
request: Tells FastAPI to show us the full request object
path: A Horreum API path, like /api/test/byName/name
"""
response = HorreumService("horreum").get(path, dict(request.query_params.items()))
return Response(
status_code=response.status_code,
headers=response.headers,
content=response.content,
)
39 changes: 39 additions & 0 deletions backend/app/services/horreum_svc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import sys
from typing import Optional

import requests
from app import config
from keycloak.keycloak_openid import KeycloakOpenID


class HorreumService:

def __init__(self, configpath="horreum"):
self.cfg = config.get_config()
self.user = self.cfg.get(configpath + ".username")
self.password = self.cfg.get(configpath + ".password")
self.url = self.cfg.get(configpath + ".url")
try:
kc = requests.get(f"{self.url}/api/config/keycloak")
kc.raise_for_status()
except Exception as e:
print(f"Failed {str(e)!r}", file=sys.stderr)
raise
keycloak = kc.json()
self.keycloak = KeycloakOpenID(
keycloak["url"],
client_id=keycloak["clientId"],
realm_name=keycloak["realm"],
)

def get(
self, path: str, queries: Optional[dict[str, str]] = None
) -> requests.Response:
token = self.keycloak.token(username=self.user, password=self.password)[
"access_token"
]
return requests.get(
f"{self.url}/api/{path}",
params=queries,
headers={"authorization": f"Bearer {token}"},
)
2 changes: 0 additions & 2 deletions backend/backend.containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,3 @@ RUN pip install --user poetry && \
pip install --no-cache-dir -r requirements.txt

ENTRYPOINT ["/bin/bash", "./scripts/start.sh"]


50 changes: 50 additions & 0 deletions backend/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ aiohttp = "^3.7.4"
httpx = "^0.18.1"
orjson = "^3.5.3"
atlassian-python-api = "^3.41.9"
python-keycloak = "^3.12.0"

[tool.poetry.dev-dependencies]
watchgod = "^0.7"
Expand Down
5 changes: 5 additions & 0 deletions backend/skeleton.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ port=8000
[jira]
url=
personal_access_token=

[horreum]
url=
username=
password=
9 changes: 7 additions & 2 deletions local-compose.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@
# - Users will need to update the backend/ocpperf.toml file to meet their needs.
#
#
CPT_BACKEND_PORT=${CPT_BACKEND_PORT:-8000}
CPT_FRONTEND_PORT=${CPT_FRONTEND_PORT:-3000}
CPT_CONFIG=${CPT_CONFIG:-"$PWD/backend/ocpperf.toml"}
podman rm -f front back

podman build -f backend/backend.containerfile --tag backend
podman build -f frontend/frontend.containerfile --tag frontend

podman run -d --name=back -p 8000:8000 -v "$PWD/backend/ocpperf.toml:/backend/ocpperf.toml:Z" localhost/backend
# NOTE: add --network=host to test against a local containerized Horreum
podman run -d --name=back -p ${CPT_BACKEND_PORT}:8000 --network=host -v "${CPT_CONFIG}:/backend/ocpperf.toml:Z" localhost/backend

podman run -d --name=front -p 3000:3000 localhost/frontend
# NOTE: publish 3001 because I have Horreum UI running at 3000
podman run -d --name=front -p ${CPT_FRONTEND_PORT}:3000 localhost/frontend

0 comments on commit 3d4ad34

Please sign in to comment.