summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Wittig <max.wittig.ch@gmail.com>2021-02-21 13:31:32 +0100
committerGitHub <noreply@github.com>2021-02-21 13:31:32 +0100
commit2b29776a033b9903d055df7c0716805e86d13fa2 (patch)
treeed8b2c5b5262feb2ac6e0e9f25362e08e338956e
parent338170029c9c8855a6c44de8f3576e8389338652 (diff)
parent36d65f03db253d710938c2d827c1124c94a40506 (diff)
downloadgitlab-2b29776a033b9903d055df7c0716805e86d13fa2.tar.gz
Merge pull request #1314 from python-gitlab/feat/release-links
feat: add release links API support
-rw-r--r--docs/api-objects.rst1
-rw-r--r--docs/gl_objects/projects.rst33
-rw-r--r--docs/gl_objects/releases.rst77
-rw-r--r--gitlab/tests/conftest.py10
-rw-r--r--gitlab/tests/objects/test_releases.py131
-rw-r--r--gitlab/v4/objects/__init__.py1
-rw-r--r--gitlab/v4/objects/projects.py5
-rw-r--r--gitlab/v4/objects/releases.py36
-rw-r--r--gitlab/v4/objects/tags.py13
-rw-r--r--tools/functional/api/test_projects.py36
-rw-r--r--tools/functional/api/test_releases.py36
-rw-r--r--tools/functional/conftest.py33
12 files changed, 329 insertions, 83 deletions
diff --git a/docs/api-objects.rst b/docs/api-objects.rst
index 8221f63..5bcbe24 100644
--- a/docs/api-objects.rst
+++ b/docs/api-objects.rst
@@ -37,6 +37,7 @@ API examples
gl_objects/pipelines_and_jobs
gl_objects/projects
gl_objects/protected_branches
+ gl_objects/releases
gl_objects/runners
gl_objects/remote_mirrors
gl_objects/repositories
diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst
index e483a32..e61bb6a 100644
--- a/docs/gl_objects/projects.rst
+++ b/docs/gl_objects/projects.rst
@@ -702,39 +702,6 @@ Delete project push rules::
pr.delete()
-Project releases
-================
-
-Reference
----------
-
-* v4 API:
-
- + :class:`gitlab.v4.objects.ProjectRelease`
- + :class:`gitlab.v4.objects.ProjectReleaseManager`
- + :attr:`gitlab.v4.objects.Project.releases`
-
-* Gitlab API: https://docs.gitlab.com/ee/api/releases/index.html
-
-Examples
---------
-
-Get a list of releases from a project::
-
- release = project.releases.list()
-
-Get a single release::
-
- release = project.releases.get('v1.2.3')
-
-Create a release for a project tag::
-
- release = project.releases.create({'name':'Demo Release', 'tag_name':'v1.2.3', 'description':'release notes go here'})
-
-Delete a release::
-
- release = p.releases.delete('v1.2.3')
-
Project protected tags
======================
diff --git a/docs/gl_objects/releases.rst b/docs/gl_objects/releases.rst
new file mode 100644
index 0000000..3813857
--- /dev/null
+++ b/docs/gl_objects/releases.rst
@@ -0,0 +1,77 @@
+########
+Releases
+########
+
+Project releases
+================
+
+Reference
+---------
+
+* v4 API:
+
+ + :class:`gitlab.v4.objects.ProjectRelease`
+ + :class:`gitlab.v4.objects.ProjectReleaseManager`
+ + :attr:`gitlab.v4.objects.Project.releases`
+
+* Gitlab API: https://docs.gitlab.com/ee/api/releases/index.html
+
+Examples
+--------
+
+Get a list of releases from a project::
+
+ release = project.releases.list()
+
+Get a single release::
+
+ release = project.releases.get('v1.2.3')
+
+Create a release for a project tag::
+
+ release = project.releases.create({'name':'Demo Release', 'tag_name':'v1.2.3', 'description':'release notes go here'})
+
+Delete a release::
+
+ # via its tag name from project attributes
+ release = project.releases.delete('v1.2.3')
+
+ # delete object directly
+ release.delete()
+
+Project release links
+=====================
+
+Reference
+---------
+
+* v4 API:
+
+ + :class:`gitlab.v4.objects.ProjectReleaseLink`
+ + :class:`gitlab.v4.objects.ProjectReleaseLinkManager`
+ + :attr:`gitlab.v4.objects.ProjectRelease.links`
+
+* Gitlab API: https://docs.gitlab.com/ee/api/releases/links.html
+
+Examples
+--------
+
+Get a list of releases from a project::
+
+ links = release.links.list()
+
+Get a single release link::
+
+ link = release.links.get(1)
+
+Create a release link for a release::
+
+ link = release.links.create({"url": "https://example.com/asset", "name": "asset"})
+
+Delete a release link::
+
+ # via its ID from release attributes
+ release.links.delete(1)
+
+ # delete object directly
+ link.delete()
diff --git a/gitlab/tests/conftest.py b/gitlab/tests/conftest.py
index 98d97ae..fc8312f 100644
--- a/gitlab/tests/conftest.py
+++ b/gitlab/tests/conftest.py
@@ -38,6 +38,11 @@ def default_config(tmpdir):
@pytest.fixture
+def tag_name():
+ return "v1.0.0"
+
+
+@pytest.fixture
def group(gl):
return gl.groups.get(1, lazy=True)
@@ -48,5 +53,10 @@ def project(gl):
@pytest.fixture
+def release(project, tag_name):
+ return project.releases.get(tag_name, lazy=True)
+
+
+@pytest.fixture
def user(gl):
return gl.users.get(1, lazy=True)
diff --git a/gitlab/tests/objects/test_releases.py b/gitlab/tests/objects/test_releases.py
new file mode 100644
index 0000000..6c38a7c
--- /dev/null
+++ b/gitlab/tests/objects/test_releases.py
@@ -0,0 +1,131 @@
+"""
+GitLab API:
+https://docs.gitlab.com/ee/api/releases/index.html
+https://docs.gitlab.com/ee/api/releases/links.html
+"""
+import re
+
+import pytest
+import responses
+
+from gitlab.v4.objects import ProjectReleaseLink
+
+encoded_tag_name = "v1%2E0%2E0"
+link_name = "hello-world"
+link_url = "https://gitlab.example.com/group/hello/-/jobs/688/artifacts/raw/bin/hello-darwin-amd64"
+direct_url = f"https://gitlab.example.com/group/hello/-/releases/{encoded_tag_name}/downloads/hello-world"
+new_link_type = "package"
+link_content = {
+ "id": 2,
+ "name": link_name,
+ "url": link_url,
+ "direct_asset_url": direct_url,
+ "external": False,
+ "link_type": "other",
+}
+
+links_url = re.compile(
+ rf"http://localhost/api/v4/projects/1/releases/{encoded_tag_name}/assets/links"
+)
+link_id_url = re.compile(
+ rf"http://localhost/api/v4/projects/1/releases/{encoded_tag_name}/assets/links/1"
+)
+
+
+@pytest.fixture
+def resp_list_links():
+ with responses.RequestsMock() as rsps:
+ rsps.add(
+ method=responses.GET,
+ url=links_url,
+ json=[link_content],
+ content_type="application/json",
+ status=200,
+ )
+ yield rsps
+
+
+@pytest.fixture
+def resp_get_link():
+ with responses.RequestsMock() as rsps:
+ rsps.add(
+ method=responses.GET,
+ url=link_id_url,
+ json=link_content,
+ content_type="application/json",
+ status=200,
+ )
+ yield rsps
+
+
+@pytest.fixture
+def resp_create_link():
+ with responses.RequestsMock() as rsps:
+ rsps.add(
+ method=responses.POST,
+ url=links_url,
+ json=link_content,
+ content_type="application/json",
+ status=200,
+ )
+ yield rsps
+
+
+@pytest.fixture
+def resp_update_link():
+ updated_content = dict(link_content)
+ updated_content["link_type"] = new_link_type
+
+ with responses.RequestsMock() as rsps:
+ rsps.add(
+ method=responses.PUT,
+ url=link_id_url,
+ json=updated_content,
+ content_type="application/json",
+ status=200,
+ )
+ yield rsps
+
+
+@pytest.fixture
+def resp_delete_link(no_content):
+ with responses.RequestsMock() as rsps:
+ rsps.add(
+ method=responses.DELETE,
+ url=link_id_url,
+ json=link_content,
+ content_type="application/json",
+ status=204,
+ )
+ yield rsps
+
+
+def test_list_release_links(release, resp_list_links):
+ links = release.links.list()
+ assert isinstance(links, list)
+ assert isinstance(links[0], ProjectReleaseLink)
+ assert links[0].url == link_url
+
+
+def test_get_release_link(release, resp_get_link):
+ link = release.links.get(1)
+ assert isinstance(link, ProjectReleaseLink)
+ assert link.url == link_url
+
+
+def test_create_release_link(release, resp_create_link):
+ link = release.links.create({"url": link_url, "name": link_name})
+ assert isinstance(link, ProjectReleaseLink)
+ assert link.url == link_url
+
+
+def test_update_release_link(release, resp_update_link):
+ link = release.links.get(1, lazy=True)
+ link.link_type = new_link_type
+ link.save()
+ assert link.link_type == new_link_type
+
+
+def test_delete_release_link(release, resp_delete_link):
+ link = release.links.get(1, lazy=True)
+ link.delete()
diff --git a/gitlab/v4/objects/__init__.py b/gitlab/v4/objects/__init__.py
index 9f91f53..8a2ed7c 100644
--- a/gitlab/v4/objects/__init__.py
+++ b/gitlab/v4/objects/__init__.py
@@ -56,6 +56,7 @@ from .pages import *
from .pipelines import *
from .projects import *
from .push_rules import *
+from .releases import *
from .runners import *
from .services import *
from .settings import *
diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py
index 722b9ea..320e511 100644
--- a/gitlab/v4/objects/projects.py
+++ b/gitlab/v4/objects/projects.py
@@ -33,6 +33,7 @@ from .packages import ProjectPackageManager
from .pages import ProjectPagesDomainManager
from .pipelines import ProjectPipelineManager, ProjectPipelineScheduleManager
from .push_rules import ProjectPushRulesManager
+from .releases import ProjectReleaseManager
from .runners import ProjectRunnerManager
from .services import ProjectServiceManager
from .snippets import ProjectSnippetManager
@@ -40,7 +41,7 @@ from .statistics import (
ProjectAdditionalStatisticsManager,
ProjectIssuesStatisticsManager,
)
-from .tags import ProjectProtectedTagManager, ProjectReleaseManager, ProjectTagManager
+from .tags import ProjectProtectedTagManager, ProjectTagManager
from .triggers import ProjectTriggerManager
from .users import ProjectUserManager
from .variables import ProjectVariableManager
@@ -86,7 +87,7 @@ class GroupProjectManager(ListMixin, RESTManager):
)
-class Project(SaveMixin, ObjectDeleteMixin, RESTObject):
+class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RESTObject):
_short_print_attr = "path"
_managers = (
("accessrequests", "ProjectAccessRequestManager"),
diff --git a/gitlab/v4/objects/releases.py b/gitlab/v4/objects/releases.py
new file mode 100644
index 0000000..d9112e4
--- /dev/null
+++ b/gitlab/v4/objects/releases.py
@@ -0,0 +1,36 @@
+from gitlab import cli
+from gitlab import exceptions as exc
+from gitlab.base import * # noqa
+from gitlab.mixins import * # noqa
+
+
+__all__ = [
+ "ProjectRelease",
+ "ProjectReleaseManager",
+ "ProjectReleaseLink",
+ "ProjectReleaseLinkManager",
+]
+
+
+class ProjectRelease(RESTObject):
+ _id_attr = "tag_name"
+ _managers = (("links", "ProjectReleaseLinkManager"),)
+
+
+class ProjectReleaseManager(NoUpdateMixin, RESTManager):
+ _path = "/projects/%(project_id)s/releases"
+ _obj_cls = ProjectRelease
+ _from_parent_attrs = {"project_id": "id"}
+ _create_attrs = (("name", "tag_name", "description"), ("ref", "assets"))
+
+
+class ProjectReleaseLink(RESTObject, ObjectDeleteMixin, SaveMixin):
+ pass
+
+
+class ProjectReleaseLinkManager(CRUDMixin, RESTManager):
+ _path = "/projects/%(project_id)s/releases/%(tag_name)s/assets/links"
+ _obj_cls = ProjectReleaseLink
+ _from_parent_attrs = {"project_id": "project_id", "tag_name": "tag_name"}
+ _create_attrs = (("name", "url"), ("filepath", "link_type"))
+ _update_attrs = ((), ("name", "url", "filepath", "link_type"))
diff --git a/gitlab/v4/objects/tags.py b/gitlab/v4/objects/tags.py
index c4d60db..1f333c5 100644
--- a/gitlab/v4/objects/tags.py
+++ b/gitlab/v4/objects/tags.py
@@ -9,8 +9,6 @@ __all__ = [
"ProjectTagManager",
"ProjectProtectedTag",
"ProjectProtectedTagManager",
- "ProjectRelease",
- "ProjectReleaseManager",
]
@@ -71,14 +69,3 @@ class ProjectProtectedTagManager(NoUpdateMixin, RESTManager):
_obj_cls = ProjectProtectedTag
_from_parent_attrs = {"project_id": "id"}
_create_attrs = (("name",), ("create_access_level",))
-
-
-class ProjectRelease(RESTObject):
- _id_attr = "tag_name"
-
-
-class ProjectReleaseManager(NoUpdateMixin, RESTManager):
- _path = "/projects/%(project_id)s/releases"
- _obj_cls = ProjectRelease
- _from_parent_attrs = {"project_id": "id"}
- _create_attrs = (("name", "tag_name", "description"), ("ref", "assets"))
diff --git a/tools/functional/api/test_projects.py b/tools/functional/api/test_projects.py
index 945a6ec..404f89d 100644
--- a/tools/functional/api/test_projects.py
+++ b/tools/functional/api/test_projects.py
@@ -197,32 +197,6 @@ def test_project_protected_branches(project):
assert len(project.protectedbranches.list()) == 0
-def test_project_releases(gl):
- project = gl.projects.create(
- {"name": "release-test-project", "initialize_with_readme": True}
- )
- release_name = "Demo Release"
- release_tag_name = "v1.2.3"
- release_description = "release notes go here"
- release = project.releases.create(
- {
- "name": release_name,
- "tag_name": release_tag_name,
- "description": release_description,
- "ref": "master",
- }
- )
- assert len(project.releases.list()) == 1
- assert project.releases.get(release_tag_name)
- assert release.name == release_name
- assert release.tag_name == release_tag_name
- assert release.description == release_description
-
- project.releases.delete(release_tag_name)
- assert len(project.releases.list()) == 0
- project.delete()
-
-
def test_project_remote_mirrors(project):
mirror_url = "http://gitlab.test/root/mirror.git"
@@ -260,15 +234,7 @@ def test_project_stars(project):
assert project.star_count == 0
-def test_project_tags(project):
- project.files.create(
- {
- "file_path": "README",
- "branch": "master",
- "content": "Initial content",
- "commit_message": "Initial commit",
- }
- )
+def test_project_tags(project, project_file):
tag = project.tags.create({"tag_name": "v1.0", "ref": "master"})
assert len(project.tags.list()) == 1
diff --git a/tools/functional/api/test_releases.py b/tools/functional/api/test_releases.py
new file mode 100644
index 0000000..55f7920
--- /dev/null
+++ b/tools/functional/api/test_releases.py
@@ -0,0 +1,36 @@
+release_name = "Demo Release"
+release_tag_name = "v1.2.3"
+release_description = "release notes go here"
+
+link_data = {"url": "https://example.com", "name": "link_name"}
+
+
+def test_create_project_release(project, project_file):
+ project.refresh() # Gets us the current default branch
+ release = project.releases.create(
+ {
+ "name": release_name,
+ "tag_name": release_tag_name,
+ "description": release_description,
+ "ref": project.default_branch,
+ }
+ )
+
+ assert len(project.releases.list()) == 1
+ assert project.releases.get(release_tag_name)
+ assert release.name == release_name
+ assert release.tag_name == release_tag_name
+ assert release.description == release_description
+
+
+def test_delete_project_release(project, release):
+ project.releases.delete(release.tag_name)
+ assert release not in project.releases.list()
+
+
+def test_create_project_release_links(project, release):
+ link = release.links.create(link_data)
+
+ release = project.releases.get(release.tag_name)
+ assert release.assets["links"][0]["url"] == link_data["url"]
+ assert release.assets["links"][0]["name"] == link_data["name"]
diff --git a/tools/functional/conftest.py b/tools/functional/conftest.py
index 675dba9..a0b14f9 100644
--- a/tools/functional/conftest.py
+++ b/tools/functional/conftest.py
@@ -197,6 +197,39 @@ def project(gl):
@pytest.fixture(scope="module")
+def project_file(project):
+ """File fixture for tests requiring a project with files and branches."""
+ project_file = project.files.create(
+ {
+ "file_path": "README",
+ "branch": "master",
+ "content": "Initial content",
+ "commit_message": "Initial commit",
+ }
+ )
+
+ return project_file
+
+
+@pytest.fixture(scope="function")
+def release(project, project_file):
+ _id = uuid.uuid4().hex
+ name = f"test-release-{_id}"
+
+ project.refresh() # Gets us the current default branch
+ release = project.releases.create(
+ {
+ "name": name,
+ "tag_name": _id,
+ "description": "description",
+ "ref": project.default_branch,
+ }
+ )
+
+ return release
+
+
+@pytest.fixture(scope="module")
def user(gl):
"""User fixture for user API resource tests."""
_id = uuid.uuid4().hex