endpoints.appr: init
This commit is contained in:
parent
6fe6ea0bcb
commit
102c671587
3 changed files with 501 additions and 0 deletions
72
endpoints/appr/__init__.py
Normal file
72
endpoints/appr/__init__.py
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from cnr.exception import UnauthorizedAccess
|
||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
from app import metric_queue
|
||||||
|
from auth.permissions import (AdministerRepositoryPermission, ReadRepositoryPermission,
|
||||||
|
ModifyRepositoryPermission)
|
||||||
|
from data import model # TODO: stop using model directly
|
||||||
|
from util.metrics.metricqueue import time_blueprint
|
||||||
|
|
||||||
|
|
||||||
|
appr_bp = Blueprint('appr', __name__)
|
||||||
|
time_blueprint(appr_bp, metric_queue)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _raise_unauthorized(repository, scopes):
|
||||||
|
raise StandardError("Unauthorized acces to %s", repository)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_reponame_kwargs(*args, **kwargs):
|
||||||
|
return [kwargs['namespace_name'], kwargs['repo_name']]
|
||||||
|
|
||||||
|
|
||||||
|
def require_repo_permission(permission_class, scopes=None, allow_public=False,
|
||||||
|
raise_method=_raise_unauthorized,
|
||||||
|
get_reponame_method=_get_reponame_kwargs):
|
||||||
|
def wrapper(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapped(*args, **kwargs):
|
||||||
|
namespace_name, repo_name = get_reponame_method(*args, **kwargs)
|
||||||
|
|
||||||
|
logger.debug('Checking permission %s for repo: %s/%s', permission_class,
|
||||||
|
namespace_name, repo_name)
|
||||||
|
permission = permission_class(namespace_name, repo_name)
|
||||||
|
if (permission.can() or
|
||||||
|
(allow_public and
|
||||||
|
model.repository.repository_is_public(namespace_name, repo_name))):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
repository = namespace_name + '/' + repo_name
|
||||||
|
raise_method(repository, scopes)
|
||||||
|
return wrapped
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def _raise_method(repository, scopes):
|
||||||
|
raise UnauthorizedAccess("Unauthorized access for: %s" % repository,
|
||||||
|
{"package": repository, "scopes": scopes})
|
||||||
|
|
||||||
|
|
||||||
|
def _get_reponame_kwargs(*args, **kwargs):
|
||||||
|
return [kwargs['namespace'], kwargs['package_name']]
|
||||||
|
|
||||||
|
|
||||||
|
require_app_repo_read = require_repo_permission(ReadRepositoryPermission,
|
||||||
|
scopes=['pull'],
|
||||||
|
allow_public=True,
|
||||||
|
raise_method=_raise_method,
|
||||||
|
get_reponame_method=_get_reponame_kwargs)
|
||||||
|
|
||||||
|
require_app_repo_write = require_repo_permission(ModifyRepositoryPermission,
|
||||||
|
scopes=['pull', 'push'],
|
||||||
|
raise_method=_raise_method,
|
||||||
|
get_reponame_method=_get_reponame_kwargs)
|
||||||
|
|
||||||
|
require_app_repo_admin = require_repo_permission(AdministerRepositoryPermission,
|
||||||
|
scopes=['pull', 'push'],
|
||||||
|
raise_method=_raise_method,
|
||||||
|
get_reponame_method=_get_reponame_kwargs)
|
160
endpoints/appr/cnr_backend.py
Normal file
160
endpoints/appr/cnr_backend.py
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from cnr.exception import raise_package_not_found
|
||||||
|
from cnr.models.blob_base import BlobBase
|
||||||
|
from cnr.models.channel_base import ChannelBase
|
||||||
|
from cnr.models.db_base import CnrDB
|
||||||
|
from cnr.models.package_base import PackageBase, manifest_media_type
|
||||||
|
|
||||||
|
from app import storage
|
||||||
|
from data.interfaces.appr import oci_app_model
|
||||||
|
from data.oci_model import blob # TODO these calls should be through oci_app_model
|
||||||
|
|
||||||
|
|
||||||
|
class Blob(BlobBase):
|
||||||
|
@classmethod
|
||||||
|
def upload_url(cls, digest):
|
||||||
|
return "cnr/blobs/sha256/%s/%s" % (digest[0:2], digest)
|
||||||
|
|
||||||
|
def save(self, content_media_type):
|
||||||
|
oci_app_model.store_blob(self, content_media_type)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete(cls, package_name, digest):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _fetch_b64blob(cls, package_name, digest):
|
||||||
|
blobpath = cls.upload_url(digest)
|
||||||
|
locations = blob.get_blob_locations(digest)
|
||||||
|
if not locations:
|
||||||
|
raise_package_not_found(package_name, digest)
|
||||||
|
return base64.b64encode(storage.get_content(locations, blobpath))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def download_url(cls, package_name, digest):
|
||||||
|
blobpath = cls.upload_url(digest)
|
||||||
|
locations = blob.get_blob_locations(digest)
|
||||||
|
if not locations:
|
||||||
|
raise_package_not_found(package_name, digest)
|
||||||
|
return storage.get_direct_download_url(locations, blobpath)
|
||||||
|
|
||||||
|
|
||||||
|
class Channel(ChannelBase):
|
||||||
|
""" CNR Channel model implemented against the Quay data model. """
|
||||||
|
def __init__(self, name, package, current=None):
|
||||||
|
super(Channel, self).__init__(name, package, current=current)
|
||||||
|
self._channel_data = None
|
||||||
|
|
||||||
|
def _exists(self):
|
||||||
|
""" Check if the channel is saved already """
|
||||||
|
return oci_app_model.channel_exists(self.package, self.name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, name, package):
|
||||||
|
chanview = oci_app_model.fetch_channel(package, name, with_releases=False)
|
||||||
|
return cls(name, package, chanview.current)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
oci_app_model.update_channel(self.package, self.name, self.current)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
oci_app_model.delete_channel(self.package, self.name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def all(cls, package_name):
|
||||||
|
return [Channel(c.name, package_name, c.current)
|
||||||
|
for c in oci_app_model.list_channels(package_name)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _channel(self):
|
||||||
|
if self._channel_data is None:
|
||||||
|
self._channel_data = oci_app_model.fetch_channel(self.package, self.name)
|
||||||
|
return self._channel_data
|
||||||
|
|
||||||
|
def releases(self):
|
||||||
|
""" Returns the list of versions """
|
||||||
|
return self._channel.releases
|
||||||
|
|
||||||
|
def _add_release(self, release):
|
||||||
|
return oci_app_model.update_channel(self.package, self.name, release)._asdict
|
||||||
|
|
||||||
|
def _remove_release(self, release):
|
||||||
|
oci_app_model.delete_channel(self.package, self.name)
|
||||||
|
|
||||||
|
|
||||||
|
class Package(PackageBase):
|
||||||
|
""" CNR Package model implemented against the Quay data model. """
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _apptuple_to_dict(cls, apptuple):
|
||||||
|
return {'release': apptuple.release,
|
||||||
|
'created_at': apptuple.created_at,
|
||||||
|
'digest': apptuple.manifest.digest,
|
||||||
|
'mediaType': apptuple.manifest.mediaType,
|
||||||
|
'package': apptuple.name,
|
||||||
|
'content': apptuple.manifest.content._asdict()}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_repository(cls, package_name, visibility, owner):
|
||||||
|
oci_app_model.create_application(package_name, visibility, owner)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def exists(cls, package_name):
|
||||||
|
return oci_app_model.application_exists(package_name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def all(cls, organization=None, media_type=None, search=None, username=None, **kwargs):
|
||||||
|
return [dict(x._asdict()) for x in oci_app_model.list_applications(namespace=organization,
|
||||||
|
media_type=media_type,
|
||||||
|
search=search,
|
||||||
|
username=username)]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _fetch(cls, package_name, release, media_type):
|
||||||
|
data = oci_app_model.fetch_release(package_name, release, manifest_media_type(media_type))
|
||||||
|
return cls._apptuple_to_dict(data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def all_releases(cls, package_name, media_type=None):
|
||||||
|
return oci_app_model.list_releases(package_name, media_type)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def search(cls, query, username=None):
|
||||||
|
return oci_app_model.basic_search(query, username=username)
|
||||||
|
|
||||||
|
def _save(self, force=False, **kwargs):
|
||||||
|
user = kwargs['user']
|
||||||
|
visibility = kwargs['visibility']
|
||||||
|
oci_app_model.create_release(self, user, visibility, force)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _delete(cls, package_name, release, media_type):
|
||||||
|
oci_app_model.delete_release(package_name, release, manifest_media_type(media_type))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def isdeleted_release(cls, package, release):
|
||||||
|
return oci_app_model.release_exists(package, release)
|
||||||
|
|
||||||
|
def channels(self, channel_class, iscurrent=True):
|
||||||
|
return [c.name for c in oci_app_model.list_release_channels(self.package, self.release,
|
||||||
|
active=iscurrent)]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def manifests(cls, package, release=None):
|
||||||
|
return oci_app_model.list_manifests(package, release)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def dump_all(cls, blob_cls):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class QuayDB(CnrDB):
|
||||||
|
""" Wrapper Class to embed all CNR Models """
|
||||||
|
Channel = Channel
|
||||||
|
Package = Package
|
||||||
|
Blob = Blob
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def reset_db(cls, force=False):
|
||||||
|
pass
|
269
endpoints/appr/registry.py
Normal file
269
endpoints/appr/registry.py
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from base64 import b64encode
|
||||||
|
|
||||||
|
import cnr
|
||||||
|
|
||||||
|
from cnr.api.impl import registry as cnr_registry
|
||||||
|
from cnr.api.registry import repo_name, _pull
|
||||||
|
from cnr.exception import (CnrException, InvalidUsage, InvalidParams, InvalidRelease,
|
||||||
|
UnableToLockResource, UnauthorizedAccess, Unsupported, ChannelNotFound,
|
||||||
|
PackageAlreadyExists, PackageNotFound, PackageReleaseNotFound)
|
||||||
|
from flask import request, jsonify
|
||||||
|
|
||||||
|
from auth.process import process_auth
|
||||||
|
from auth.auth_context import get_authenticated_user
|
||||||
|
from auth.permissions import CreateRepositoryPermission, ModifyRepositoryPermission
|
||||||
|
from endpoints.appr import appr_bp, require_app_repo_read, require_app_repo_write
|
||||||
|
from endpoints.appr.cnr_backend import Package, Channel, Blob
|
||||||
|
from endpoints.decorators import anon_allowed, anon_protect
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.errorhandler(Unsupported)
|
||||||
|
@appr_bp.errorhandler(PackageAlreadyExists)
|
||||||
|
@appr_bp.errorhandler(InvalidRelease)
|
||||||
|
@appr_bp.errorhandler(UnableToLockResource)
|
||||||
|
@appr_bp.errorhandler(UnauthorizedAccess)
|
||||||
|
@appr_bp.errorhandler(PackageNotFound)
|
||||||
|
@appr_bp.errorhandler(PackageReleaseNotFound)
|
||||||
|
@appr_bp.errorhandler(CnrException)
|
||||||
|
@appr_bp.errorhandler(InvalidUsage)
|
||||||
|
@appr_bp.errorhandler(InvalidParams)
|
||||||
|
@appr_bp.errorhandler(ChannelNotFound)
|
||||||
|
def render_error(error):
|
||||||
|
response = jsonify({"error": error.to_dict()})
|
||||||
|
response.status_code = error.status_code
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.route("/version")
|
||||||
|
@anon_allowed
|
||||||
|
def version():
|
||||||
|
return jsonify({"cnr-api": cnr.__version__})
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.route("/api/v1/users/login", methods=['POST'])
|
||||||
|
@anon_allowed
|
||||||
|
def login():
|
||||||
|
"""
|
||||||
|
Todo:
|
||||||
|
* Implement better login protocol
|
||||||
|
"""
|
||||||
|
values = request.get_json(force=True, silent=True)
|
||||||
|
return jsonify({'token': "basic " + b64encode("%s:%s" % (values['user']['username'],
|
||||||
|
values['user']['password']))})
|
||||||
|
|
||||||
|
|
||||||
|
# @TODO: Redirect to S3 url
|
||||||
|
@appr_bp.route(
|
||||||
|
"/api/v1/packages/<string:namespace>/<string:package_name>/blobs/sha256/<string:digest>",
|
||||||
|
methods=['GET'],
|
||||||
|
strict_slashes=False,
|
||||||
|
)
|
||||||
|
def blobs(namespace, package_name, digest):
|
||||||
|
reponame = repo_name(namespace, package_name)
|
||||||
|
data = cnr_registry.pull_blob(reponame, digest, blob_class=Blob)
|
||||||
|
json_format = request.args.get('format', None) == 'json'
|
||||||
|
return _pull(data, json_format=json_format)
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.route("/api/v1/packages", methods=['GET'], strict_slashes=False)
|
||||||
|
@process_auth
|
||||||
|
@anon_protect
|
||||||
|
def list_packages():
|
||||||
|
namespace = request.args.get('namespace', None)
|
||||||
|
media_type = request.args.get('media_type', None)
|
||||||
|
query = request.args.get('query', None)
|
||||||
|
user = get_authenticated_user()
|
||||||
|
username = None
|
||||||
|
if user:
|
||||||
|
username = user.username
|
||||||
|
result_data = cnr_registry.list_packages(namespace,
|
||||||
|
package_class=Package,
|
||||||
|
search=query,
|
||||||
|
media_type=media_type,
|
||||||
|
username=username)
|
||||||
|
return jsonify(result_data)
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.route(
|
||||||
|
"/api/v1/packages/<string:namespace>/<string:package_name>/<string:release>/<string:media_type>",
|
||||||
|
methods=['DELETE'], strict_slashes=False)
|
||||||
|
@process_auth
|
||||||
|
@require_app_repo_write
|
||||||
|
@anon_protect
|
||||||
|
def delete_package(namespace, package_name, release, media_type):
|
||||||
|
reponame = repo_name(namespace, package_name)
|
||||||
|
result = cnr_registry.delete_package(reponame,
|
||||||
|
release,
|
||||||
|
media_type,
|
||||||
|
package_class=Package)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.route(
|
||||||
|
"/api/v1/packages/<string:namespace>/<string:package_name>/<string:release>/<string:media_type>",
|
||||||
|
methods=['GET'],
|
||||||
|
strict_slashes=False
|
||||||
|
)
|
||||||
|
def show_package(namespace, package_name, release, media_type):
|
||||||
|
reponame = repo_name(namespace, package_name)
|
||||||
|
result = cnr_registry.show_package(reponame, release,
|
||||||
|
media_type,
|
||||||
|
channel_class=Channel,
|
||||||
|
package_class=Package)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.route("/api/v1/packages/<string:namespace>/<string:package_name>", methods=['GET'],
|
||||||
|
strict_slashes=False)
|
||||||
|
@process_auth
|
||||||
|
@require_app_repo_read
|
||||||
|
@anon_protect
|
||||||
|
def show_package_releases(namespace, package_name):
|
||||||
|
reponame = repo_name(namespace, package_name)
|
||||||
|
media_type = request.args.get('media_type', None)
|
||||||
|
result = cnr_registry.show_package_releases(reponame,
|
||||||
|
media_type=media_type,
|
||||||
|
package_class=Package)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.route("/api/v1/packages/<string:namespace>/<string:package_name>/<string:release>",
|
||||||
|
methods=['GET'], strict_slashes=False)
|
||||||
|
@process_auth
|
||||||
|
@require_app_repo_read
|
||||||
|
@anon_protect
|
||||||
|
def show_package_releasse_manifests(namespace, package_name, release):
|
||||||
|
reponame = repo_name(namespace, package_name)
|
||||||
|
result = cnr_registry.show_package_manifests(reponame,
|
||||||
|
release,
|
||||||
|
package_class=Package)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.route(
|
||||||
|
"/api/v1/packages/<string:namespace>/<string:package_name>/<string:release>/<string:media_type>/pull",
|
||||||
|
methods=['GET'],
|
||||||
|
strict_slashes=False,
|
||||||
|
)
|
||||||
|
@process_auth
|
||||||
|
@require_app_repo_read
|
||||||
|
@anon_protect
|
||||||
|
def pull(namespace, package_name, release, media_type):
|
||||||
|
reponame = repo_name(namespace, package_name)
|
||||||
|
logger.info("pull %s", reponame)
|
||||||
|
data = cnr_registry.pull(reponame, release, media_type, Package, blob_class=Blob)
|
||||||
|
return _pull(data)
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.route("/api/v1/packages/<string:namespace>/<string:package_name>", methods=['POST'],
|
||||||
|
strict_slashes=False)
|
||||||
|
@process_auth
|
||||||
|
@anon_protect
|
||||||
|
def push(namespace, package_name):
|
||||||
|
reponame = repo_name(namespace, package_name)
|
||||||
|
values = request.get_json(force=True, silent=True)
|
||||||
|
release_version = values['release']
|
||||||
|
media_type = values['media_type']
|
||||||
|
force = request.args.get('force', 'false') == 'true'
|
||||||
|
private = values.get('visibility', 'public')
|
||||||
|
owner = get_authenticated_user()
|
||||||
|
if not Package.exists(reponame):
|
||||||
|
if not CreateRepositoryPermission(namespace).can():
|
||||||
|
raise UnauthorizedAccess("Unauthorized access for: %s" % reponame,
|
||||||
|
{"package": reponame, "scopes": ['create']})
|
||||||
|
Package.create_repository(reponame, private, owner)
|
||||||
|
|
||||||
|
|
||||||
|
if not ModifyRepositoryPermission(namespace, package_name).can():
|
||||||
|
raise UnauthorizedAccess("Unauthorized access for: %s" % reponame,
|
||||||
|
{"package": reponame, "scopes": ['push']})
|
||||||
|
|
||||||
|
blob = Blob(reponame, values['blob'])
|
||||||
|
app_release = cnr_registry.push(reponame, release_version, media_type, blob, force,
|
||||||
|
package_class=Package, user=owner, visibility=private)
|
||||||
|
return jsonify(app_release)
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.route("/api/v1/packages/search", methods=['GET'], strict_slashes=False)
|
||||||
|
@process_auth
|
||||||
|
@anon_protect
|
||||||
|
def search_packages():
|
||||||
|
query = request.args.get("q")
|
||||||
|
user = get_authenticated_user()
|
||||||
|
username = None
|
||||||
|
if user:
|
||||||
|
username = user.username
|
||||||
|
|
||||||
|
search_results = cnr_registry.search(query, Package, username=username)
|
||||||
|
return jsonify(search_results)
|
||||||
|
|
||||||
|
|
||||||
|
# CHANNELS
|
||||||
|
@appr_bp.route("/api/v1/packages/<string:namespace>/<string:package_name>/channels",
|
||||||
|
methods=['GET'], strict_slashes=False)
|
||||||
|
@process_auth
|
||||||
|
@require_app_repo_read
|
||||||
|
@anon_protect
|
||||||
|
def list_channels(namespace, package_name):
|
||||||
|
reponame = repo_name(namespace, package_name)
|
||||||
|
return jsonify(cnr_registry.list_channels(reponame, channel_class=Channel))
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.route("/api/v1/packages/<string:namespace>/<string:package_name>/channels/<string:channel_name>", methods=['GET'], strict_slashes=False)
|
||||||
|
@process_auth
|
||||||
|
@require_app_repo_read
|
||||||
|
@anon_protect
|
||||||
|
def show_channel(namespace, package_name, channel_name):
|
||||||
|
reponame = repo_name(namespace, package_name)
|
||||||
|
channel = cnr_registry.show_channel(reponame, channel_name, channel_class=Channel)
|
||||||
|
return jsonify(channel)
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.route(
|
||||||
|
"/api/v1/packages/<string:namespace>/<string:package_name>/channels/<string:channel_name>/<string:release>",
|
||||||
|
methods=['POST'],
|
||||||
|
strict_slashes=False,
|
||||||
|
)
|
||||||
|
@process_auth
|
||||||
|
@require_app_repo_write
|
||||||
|
@anon_protect
|
||||||
|
def add_channel_release(namespace, package_name, channel_name, release):
|
||||||
|
reponame = repo_name(namespace, package_name)
|
||||||
|
result = cnr_registry.add_channel_release(reponame, channel_name, release, channel_class=Channel,
|
||||||
|
package_class=Package)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.route(
|
||||||
|
"/api/v1/packages/<string:namespace>/<string:package_name>/channels/<string:channel_name>/<string:release>",
|
||||||
|
methods=['DELETE'],
|
||||||
|
strict_slashes=False,
|
||||||
|
)
|
||||||
|
@process_auth
|
||||||
|
@require_app_repo_write
|
||||||
|
@anon_protect
|
||||||
|
def delete_channel_release(namespace, package_name, channel_name, release):
|
||||||
|
reponame = repo_name(namespace, package_name)
|
||||||
|
result = cnr_registry.delete_channel_release(reponame, channel_name, release,
|
||||||
|
channel_class=Channel, package_class=Package)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
@appr_bp.route(
|
||||||
|
"/api/v1/packages/<string:namespace>/<string:package_name>/channels/<string:channel_name>",
|
||||||
|
methods=['DELETE'],
|
||||||
|
strict_slashes=False,
|
||||||
|
)
|
||||||
|
@process_auth
|
||||||
|
@require_app_repo_write
|
||||||
|
@anon_protect
|
||||||
|
def delete_channel(namespace, package_name, channel_name):
|
||||||
|
reponame = repo_name(namespace, package_name)
|
||||||
|
result = cnr_registry.delete_channel(reponame, channel_name, channel_class=Channel)
|
||||||
|
return jsonify(result)
|
Reference in a new issue