Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drs endpoints #261

Merged
merged 18 commits into from
Mar 17, 2020
6 changes: 6 additions & 0 deletions indexd/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
from .index.blueprint import blueprint as indexd_index_blueprint
from .alias.blueprint import blueprint as indexd_alias_blueprint
from .dos.blueprint import blueprint as indexd_dos_blueprint
from .drs.blueprint import blueprint as indexd_drs_blueprint
from .blueprint import blueprint as cross_blueprint

from indexd.fence_client import FenceClient
from indexd.urls.blueprint import blueprint as index_urls_blueprint

import os
Expand All @@ -19,10 +21,14 @@ def app_init(app, settings=None):
from .default_settings import settings
app.config.update(settings["config"])
app.auth = settings["auth"]
app.fence_client = FenceClient(
url=os.environ.get("FENCE_URL") or "http://fence-service"
paulineribeyre marked this conversation as resolved.
Show resolved Hide resolved
)
app.register_blueprint(indexd_bulk_blueprint)
app.register_blueprint(indexd_index_blueprint)
app.register_blueprint(indexd_alias_blueprint)
app.register_blueprint(indexd_dos_blueprint)
app.register_blueprint(indexd_drs_blueprint)
app.register_blueprint(cross_blueprint)
app.register_blueprint(index_urls_blueprint, url_prefix="/_query/urls")

