Skip to content
This repository has been archived by the owner on May 24, 2023. It is now read-only.

Commit

Permalink
Merge b31139c into 75fa94d
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinBasti committed Feb 25, 2019
2 parents 75fa94d + b31139c commit d0117ac
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 7 deletions.
2 changes: 2 additions & 0 deletions omps/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from .errors import init_errors_handling
from .logger import init_logging
from .packages import BLUEPRINT as PACKAGES_BP
from .push import BLUEPRINT as PUSH_BP
from .settings import init_config
from .quay import QUAY_ORG_MANAGER
Expand Down Expand Up @@ -43,6 +44,7 @@ def _init_errors_handling(app):
def _register_blueprints(app):
logger.debug('Registering blueprints')
app.register_blueprint(PUSH_BP, url_prefix='/push')
app.register_blueprint(PACKAGES_BP, url_prefix='/packages')


app = create_app()
52 changes: 52 additions & 0 deletions omps/packages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#
# Copyright (C) 2019 Red Hat, Inc
# see the LICENSE file for license
#
import logging

from flask import Blueprint, jsonify


from .quay import QUAY_ORG_MANAGER, ReleaseVersion

logger = logging.getLogger(__name__)
BLUEPRINT = Blueprint('packages', __name__)


@BLUEPRINT.route("/<organization>/<repo>/<version>", methods=('DELETE',))
def delete_package_release(organization, repo, version=None):
"""
Delete particular version of released package from quay.io
:param organization: quay.io organization
:param repo: target repository
:param version: version of operator manifest
:return: HTTP response
"""
quay_org = QUAY_ORG_MANAGER.organization_login(organization)

# quay.io may contain OMPS incompatible release version format string
# but we want to be able to delete everything there, thus using _raw
# method
if version is None:
versions = quay_org.get_releases_raw(repo)
else:
versions = [version]

for ver in versions:
quay_org.delete_release(repo, ver)

data = {
'organization': organization,
'repo': repo,
'deleted': versions,
}

resp = jsonify(data)
resp.status_code = 200
return resp


@BLUEPRINT.route("/<organization>/<repo>", methods=('DELETE',))
def delete_package_releases_all(organization, repo):
return delete_package_release(organization, repo, version=None)
79 changes: 72 additions & 7 deletions omps/quay.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,14 +242,15 @@ def _get_repo_content(self, repo):
res.raise_for_status()
return res.json()

