Package copr :: Package client :: Module client
[hide private]
[frames] | no frames]

Source Code for Module copr.client.client

  1  #-*- coding: UTF-8 -*- 
  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 
30 31 32 33 34 -class CoprClient(object):
35 - def __init__(self, config=None):
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
50 - def __str__(self):
51 return "<Copr client. username: {0}, api url: {1}>".format( 52 self.username, self.api_url 53 )
54 55 @property
56 - def api_url(self):
57 return "{0}/api".format(self.copr_url)
58 59 @staticmethod
60 - def create_from_file_config(filepath=None):
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 #TODO: better status code handling 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
152 - def get_build_status(self, build_id):
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
160 - def get_build_details(self, build_id):
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
167 - def cancel_build(self, build_id):
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
173 - def delete_project(self, projectname):
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
184 - def get_project_details(self, projectname):
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 #def on bad_response() 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
230 - def get_projects_list(self, username=None):
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
239 - def get_project_chroot_details(self, name, chroot):
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
246 - def modify_project_chroot_details(self, projectname, chroot, pkgs=None):
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
316 - def search_projects(self, query):
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