Expand Down
2 changes: 1 addition & 1 deletion indexd/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def authorize(*p):
not present, or fallback to the previous check.
"""
if len(p) == 1:
f, = p
(f,) = p

@wraps(f)
def check_auth(*args, **kwargs):
Expand Down
8 changes: 8 additions & 0 deletions indexd/default_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
)
}

CONFIG["FENCE_URL"] = "http://fence-service/"

CONFIG["DIST"] = [
{
"name": "Other IndexD",
Expand All @@ -46,6 +48,12 @@
"hints": [],
"type": "dos",
},
{
"name": "DRS System",
"host": "https://example.com/api/ga4gh/drs/v1/",
"hints": [],
"type": "drs",
},
]

AUTH = SQLAlchemyAuthDriver("sqlite:///auth.sq3")
Expand Down
Empty file added indexd/drs/__init__.py
Empty file.
147 changes: 147 additions & 0 deletions indexd/drs/blueprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import flask
import os
from indexd.errors import AuthError
from indexd.errors import UserError
from indexd.index.errors import NoRecordFound as IndexNoRecordFound
from indexd.errors import UnexpectedError
from indexd.index.blueprint import get_index

blueprint = flask.Blueprint("drs", __name__)

blueprint.config = dict()
blueprint.index_driver = None


@blueprint.route("/ga4gh/drs/v1/objects/<path:object_id>", methods=["GET"])
def get_drs_object(object_id):
BinamB marked this conversation as resolved.
Show resolved Hide resolved
"""
Returns a specific DRSobject with object_id
"""
ret = blueprint.index_driver.get(object_id)

return flask.jsonify(indexd_to_drs(ret)), 200


@blueprint.route("/ga4gh/drs/v1/objects", methods=["GET"])
def list_drs_records():
BinamB marked this conversation as resolved.
Show resolved Hide resolved
records = get_index()[0].json["records"]
ret = {
"drs_objects": [indexd_to_drs(record, True)["drs_object"] for record in records]
}

return flask.jsonify(ret), 200


@blueprint.route(
"/ga4gh/drs/v1/objects/<path:object_id>/access/<path:access_id>", methods=["GET"]
)
def get_signed_url(object_id, access_id):
res = flask.current_app.fence_client.get_signed_url_for_object(
object_id=object_id, access_id=access_id
)
if not res:
raise IndexNoRecordFound("No signed url found")

return res, 200


def indexd_to_drs(record, list_drs=False):
bearer_token = flask.request.headers.get("AUTHORIZATION")

server = os.environ.get("FENCE_URL") or "http://fence-service"

description = "drs://" + get_server_name(server) + "/" + record["did"]
drs_object = {
"id": record["did"],
"description": description,
"mime_type": "application/json",
}

drs_object["name"] = record["file_name"]
drs_object["created_time"] = record["created_date"]
drs_object["updated_time"] = record["updated_date"]
drs_object["size"] = record["size"]

drs_object["self_uri"] = ""
drs_object["aliases"] = drs_object["contents"] = []
if "self_uri" in record:
drs_object["self_uri"] = record["self_uri"]

if "alias" in record:
drs_object["aliases"].append(record["alias"])

if "contents" in record:
drs_object["contents"] = record["contents"]

# access_methods mapping
if "urls" in record:
drs_object["access_methods"] = []
for location in record["urls"]:
location_type = location.split(":")[
0
] # (s3, gs, ftp, gsiftp, globus, htsget, https, file)

drs_object["access_methods"].append(
{
"type": location_type,
"access_url": flask.current_app.fence_client.get_signed_url_for_object(
record["did"], ""
)
if bearer_token and not list_drs
else {"url": location},
"access_id": location_type,
"region": "",
}
)
print(drs_object)

# parse out checksums
drs_object["checksums"] = []
for k in record["hashes"]:
drs_object["checksums"].append({"checksum": record["hashes"][k], "type": k})

result = {"drs_object": drs_object}

return result


def get_server_name(server):
for i in range(len(server)):
if server[i] == ":":
return server[i + 3 :].rstrip("/")


@blueprint.errorhandler(UserError)
def handle_user_error(err):
ret = {"msg": str(err), "status_code": 400}
return flask.jsonify(ret), 400


@blueprint.errorhandler(AuthError)
def handle_auth_error(err):
ret = {"msg": str(err), "status_code": 401}
return flask.jsonify(ret), 401


@blueprint.errorhandler(AuthError)
def handle_requester_auth_error(err):
ret = {"msg": str(err), "status_code": 403}
return flask.jsonify(ret), 403


@blueprint.errorhandler(IndexNoRecordFound)
def handle_no_index_record_error(err):
ret = {"msg": str(err), "status_code": 404}
return flask.jsonify(ret), 404


@blueprint.errorhandler(UnexpectedError)
def handle_unexpected_error(err):
ret = {"msg": str(err), "status_code": 500}
return flask.jsonify(ret), 500


@blueprint.record
def get_config(setup_state):
index_config = setup_state.app.config["INDEX"]
blueprint.index_driver = index_config["driver"]
6 changes: 6 additions & 0 deletions indexd/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ class ConfigurationError(Exception):
"""
Configuration error.
"""


class UnexpectedError(Exception):
"""
Unexpected Error
"""
51 changes: 51 additions & 0 deletions indexd/fence_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from cached_property import cached_property
paulineribeyre marked this conversation as resolved.
Show resolved Hide resolved
from cdislogging import get_logger
import os

# from cdispyutils.config import get_value
# from cdispyutils.hmac4 import generate_aws_presigned_url
paulineribeyre marked this conversation as resolved.
Show resolved Hide resolved
import flask
import requests

try:
from local_settings import settings
except ImportError:
from indexd.default_settings import settings

from indexd.index.errors import NoRecordFound as IndexNoRecordFound
from indexd.errors import UnexpectedError


logger = get_logger(__name__)


class FenceClient(object):
def __init__(self, url):
self.url = url

def get_signed_url_for_object(self, object_id, access_id):
fence_server = self.url
api_url = fence_server.rstrip("/") + "/data/download/"
url = api_url + object_id
if access_id:
url += "?protocol=" + access_id
headers = flask.request.headers
try:
req = requests.get(url, headers=headers)
except Exception as e:
logger.error("failed to reach fence at {0}: {1}".format(url + object_id, e))
raise UnexpectedError("Failed to retrieve access url")
if req.status_code == 404:
logger.error(
"Not found. Fence could not find {}: {} with access id: {}".format(
url + object_id, req.text, access_id
)
)
raise IndexNoRecordFound(
"No document with id:{} with access_id:{}".format(
self.file_id, access_id
)
)
elif req.status_code != 200:
raise UnexpectedError(req.text)
return req.json()
BinamB marked this conversation as resolved.
Show resolved Hide resolved
Loading