Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@
!pyproject.toml
!LICENSE
!README.md
!client_secrets.json
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## v0.4.1 (2026-07-20)

- Load OIDC client configuration from a mounted file instead of the image.

## v0.4.0 (2026-07-14)

- Add parallel execution for `viz-workflow` recipes.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Cite this software as:
> Trey Stafford, Robyn Marowitz, Rushiraj Nenuji, Matthew B. Jones, Twila
> Moon, Jeanette Clark. 2026. Open Geospatial Data Cloud (OGDC) Runner: Client tool and API for
> scalable and repeatable geospatial data transformation recipes. (version
> 0.4.0). Arctic Data Center.
> 0.4.1). Arctic Data Center.
> [doi:10.18739/A2P26Q57N](https://doi.org/doi:10.18739/A2P26Q57N)

<!-- prettier-ignore-start -->
Expand Down
6 changes: 0 additions & 6 deletions client_secrets.json

This file was deleted.

4 changes: 4 additions & 0 deletions docs/architecture/service.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ New users can be given access via the keycloak administrator user interface.
- `OGDC_DB_NAME` (optional): database name, defaults to `ogdc`.
- `OGDC_SECRET_KEY` (optional): value of the secret key used to encode/decode
cookies used for authentication.
- `ACCESS_MODE` (optional): one of `authenticated`, `read_only`, or `open`;
defaults to `authenticated`.
- `OIDC_CLIENT_SECRETS_FILE`: mounted OIDC client JSON path. Required in
authenticated mode; changes require a service restart.
- `OGDC_WORKFLOW_PVC_NAME` (optional): name of the PersistentVolumeClaim mounted
into workflow pods, defaults to `cephfs-qgnet-ogdc-workflow-pvc`. The
ogdc-helm chart sets this to `cephfs-${RELEASE_NAME}-workflow-pvc`.
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ authors = [
{ name = "Robyn Marowitz", email = "robyn.marowitz@colorado.edu" },
{ name = "Jeanette Clark", email = "clark@nceas.ucsb.edu" },
]
version = "0.4.0"
version = "0.4.1"
description = "Defines OGDC recipe API(s) and submits recipes to OGDC for execution"
readme = "README.md"
license.file = "LICENSE"
Expand Down Expand Up @@ -94,7 +94,7 @@ requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.bumpversion]
current_version = "0.4.0"
current_version = "0.4.1"
commit = false
tag = false

Expand Down
2 changes: 1 addition & 1 deletion src/ogdc_runner/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
) # Use your package's name as registered on PyPI
except PackageNotFoundError:
# This block handles cases where the package is imported but not yet installed (e.g., development mode)
__version__ = "0.4.0"
__version__ = "0.4.1"

__all__ = ["__version__"]
31 changes: 27 additions & 4 deletions src/ogdc_runner/service/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,35 @@
from __future__ import annotations

import os
from typing import Any

from dataone.auth import AuthFactory, load_client_secrets
from dataone.auth import (
ACCESS_MODE_AUTHENTICATED,
AuthFactory,
ConfigurationError,
get_access_mode,
load_client_secrets,
)

SCOPE_ADMIN = os.getenv("OGDC_SCOPE_ADMIN", "odgc:admin")
SCOPE_ADMIN = os.getenv("OGDC_SCOPE_ADMIN", "ogdc:admin")
scopes = [SCOPE_ADMIN]

# load secrets and instantiate the client
secrets = load_client_secrets()

def load_oidc_client_secrets() -> dict[str, Any]:
"""Load mounted OIDC credentials when authentication is enabled."""
if get_access_mode() != ACCESS_MODE_AUTHENTICATED:
return {}

secrets_file = os.getenv("OIDC_CLIENT_SECRETS_FILE")
if not secrets_file:
msg = (
"OIDC_CLIENT_SECRETS_FILE must point to a client secrets JSON file "
"when ACCESS_MODE=authenticated"
)
raise ConfigurationError(msg)

return load_client_secrets(secrets_file)


secrets = load_oidc_client_secrets()
auth_client = AuthFactory.create_client("fastapi", secrets, scopes)
2 changes: 1 addition & 1 deletion src/ogdc_runner/service/auth_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

bearer_schema = HTTPBearer(auto_error=False)

SCOPE_ADMIN = os.getenv("OGDC_SCOPE_ADMIN", "odgc:admin")
SCOPE_ADMIN = os.getenv("OGDC_SCOPE_ADMIN", "ogdc:admin")

router = APIRouter(
# Require that all routes in this module be authenticated via an access
Expand Down
2 changes: 1 addition & 1 deletion src/ogdc_runner/workflow/viz_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
# Viz worker container image. Override via VIZ_WORKFLOW_IMAGE env var.
VIZ_WORKFLOW_IMAGE: str = os.environ.get(
"VIZ_WORKFLOW_IMAGE",
"ghcr.io/permafrostdiscoverygateway/viz-workflow:latest",
"ghcr.io/permafrostdiscoverygateway/pdgworkflow:latest",
)
VIZ_WORKFLOW_IMAGE_PULL_POLICY: str = os.environ.get(
"VIZ_WORKFLOW_IMAGE_PULL_POLICY",
Expand Down
4 changes: 4 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from __future__ import annotations

import os
from functools import cache
from pathlib import Path

import pytest
from sqlmodel import StaticPool, create_engine

# OIDC-specific tests provide their own temporary credentials.
os.environ.setdefault("ACCESS_MODE", "open")

from ogdc_runner.service import db

SHELL_RECIPE_TEST_PATH = Path(__file__).parent / "test_shell_workflow_recipe_dir"
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_ogdc_viz_recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from ogdc_runner.workflow import viz_workflow

DEFAULT_VIZ_WORKFLOW_TEST_IMAGE = (
"ghcr.io/permafrostdiscoverygateway/viz-workflow:1.1.0-dev-2"
"ghcr.io/permafrostdiscoverygateway/pdgworkflow:latest"
)


Expand Down
39 changes: 39 additions & 0 deletions tests/unit/test_auth_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from __future__ import annotations

import json

import pytest
from dataone.auth import ConfigurationError

from ogdc_runner.service import auth


def test_authenticated_mode_loads_explicit_secrets_file(tmp_path, monkeypatch):
secrets_path = tmp_path / "client_secrets.json"
expected = {
"server_metadata_url": "https://auth.example/realms/test/.well-known/openid-configuration",
"client_id": "ogdc-test",
"client_secret": "not-a-real-secret",
"redirect_uris": ["https://ogdc.example/api/authorize"],
}
secrets_path.write_text(json.dumps(expected))
monkeypatch.setenv("ACCESS_MODE", "authenticated")
monkeypatch.setenv("OIDC_CLIENT_SECRETS_FILE", str(secrets_path))

assert auth.load_oidc_client_secrets() == expected


def test_authenticated_mode_requires_explicit_secrets_file(monkeypatch):
monkeypatch.setenv("ACCESS_MODE", "authenticated")
monkeypatch.delenv("OIDC_CLIENT_SECRETS_FILE", raising=False)

with pytest.raises(ConfigurationError, match="OIDC_CLIENT_SECRETS_FILE"):
auth.load_oidc_client_secrets()


@pytest.mark.parametrize("access_mode", ["open", "read_only"])
def test_unauthenticated_modes_do_not_require_oidc_secret(access_mode, monkeypatch):
monkeypatch.setenv("ACCESS_MODE", access_mode)
monkeypatch.delenv("OIDC_CLIENT_SECRETS_FILE", raising=False)

assert auth.load_oidc_client_secrets() == {}
Loading