Skip to content

Commit

Permalink
Merge pull request #1710 from cutwater/feature/apiv2/imports
Browse files Browse the repository at this point in the history
APIv2: Collection and repository imports endpoints
  • Loading branch information
cutwater authored Apr 12, 2019
2 parents 04bd586 + 6854683 commit f82b3e6
Show file tree
Hide file tree
Showing 15 changed files with 992 additions and 6 deletions.
6 changes: 6 additions & 0 deletions galaxy/api/internal/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@
from .collections import ( # noqa: F401
CollectionListSerializer
)
from .imports import ( # noqa: F401
CollectionImportTaskItem,
RepositoryImportTaskItem,
)

__all__ = (
'CollectionListSerializer',
'RepositoryImportTaskItem',
'CollectionImportTaskItem',
)
93 changes: 93 additions & 0 deletions galaxy/api/internal/serializers/imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# (c) 2012-2019, Ansible by Red Hat
#
# This file is part of Ansible Galaxy
#
# Ansible Galaxy is free software: you can redistribute it and/or modify
# it under the terms of the Apache License as published by
# the Apache Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Ansible Galaxy 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
# Apache License for more details.
#
# You should have received a copy of the Apache License

from rest_framework import serializers
from rest_framework.reverse import reverse

from galaxy import constants as const
from galaxy.main import models


TYPE_COLLECTION = 'collection'
TYPE_REPOSITORY = 'repository'


class CollectionImportTaskItem(serializers.ModelSerializer):
href = serializers.HyperlinkedIdentityField(
'api:v2:collection-import-detail')
type = serializers.SerializerMethodField()
state = serializers.CharField()
started_at = serializers.DateTimeField()
finished_at = serializers.DateTimeField()

namespace = serializers.SerializerMethodField()

class Meta:
model = models.CollectionImport
fields = ('id', 'href', 'type', 'state', 'started_at', 'finished_at',
'namespace', 'name', 'version')

def get_type(self, obj):
return TYPE_COLLECTION

def get_namespace(self, obj: models.CollectionImport):
ns = obj.namespace
request = self.context.get('request')
return {
'id': ns.id,
'href': reverse('api:namespace_detail',
kwargs={'pk': ns.id}, request=request),
'name': ns.name,
}


class RepositoryImportTaskItem(serializers.ModelSerializer):
href = serializers.HyperlinkedIdentityField(
'api:import_task_detail')
type = serializers.SerializerMethodField()
state = serializers.SerializerMethodField()
started_at = serializers.DateTimeField(source='started')
finished_at = serializers.DateTimeField(source='finished')

namespace = serializers.SerializerMethodField()
name = serializers.CharField(source='repository.name')

class Meta:
model = models.ImportTask
fields = ('id', 'href', 'type', 'state', 'started_at', 'finished_at',
'namespace', 'name')

def get_type(self, obj):
return TYPE_REPOSITORY

def get_state(self, obj):
return const.ImportTaskState(obj.state).as_task_state()

def get_namespace(self, obj: models.ImportTask):
ns = obj.repository.provider_namespace.namespace
request = self.context.get('request')
return {
'id': ns.id,
'href': reverse('api:namespace_detail',
kwargs={'pk': ns.id}, request=request),
'name': ns.name,
}


SERIALIZER_BY_TYPE = {
TYPE_COLLECTION: CollectionImportTaskItem,
TYPE_REPOSITORY: RepositoryImportTaskItem,
}
Empty file.
222 changes: 222 additions & 0 deletions galaxy/api/internal/tests/test_imports_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
# (c) 2012-2019, Ansible by Red Hat
#
# This file is part of Ansible Galaxy
#
# Ansible Galaxy is free software: you can redistribute it and/or modify
# it under the terms of the Apache License as published by
# the Apache Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Ansible Galaxy 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
# Apache License for more details.
#
# You should have received a copy of the Apache License
# along with Galaxy. If not, see <http://www.apache.org/licenses/>.

import datetime as dt

from django.contrib.auth import get_user_model
from django.utils import timezone
from pulpcore import constants as pulp_const
from pulpcore.app import models as pulp_models
from rest_framework.test import APITestCase
from rest_framework import status as http_codes

from galaxy.main import models
from galaxy import constants as const

UserModel = get_user_model()


class NamespaceImportsList(APITestCase):
url = '/api/internal/ui/namespaces/{ns}/imports/'

def setUp(self):
super().setUp()

self.user = UserModel.objects.create_user(
username='testuser', password='secret')

self.namespace = self._create_namespace('myns')
self.provider_ns = models.ProviderNamespace.objects.create(
provider=models.Provider.objects.get(name__iexact='github'),
namespace=self.namespace,
)
self.repos = [
self._create_repo('myrepo1'),
self._create_repo('myrepo2'),
]

now = timezone.now()
start = now - dt.timedelta(hours=10)

self.repo_imports = [
self._create_repo_import(
repository=self.repos[0],
state=const.ImportTaskState.SUCCESS,
started_at=start + dt.timedelta(hours=4),
finished_at=now,

),
self._create_repo_import(
repository=self.repos[1],
state=const.ImportTaskState.PENDING,
started_at=start + dt.timedelta(hours=2),
),
]

