Skip to content

Commit

Permalink
Update instructions for local development (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulineribeyre authored Nov 8, 2024
1 parent 408f707 commit dcd7a5c
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 371 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
poetry install -vv --no-interaction
poetry show -vv
- name: Build docs
run: AUDIT_SERVICE_CONFIG_PATH=src/audit/config-default.yaml poetry run python run.py openapi
run: poetry run python run.py openapi

- uses: stefanzweifel/[email protected]
with:
Expand Down
4 changes: 1 addition & 3 deletions cfg_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@ def create_config_file(file_name, full_path=None):
if dir_name and not os.path.exists(dir_name):
os.makedirs(os.path.dirname(config_path))

copyfile(
os.path.join(ROOT_DIR, "src/gen3workflow/config-default.yaml"), config_path
)
copyfile(os.path.join(ROOT_DIR, "gen3workflow/config-default.yaml"), config_path)

return config_path

Expand Down
39 changes: 29 additions & 10 deletions docs/local_installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,44 @@ To use a configuration file in a custom location, you can set the `GEN3WORKFLOW_

## Run Gen3Workflow

Update your configuration file to set `LOCAL_MIGRATION` to `true`, so that no attempts to interact with Arborist are made.
You will need to run a TES server for Gen3Workflow to talk to. For example, you can start a local Funnel server: https://ohsu-comp-bio.github.io/funnel/#intro.

Run database schema migration:
Update your configuration file:
- set `TES_SERVER_URL` to the TES server URL
- set `MOCK_AUTH` to `true`, so that no attempts to interact with Arborist are made.

Start the Gen3Workflow app:

```bash
alembic upgrade head
python run.py
```

Run the server with auto-reloading:
Try out the API at <http://localhost:8080/_status> or <http://localhost:8080/docs> (you might have to set `DOCS_URL_PREFIX` to `""` in your configuration file for the docs endpoint to work).

```bash
python run.py
OR
uvicorn gen3workflow.app:app --reload
## Run Nextflow workflows with Gen3Workflow

Example Nextflow configuration:
```
plugins {
id 'nf-ga4gh'
}
process {
executor = 'tes'
container = 'quay.io/nextflow/bash'
}
tes {
endpoint = 'http://localhost:8080/ga4gh-tes'
}
```
> `http://localhost:8080/ga4gh-tes` is where Gen3Workflow runs by default when started with `python run.py`.
Run a workflow:
```
nextflow run hello
```

Try out the API at <http://localhost:8000/_status> or <http://localhost:8000/docs> (you might have to set `DOCS_URL_PREFIX` to `""` in your configuration file for the docs endpoint to work).

### Quickstart with Helm
## Quickstart with Helm

You can now deploy individual services via Helm!

Expand Down
4 changes: 4 additions & 0 deletions gen3workflow/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ def get_app(httpx_client=None) -> FastAPI:
get_logger("gen3workflow", log_level=log_level)

logger.info("Initializing Arborist client")
if config["MOCK_AUTH"]:
logger.warning(
"Mock authentication and authorization are enabled! 'MOCK_AUTH' should NOT be enabled in production!"
)
custom_arborist_url = os.environ.get("ARBORIST_URL", config["ARBORIST_URL"])
if custom_arborist_url:
app.arborist_client = ArboristClient(
Expand Down
11 changes: 10 additions & 1 deletion gen3workflow/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from gen3authz.client.arborist.errors import ArboristError

from gen3workflow import logger
from gen3workflow.config import config


# auto_error=False prevents FastAPI from raising a 403 when the request
Expand All @@ -29,13 +30,19 @@ def __init__(
self.bearer_token = bearer_token

def get_access_token(self):
if config["MOCK_AUTH"]:
return "123"

return (
self.bearer_token.credentials
if self.bearer_token and hasattr(self.bearer_token, "credentials")
else None
)

async def get_token_claims(self) -> dict:
if config["MOCK_AUTH"]:
return {"sub": 64, "context": {"user": {"name": "mocked-user"}}}

if not self.bearer_token:
err_msg = "Must provide an access token"
logger.error(err_msg)
Expand Down Expand Up @@ -64,8 +71,10 @@ async def authorize(
resources: list,
throw: bool = True,
) -> bool:
token = self.get_access_token()
if config["MOCK_AUTH"]:
return True

token = self.get_access_token()
try:
authorized = await self.arborist_client.auth_request(
token, "gen3-workflow", method, resources
Expand Down
3 changes: 3 additions & 0 deletions gen3workflow/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ DOCS_URL_PREFIX: /gen3workflow
# override the default Arborist URL; ignored if already set as an environment variable
ARBORIST_URL:

# /!\ only use for development! Allows running gen3workflow locally without Arborist interaction
MOCK_AUTH: false

####################
# GA4GH TES #
####################
Expand Down
40 changes: 18 additions & 22 deletions gen3workflow/routes/ga4gh_tes.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ async def get_request_body(request: Request):

@router.get("/service-info", status_code=HTTP_200_OK)
async def service_info(request: Request):
res = await request.app.async_client.get(f"{config['TES_SERVER_URL']}/service-info")
url = f"{config['TES_SERVER_URL']}/service-info"
res = await request.app.async_client.get(url)
if res.status_code != HTTP_200_OK:
logger.error(f"TES server error: {res.status_code} {res.text}")
logger.error(f"TES server error at '{url}': {res.status_code} {res.text}")
raise HTTPException(res.status_code, res.text)
return res.json()

Expand All @@ -60,11 +61,10 @@ async def create_task(request: Request, auth=Depends(Auth)):
body["tags"] = {}
body["tags"]["AUTHZ"] = f"/users/{user_id}/gen3-workflow/tasks/TASK_ID_PLACEHOLDER"

res = await request.app.async_client.post(
f"{config['TES_SERVER_URL']}/tasks", json=body
)
url = f"{config['TES_SERVER_URL']}/tasks"
res = await request.app.async_client.post(url, json=body)
if res.status_code != HTTP_200_OK:
logger.error(f"TES server error: {res.status_code} {res.text}")
logger.error(f"TES server error at '{url}': {res.status_code} {res.text}")
raise HTTPException(res.status_code, res.text)

try:
Expand Down Expand Up @@ -121,11 +121,10 @@ async def list_tasks(request: Request, auth=Depends(Auth)):
query_params["view"] = "FULL"

# get all the tasks, regardless of access
res = await request.app.async_client.get(
f"{config['TES_SERVER_URL']}/tasks", params=query_params
)
url = f"{config['TES_SERVER_URL']}/tasks"
res = await request.app.async_client.get(url, params=query_params)
if res.status_code != HTTP_200_OK:
logger.error(f"TES server error: {res.status_code} {res.text}")
logger.error(f"TES server error at '{url}': {res.status_code} {res.text}")
raise HTTPException(res.status_code, res.text)
listed_tasks = res.json()

Expand Down Expand Up @@ -172,11 +171,10 @@ async def get_task(request: Request, task_id: str, auth=Depends(Auth)):
requested_view = query_params.get("view")
query_params["view"] = "FULL"

res = await request.app.async_client.get(
f"{config['TES_SERVER_URL']}/tasks/{task_id}", params=query_params
)
url = f"{config['TES_SERVER_URL']}/tasks/{task_id}"
res = await request.app.async_client.get(url, params=query_params)
if res.status_code != HTTP_200_OK:
logger.error(f"TES server error: {res.status_code} {res.text}")
logger.error(f"TES server error at '{url}': {res.status_code} {res.text}")
raise HTTPException(res.status_code, res.text)

# check if this user has access to see this task
Expand All @@ -195,11 +193,10 @@ async def get_task(request: Request, task_id: str, auth=Depends(Auth)):
@router.post("/tasks/{task_id}:cancel", status_code=HTTP_200_OK)
async def cancel_task(request: Request, task_id: str, auth=Depends(Auth)):
# check if this user has access to delete this task
res = await request.app.async_client.get(
f"{config['TES_SERVER_URL']}/tasks/{task_id}?view=FULL"
)
url = f"{config['TES_SERVER_URL']}/tasks/{task_id}?view=FULL"
res = await request.app.async_client.get(url)
if res.status_code != HTTP_200_OK:
logger.error(f"TES server error: {res.status_code} {res.text}")
logger.error(f"TES server error at '{url}': {res.status_code} {res.text}")
raise HTTPException(res.status_code, res.text)
body = res.json()
authz_path = body.get("tags", {}).get("AUTHZ")
Expand All @@ -211,11 +208,10 @@ async def cancel_task(request: Request, task_id: str, auth=Depends(Auth)):
await auth.authorize("delete", [authz_path])

# the user has access: delete the task
res = await request.app.async_client.post(
f"{config['TES_SERVER_URL']}/tasks/{task_id}:cancel"
)
url = f"{config['TES_SERVER_URL']}/tasks/{task_id}:cancel"
res = await request.app.async_client.post(url)
if res.status_code != HTTP_200_OK:
logger.error(f"TES server error: {res.status_code} {res.text}")
logger.error(f"TES server error at '{url}': {res.status_code} {res.text}")
raise HTTPException(res.status_code, res.text)

return res.json()
Loading

0 comments on commit dcd7a5c

Please sign in to comment.