glpkg 0.0.1__py3-none-any.whl → 1.1.0__py3-none-any.whl
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.
- gitlab/__init__.py +1 -1
- gitlab/cli_handler.py +33 -17
- gitlab/packages.py +109 -76
- {glpkg-0.0.1.dist-info → glpkg-1.1.0.dist-info}/METADATA +1 -2
- glpkg-1.1.0.dist-info/RECORD +10 -0
- glpkg-0.0.1.dist-info/RECORD +0 -10
- {glpkg-0.0.1.dist-info → glpkg-1.1.0.dist-info}/WHEEL +0 -0
- {glpkg-0.0.1.dist-info → glpkg-1.1.0.dist-info}/entry_points.txt +0 -0
- {glpkg-0.0.1.dist-info → glpkg-1.1.0.dist-info}/licenses/LICENSE.md +0 -0
- {glpkg-0.0.1.dist-info → glpkg-1.1.0.dist-info}/top_level.txt +0 -0
gitlab/__init__.py
CHANGED
gitlab/cli_handler.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
import netrc
|
|
2
3
|
import os
|
|
3
4
|
from gitlab import Packages, __version__
|
|
4
5
|
|
|
@@ -47,7 +48,7 @@ class CLIHandler:
|
|
|
47
48
|
"-c",
|
|
48
49
|
"--ci",
|
|
49
50
|
action="store_true",
|
|
50
|
-
help="Use this
|
|
51
|
+
help="Use this in GitLab jobs. In this case CI_SERVER_HOST, CI_PROJECT_ID, and CI_JOB_TOKEN variables from the environment are used. --project and --token can be used to override project ID and the CI_JOB_TOKEN to a personal or project access token.",
|
|
51
52
|
)
|
|
52
53
|
parser.add_argument(
|
|
53
54
|
"-p",
|
|
@@ -56,12 +57,18 @@ class CLIHandler:
|
|
|
56
57
|
help="The project ID or path. For example 123456 or namespace/project.",
|
|
57
58
|
)
|
|
58
59
|
parser.add_argument("-n", "--name", type=str, help="The package name.")
|
|
59
|
-
parser.
|
|
60
|
+
group2 = parser.add_mutually_exclusive_group()
|
|
61
|
+
group2.add_argument(
|
|
60
62
|
"-t",
|
|
61
63
|
"--token",
|
|
62
64
|
type=str,
|
|
63
65
|
help="Private or project access token that is used to authenticate with the package registry. Leave empty if the registry is public. The token must have 'read API' or 'API' scope.",
|
|
64
66
|
)
|
|
67
|
+
group2.add_argument(
|
|
68
|
+
"--netrc",
|
|
69
|
+
action="store_true",
|
|
70
|
+
help="Set to use a token from .netrc file (~/.netrc) for the host. The .netrc username is ignored due to API restrictions. PRIVATE-TOKEN is used instead. Note that .netrc file access rights must be correct.",
|
|
71
|
+
)
|
|
65
72
|
|
|
66
73
|
def _register_download_parser(self, parser):
|
|
67
74
|
self._register_common_arguments(parser)
|
|
@@ -71,35 +78,40 @@ class CLIHandler:
|
|
|
71
78
|
def _args(self, args):
|
|
72
79
|
if args.ci:
|
|
73
80
|
host = os.environ["CI_SERVER_HOST"]
|
|
81
|
+
project = os.environ["CI_PROJECT_ID"]
|
|
82
|
+
token = os.environ["CI_JOB_TOKEN"]
|
|
83
|
+
token_user = "JOB-TOKEN"
|
|
74
84
|
if args.project:
|
|
75
85
|
project = args.project
|
|
76
|
-
else:
|
|
77
|
-
project = os.environ["CI_PROJECT_ID"]
|
|
78
86
|
if args.token:
|
|
79
|
-
token_user = "PRIVATE-TOKEN"
|
|
80
87
|
token = args.token
|
|
81
|
-
|
|
82
|
-
token_user = "JOB-TOKEN"
|
|
83
|
-
token = os.environ["CI_JOB_TOKEN"]
|
|
88
|
+
token_user = "PRIVATE-TOKEN"
|
|
84
89
|
else:
|
|
85
90
|
host = args.host
|
|
86
91
|
project = args.project
|
|
87
|
-
token_user = "PRIVATE-TOKEN"
|
|
88
92
|
token = args.token
|
|
93
|
+
token_user = "PRIVATE-TOKEN"
|
|
94
|
+
if args.netrc:
|
|
95
|
+
_, _, token = netrc.netrc().authenticators(host)
|
|
96
|
+
token_user = "PRIVATE-TOKEN"
|
|
89
97
|
name = args.name
|
|
90
98
|
return host, project, name, token_user, token
|
|
91
99
|
|
|
92
100
|
def _download_handler(self, args) -> int:
|
|
101
|
+
ret = 1
|
|
93
102
|
host, project, name, token_user, token = self._args(args)
|
|
94
103
|
version = args.version
|
|
95
104
|
gitlab = Packages(host, token_user, token)
|
|
96
105
|
package_id = gitlab.get_package_id(project, name, version)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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!")
|
|
103
115
|
return ret
|
|
104
116
|
|
|
105
117
|
def _register_list_parser(self, parser):
|
|
@@ -126,9 +138,13 @@ class CLIHandler:
|
|
|
126
138
|
parser.set_defaults(action=self._upload)
|
|
127
139
|
|
|
128
140
|
def _upload(self, args) -> int:
|
|
141
|
+
ret = 1
|
|
129
142
|
host, project, name, token_user, token = self._args(args)
|
|
130
143
|
version = args.version
|
|
131
144
|
file = args.file
|
|
132
|
-
|
|
133
|
-
|
|
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!")
|
|
134
150
|
return ret
|
gitlab/packages.py
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
from http.client import HTTPMessage
|
|
1
2
|
import json
|
|
3
|
+
import logging
|
|
2
4
|
from urllib import request, parse
|
|
3
5
|
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
4
8
|
|
|
5
9
|
class Packages:
|
|
6
10
|
def __init__(self, host: str, token_type: str, token: str):
|
|
@@ -14,95 +18,123 @@ class Packages:
|
|
|
14
18
|
def project_api_url(self, project: str) -> str:
|
|
15
19
|
return self.api_url() + "projects/{}/".format(parse.quote_plus(project))
|
|
16
20
|
|
|
17
|
-
def get_headers(self):
|
|
21
|
+
def get_headers(self) -> dict:
|
|
18
22
|
headers = {}
|
|
19
23
|
if self.token_type and self.token:
|
|
20
24
|
headers = {self.token_type: self.token}
|
|
21
25
|
return headers
|
|
22
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
|
+
|
|
23
70
|
def list_packages(self, project: str, package_name: str) -> list:
|
|
24
71
|
packages = []
|
|
25
|
-
with
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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})
|
|
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})
|
|
42
84
|
return packages
|
|
43
85
|
|
|
44
86
|
def list_files(self, project: str, package_id: int) -> list:
|
|
45
87
|
files = []
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
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)
|
|
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)
|
|
65
98
|
return files
|
|
66
99
|
|
|
67
100
|
def get_package_id(
|
|
68
101
|
self, project: str, package_name: str, package_version: str
|
|
69
102
|
) -> int:
|
|
70
103
|
id = 0
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
id = package["id"]
|
|
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"]
|
|
86
118
|
return id
|
|
87
119
|
|
|
88
120
|
def download_file(
|
|
89
121
|
self, project: str, package_name: str, package_version: str, file: str
|
|
90
122
|
) -> int:
|
|
91
123
|
ret = 1
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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:
|
|
104
136
|
with open(str(file), "wb") as file:
|
|
105
|
-
file.write(
|
|
137
|
+
file.write(data)
|
|
106
138
|
ret = 0
|
|
107
139
|
return ret
|
|
108
140
|
|
|
@@ -110,21 +142,22 @@ class Packages:
|
|
|
110
142
|
self, project: str, package_name: str, package_version: str, file: str
|
|
111
143
|
) -> int:
|
|
112
144
|
ret = 1
|
|
145
|
+
logger.debug("Uploading file " + file)
|
|
113
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
|
+
)
|
|
114
156
|
res = request.urlopen(
|
|
115
157
|
request.Request(
|
|
116
|
-
self.
|
|
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(),
|
|
158
|
+
url, method="PUT", data=data, headers=self.get_headers()
|
|
126
159
|
)
|
|
127
160
|
)
|
|
128
|
-
if res.
|
|
161
|
+
if res.status == 201: # 201 is created
|
|
129
162
|
ret = 0
|
|
130
163
|
return ret
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: glpkg
|
|
3
|
-
Version:
|
|
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.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
gitlab/__init__.py,sha256=EsSFZxYgGAL2i0sL5zkoz71JP7XbsWdg_jv_uOqrrXE,60
|
|
2
|
+
gitlab/__main__.py,sha256=88VNY5Qrmn8g0rNcnjKNdN746--0chHsKBMH3PD3Nao,177
|
|
3
|
+
gitlab/cli_handler.py,sha256=6B22RXAtOzVL-mpm7U3OPX1_SSrepQk12cwTG0TI3eU,6102
|
|
4
|
+
gitlab/packages.py,sha256=l9tFEUsHMWF0vtv5ouVB4nvmWrJ4KDaS6f5e1lpjNyk,5683
|
|
5
|
+
glpkg-1.1.0.dist-info/licenses/LICENSE.md,sha256=josGXvZq628dNS0Iru58-DPE7dRpDXzjJxKKT35103g,1065
|
|
6
|
+
glpkg-1.1.0.dist-info/METADATA,sha256=65URv9IMuUo1XnmqKolpuJ12nkjcFKJg8rKMz4XeUIQ,5500
|
|
7
|
+
glpkg-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
glpkg-1.1.0.dist-info/entry_points.txt,sha256=xHPZwx2oShYDZ3AyH7WSIvuhFMssy7QLlQk-JAbje_w,46
|
|
9
|
+
glpkg-1.1.0.dist-info/top_level.txt,sha256=MvIaP8p_Oaf4gO_hXmHkX-5y2deHLp1pe6tJR3ukQ6o,7
|
|
10
|
+
glpkg-1.1.0.dist-info/RECORD,,
|
glpkg-0.0.1.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
gitlab/__init__.py,sha256=xA73jnLd2tGhd-9FwN86PRC0GscMytsHi3Sa6W-_WMk,60
|
|
2
|
-
gitlab/__main__.py,sha256=88VNY5Qrmn8g0rNcnjKNdN746--0chHsKBMH3PD3Nao,177
|
|
3
|
-
gitlab/cli_handler.py,sha256=kbbKMAxdTqeso_xSO5ZwmXI9ppGt0mABlxlqifkmaaM,5346
|
|
4
|
-
gitlab/packages.py,sha256=Aw2Zt3Ok1uA9K8p8kugP3vizvcAxFMDaTQTwC-HH_sI,4431
|
|
5
|
-
glpkg-0.0.1.dist-info/licenses/LICENSE.md,sha256=josGXvZq628dNS0Iru58-DPE7dRpDXzjJxKKT35103g,1065
|
|
6
|
-
glpkg-0.0.1.dist-info/METADATA,sha256=IbfrEJtkS8OLW0W1VrfHnOlB8bdtpvGsUXSaun4YEOY,5617
|
|
7
|
-
glpkg-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
-
glpkg-0.0.1.dist-info/entry_points.txt,sha256=xHPZwx2oShYDZ3AyH7WSIvuhFMssy7QLlQk-JAbje_w,46
|
|
9
|
-
glpkg-0.0.1.dist-info/top_level.txt,sha256=MvIaP8p_Oaf4gO_hXmHkX-5y2deHLp1pe6tJR3ukQ6o,7
|
|
10
|
-
glpkg-0.0.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|