1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import logging
19 import gzip
20 import os
21 import traceback
22 from itertools import chain
23 from urlparse import urlparse
24
25
26 from pulp.server import comps_util
27 from pulp.server import crontab
28 from pulp.server import upload
29 from pulp.server.api import repo_sync
30 from pulp.server.api.base import BaseApi
31 from pulp.server.api.package import PackageApi
32 from pulp.server.api.errata import ErrataApi
33 from pulp.server.auditing import audit
34 from pulp.server.event.dispatcher import event
35 from pulp.server import config
36 from pulp.server.db import model
37 from pulp.server.db.connection import get_object_db
38 from pulp.server.pexceptions import PulpException
39 from pulp.server.api.fetch_listings import CDNConnection
40
41 log = logging.getLogger(__name__)
42
43 repo_fields = model.Repo(None, None, None).keys()
46 """
47 API for create/delete/syncing of Repo objects
48 """
49
55
56 @property
58 return ["packages", "packagegroups", "packagegroupcategories"]
59
60 @property
63
68
70 '''
71 Verifies the sync schedule is in the correct cron syntax, throwing an exception if
72 it is not.
73 '''
74 if sync_schedule:
75 item = crontab.CronItem(sync_schedule + ' null')
76 if not item.is_valid():
77 raise PulpException('Invalid sync schedule specified [%s]' % sync_schedule)
78
80 """
81 Protected helper function to look up a repository by id and raise a
82 PulpException if it is not found.
83 """
84 repo = self.repository(id)
85 if repo is None:
86 raise PulpException("No Repo with id: %s found" % id)
87 return repo
88
89 @event(subject='repo.created')
90 @audit(params=['id', 'name', 'arch', 'feed'])
91 - def create(self, id, name, arch, feed=None, symlinks=False, sync_schedule=None, cert_data=None, productid=None):
119
121 CONTENT_CERTS_PATH = config.config.get("repos", "content_cert_location")
122 cert_dir = os.path.join(CONTENT_CERTS_PATH, repoid)
123
124 if not os.path.exists(cert_dir):
125 os.makedirs(cert_dir)
126 cert_files = {}
127 for key, value in cert_data.items():
128 fname = os.path.join(cert_dir, repoid + "." + key)
129 try:
130 log.error("storing file %s" % fname)
131 f = open(fname, 'w')
132 f.write(value)
133 f.close()
134 cert_files[key] = fname
135 except:
136 raise PulpException("Error storing certificate file %s " % key)
137 return cert_files
138
139 @audit(params=['productid', 'content_set'])
141 """
142 Creates a repo associated to a product. Usually through an event raised
143 from candlepin
144 @param productid: A product the candidate repo should be associated with.
145 @type productid: str
146 @param content_set: a dict of content set labels and relative urls
147 @type content_set: dict(<label> : <relative_url>,)
148 @param cert_data: a dictionary of ca_cert, cert and key for this product
149 @type cert_data: dict(ca : <ca_cert>, cert: <ent_cert>, key : <cert_key>)
150 """
151 if not cert_data:
152
153 return
154 cert_files = self._write_certs_to_disk(productid, cert_data)
155 CDN_URL= config.config.get("repos", "content_url")
156 CDN_HOST = urlparse(CDN_URL).hostname
157 serv = CDNConnection(CDN_HOST, cacert=cert_files['ca'],
158 cert=cert_files['cert'], key=cert_files['key'])
159 serv.connect()
160 repo_info = serv.fetch_urls(content_set)
161
162 for label, uri in repo_info.items():
163 try:
164 repo = self.create(label, label, arch=label.split("-")[-1],
165 feed="yum:" + CDN_URL + '/' + uri, cert_data=cert_data, productid=productid)
166 repo['relative_path'] = uri
167 self.update(repo)
168 except:
169 log.error("Error creating repo %s for product %s" % (label, productid))
170 continue
171
172 serv.disconnect()
173
174 @audit()
176 """
177 Lookup available repos associated to a product id
178 @param productid: productid a candidate repo is associated.
179 @type productid: str
180 """
181 return list(self.objectdb.find({"productid" : productid}))
182
183 @audit()
188
189 @audit()
206
208 """
209 Return a list of Repositories
210 """
211 return list(self.objectdb.find(spec=spec, fields=fields))
212
214 """
215 Return a single Repository object
216 """
217 repos = self.repositories({'id': id}, fields)
218 if not repos:
219 return None
220 return repos[0]
221
233
234
243
244 @audit()
257
259 """
260 Responsible for properly associating a Package to a Repo
261 """
262 packages = repo['packages']
263 if p['id'] in packages:
264
265 return
266 packages[p['id']] = p
267
268 @audit()
274
275 - def errata(self, id, types=()):
276 """
277 Look up all applicable errata for a given repo id
278 """
279 repo = self._get_existing_repo(id)
280 errata = repo['errata']
281 if not errata:
282 return []
283 if types:
284 try:
285 return [item for type in types for item in errata[type]]
286 except KeyError, ke:
287 log.debug("Invalid errata type requested :[%s]" % (ke))
288 raise PulpException("Invalid errata type requested :[%s]" % (ke))
289 return list(chain.from_iterable(errata.values()))
290
291 @audit()
299
308
326
327 @audit()
335
344
346 """
347 Responsible for properly removing an Erratum from a Repo
348 """
349 erratum = self.errataapi.erratum(erratumid)
350 if erratum is None:
351 raise PulpException("No Erratum with id: %s found" % erratumid)
352 try:
353 curr_errata = repo['errata'][erratum['type']]
354 if erratum['id'] not in curr_errata:
355 log.debug("Erratum %s Not in repo. Nothing to delete" % erratum['id'])
356 return
357 del curr_errata[curr_errata.index(erratum['id'])]
358 except Exception, e:
359 raise PulpException("Erratum %s delete failed due to Error: %s" % (erratum['id'], e))
360
361 @audit(params=['repoid', 'group_id', 'group_name'])
363 """
364 Creates a new packagegroup saved in the referenced repo
365 @param repoid:
366 @param group_id:
367 @param group_name:
368 @param description:
369 @return packagegroup object
370 """
371 repo = self._get_existing_repo(repoid)
372 if group_id in repo['packagegroups']:
373 raise PulpException("Package group %s already exists in repo %s" %
374 (group_id, repoid))
375 group = model.PackageGroup(group_id, group_name, description)
376 repo["packagegroups"][group_id] = group
377 self.update(repo)
378 self._update_groups_metadata(repo["id"])
379 return group
380
381 @audit()
383 """
384 Remove a packagegroup from a repo
385 @param repoid: repo id
386 @param groupid: package group id
387 """
388 repo = self._get_existing_repo(repoid)
389 if groupid not in repo['packagegroups']:
390 return
391 if repo['packagegroups'][groupid]["immutable"]:
392 raise PulpException("Changes to immutable groups are not supported: %s" % (groupid))
393 del repo['packagegroups'][groupid]
394 self.update(repo)
395 self._update_groups_metadata(repo["id"])
396
397 @audit()
399 """
400 Save the passed in PackageGroup to this repo
401 @param repoid: repo id
402 @param pg: packagegroup
403 """
404 repo = self._get_existing_repo(repoid)
405 pg_id = pg['id']
406 if pg_id in repo['packagegroups']:
407 if repo["packagegroups"][pg_id]["immutable"]:
408 raise PulpException("Changes to immutable groups are not supported: %s" % (pg["id"]))
409 repo['packagegroups'][pg_id] = pg
410 self.update(repo)
411 self._update_groups_metadata(repo["id"])
412
413 @audit()
415 """
416 Save the list of passed in PackageGroup objects to this repo
417 @param repoid: repo id
418 @param pglist: list of packagegroups
419 """
420 repo = self._get_existing_repo(repoid)
421 for item in pglist:
422 if item['id'] in repo['packagegroups']:
423 if repo['packagegroups'][item["id"]]["immutable"]:
424 raise PulpException("Changes to immutable groups are not supported: %s" % (item["id"]))
425 repo['packagegroups'][item['id']] = item
426 self.update(repo)
427 self._update_groups_metadata(repo["id"])
428
430 """
431 Return list of PackageGroup objects in this Repo
432 @param id: repo id
433 @return: packagegroup or None
434 """
435 repo = self._get_existing_repo(id)
436 return repo['packagegroups']
437
439 """
440 Return a PackageGroup from this Repo
441 @param repoid: repo id
442 @param groupid: packagegroup id
443 @return: packagegroup or None
444 """
445 repo = self._get_existing_repo(repoid)
446 return repo['packagegroups'].get(groupid, None)
447
448
449 @audit()
451 """
452 @param repoid: repository id
453 @param groupid: group id
454 @param pkg_names: package names
455 @param gtype: OPTIONAL type of package group,
456 example "mandatory", "default", "optional"
457 """
458 repo = self._get_existing_repo(repoid)
459 if groupid not in repo['packagegroups']:
460 raise PulpException("No PackageGroup with id: %s exists in repo %s"
461 % (groupid, repoid))
462 group = repo["packagegroups"][groupid]
463 if group["immutable"]:
464 raise PulpException("Changes to immutable groups are not supported: %s" % (group["id"]))
465 for pkg_name in pkg_names:
466 if gtype == "mandatory":
467 if pkg_name not in group["mandatory_package_names"]:
468 group["mandatory_package_names"].append(pkg_name)
469 elif gtype == "conditional":
470 raise NotImplementedError("No support for creating conditional groups")
471 elif gtype == "optional":
472 if pkg_name not in group["optional_package_names"]:
473 group["optional_package_names"].append(pkg_name)
474 else:
475 if pkg_name not in group["default_package_names"]:
476 group["default_package_names"].append(pkg_name)
477 self.update(repo)
478 self._update_groups_metadata(repo["id"])
479
480
481 @audit()
483 """
484 @param repoid: repository id
485 @param groupid: group id
486 @param pkg_name: package name
487 @param gtype: OPTIONAL type of package group,
488 example "mandatory", "default", "optional"
489 """
490 repo = self._get_existing_repo(repoid)
491 if groupid not in repo['packagegroups']:
492 raise PulpException("No PackageGroup with id: %s exists in repo %s"
493 % (groupid, repoid))
494 group = repo["packagegroups"][groupid]
495 if group["immutable"]:
496 raise PulpException("Changes to immutable groups are not supported: %s" % (group["id"]))
497 if gtype == "mandatory":
498 if pkg_name in group["mandatory_package_names"]:
499 group["mandatory_package_names"].remove(pkg_name)
500 elif gtype == "conditional":
501 raise NotImplementedError("No support for creating conditional groups")
502 elif gtype == "optional":
503 if pkg_name in group["optional_package_names"]:
504 group["optional_package_names"].remove(pkg_name)
505 else:
506 if pkg_name in group["default_package_names"]:
507 group["default_package_names"].remove(pkg_name)
508 self.update(repo)
509 self._update_groups_metadata(repo["id"])
510
511 @audit(params=['repoid', 'cat_id', 'cat_name'])
513 """
514 Creates a new packagegroupcategory saved in the referenced repo
515 @param repoid:
516 @param cat_id:
517 @param cat_name:
518 @param description:
519 @return packagegroupcategory object
520 """
521 repo = self._get_existing_repo(repoid)
522 if cat_id in repo['packagegroupcategories']:
523 raise PulpException("Package group category %s already exists in repo %s" %
524 (cat_id, repoid))
525 cat = model.PackageGroupCategory(cat_id, cat_name, description)
526 repo["packagegroupcategories"][cat_id] = cat
527 self.update(repo)
528 self._update_groups_metadata(repo["id"])
529 return cat
530
531 @audit()
533 """
534 Remove a packagegroupcategory from a repo
535 """
536 repo = self._get_existing_repo(repoid)
537 if categoryid not in repo['packagegroupcategories']:
538 return
539 if repo['packagegroupcategories'][categoryid]["immutable"]:
540 raise PulpException("Changes to immutable categories are not supported: %s" % (categoryid))
541 del repo['packagegroupcategories'][categoryid]
542 self.update(repo)
543 self._update_groups_metadata(repo["id"])
544
545 @audit()
547 """
548 Save the passed in PackageGroupCategory to this repo
549 """
550 repo = self._get_existing_repo(repoid)
551 if pgc['id'] in repo['packagegroupcategories']:
552 if repo["packagegroupcategories"][pgc["id"]]["immutable"]:
553 raise PulpException("Changes to immutable categories are not supported: %s" % (pgc["id"]))
554 repo['packagegroupcategories'][pgc['id']] = pgc
555 self.update(repo)
556 self._update_groups_metadata(repo["id"])
557
558 @audit()
560 """
561 Save the list of passed in PackageGroupCategory objects to this repo
562 """
563 repo = self._get_existing_repo(repoid)
564 for item in pgclist:
565 if item['id'] in repo['packagegroupcategories']:
566 if repo["packagegroupcategories"][item["id"]]["immutable"]:
567 raise PulpException("Changes to immutable categories are not supported: %s" % item["id"])
568 repo['packagegroupcategories'][item['id']] = item
569 self.update(repo)
570 self._update_groups_metadata(repo["id"])
571
573 """
574 Return list of PackageGroupCategory objects in this Repo
575 """
576 repo = self._get_existing_repo(id)
577 return repo['packagegroupcategories']
578
580 """
581 Return a PackageGroupCategory object from this Repo
582 """
583 repo = self._get_existing_repo(repoid)
584 return repo['packagegroupcategories'].get(categoryid, None)
585
620
621 @audit()
622 - def sync(self, id, progress_callback=None):
623 """
624 Sync a repo from the URL contained in the feed
625 """
626 repo = self._get_existing_repo(id)
627 repo_source = repo['source']
628 if not repo_source:
629 raise PulpException("This repo is not setup for sync. Please add packages using upload.")
630 sync_packages, sync_errataids = repo_sync.sync(repo, repo_source, progress_callback)
631 log.info("Sync returned %s packages, %s errata" % (len(sync_packages),
632 len(sync_errataids)))
633
634
635 self.update(repo)
636
637 for pid in repo["packages"]:
638 if pid not in sync_packages:
639 log.info("Removing package <%s> from repo <%s>" % (repo["packages"][pid], repo["id"]))
640 self.remove_package(repo["id"], repo["packages"][pid])
641
642 repo = self._get_existing_repo(id)
643 for p in sync_packages.values():
644 self._add_package(repo, p)
645
646 self.update(repo)
647
648 log.info("Examining %s errata from repo %s" % (len(self.errata(id)), id))
649 for eid in self.errata(id):
650 if eid not in sync_errataids:
651 log.info("Removing errata %s from repo %s" % (eid, id))
652 self.delete_erratum(id, eid)
653
654 repo = self._get_existing_repo(id)
655 for eid in sync_errataids:
656 self._add_erratum(repo, eid)
657 self.update(repo)
658
659 @audit(params=['id', 'pkginfo'])
660 - def upload(self, id, pkginfo, pkgstream):
671
673 '''
674 For all repositories, returns a mapping of repository name to sync schedule.
675
676 @rtype: dict
677 @return: key - repo name, value - sync schedule
678 '''
679 return dict((r['id'], r['sync_schedule']) for r in self.repositories())
680