1
2
3 from __future__ import print_function
4 from __future__ import unicode_literals
5 from __future__ import division
6 from __future__ import absolute_import
7
8 import json
9 import os
10 import logging
11
12 import requests
13 import six
14
15 from six.moves import configparser
16
17
18 logging.basicConfig(level=logging.WARN)
19 log = logging.getLogger(__name__)
20
21
22 from .exceptions import CoprConfigException, CoprNoConfException, CoprRequestException, \
23 CoprUnknownResponseException
24
25 from .responses import BuildStatusResponse, BuildRequestResponse, \
26 CreateProjectResponse, BaseResponse, \
27 DeleteProjectResponse, CancelBuildResponse, \
28 ProjectDetailsResponse, BuildDetailsResponse, \
29 GetProjectsListResponse, ModifyProjectResponse, SearchResponse
36 """ Main interface to the copr service
37 :param config: Configuration dictionary.
38 Fields:
39 copr_url - copr service location
40 login - user login, used for identification
41 token - copr api token
42 username - used as copr projects root
43
44 """
45 self.token = config.get("token")
46 self.login = config.get("login")
47 self.username = config.get("username")
48 self.copr_url = config.get("copr_url", "http://copr.fedoraproject.org/")
49
51 return "<Copr client. username: {0}, api url: {1}>".format(
52 self.username, self.api_url
53 )
54
55 @property
57 return "{0}/api".format(self.copr_url)
58
59 @staticmethod
61 """
62 Retrieve copr client information from the config file.
63 :param filepath: specifies config location, default: "~/.config/copr"
64 """
65 raw_config = configparser.ConfigParser()
66 if not filepath:
67 filepath = os.path.join(os.path.expanduser("~"), ".config", "copr")
68 config = {}
69 if not raw_config.read(filepath):
70 raise CoprNoConfException(
71 "No configuration file '~/.config/copr' found. "
72 "See man copr-cli for more information")
73 try:
74 for field in ["username", "login", "token", "copr_url"]:
75 if six.PY2:
76 config[field] = raw_config.get("copr-cli", field, None)
77 elif six.PY3:
78 config[field] = raw_config["copr-cli"].get(field, None)
79
80 except configparser.Error as err:
81 raise CoprConfigException(
82 "Bad configuration file: {0}".format(err))
83 return CoprClient(config=config)
84
85 - def _fetch(self, url, data=None, projectname=None, method=None,
86 skip_auth=False, on_error_response=None):
87 """ Fetch data from server,
88 checks response and raises a CoprCliRequestException with nice error message
89 or CoprCliUnknownResponseException in case of some some error.
90 Otherwise return json object.
91
92 :param url: formed url to fetch
93 :param data: serialised data to send
94 :param skip_auth: don't send auth credentials
95 :param projectname: name of the copr project
96 :param on_error_response: function to handle responses with bad status code
97 """
98 if method is None:
99 method = "get"
100
101 log.debug("Fetching url: {0}, for login: {1}".format(url, self.login))
102 kwargs = {}
103 if not skip_auth:
104 kwargs["auth"] = (self.login, self.token)
105 if data is not None:
106 kwargs["data"] = data
107
108 if method not in ["get", "post", "head", "delete", "put"]:
109 raise Exception("Method {0} not allowed".format(method))
110
111 response = requests.request(
112 method=method.upper(),
113 url=url,
114 **kwargs
115 )
116 log.debug("raw response: {0}".format(response.text))
117
118 if "<title>Sign in Coprs</title>" in response.text:
119 raise CoprRequestException("Invalid API token\n")
120
121 if response.status_code > 299 and on_error_response is not None:
122 return on_error_response(response)
123
124
125 if response.status_code == 404:
126 if projectname is None:
127 raise CoprRequestException(
128 "User {0} is unknown.\n".format(self.username))
129 else:
130 raise CoprRequestException(
131 "Project {0}/{1} not found.\n".format(
132 self.username, projectname))
133
134 if 400 <= response.status_code < 500:
135 log.error("Bad request, raw response body: {0}".format(response.text))
136 elif response.status_code >= 500:
137 log.error("Server error, raw response body: {0}".format(response.text))
138
139 try:
140 output = json.loads(response.text)
141 except ValueError:
142 raise CoprUnknownResponseException(
143 "Unknown response from the server.")
144 if response.status_code != 200:
145 raise CoprRequestException(output["error"])
146
147 if output is None:
148 raise CoprUnknownResponseException(
149 "No response from the server.")
150 return output
151
153 url = "{0}/coprs/build_status/{1}/".format(
154 self.api_url, build_id)
155
156 response = self._fetch(url)
157 return BuildStatusResponse(
158 client=self, build_id=build_id, response=response)
159
161 url = "{0}/coprs/build/{1}/".format(
162 self.api_url, build_id)
163
164 response = self._fetch(url, skip_auth=True)
165 return BuildDetailsResponse(self, response, build_id)
166
168 url = "{0}/coprs/cancel_build/{1}/".format(
169 self.api_url, build_id)
170 response = self._fetch(url, method="post")
171 return CancelBuildResponse(self, response, build_id)
172
174 """
175 Delete the entire project
176 """
177 url = "{0}/coprs/{1}/{2}/delete/".format(
178 self.api_url, self.username, projectname
179 )
180 response = self._fetch(
181 url, data={"verify": "yes"}, method="post")
182 return DeleteProjectResponse(self, response, projectname)
183
185 """
186 Get project details
187 """
188 url = "{0}/coprs/{1}/{2}/detail/".format(
189 self.api_url, self.username, projectname
190 )
191
192 response = self._fetch(url, skip_auth=True)
193 return ProjectDetailsResponse(self, response, projectname)
194
195 - def create_project(
196 self, projectname,
197 description=None, instructions=None,
198 chroots=None, repos=None, initial_pkgs=None
199 ):
200 """
201 Create a new copr project
202 """
203
204 url = "{0}/coprs/{1}/new/".format(
205 self.api_url, self.username)
206
207 if type(repos) == list():
208 repos = " ".join(repos)
209
210 if type(initial_pkgs) == list():
211 initial_pkgs = " ".join(initial_pkgs)
212
213 data = {"name": projectname,
214 "repos": repos,
215 "initial_pkgs": initial_pkgs,
216 "description": description,
217 "instructions": instructions
218 }
219 for chroot in chroots:
220 data[chroot] = "y"
221
222
223 response = self._fetch(url, data=data, method="post")
224 return CreateProjectResponse(
225 self, response,
226 name=projectname, description=description, instructions=instructions,
227 repos=repos, chroots=chroots, initial_pkgs=initial_pkgs
228 )
229
231 """
232 Get list of projects created by the user
233 """
234 url = "{0}/coprs/{1}/".format(
235 self.api_url, username or self.username)
236 response = self._fetch(url)
237 return GetProjectsListResponse(self, response)
238
240 url = "{0}/coprs/{1}/{2}/detail/{3}/".format(
241 self.api_url, self.username, name, chroot
242 )
243 response = self._fetch(url, skip_auth=True)
244 return BaseResponse(self, response)
245
247 if pkgs is None:
248 pkgs = []
249
250 url = "{0}/coprs/{1}/{2}/modify/{3}/".format(
251 self.api_url, self.username, projectname, chroot
252 )
253 data = {
254 "buildroot_pkgs": " ".join(pkgs)
255 }
256 response = self._fetch(url, data=data, method="post")
257 return BaseResponse(self, response)
258
259 - def modify_project(self, projectname, description=None,
260 instructions=None, repos=None):
261
262 """
263 Modifies main project settings.
264
265 :param projectname:
266 :param description:
267 :param instructions:
268 :param repos:
269 :return:
270 """
271 url = "{0}/coprs/{1}/{2}/modify/".format(
272 self.api_url, self.username, projectname
273 )
274 data = {}
275 if description:
276 data["description"] = description
277 if instructions:
278 data["instructions"] = instructions
279 if repos:
280 data["repos"] = repos
281
282 response = self._fetch(url, data=data, method="post")
283 return ModifyProjectResponse(self, response, projectname, description,
284 instructions, repos)
285
286 - def create_new_build(self, projectname, pkgs,
287 timeout=None, memory=None, chroots=None):
288 """
289 Creates new build in `projectname` copr.
290
291 :param projectname: name of copr project (without user namespace)
292 :param pkgs: list of packages to include in build
293 :param timeout: ?build timeout
294 :param memory: amount of required memory for build process
295 :param wait: if True function wait for packages to be build
296 :param chroots: build only with given chroots
297
298 """
299
300 url = "{0}/coprs/{1}/{2}/new_build/".format(
301 self.api_url, self.username, projectname
302 )
303 data = {
304 "pkgs": " ".join(pkgs),
305 "memory_reqs": memory,
306 "timeout": timeout
307 }
308 for chroot in chroots or []:
309 data[chroot] = "y"
310
311 response = self._fetch(url, data, method="post")
312 return BuildRequestResponse(
313 self, response,
314 projectname, pkgs, memory, timeout, chroots)
315
317 url = "{0}/coprs/search/{1}/".format(
318 self.api_url, query
319 )
320 response = self._fetch(url, skip_auth=True)
321 return SearchResponse(self, response, query)
322