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 CHANGED
@@ -1,3 +1,3 @@
1
1
  from gitlab.packages import Packages
2
2
 
3
- __version__ = "0.0.1"
3
+ __version__ = "1.1.0"
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 to run the tool in GitLab pipelines. 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
+ 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.add_argument(
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
- else:
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
- files = gitlab.list_files(project, package_id)
98
- ret = 1
99
- for file in files:
100
- ret = gitlab.download_file(project, name, version, file)
101
- if not ret:
102
- 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!")
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
- gitlab = Packages(host, token_user, token)
133
- 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!")
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 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})
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
- 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)
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
- 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"]
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
- 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:
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(req.read())
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.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(),
158
+ url, method="PUT", data=data, headers=self.get_headers()
126
159
  )
127
160
  )
128
- if res.getcode() == 201: # 201 is created
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: 0.0.1
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,,
@@ -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