Skip to content

Commit

Permalink
add and enable v3 endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
briantist committed Oct 6, 2023
1 parent 54c880f commit 1b51084
Show file tree
Hide file tree
Showing 3 changed files with 349 additions and 8 deletions.
8 changes: 4 additions & 4 deletions galactory/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
# (c) 2022 Brian Scholer (@briantist)

from flask import Blueprint, jsonify
from flask import Blueprint

from .v2 import bp as v2
from .v3 import bp as v3

API_RESPONSE = {
'available_versions': {
'v2': 'v2/',
# 'v3': 'v3/',
'v3': 'v3/',
},
'current_version': 'v2',
'current_version': 'v2', # This field doesn't exist in the v3 output anyway.
'description': 'GALAXY REST API',
}

Expand All @@ -22,4 +22,4 @@
@bp.route('')
@bp.route('/')
def api():
return jsonify(API_RESPONSE)
return API_RESPONSE
8 changes: 4 additions & 4 deletions galactory/api/v3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

bp = Blueprint('v3', __name__, url_prefix='/v3')

# from ..v2 import (
# collection_imports,
# collections,
# )
from . import (
# collection_imports,
collections,
)
341 changes: 341 additions & 0 deletions galactory/api/v3/collections.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
# -*- coding: utf-8 -*-
# (c) 2023 Brian Scholer (@briantist)

from semver import VersionInfo
from base64io import Base64IO
from flask import Response, jsonify, abort, url_for, request, current_app

from . import bp as v3
from ... import constants as C
from ...utilities import (
discover_collections,
authorize,
_chunk_to_temp,
upload_collection_from_hashed_tempfile,
)
from ...upstream import ProxyUpstream
from ...models import CollectionCollection


@v3.route('/collections')
@v3.route('/collections/')
@v3.route('/plugin/ansible/content/published/collections/index')
@v3.route('/plugin/ansible/content/published/collections/index/', endpoint='collections')
def collections():
repository = authorize(request, current_app.config['ARTIFACTORY_PATH'])
scheme = current_app.config.get('PREFERRED_URL_SCHEME')

results = []
colcol = CollectionCollection.from_collections(discover_collections(repo=repository))

for colgroup in colcol.values():
result = {
'href': url_for(
".collection",
namespace=colgroup.namespace,
collection=colgroup.name,
_external=False,
_scheme=scheme
),
'name': colgroup.name,
'namespace': colgroup.namespace,
'deprecated': False, # FIXME
'created_at': colgroup.latest.created,
'updated_at': colgroup.latest.modified,
'versions_url': url_for(
".versions",
namespace=colgroup.latest.namespace,
collection=colgroup.latest.name,
_external=False,
_scheme=scheme,
),
'highest_version': {
'href': url_for(
".version",
namespace=colgroup.latest.namespace,
collection=colgroup.latest.name,
version=colgroup.latest.version,
_external=False,
_scheme=scheme,
),
"version": colgroup.latest.version,
},
}
results.append(result)

this_url = url_for(
".collections",
_external=False,
_scheme=scheme,
**request.args
)

out = {
'meta': {
'count': len(results),
},
'links': { # FIXME
'first': this_url,
'previous': None,
'next': None,
'last': this_url,
},
'data': results,
}

return out


@v3.route('/collections/<namespace>/<collection>')
@v3.route('/collections/<namespace>/<collection>/')
@v3.route('/plugin/ansible/content/published/collections/index/<namespace>/<collection>')
@v3.route('/plugin/ansible/content/published/collections/index/<namespace>/<collection>/', endpoint='collection')
def collection(namespace, collection):
repository = authorize(request, current_app.config['ARTIFACTORY_PATH'])
upstream = current_app.config['PROXY_UPSTREAM']
no_proxy = current_app.config['NO_PROXY_NAMESPACES']
cache_minutes = current_app.config['CACHE_MINUTES']
cache_read = current_app.config['CACHE_READ']
cache_write = current_app.config['CACHE_WRITE']
scheme = current_app.config.get('PREFERRED_URL_SCHEME')

upstream_result = None
if upstream and (not no_proxy or namespace not in no_proxy):
proxy = ProxyUpstream(repository, upstream, cache_read, cache_write, cache_minutes)
upstream_result = proxy.proxy(request)

colcol = CollectionCollection.from_collections(discover_collections(repo=repository, namespace=namespace, name=collection))


if not (colcol or upstream_result):
abort(C.HTTP_NOT_FOUND)

colgroup = None
if colcol:
if len(colcol) > 1:
abort(C.HTTP_INTERNAL_SERVER_ERROR)
colgroup = next(iter(colcol.values()))

if upstream_result:
if colgroup is None:
result = upstream_result
else:
try:
upstream_version = VersionInfo.parse(upstream_result['highest_version']['version'])
except (KeyError, ValueError):
# TODO: warn?
pass
else:
if colgroup.latest < upstream_version:
return upstream_result

result = {
'href': url_for(
".collection",
namespace=colgroup.namespace,
collection=colgroup.name,
_external=False,
_scheme=scheme
),
'name': colgroup.latest.name,
'namespace': colgroup.latest.namespace,
'deprecated': False, # FIXME
'created_at': colgroup.latest.created,
'updated_at': colgroup.latest.modified,
'versions_url': url_for(
".versions",
namespace=colgroup.latest.namespace,
collection=colgroup.latest.name,
_external=False,
_scheme=scheme,
),
'highest_version': {
'href': url_for(
".version",
namespace=colgroup.latest.namespace,
collection=colgroup.latest.name,
version=colgroup.latest.version,
_external=False,
_scheme=scheme,
),
"version": colgroup.latest.version,
},
}
return result

