Skip to content

Commit

Permalink
Add a way for Butler server to serve static files
Browse files Browse the repository at this point in the history
You can now set the environment variable DAF_BUTLER_SERVER_STATIC_FILES_PATH to point to a directory of static files to be served by Butler server from its `/configs/` HTTP path.

There isn't currently a good place for the DirectButler configuration files used in the RSP lab containers to live.  These need to be controlled by Phalanx so we can update them as part of the IDF deployment process, but Phalanx doesn't provide a convenient way of deploying static files.  Much of this configuration is shared with Butler server or updated at the same time that Butler server is updated.  So it is convenient for the moment to just serve these files from Butler server rather than standing up additional infrastructure to host them.
  • Loading branch information
dhirving committed Aug 5, 2024
1 parent 106dc9f commit 9189050
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 0 deletions.
43 changes: 43 additions & 0 deletions python/lsst/daf/butler/remote_butler/server/_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# This file is part of daf_butler.
#
# Developed for the LSST Data Management System.
# This product includes software developed by the LSST Project
# (http://www.lsst.org).
# See the COPYRIGHT file at the top-level directory of this distribution
# for details of code ownership.
#
# This software is dual licensed under the GNU General Public License and also
# under a 3-clause BSD license. Recipients may choose which of these licenses
# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
# respectively. If you choose the GPL option then the following text applies
# (but note that there is still no warranty even if you opt for BSD instead):
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from __future__ import annotations

import dataclasses
import os


@dataclasses.dataclass(frozen=True)
class ButlerServerConfig:
static_files_path: str | None
"""Absolute path to a directory of files that will be served to end-users
as static files from the `configs/` HTTP route.
"""


def load_config() -> ButlerServerConfig:
return ButlerServerConfig(static_files_path=os.environ.get("DAF_BUTLER_SERVER_STATIC_FILES_PATH"))
15 changes: 15 additions & 0 deletions python/lsst/daf/butler/remote_butler/server/_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@
import safir.dependencies.logger
from fastapi import FastAPI, Request, Response
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.staticfiles import StaticFiles
from safir.logging import configure_logging, configure_uvicorn_logging

from ..._exceptions import ButlerUserError
from .._errors import serialize_butler_user_error
from ..server_models import CLIENT_REQUEST_ID_HEADER_NAME, ERROR_STATUS_CODE, ErrorResponseModel
from ._config import load_config
from .handlers._external import external_router
from .handlers._external_query import query_router
from .handlers._internal import internal_router
Expand All @@ -49,6 +51,8 @@

def create_app() -> FastAPI:
"""Create a Butler server FastAPI application."""
config = load_config()

app = FastAPI()
app.add_middleware(GZipMiddleware, minimum_size=1000)

Expand All @@ -67,6 +71,17 @@ def create_app() -> FastAPI:
)
app.include_router(internal_router)

# If configured to do so, serve a directory of static files via HTTP.
#
# Until we are able to fully transition away from DirectButler for the RSP,
# we need a place to host DirectButler configuration files. Since this
# same configuration is needed for Butler server, it's easier to configure
# in Phalanx if we just host them from Butler server itself.
# This will also host the end-user repository index file for the RSP, for
# lack of a better place to put it.
if config.static_files_path:
app.mount(f"{default_api_path}/configs", StaticFiles(directory=config.static_files_path))

# Any time an exception is returned by a handler, add a log message that
# includes the username and request ID from the client. This will make it
# easier to track down user-reported issues in the logs.
Expand Down
2 changes: 2 additions & 0 deletions python/lsst/daf/butler/tests/server_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,7 @@ def _is_authenticated_endpoint(path: str) -> bool:
return False
if path.endswith("/butler.json"):
return False
if path.startswith("/api/butler/configs"):
return False

return True
12 changes: 12 additions & 0 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os.path
import tempfile
import unittest
import uuid

Expand Down Expand Up @@ -106,6 +107,17 @@ def test_health_check(self):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()["name"], "butler")

def test_static_files(self):
with tempfile.TemporaryDirectory() as tmpdir:
with open(os.path.join(tmpdir, "temp.txt"), "w") as fh:
fh.write("test data 123")

with mock_env({"DAF_BUTLER_SERVER_STATIC_FILES_PATH": tmpdir}):
with create_test_server(TESTDIR) as server:
response = server.client.get("/api/butler/configs/temp.txt")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.text, "test data 123")

def test_dimension_universe(self):
universe = self.butler.dimensions
self.assertEqual(universe.namespace, "daf_butler")
Expand Down

0 comments on commit 9189050

Please sign in to comment.