glpkg 1.0.0__tar.gz → 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: glpkg
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: Tool to make GitLab generic package registry operations easy.
5
5
  Author-email: bugproduction <bugproduction@outlook.com>
6
6
  License-Expression: MIT
@@ -124,4 +124,3 @@ The tool is not perfect (yet) and has limitations. The following limitations are
124
124
 
125
125
  - Uploading files must be done one-by-one.
126
126
  - Only project registries are supported for now.
127
- - Pagination is not supported for now - in case you have more than 100 versions of a package, not all will be shown.
@@ -104,5 +104,4 @@ To use the `CI_JOB_TOKEN` with package registry of another projects, add `--proj
104
104
  The tool is not perfect (yet) and has limitations. The following limitations are known, but more can exist:
105
105
 
106
106
  - Uploading files must be done one-by-one.
107
- - Only project registries are supported for now.
108
- - Pagination is not supported for now - in case you have more than 100 versions of a package, not all will be shown.
107
+ - Only project registries are supported for now.
@@ -1,3 +1,3 @@
1
1
  from gitlab.packages import Packages
2
2
 
3
- __version__ = "1.0.0"
3
+ __version__ = "1.1.0"
@@ -98,16 +98,20 @@ class CLIHandler:
98
98
  return host, project, name, token_user, token
99
99
 
100
100
  def _download_handler(self, args) -> int:
101
+ ret = 1
101
102
  host, project, name, token_user, token = self._args(args)
102
103
  version = args.version
103
104
  gitlab = Packages(host, token_user, token)
104
105
  package_id = gitlab.get_package_id(project, name, version)
105
- files = gitlab.list_files(project, package_id)
106
- ret = 1
107
- for file in files:
108
- ret = gitlab.download_file(project, name, version, file)
109
- if not ret:
110
- break
106
+ if package_id:
107
+ files = gitlab.list_files(project, package_id)
108
+ for file in files:
109
+ ret = gitlab.download_file(project, name, version, file)
110
+ if ret:
111
+ print("Failed to download file " + file)
112
+ break
113
+ else:
114
+ print("No package " + name + " version " + version + " found!")
111
115
  return ret
112
116
 
113
117
  def _register_list_parser(self, parser):
@@ -134,9 +138,13 @@ class CLIHandler:
134
138
  parser.set_defaults(action=self._upload)
135
139
 
136
140
  def _upload(self, args) -> int:
141
+ ret = 1
137
142
  host, project, name, token_user, token = self._args(args)
138
143
  version = args.version
139
144
  file = args.file
140
- gitlab = Packages(host, token_user, token)
141
- ret = gitlab.upload_file(project, name, version, file)
145
+ if os.path.isfile(file):
146
+ gitlab = Packages(host, token_user, token)
147
+ ret = gitlab.upload_file(project, name, version, file)
148
+ else:
149
+ print("File " + file + " does not exist!")
142
150
  return ret
@@ -0,0 +1,163 @@
1
+ from http.client import HTTPMessage
2
+ import json
3
+ import logging
4
+ from urllib import request, parse
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class Packages:
10
+ def __init__(self, host: str, token_type: str, token: str):
11
+ self.host = host
12
+ self.token_type = token_type
13
+ self.token = token
14
+
15
+ def api_url(self) -> str:
16
+ return "https://{}/api/v4/".format(parse.quote(self.host))
17
+
18
+ def project_api_url(self, project: str) -> str:
19
+ return self.api_url() + "projects/{}/".format(parse.quote_plus(project))
20
+
21
+ def get_headers(self) -> dict:
22
+ headers = {}
23
+ if self.token_type and self.token:
24
+ headers = {self.token_type: self.token}
25
+ return headers
26
+
27
+ def _request(self, url: str) -> tuple[int, bytes, HTTPMessage]:
28
+ logger.debug("Requesting " + url)
29
+ req = request.Request(url, headers=self.get_headers())
30
+ with request.urlopen(req) as response:
31
+ return response.status, response.read(), response.headers
32
+
33
+ def _get_next_page(self, headers: HTTPMessage) -> int:
34
+ ret = 0
35
+ if headers:
36
+ next_page = headers.get("x-next-page")
37
+ if next_page:
38
+ ret = int(next_page)
39
+ logger.debug("Response incomplete, next page is " + next_page)
40
+ else:
41
+ logger.debug("Response complete")
42
+ return ret
43
+
44
+ def _build_query(self, arg: str, page: int) -> str:
45
+ query = ""
46
+ if arg or page:
47
+ if page:
48
+ page = "page=" + str(page)
49
+ query = "?{}".format("&".join(filter(None, (arg, page))))
50
+ return query
51
+
52
+ def gl_project_api(self, project: str, path: str, arg: str = None) -> list:
53
+ data = []
54
+ more = True
55
+ page = None
56
+ while more:
57
+ more = False
58
+ query = self._build_query(arg, page)
59
+ url = self.project_api_url(project) + path + query
60
+ status, res_data, headers = self._request(url)
61
+ logger.debug("Response status: " + str(status))
62
+ res_data = json.loads(res_data)
63
+ logger.debug("Response data: " + str(res_data))
64
+ data = data + res_data
65
+ page = self._get_next_page(headers)
66
+ if page:
67
+ more = True
68
+ return data
69
+
70
+ def list_packages(self, project: str, package_name: str) -> list:
71
+ packages = []
72
+ logger.debug("Listing packages with name " + package_name)
73
+ data = self.gl_project_api(
74
+ project, "packages", "package_name=" + parse.quote_plus(package_name)
75
+ )
76
+ for package in data:
77
+ name = parse.unquote(package["name"])
78
+ version = parse.unquote(package["version"])
79
+ # GitLab API returns packages that have some match to the filter;
80
+ # let's filter out non-exact matches
81
+ if package_name != name:
82
+ continue
83
+ packages.append({"name": name, "version": version})
84
+ return packages
85
+
86
+ def list_files(self, project: str, package_id: int) -> list:
87
+ files = []
88
+ logger.debug("Listing package " + str(package_id) + " files")
89
+ path = "packages/" + parse.quote_plus(str(package_id)) + "/package_files"
90
+ data = self.gl_project_api(project, path)
91
+ for package in data:
92
+ # Only append the filename once to the list of files
93
+ # as there's no way to download them separately through
94
+ # the API
95
+ filename = parse.unquote(package["file_name"])
96
+ if filename not in files:
97
+ files.append(filename)
98
+ return files
99
+
100
+ def get_package_id(
101
+ self, project: str, package_name: str, package_version: str
102
+ ) -> int:
103
+ id = 0
104
+ logger.debug(
105
+ "Fetching package " + package_name + " (" + package_version + ") ID"
106
+ )
107
+ path = "packages"
108
+ arg = (
109
+ "package_name="
110
+ + parse.quote_plus(package_name)
111
+ + "&package_version="
112
+ + parse.quote_plus(package_version)
113
+ )
114
+ data = self.gl_project_api(project, path, arg)
115
+ if len(data) == 1:
116
+ package = data.pop()
117
+ id = package["id"]
118
+ return id
119
+
120
+ def download_file(
121
+ self, project: str, package_name: str, package_version: str, file: str
122
+ ) -> int:
123
+ ret = 1
124
+ logger.debug("Downloading file " + file)
125
+ url = (
126
+ self.project_api_url(project)
127
+ + "packages/generic/"
128
+ + parse.quote_plus(package_name)
129
+ + "/"
130
+ + parse.quote_plus(package_version)
131
+ + "/"
132
+ + parse.quote(str(file))
133
+ )
134
+ status, data, _ = self._request(url)
135
+ if status == 200:
136
+ with open(str(file), "wb") as file:
137
+ file.write(data)
138
+ ret = 0
139
+ return ret
140
+
141
+ def upload_file(
142
+ self, project: str, package_name: str, package_version: str, file: str
143
+ ) -> int:
144
+ ret = 1
145
+ logger.debug("Uploading file " + file)
146
+ with open(str(file), "rb") as data:
147
+ url = (
148
+ self.project_api_url(project)
149
+ + "packages/generic/"
150
+ + parse.quote_plus(package_name)
151
+ + "/"
152
+ + parse.quote_plus(package_version)
153
+ + "/"
154
+ + parse.quote(str(file))
155
+ )
156
+ res = request.urlopen(
157
+ request.Request(
158
+ url, method="PUT", data=data, headers=self.get_headers()
159
+ )
160
+ )
161
+ if res.status == 201: # 201 is created
162
+ ret = 0
163
+ return ret
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: glpkg
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: Tool to make GitLab generic package registry operations easy.
5
5
  Author-email: bugproduction <bugproduction@outlook.com>
6
6
  License-Expression: MIT
@@ -124,4 +124,3 @@ The tool is not perfect (yet) and has limitations. The following limitations are
124
124
 
125
125
  - Uploading files must be done one-by-one.
126
126
  - Only project registries are supported for now.
127
- - Pagination is not supported for now - in case you have more than 100 versions of a package, not all will be shown.
@@ -11,4 +11,5 @@ src/glpkg.egg-info/dependency_links.txt
11
11
  src/glpkg.egg-info/entry_points.txt
12
12
  src/glpkg.egg-info/requires.txt
13
13
  src/glpkg.egg-info/top_level.txt
14
- test/test_gitlab.py
14
+ test/test_cli_handler.py
15
+ test/test_packages.py
@@ -0,0 +1,45 @@
1
+ from unittest.mock import patch
2
+
3
+ import urllib
4
+
5
+ from gitlab import __version__
6
+ from gitlab.cli_handler import CLIHandler
7
+ import sys
8
+
9
+ from utils import mock_empty_response, mock_one_response
10
+
11
+
12
+ class TestCliHandler:
13
+
14
+ def test_version(self, capsys):
15
+ args = ["glpkg", "-v"]
16
+ with patch.object(sys, "argv", args):
17
+ handler = CLIHandler()
18
+ handler.do_it()
19
+ out, err = capsys.readouterr()
20
+ assert out == __version__ + "\n"
21
+ assert err == ""
22
+
23
+ def test_list_empty(self, mock_empty_response, capsys):
24
+ args = ["glpkg", "list", "--project", "18105942", "--name", "AABCComponent"]
25
+ with patch.object(sys, "argv", args):
26
+ with patch.object(
27
+ urllib.request, "urlopen", return_value=mock_empty_response
28
+ ):
29
+ handler = CLIHandler()
30
+ handler.do_it()
31
+ out, err = capsys.readouterr()
32
+ assert out == "Name\t\tVersion\n"
33
+ assert err == ""
34
+
35
+ def test_list_one(self, mock_one_response, capsys):
36
+ args = ["glpkg", "list", "--project", "18105942", "--name", "ABCComponent"]
37
+ with patch.object(sys, "argv", args):
38
+ with patch.object(
39
+ urllib.request, "urlopen", return_value=mock_one_response
40
+ ):
41
+ handler = CLIHandler()
42
+ handler.do_it()
43
+ out, err = capsys.readouterr()
44
+ assert out == "Name\t\tVersion\nABCComponent\t0.0.1\n"
45
+ assert err == ""
@@ -1,14 +1,17 @@
1
- import io
2
- import pytest
3
1
  import urllib
4
2
  from gitlab.packages import *
5
3
  from unittest.mock import mock_open, patch
6
4
 
5
+ from utils import (
6
+ ResponseMock,
7
+ test_gitlab,
8
+ mock_empty_response,
9
+ mock_one_response,
10
+ mock_five_response,
11
+ )
7
12
 
8
- class TestGitLab:
9
- @pytest.fixture
10
- def test_gitlab(self):
11
- return Packages("gl-host", "token-name", "token-value")
13
+
14
+ class TestPackages:
12
15
 
13
16
  def test_api_url(self, test_gitlab):
14
17
  assert test_gitlab.api_url() == "https://gl-host/api/v4/"
@@ -33,79 +36,72 @@ class TestGitLab:
33
36
  test_gitlab = Packages("gl-host", "", "")
34
37
  assert test_gitlab.get_headers() == {}
35
38
 
36
- def test_list_packages_none(self, test_gitlab):
37
- data = io.StringIO("[]")
38
- with patch.object(urllib.request, "urlopen", return_value=data):
39
+ def test_list_packages_none(self, test_gitlab, mock_empty_response):
40
+ with patch.object(urllib.request, "urlopen", return_value=mock_empty_response):
39
41
  packages = test_gitlab.list_packages("24", "package-name")
40
42
  assert len(packages) == 0
41
43
 
42
- def test_list_packages_one(self, test_gitlab):
43
- data = io.StringIO('[{"name": "package-name", "version": "0.1.2"}]')
44
- with patch.object(urllib.request, "urlopen", return_value=data):
45
- packages = test_gitlab.list_packages("24", "package-name")
44
+ def test_list_packages_one(self, test_gitlab, mock_one_response):
45
+ with patch.object(urllib.request, "urlopen", return_value=mock_one_response):
46
+ packages = test_gitlab.list_packages("18105942", "ABCComponent")
46
47
  assert len(packages) == 1
47
48
 
48
49
  def test_list_name_packages_filter(self, test_gitlab):
49
- data = io.StringIO(
50
- '[{"name": "package-name", "version": "0.1.2"}, {"name": "package-name-something", "version": "0.1.2"}]'
50
+ data = ResponseMock(
51
+ 200,
52
+ "",
53
+ '[{"name": "package-name", "version": "0.1.2"}, {"name": "package-name-something", "version": "0.1.2"}]',
51
54
  )
52
55
  with patch.object(urllib.request, "urlopen", return_value=data):
53
56
  packages = test_gitlab.list_packages("24", "package-name")
54
57
  assert len(packages) == 1
55
58
 
56
- def test_list_name_packages_five(self, test_gitlab):
57
- data = io.StringIO(
58
- '[{"name": "package-name", "version": "0.1"}, {"name": "package-name", "version": "0.2"}, {"name": "package-name", "version": "0.3"}, {"name": "package-name", "version": "0.4"}, {"name": "package-name", "version": "0.5"}]'
59
- )
60
- with patch.object(urllib.request, "urlopen", return_value=data):
61
- packages = test_gitlab.list_packages("24", "package-name")
59
+ def test_list_name_packages_five(self, test_gitlab, mock_five_response):
60
+ with patch.object(urllib.request, "urlopen", return_value=mock_five_response):
61
+ packages = test_gitlab.list_packages("18105942", "ABCComponent")
62
62
  assert len(packages) == 5
63
63
 
64
- def test_list_files_none(self, test_gitlab):
65
- data = io.StringIO("[]")
66
- with patch.object(urllib.request, "urlopen", return_value=data):
64
+ def test_list_files_none(self, test_gitlab, mock_empty_response):
65
+ with patch.object(urllib.request, "urlopen", return_value=mock_empty_response):
67
66
  packages = test_gitlab.list_files("24", "123")
68
67
  assert len(packages) == 0
69
68
 
70
69
  def test_list_files_one(self, test_gitlab):
71
- data = io.StringIO('[{"file_name": "filea.txt"}]')
70
+ data = ResponseMock(200, "", '[{"file_name": "filea.txt"}]')
72
71
  with patch.object(urllib.request, "urlopen", return_value=data):
73
72
  packages = test_gitlab.list_files("24", "123")
74
73
  assert len(packages) == 1
75
74
 
76
75
  def test_list_files_five(self, test_gitlab):
77
- data = io.StringIO(
78
- '[{"file_name": "filea.txt"}, {"file_name": "fileb.txt"}, {"file_name": "filec.txt"}, {"file_name": "filed.txt"}, {"file_name": "filee.txt"}]'
76
+ data = ResponseMock(
77
+ 200,
78
+ "",
79
+ '[{"file_name": "filea.txt"}, {"file_name": "fileb.txt"}, {"file_name": "filec.txt"}, {"file_name": "filed.txt"}, {"file_name": "filee.txt"}]',
79
80
  )
80
81
  with patch.object(urllib.request, "urlopen", return_value=data):
81
82
  packages = test_gitlab.list_files("24", "123")
82
83
  assert len(packages) == 5
83
84
 
84
- def test_package_id_none(self, test_gitlab):
85
- data = io.StringIO("[]")
86
- with patch.object(urllib.request, "urlopen", return_value=data):
85
+ def test_package_id_none(self, test_gitlab, mock_empty_response):
86
+ with patch.object(urllib.request, "urlopen", return_value=mock_empty_response):
87
87
  packages = test_gitlab.get_package_id("24", "package-name", "0.1")
88
88
  assert packages == 0
89
89
 
90
90
  def test_package_id_one(self, test_gitlab):
91
- data = io.StringIO('[{"id": 123}]')
91
+ data = ResponseMock(200, "", '[{"id": 123}]')
92
92
  with patch.object(urllib.request, "urlopen", return_value=data):
93
93
  packages = test_gitlab.get_package_id("24", "package-name", "0.1")
94
94
  assert packages == 123
95
95
 
96
96
  def test_upload_file(self, test_gitlab):
97
- class rmock:
98
- def getcode():
99
- return 201
100
-
97
+ data = ResponseMock(201, "", "[]")
101
98
  with patch("builtins.open", mock_open(read_data="data")):
102
- with patch.object(urllib.request, "urlopen", return_value=rmock):
99
+ with patch.object(urllib.request, "urlopen", return_value=data):
103
100
  packages = test_gitlab.upload_file("24", "package-name", "0.1", "file")
104
101
  assert packages == 0
105
102
 
106
103
  def test_download_file(self, test_gitlab):
107
- data = io.StringIO("file-content")
108
- m = mock_open()
104
+ data = ResponseMock(200, "", "file-content")
109
105
  with patch("builtins.open", mock_open()) as file_mock:
110
106
  # mock_open.write.return_value = 0
111
107
  with patch.object(urllib.request, "urlopen", return_value=data):
@@ -1,130 +0,0 @@
1
- import json
2
- from urllib import request, parse
3
-
4
-
5
- class Packages:
6
- def __init__(self, host: str, token_type: str, token: str):
7
- self.host = host
8
- self.token_type = token_type
9
- self.token = token
10
-
11
- def api_url(self) -> str:
12
- return "https://{}/api/v4/".format(parse.quote(self.host))
13
-
14
- def project_api_url(self, project: str) -> str:
15
- return self.api_url() + "projects/{}/".format(parse.quote_plus(project))
16
-
17
- def get_headers(self):
18
- headers = {}
19
- if self.token_type and self.token:
20
- headers = {self.token_type: self.token}
21
- return headers
22
-
23
- def list_packages(self, project: str, package_name: str) -> list:
24
- packages = []
25
- with request.urlopen(
26
- request.Request(
27
- self.project_api_url(project)
28
- + "packages?package_name="
29
- + parse.quote_plus(package_name),
30
- headers=self.get_headers(),
31
- )
32
- ) as res:
33
- data = res.read()
34
- for package in json.loads(data):
35
- name = parse.unquote(package["name"])
36
- version = parse.unquote(package["version"])
37
- # The GitLab API returns packages that have some match to the filter;
38
- # let's filter out non-exact matches
39
- if package_name != name:
40
- continue
41
- packages.append({"name": name, "version": version})
42
- return packages
43
-
44
- def list_files(self, project: str, package_id: int) -> list:
45
- files = []
46
- with request.urlopen(
47
- request.Request(
48
- self.project_api_url(project)
49
- + "packages/"
50
- + parse.quote_plus(str(package_id))
51
- + "/package_files",
52
- headers=self.get_headers(),
53
- )
54
- ) as x:
55
- data = x.read()
56
- for package in json.loads(
57
- data,
58
- ):
59
- # Only append the filename once to the list of files
60
- # as there's no way to download them separately through
61
- # the API
62
- filename = parse.unquote(package["file_name"])
63
- if filename not in files:
64
- files.append(filename)
65
- return files
66
-
67
- def get_package_id(
68
- self, project: str, package_name: str, package_version: str
69
- ) -> int:
70
- id = 0
71
- with request.urlopen(
72
- request.Request(
73
- self.project_api_url(project)
74
- + "packages?package_name="
75
- + parse.quote_plus(package_name)
76
- + "&package_version="
77
- + parse.quote_plus(package_version),
78
- headers=self.get_headers(),
79
- )
80
- ) as res:
81
- data = res.read()
82
- package = json.loads(data)
83
- if len(package) == 1:
84
- package = package.pop()
85
- id = package["id"]
86
- return id
87
-
88
- def download_file(
89
- self, project: str, package_name: str, package_version: str, file: str
90
- ) -> int:
91
- ret = 1
92
- with request.urlopen(
93
- request.Request(
94
- self.project_api_url(project)
95
- + "packages/generic/"
96
- + parse.quote_plus(package_name)
97
- + "/"
98
- + parse.quote_plus(package_version)
99
- + "/"
100
- + parse.quote(str(file)),
101
- headers=self.get_headers(),
102
- )
103
- ) as req:
104
- with open(str(file), "wb") as file:
105
- file.write(req.read())
106
- ret = 0
107
- return ret
108
-
109
- def upload_file(
110
- self, project: str, package_name: str, package_version: str, file: str
111
- ) -> int:
112
- ret = 1
113
- with open(str(file), "rb") as data:
114
- res = request.urlopen(
115
- request.Request(
116
- self.project_api_url(project)
117
- + "packages/generic/"
118
- + parse.quote_plus(package_name)
119
- + "/"
120
- + parse.quote_plus(package_version)
121
- + "/"
122
- + parse.quote(str(file)),
123
- method="PUT",
124
- data=data,
125
- headers=self.get_headers(),
126
- )
127
- )
128
- if res.getcode() == 201: # 201 is created
129
- ret = 0
130
- return ret
File without changes
File without changes
File without changes
File without changes