@v3.route('/collections/<namespace>/<collection>/versions')
@v3.route('/collections/<namespace>/<collection>/versions/')
@v3.route('/plugin/ansible/content/published/collections/index/<namespace>/<collection>/versions')
@v3.route('/plugin/ansible/content/published/collections/index/<namespace>/<collection>/versions/', endpoint='versions')
def versions(namespace, collection):
results = []
repository = authorize(request, current_app.config['ARTIFACTORY_PATH'])
upstream = current_app.config['PROXY_UPSTREAM']
no_proxy = current_app.config['NO_PROXY_NAMESPACES']
cache_minutes = current_app.config['CACHE_MINUTES']
cache_read = current_app.config['CACHE_READ']
cache_write = current_app.config['CACHE_WRITE']
scheme = current_app.config.get('PREFERRED_URL_SCHEME')

upstream_result = None
if upstream and (not no_proxy or namespace not in no_proxy):
proxy = ProxyUpstream(repository, upstream, cache_read, cache_write, cache_minutes)
upstream_result = proxy.proxy(request)

collections = CollectionCollection.from_collections(discover_collections(repo=repository, namespace=namespace, name=collection))

if not (collections or upstream_result):
abort(C.HTTP_NOT_FOUND)

if len(collections) > 1:
abort(C.HTTP_INTERNAL_SERVER_ERROR)

col = next(iter(collections.values()))
vers = set()
for i in col.values():
results.append(
{
'href': url_for(
".version",
namespace=i.namespace,
collection=i.name,
version=i.version,
_external=False,
_scheme=scheme,
),
'version': i.version,
'created_at': i.created,
'updated_at': i.modified,
'marks': [],
'requires_ansible': None, # FIXME
}
)
vers.add(i.version)

if upstream_result:
for item in upstream_result['data']:
if item['version'] not in vers:
results.append(item)

this_url = url_for(
".versions",
namespace=namespace,
collection=collection,
_external=False,
_scheme=scheme,
**request.args
)

out = {
'meta': {
'count': len(results),
},
'links': { # FIXME
'first': this_url,
'previous': None,
'next': None,
'last': this_url,
},
'data': results,
}

return out


@v3.route('/collections/<namespace>/<collection>/versions/<version>')
@v3.route('/collections/<namespace>/<collection>/versions/<version>/')
@v3.route('/plugin/ansible/content/published/collections/index/<namespace>/<collection>/versions/<version>')
@v3.route('/plugin/ansible/content/published/collections/index/<namespace>/<collection>/versions/<version>/', endpoint='version')
def version(namespace, collection, version):
repository = authorize(request, current_app.config['ARTIFACTORY_PATH'])
upstream = current_app.config['PROXY_UPSTREAM']
no_proxy = current_app.config['NO_PROXY_NAMESPACES']
cache_minutes = current_app.config['CACHE_MINUTES']
cache_read = current_app.config['CACHE_READ']
cache_write = current_app.config['CACHE_WRITE']
scheme = current_app.config.get('PREFERRED_URL_SCHEME')

try:
info = next(discover_collections(repository, namespace=namespace, name=collection, version=version))
except StopIteration:
if upstream and (not no_proxy or namespace not in no_proxy):
proxy = ProxyUpstream(repository, upstream, cache_read, cache_write, cache_minutes)
upstream_result = proxy.proxy(request)
upstream_result['download_url'] = url_for(
'download.download',
filename=upstream_result['artifact']['filename'],
_external=True,
_scheme=scheme,
**{C.QUERY_DOWNLOAD_UPSTREAM_URL: upstream_result['download_url']},
)
return upstream_result
else:
abort(C.HTTP_NOT_FOUND)

out = {
'artifact': {
'filename': info.filename,
'sha256': info.sha256,
'size': info.size,
},
'collection': {
'href': url_for(
".collection",
namespace=info.namespace,
collection=info.name,
_external=False,
_scheme=scheme,
),
'name': info.name,
},
'namespace': {
'name': info.namespace,
},
'download_url': url_for(
'download.download',
filename=info.filename,
_external=True,
_scheme=scheme,
),
'name': info.name,
'signatures': [],
'hidden': False,
'href': url_for(
".collection",
namespace=info.namespace,
collection=info.name,
_external=False,
_scheme=scheme
),
'id': 0,
'metadata': info.collection_info,
'version': info.version,
'created_at': info.created,
'updated_at': info.modified,
'requires_ansible': None, # FIXME
'marks': [],
}
return out


# not going to preserve the v2 paths for uploading
# @v3.route('/collections', methods=['POST'])
# @v3.route('/collections/', methods=['POST'])
@v3.route('/artifacts/collections', methods=['POST'])
@v3.route('/artifacts/collections/', methods=['POST'], endpoint='publish')
def publish():
sha256 = request.form['sha256']
file = request.files['file']
skip_configured_auth = current_app.config['PUBLISH_SKIP_CONFIGURED_AUTH']
property_fallback = current_app.config.get('USE_PROPERTY_FALLBACK', False)
_scheme = current_app.config.get('PREFERRED_URL_SCHEME')

target = authorize(request, current_app.config['ARTIFACTORY_PATH'] / file.filename, skip_configured_auth=skip_configured_auth)

with _chunk_to_temp(Base64IO(file)) as tmp:
if tmp.sha256 != sha256:
abort(Response(f"Hash mismatch: uploaded=='{sha256}', calculated=='{tmp.sha256}'", C.HTTP_INTERNAL_SERVER_ERROR))

upload_collection_from_hashed_tempfile(target, tmp, property_fallback=property_fallback)

return jsonify(task=url_for(".import_singleton", _external=True, _scheme=_scheme)) # FIXME: external?

0 comments on commit 1b51084

Please sign in to comment.