def get_latest_release_version(self, repo):
"""Get the latest release version
def get_releases_raw(self, repo):
"""Get raw releases from quay, these release may not be compatible
with OMPS versioning
:param repo: repository name
:raise QuayPackageNotFound: package doesn't exist
:raise QuayPackageError: failed to retrieve info about package
:return: Latest release version
:rtype: PackageRelease
:return: Releases
:rtype: List[str]
"""
def _raise(exc):
raise QuayPackageError(
Expand All @@ -270,9 +271,22 @@ def _raise(exc):
except requests.exceptions.RequestException as e:
_raise(e)

releases = [package['release'] for package in res]
return releases

def get_releases(self, repo):
"""Get release versions (only valid)
:param repo: repository name
:raise QuayPackageNotFound: package doesn't exist
:raise QuayPackageError: failed to retrieve info about package
:return: valid releases
:rtype: List[ReleaseVersion]
"""
releases_raw = self.get_releases_raw(repo)

releases = []
for package in res:
release = package['release']
for release in releases_raw:
try:
version = ReleaseVersion.from_str(release)
except ValueError as e:
Expand All @@ -282,6 +296,18 @@ def _raise(exc):
else:
releases.append(version)

return releases

def get_latest_release_version(self, repo):
"""Get the latest release version
:param repo: repository name
:raise QuayPackageNotFound: package doesn't exist
:raise QuayPackageError: failed to retrieve info about package
:return: Latest release version
:rtype: ReleaseVersion
"""
releases = self.get_releases(repo)
if not releases:
# no valid versions found, assume that this will be first package
# uploaded by service
Expand All @@ -291,7 +317,46 @@ def _raise(exc):
)
)

return max(releases)
return max(self.get_releases(repo))

def delete_release(self, repo, version):
"""
Delete specified version of release from repository
:param str repo: name of repository
:param ReleaseVersion|str version: version of release
:raises QuayPackageNotFound: when package is not found
:raises QuayPackageError: when an error happened during removal
"""
endpoint = '/cnr/api/v1/packages/{org}/{repo}/{version}/helm'
url = '{q}{e}'.format(
q=self._quay_url,
e=endpoint.format(
org=self._organization,
repo=repo,
version=version,
)
)
headers = {'Authorization': self._token}

logger.info('Deleting release %s/%s, v:%s',
self._organization, repo, version)

r = requests.delete(url, headers=headers)

if r.status_code != requests.codes.ok:

try:
msg = r.json()['error']['message']
except Exception:
msg = "Unknown error"

if r.status_code == 404:
logger.info("Delete release (404): %s", msg)
raise QuayPackageNotFound(msg)

logger.error("Delete release (%s): %s", r.status_code, msg)
raise QuayPackageError(msg)


QUAY_ORG_MANAGER = QuayOrganizationManager()
42 changes: 42 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from omps.quay import QuayOrganization


MOCK_VERSION = "0.0.1"

EntrypointMeta = namedtuple('EntrypointMeta', 'url_path,org,repo,version')

Expand Down Expand Up @@ -79,6 +80,26 @@ def endpoint_push_zipfile(request):
)


@pytest.fixture(params=[
True, # endpoint with version
False, # endpoint without version
])
def endpoint_packages(request):
"""Returns URL for packages endpoints"""
organization = 'testorg'
repo = 'repo-Y'
version = MOCK_VERSION

url_path = '/packages/{}/{}'.format(organization, repo)
if request.param:
url_path = '{}/{}'.format(url_path, version)

yield EntrypointMeta(
url_path=url_path, org=organization,
repo=repo, version=version
)


@pytest.fixture
def mocked_quay_io():
"""Mocking quay.io answers"""
Expand All @@ -94,6 +115,27 @@ def mocked_quay_io():
yield m


@pytest.fixture
def mocked_packages_delete_quay_io():
"""Mocking quay.io answers for retrieving and deleting packages"""
with requests_mock.Mocker() as m:
m.post(
'/cnr/api/v1/users/login',
json={'token': 'faketoken'}
)
m.get(
re.compile(r'/cnr/api/v1/packages/.*'),
json=[
{"release": MOCK_VERSION},
],
)
m.delete(
re.compile(r'/cnr/api/v1/packages/.*'),
status_code=200,
)
yield m


@pytest.fixture
def mocked_failed_quay_login():
"""Returns HTTP 401 with error message"""
Expand Down
Empty file added tests/packages/__init__.py
Empty file.
55 changes: 55 additions & 0 deletions tests/packages/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#
# Copyright (C) 2019 Red Hat, Inc
# see the LICENSE file for license
#

import pytest


def test_delete_released_package(
client, valid_manifests_archive, endpoint_packages,
mocked_packages_delete_quay_io):
"""Test REST API for deleting released operators manifest packages"""

rv = client.delete(
endpoint_packages.url_path,
)

assert rv.status_code == 200, rv.get_json()
expected = {
'organization': endpoint_packages.org,
'repo': endpoint_packages.repo,
'deleted': ["0.0.1"],
}
assert rv.get_json() == expected


@pytest.mark.parametrize('endpoint', [
'/packages/organization-X/repo-Y',
'/packages/organization-X/repo-Y/1.0.1',
])
@pytest.mark.parametrize('method', [
'GET', 'PATCH' 'PUT', 'HEAD', 'POST', 'TRACE',
])
def test_method_not_allowed(client, endpoint, method):
"""Specified endpoints currently support only DELETE method, test if other
HTTP methods returns proper error code
Method OPTIONS is excluded from testing due its special meaning
"""
rv = client.open(endpoint, method=method)
assert rv.status_code == 405


@pytest.mark.parametrize('endpoint', [
'/packages',
'/packages/organization-X/',
'/packages/organization-X/repo-Y/version-Z/extra-something',
])
def test_404_for_mistyped_entrypoints(client, endpoint):
"""Test if HTTP 404 is returned for unexpected endpoints"""
rv = client.post(endpoint)
assert rv.status_code == 404
rv_json = rv.get_json()
assert rv_json['error'] == 'NotFound'
assert rv_json['status'] == 404
Empty file added tests/push/__init__.py
Empty file.
Loading

0 comments on commit d0117ac

Please sign in to comment.