Package pulp :: Package client :: Module repolib
[hide private]
[frames] | no frames]

Source Code for Module pulp.client.repolib

  1  # 
  2  # Copyright (c) 2010 Red Hat, Inc. 
  3  # 
  4  # This software is licensed to you under the GNU General Public License, 
  5  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  6  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  8  # along with this software; if not, see 
  9  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
 10  # 
 11  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 12  # granted to use or replicate Red Hat trademarks that are incorporated 
 13  # in this software or its documentation. 
 14  # 
 15   
 16  """ 
 17  Contains repo management (backend) classes. 
 18  """ 
 19   
 20  import os 
 21  from iniparse import ConfigParser as Parser 
 22  from pulp.client import ConsumerId 
 23  from pulp.client.connection import ConsumerConnection, RepoConnection 
 24  from pulp.client.lock import Lock 
 25  from pulp.client.config import Config 
 26  from pulp.client.logutil import getLogger 
 27   
 28  log = getLogger(__name__) 
 29   
 30   
31 -class ActionLock(Lock):
32 """ 33 Action lock. 34 @cvar PATH: The lock file absolute path. 35 @type PATH: str 36 """ 37 38 PATH = '/var/run/subsys/pulp/repolib.pid' 39
40 - def __init__(self):
41 Lock.__init__(self, self.PATH)
42 43
44 -class RepoLib:
45 """ 46 Library for performing yum repo management. 47 @ivar lock: The action lock. Ensures only 1 instance updating repos. 48 @type lock: L{Lock} 49 """ 50
51 - def __init__(self, lock=ActionLock()):
52 """ 53 @param lock: A lock. 54 @type lock: L{Lock} 55 """ 56 self.lock = lock
57
58 - def update(self):
59 """ 60 Update yum repos based on pulp bind (subscription). 61 """ 62 lock = self.lock 63 lock.acquire() 64 try: 65 action = UpdateAction() 66 return action.perform() 67 finally: 68 lock.release()
69 70
71 -class Pulp:
72 """ 73 The pulp server. 74 """
75 - def __init__(self, cfg):
76 host = cfg.server.host 77 port = cfg.server.port 78 self.rapi = RepoConnection(host=host, port=port) 79 self.capi = ConsumerConnection(host=host, port=port)
80
81 - def getProducts(self):
82 """ 83 Get subscribed products. 84 @return: A list of products 85 @rtype: list 86 """ 87 repos = [] 88 product = dict(content=repos) 89 products = (product,) 90 cid = self.consumerId() 91 consumer = self.capi.consumer(cid) 92 for repoid in consumer['repoids']: 93 repo = self.rapi.repository(repoid) 94 if repo: 95 repos.append(repo) 96 return products
97
98 - def consumerId(self):
99 return str(ConsumerId())
100 101
102 -class Action:
103 """ 104 Action base class. 105 """ 106
107 - def __init__(self, ):
108 self.cfg = Config() 109 self.pulp = Pulp(self.cfg)
110 111
112 -class UpdateAction(Action):
113 """ 114 Update the yum repositores based on pulp bindings (subscription). 115 """ 116
117 - def perform(self):
118 """ 119 Perform the action. 120 - Get the content set(s) from pulp. 121 - Merge with yum .repo file. 122 - Write the merged .repo file. 123 @return: The number of updates performed. 124 @rtype: int 125 """ 126 repod = RepoFile() 127 repod.read() 128 valid = set() 129 updates = 0 130 for cont in self.getUniqueContent(): 131 valid.add(cont.id) 132 existing = repod.section(cont.id) 133 if existing is None: 134 updates += 1 135 repod.add(cont) 136 continue 137 updates += existing.update(cont) 138 repod.update(existing) 139 for section in repod.sections(): 140 if section not in valid: 141 updates += 1 142 repod.delete(section) 143 repod.write() 144 return updates
145
146 - def getUniqueContent(self):
147 """ 148 Get the B{unique} content sets from pulp. 149 @return: A unique set L{Repo} objects reported by pulp. 150 @rtype: [L{Repo},...] 151 """ 152 unique = set() 153 products = self.pulp.getProducts() 154 baseurl = self.cfg.cds.baseurl 155 for product in products: 156 for r in self.getContent(product, baseurl): 157 unique.add(r) 158 return unique
159
160 - def getContent(self, product, baseurl):
161 """ 162 Get L{Repo} object(s) for a given subscription. 163 @param product: A product (contains content sets). 164 @type product: dict 165 @param baseurl: The yum base url to be joined with relative url. 166 @type baseurl: str 167 @return: A list of L{Repo} objects for the specified product. 168 @rtype: [L{Repo},...] 169 """ 170 lst = [] 171 for cont in product['content']: 172 if not cont: 173 continue 174 id = cont['id'] 175 path = cont['relative_path'] 176 repo = Repo(id) 177 repo['name'] = cont['name'] 178 repo['baseurl'] = self.join(baseurl, path) 179 repo['enabled'] = cont.get('enabled', '1') 180 lst.append(repo) 181 return lst
182
183 - def join(self, base, url):
184 """ 185 Join the base and url. 186 @param base: The URL base (protocol, host & port). 187 @type base: str 188 @param url: The relative url. 189 @type url: str 190 @return: The complete (joined) url. 191 @rtype: str 192 """ 193 if '://' in url: 194 return url 195 else: 196 return os.path.join(base, url)
197 198
199 -class Repo(dict):
200 """ 201 A yum repo (content set). 202 @cvar CA: The absolute path to the CA. 203 @type CA: str 204 @cvar PROPERTIES: Yum repo property definitions. 205 @type PROPERTIES: tuple. 206 """ 207 208 CA = None 209 210 # (name, mutable, default) 211 PROPERTIES = ( 212 ('name', 0, None), 213 ('baseurl', 0, None), 214 ('enabled', 1, '1'), 215 ('sslverify', 0, '0'), 216 ) 217
218 - def __init__(self, id):
219 """ 220 @param id: The repo (unique) id. 221 @type id: str 222 """ 223 self.id = id 224 for k,m,d in self.PROPERTIES: 225 self[k] = d
226
227 - def items(self):
228 """ 229 Get I{ordered} items. 230 @return: A list of ordered items. 231 @rtype: list 232 """ 233 lst = [] 234 for k,m,d in self.PROPERTIES: 235 v = self[k] 236 lst.append((k,v)) 237 return tuple(lst)
238
239 - def update(self, other):
240 """ 241 Update (merge) based on property definitions. 242 @param other: The object to merge. 243 @type other: L{Repo}. 244 @return: The number of properties updated. 245 @rtype: int 246 """ 247 count = 0 248 for k,m,d in self.PROPERTIES: 249 v = other.get(k) 250 if not m: 251 if self[k] == v: 252 continue 253 self[k] = v 254 count += 1 255 return count
256
257 - def __str__(self):
258 s = [] 259 s.append('[%s]' % self.id) 260 for k in self.PROPERTIES: 261 v = self.get(k) 262 if v is None: 263 continue 264 s.append('%s=%s' % (k, v)) 265 266 return '\n'.join(s)
267
268 - def __eq__(self, other):
269 return ( self.id == other.id )
270
271 - def __hash__(self):
272 return hash(self.id)
273 274
275 -class RepoFile(Parser):
276 """ 277 Represents a .repo file and is primarily a wrapper around 278 I{iniparse} to get around its short comings and understand L{Repo} objects. 279 @cvar PATH: The absolute path to a .repo file. 280 @type PATH: str 281 """ 282 283 PATH = '/etc/yum.repos.d/' 284
285 - def __init__(self, name='pulp.repo'):
286 """ 287 @param name: The absolute path to a .repo file. 288 @type name: str 289 """ 290 Parser.__init__(self) 291 self.path = os.path.join(self.PATH, name) 292 self.create()
293
294 - def read(self):
295 """ 296 Read and parse the file. 297 """ 298 r = Reader(self.path) 299 Parser.readfp(self, r)
300
301 - def write(self):
302 """ 303 Write the file. 304 """ 305 f = open(self.path, 'w') 306 Parser.write(self, f) 307 f.close()
308
309 - def add(self, repo):
310 """ 311 Add a repo and create section if needed. 312 @param repo: A repo to add. 313 @type repo: L{Repo} 314 """ 315 self.add_section(repo.id) 316 self.update(repo)
317
318 - def delete(self, section):
319 """ 320 Delete a section (repo name). 321 @return: self 322 @rtype: L{RepoFile} 323 """ 324 return self.remove_section(section)
325
326 - def update(self, repo):
327 """ 328 Update the repo section using the specified repo. 329 @param repo: A repo used to update. 330 @type repo: L{Repo} 331 """ 332 for k,v in repo.items(): 333 Parser.set(self, repo.id, k, v)
334
335 - def section(self, section):
336 """ 337 Get a L{Repo} (section) by name. 338 @param section: A section (repo) name. 339 @type section: str 340 @return: A repo for the section name. 341 @rtype: L{Repo} 342 """ 343 if self.has_section(section): 344 repo = Repo(section) 345 for k,v in self.items(section): 346 repo[k] = v 347 return repo
348
349 - def create(self):
350 """ 351 Create the .repo file with appropriate header/footer 352 if it does not already exist. 353 """ 354 if os.path.exists(self.path): 355 return 356 f = open(self.path, 'w') 357 s = [] 358 s.append('#') 359 s.append('# Pulp Repositories') 360 s.append('# Managed by Pulp client') 361 s.append('#') 362 f.write('\n'.join(s)) 363 f.close()
364 365
366 -class Reader:
367 """ 368 Reader object used to mitigate annoying behavior of 369 iniparse of leaving blank lines when removing sections. 370 """ 371
372 - def __init__(self, path):
373 """ 374 @param path: The absolute path to a .repo file. 375 @type path: str 376 """ 377 f = open(path) 378 bfr = f.read() 379 self.idx = 0 380 self.lines = bfr.split('\n') 381 f.close()
382
383 - def readline(self):
384 """ 385 Read the next line. 386 Strips annoying blank lines left by iniparse when 387 removing sections. 388 @return: The next line (or None). 389 @rtype: str 390 """ 391 nl = 0 392 i = self.idx 393 eof = len(self.lines) 394 while 1: 395 if i == eof: 396 return 397 ln = self.lines[i] 398 i += 1 399 if not ln: 400 nl += 1 401 else: 402 break 403 if nl: 404 i -= 1 405 ln = '\n' 406 self.idx = i 407 return ln
408 409
410 -def main():
411 print 'Updating Pulp repository' 412 repolib = RepoLib() 413 updates = repolib.update() 414 print '%d updates required' % updates 415 print 'done'
416 417 if __name__ == '__main__': 418 main() 419