self.collection_imports = [
self._create_collection_import(
state=pulp_const.TASK_STATES.WAITING,
name='mycollection1',
version='0.9.9',
started_at=start + dt.timedelta(hours=3),
),
self._create_collection_import(
state=pulp_const.TASK_STATES.COMPLETED,
name='mycollection2',
version='1.0.1',
started_at=start + dt.timedelta(hours=1),
finished_at=now,
)
]

def _create_namespace(self, name):
ns = models.Namespace.objects.create(name=name)
ns.owners.set([self.user])
return ns

def _create_repo(self, name):
repo = models.Repository.objects.create(
name=name, original_name=name, provider_namespace=self.provider_ns)
repo.owners.set([self.user])
return repo

def _create_repo_import(
self, *, repository, state, started_at=None, finished_at=None):
return models.ImportTask.objects.create(
repository=repository,
state=state,
owner=self.user,
started=started_at,
finished=finished_at,
)

def _create_collection_import(
self, *, state, name, version, started_at=None, finished_at=None):
pulp_task = pulp_models.Task.objects.create(
state=state,
started_at=started_at,
finished_at=finished_at,
)
return models.CollectionImport.objects.create(
name=name,
version=version,
namespace=self.namespace,
pulp_task=pulp_task,
)

def test_list_all(self):
response = self.client.get(self.url.format(ns=self.namespace.id))
assert response.status_code == http_codes.HTTP_200_OK

data = response.json()
assert data['count'] == 4

results = data['results']
assert len(results) == 4

ns = self.namespace
task = self.repo_imports[0]
assert results[0] == {
'id': task.id,
'type': 'repository',
'href': 'http://testserver/api/v1/imports/{}/'.format(task.id),
'name': 'myrepo1',
'namespace': {
'id': self.namespace.id,
'href': 'http://testserver/api/v1/'
'namespaces/{}/'.format(ns.id),
'name': self.namespace.name,
},
'state': 'completed',
'started_at': timezone.localtime(
self.repo_imports[0].started).isoformat(),
'finished_at': timezone.localtime(
self.repo_imports[0].finished).isoformat(),
}

task = self.collection_imports[0]
assert results[1] == {
'id': task.id,
'type': 'collection',
'href': ('http://testserver/api/v2/'
'collection-imports/{}/'.format(task.id)),
'name': 'mycollection1',
'version': '0.9.9',
'namespace': {
'id': self.namespace.id,
'href': 'http://testserver/api/v1/'
'namespaces/{}/'.format(ns.id),
'name': self.namespace.name,
},
'state': 'waiting',
'started_at': timezone.localtime(task.started_at).isoformat(),
'finished_at': None,
}

self.assertDictContainsSubset({
'name': 'myrepo2',
'type': 'repository',
'state': 'waiting',
}, results[2])

self.assertDictContainsSubset({
'name': 'mycollection2',
'type': 'collection',
'state': 'completed',
}, results[3])

def test_list_filter_by_type(self):
response = self.client.get(self.url.format(ns=self.namespace.id),
data={'type': 'collection'})
assert response.status_code == http_codes.HTTP_200_OK

data = response.json()
assert data['count'] == 2

results = data['results']
assert len(results) == 2

self.assertDictContainsSubset({
'name': 'mycollection1',
'type': 'collection',
}, results[0])
self.assertDictContainsSubset({
'name': 'mycollection2',
'type': 'collection',
}, results[1])

def test_list_filter_by_name(self):
response = self.client.get(self.url.format(ns=self.namespace.id),
data={'name': 'myrepo1'})
assert response.status_code == http_codes.HTTP_200_OK

data = response.json()
assert data['count'] == 1

results = data['results']
assert len(results) == 1

self.assertDictContainsSubset({
'name': 'myrepo1',
'type': 'repository',
}, results[0])

def test_fail_method_not_allowed(self):
for method in ['POST', 'PUT', 'PATCH', 'DELETE']:
response = self.client.generic(method, self.url.format(ns=1))
assert (response.status_code
== http_codes.HTTP_405_METHOD_NOT_ALLOWED)
4 changes: 3 additions & 1 deletion galaxy/api/internal/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@


ui_urls = [
path('collections/', views.CollectionList.as_view())
path('collections/', views.CollectionList.as_view()),
path('namespaces/<int:namespace_id>/imports/',
views.NamespaceImportsList.as_view())
]

app_name = 'api'
Expand Down
8 changes: 6 additions & 2 deletions galaxy/api/internal/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
from .collections import ( # noqa: F401
CollectionList
)
from .imports import ( # noqa: F401
NamespaceImportsList,
)

__all__ = [
__all__ = (
'CollectionList',
]
'NamespaceImportsList',
)
15 changes: 15 additions & 0 deletions galaxy/api/internal/views/collections.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
# (c) 2012-2019, Ansible by Red Hat
#
# This file is part of Ansible Galaxy
#
# Ansible Galaxy is free software: you can redistribute it and/or modify
# it under the terms of the Apache License as published by
# the Apache Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Ansible Galaxy 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
# Apache License for more details.
#
# You should have received a copy of the Apache License
from galaxy.api.internal import serializers
from galaxy.main import models
from rest_framework import generics
Expand Down
Loading

0 comments on commit f82b3e6

Please sign in to comment.