class RepoUpdateActionCommand(object): """UpdateAction for yum repos. Update yum repos when triggered. Generates yum repo config based on: - entitlement certs - repo overrides - rhsm config - yum config - manual changes made to "redhat.repo". Returns an RepoActionReport. """ def __init__(self, cache_only=False, apply_overrides=True): self.identity = inj.require(inj.IDENTITY) # These should probably move closer their use self.ent_dir = inj.require(inj.ENT_DIR) self.prod_dir = inj.require(inj.PROD_DIR) self.ent_source = ent_cert.EntitlementDirEntitlementSource() self.cp_provider = inj.require(inj.CP_PROVIDER) self.uep = self.cp_provider.get_consumer_auth_cp() self.manage_repos = 1 self.apply_overrides = apply_overrides self.manage_repos = manage_repos_enabled() self.release = None self.overrides = {} self.override_supported = False try: self.override_supported = bool( self.identity.is_valid() and self.uep and self.uep.supports_resource('content_overrides')) except socket.error as e: # swallow the error to fix bz 1298327 log.exception(e) pass self.written_overrides = WrittenOverrideCache() # FIXME: empty report at the moment, should be changed to include # info about updated repos self.report = RepoActionReport() self.report.name = "Repo updates" # If we are not registered, skip trying to refresh the # data from the server if not self.identity.is_valid(): return # NOTE: if anything in the RepoActionInvoker init blocks, and it # could, yum could still block. The closest thing to an # event loop we have is the while True: sleep() in lock.py:Lock.acquire() # Only attempt to update the overrides if they are supported # by the server. if self.override_supported: self.written_overrides._read_cache() try: override_cache = inj.require(inj.OVERRIDE_STATUS_CACHE) except KeyError: override_cache = OverrideStatusCache() if cache_only: status = override_cache._read_cache() else: status = override_cache.load_status(self.uep, self.identity.uuid) for item in status or []: # Don't iterate through the list if item['contentLabel'] not in self.overrides: self.overrides[item['contentLabel']] = {} self.overrides[item['contentLabel']][ item['name']] = item['value'] def perform(self): # Load the RepoFile from disk, this contains all our managed yum repo sections: repo_file = RepoFile() # the [rhsm] manage_repos can be overridden to disable generation of the # redhat.repo file: if not self.manage_repos: log.debug("manage_repos is 0, skipping generation of: %s" % repo_file.path) if repo_file.exists(): log.info("Removing %s due to manage_repos configuration." % repo_file.path) RepoActionInvoker.delete_repo_file() return 0 repo_file.read() valid = set() # Iterate content from entitlement certs, and create/delete each section # in the RepoFile as appropriate: for cont in self.get_unique_content(): valid.add(cont.id) existing = repo_file.section(cont.id) if existing is None: repo_file.add(cont) self.report_add(cont) else: # Updates the existing repo with new content self.update_repo(existing, cont) repo_file.update(existing) self.report_update(existing) for section in repo_file.sections(): if section not in valid: self.report_delete(section) repo_file.delete(section) # Write new RepoFile to disk: repo_file.write() if self.override_supported: # Update with the values we just wrote self.written_overrides.overrides = self.overrides self.written_overrides.write_cache() log.info("repos updated: %s" % self.report) return self.report def get_unique_content(self): # FIXME Shouldn't this skip all of the repo updating? if not self.manage_repos: return [] # baseurl and ca_cert could be "CDNInfo" or # bundle with "ConnectionInfo" etc baseurl = CFG.get('rhsm', 'baseurl') ca_cert = CFG.get('rhsm', 'repo_ca_cert') content_list = self.get_all_content(baseurl, ca_cert) # assumes items in content_list are hashable return set(content_list) # Expose as public API for RepoActionInvoker.is_managed, since that # is used by openshift tooling. # See https://bugzilla.redhat.com/show_bug.cgi?id=1223038 def matching_content(self): return model.find_content(self.ent_source, content_type="yum") def get_all_content(self, baseurl, ca_cert): matching_content = self.matching_content() content_list = [] # avoid checking for release/etc if there is no matching_content if not matching_content: return content_list # wait until we know we have content before fetching # release. We could make YumReleaseverSource understand # cache_only as well. release_source = YumReleaseverSource() for content in matching_content: repo = Repo.from_ent_cert_content(content, baseurl, ca_cert, release_source) # overrides are yum repo only at the moment, but # content sources will likely need to learn how to # apply overrides as well, perhaps generically if self.override_supported and self.apply_overrides: repo = self._set_override_info(repo) content_list.append(repo) return content_list def _set_override_info(self, repo): # In the disconnected case, self.overrides will be an empty list for name, value in self.overrides.get(repo.id, {}).items(): repo[name] = value return repo def _is_overridden(self, repo, key): return key in self.overrides.get(repo.id, {}) def _was_overridden(self, repo, key, value): written_value = self.written_overrides.overrides.get(repo.id, {}).get(key) # Compare values as strings to avoid casting problems from io return written_value is not None and value is not None and str( written_value) == str(value) def _build_props(self, old_repo, new_repo): result = {} all_keys = old_repo.keys() + new_repo.keys() for key in all_keys: result[key] = Repo.PROPERTIES.get(key, (1, None)) return result def update_repo(self, old_repo, new_repo): """ Checks an existing repo definition against a potentially updated version created from most recent entitlement certificates and configuration. Creates, updates, and removes properties as appropriate and returns the number of changes made. (if any) """ changes_made = 0 for key, (mutable, default) in self._build_props(old_repo, new_repo).items(): new_val = new_repo.get(key) # Mutable properties should be added if not currently defined, # otherwise left alone. However if we see that the property was overridden # but that override has since been removed, we need to revert to the default # value. if mutable and not self._is_overridden(old_repo, key) \ and not self._was_overridden(old_repo, key, old_repo.get(key)): if (new_val is not None) and (not old_repo.get(key)): if old_repo.get(key) == new_val: continue old_repo[key] = new_val changes_made += 1 # Immutable properties should be always be added/updated, # and removed if undefined in the new repo definition. else: if new_val is None or (str(new_val).strip() == ""): # Immutable property should be removed: if key in old_repo.keys(): del old_repo[key] changes_made += 1 continue # Unchanged: if old_repo.get(key) == new_val: continue old_repo[key] = new_val changes_made += 1 return changes_made def report_update(self, repo): self.report.repo_updates.append(repo) def report_add(self, repo): self.report.repo_added.append(repo) def report_delete(self, section): self.report.repo_deleted.append(section)
class RepoUpdateActionCommand(object): """UpdateAction for yum repos. Update yum repos when triggered. Generates yum repo config based on: - entitlement certs - repo overrides - rhsm config - yum config - manual changes made to "redhat.repo". Returns an RepoActionReport. """ def __init__(self, cache_only=False, apply_overrides=True): self.identity = inj.require(inj.IDENTITY) # These should probably move closer their use self.ent_dir = inj.require(inj.ENT_DIR) self.prod_dir = inj.require(inj.PROD_DIR) self.ent_source = ent_cert.EntitlementDirEntitlementSource() self.cp_provider = inj.require(inj.CP_PROVIDER) self.uep = self.cp_provider.get_consumer_auth_cp() self.manage_repos = 1 self.apply_overrides = apply_overrides if CFG.has_option('rhsm', 'manage_repos'): self.manage_repos = int(CFG.get('rhsm', 'manage_repos')) self.release = None self.overrides = {} self.override_supported = bool(self.identity.is_valid() and self.uep and self.uep.supports_resource('content_overrides')) self.written_overrides = WrittenOverrideCache() # FIXME: empty report at the moment, should be changed to include # info about updated repos self.report = RepoActionReport() self.report.name = "Repo updates" # If we are not registered, skip trying to refresh the # data from the server if not self.identity.is_valid(): return # Only attempt to update the overrides if they are supported # by the server. if self.override_supported: self.written_overrides._read_cache() try: override_cache = inj.require(inj.OVERRIDE_STATUS_CACHE) except KeyError: override_cache = OverrideStatusCache() if cache_only: status = override_cache._read_cache() else: status = override_cache.load_status(self.uep, self.identity.uuid) for item in status or []: # Don't iterate through the list if item['contentLabel'] not in self.overrides: self.overrides[item['contentLabel']] = {} self.overrides[item['contentLabel']][item['name']] = item['value'] def perform(self): # Load the RepoFile from disk, this contains all our managed yum repo sections: repo_file = RepoFile() # the [rhsm] manage_repos can be overridden to disable generation of the # redhat.repo file: if not self.manage_repos: log.debug("manage_repos is 0, skipping generation of: %s" % repo_file.path) if repo_file.exists(): log.info("Removing %s due to manage_repos configuration." % repo_file.path) RepoActionInvoker.delete_repo_file() return 0 repo_file.read() valid = set() # Iterate content from entitlement certs, and create/delete each section # in the RepoFile as appropriate: for cont in self.get_unique_content(): valid.add(cont.id) existing = repo_file.section(cont.id) if existing is None: repo_file.add(cont) self.report_add(cont) else: # Updates the existing repo with new content self.update_repo(existing, cont) repo_file.update(existing) self.report_update(existing) for section in repo_file.sections(): if section not in valid: self.report_delete(section) repo_file.delete(section) # Write new RepoFile to disk: repo_file.write() if self.override_supported: # Update with the values we just wrote self.written_overrides.overrides = self.overrides self.written_overrides.write_cache() log.info("repos updated: %s" % self.report) return self.report def get_unique_content(self): # FIXME Shouldn't this skip all of the repo updating? if not self.manage_repos: return [] # baseurl and ca_cert could be "CDNInfo" or # bundle with "ConnectionInfo" etc baseurl = CFG.get('rhsm', 'baseurl') ca_cert = CFG.get('rhsm', 'repo_ca_cert') content_list = self.get_all_content(baseurl, ca_cert) # assumes items in content_list are hashable return set(content_list) def get_all_content(self, baseurl, ca_cert): matching_content = model.find_content(self.ent_source, content_type="yum") content_list = [] # avoid checking for release/etc if there is no matching_content if not matching_content: return content_list # wait until we know we have content before fetching # release. We could make YumReleaseverSource understand # cache_only as well. release_source = YumReleaseverSource() for content in matching_content: repo = Repo.from_ent_cert_content(content, baseurl, ca_cert, release_source) # overrides are yum repo only at the moment, but # content sources will likely need to learn how to # apply overrides as well, perhaps generically if self.override_supported and self.apply_overrides: repo = self._set_override_info(repo) content_list.append(repo) return content_list def _set_override_info(self, repo): # In the disconnected case, self.overrides will be an empty list for name, value in self.overrides.get(repo.id, {}).items(): repo[name] = value return repo def _is_overridden(self, repo, key): return key in self.overrides.get(repo.id, {}) def _was_overridden(self, repo, key, value): written_value = self.written_overrides.overrides.get(repo.id, {}).get(key) # Compare values as strings to avoid casting problems from io return written_value is not None and value is not None and str(written_value) == str(value) def _build_props(self, old_repo, new_repo): result = {} all_keys = old_repo.keys() + new_repo.keys() for key in all_keys: result[key] = Repo.PROPERTIES.get(key, (1, None)) return result def update_repo(self, old_repo, new_repo): """ Checks an existing repo definition against a potentially updated version created from most recent entitlement certificates and configuration. Creates, updates, and removes properties as appropriate and returns the number of changes made. (if any) """ changes_made = 0 for key, (mutable, default) in self._build_props(old_repo, new_repo).items(): new_val = new_repo.get(key) # Mutable properties should be added if not currently defined, # otherwise left alone. However if we see that the property was overridden # but that override has since been removed, we need to revert to the default # value. if mutable and not self._is_overridden(old_repo, key) \ and not self._was_overridden(old_repo, key, old_repo.get(key)): if (new_val is not None) and (not old_repo.get(key)): if old_repo.get(key) == new_val: continue old_repo[key] = new_val changes_made += 1 # Immutable properties should be always be added/updated, # and removed if undefined in the new repo definition. else: if new_val is None or (str(new_val).strip() == ""): # Immutable property should be removed: if key in old_repo.keys(): del old_repo[key] changes_made += 1 continue # Unchanged: if old_repo.get(key) == new_val: continue old_repo[key] = new_val changes_made += 1 return changes_made def report_update(self, repo): self.report.repo_updates.append(repo) def report_add(self, repo): self.report.repo_added.append(repo) def report_delete(self, section): self.report.repo_deleted.append(section)
class RepoUpdateActionCommand(object): """UpdateAction for yum repos. Update yum repos when triggered. Generates yum repo config based on: - entitlement certs - repo overrides - rhsm config - yum config - manual changes made to "redhat.repo". Returns an RepoActionReport. """ def __init__(self, cache_only=False, apply_overrides=True): self.identity = inj.require(inj.IDENTITY) # These should probably move closer their use self.ent_dir = inj.require(inj.ENT_DIR) self.prod_dir = inj.require(inj.PROD_DIR) self.cp_provider = inj.require(inj.CP_PROVIDER) self.uep = self.cp_provider.get_consumer_auth_cp() self.manage_repos = 1 self.apply_overrides = apply_overrides if CFG.has_option('rhsm', 'manage_repos'): self.manage_repos = int(CFG.get('rhsm', 'manage_repos')) self.release = None self.overrides = {} self.override_supported = bool(self.uep and self.uep.supports_resource('content_overrides')) self.written_overrides = WrittenOverrideCache() # FIXME: empty report at the moment, should be changed to include # info about updated repos self.report = RepoActionReport() self.report.name = "Repo updates" # If we are not registered, skip trying to refresh the # data from the server if not self.identity.is_valid(): return # Only attempt to update the overrides if they are supported # by the server. if self.override_supported: self.written_overrides._read_cache() try: override_cache = inj.require(inj.OVERRIDE_STATUS_CACHE) except KeyError: override_cache = OverrideStatusCache() if cache_only: status = override_cache._read_cache() else: status = override_cache.load_status(self.uep, self.identity.uuid) for item in status or []: # Don't iterate through the list if item['contentLabel'] not in self.overrides: self.overrides[item['contentLabel']] = {} self.overrides[item['contentLabel']][item['name']] = item['value'] message = "Release API is not supported by the server. Using default." try: result = self.uep.getRelease(self.identity.uuid) self.release = result['releaseVer'] except RemoteServerException, e: log.debug(message) except RestlibException, e: if e.code == 404: log.debug(message) else: raise
class RepoUpdateActionCommand(object): """UpdateAction for yum repos. Update yum repos when triggered. Generates yum repo config based on: - entitlement certs - repo overrides - rhsm config - yum config - manual changes made to "redhat.repo". Returns an RepoActionReport. """ def __init__(self, cache_only=False, apply_overrides=True): self.identity = inj.require(inj.IDENTITY) # These should probably move closer their use self.ent_dir = inj.require(inj.ENT_DIR) self.prod_dir = inj.require(inj.PROD_DIR) self.cp_provider = inj.require(inj.CP_PROVIDER) self.uep = self.cp_provider.get_consumer_auth_cp() self.manage_repos = 1 self.apply_overrides = apply_overrides if CFG.has_option('rhsm', 'manage_repos'): self.manage_repos = int(CFG.get('rhsm', 'manage_repos')) self.release = None self.overrides = {} self.override_supported = bool( self.identity.is_valid() and self.uep and self.uep.supports_resource('content_overrides')) self.written_overrides = WrittenOverrideCache() # FIXME: empty report at the moment, should be changed to include # info about updated repos self.report = RepoActionReport() self.report.name = "Repo updates" # If we are not registered, skip trying to refresh the # data from the server if not self.identity.is_valid(): return # Only attempt to update the overrides if they are supported # by the server. if self.override_supported: self.written_overrides._read_cache() try: override_cache = inj.require(inj.OVERRIDE_STATUS_CACHE) except KeyError: override_cache = OverrideStatusCache() if cache_only: status = override_cache._read_cache() else: status = override_cache.load_status(self.uep, self.identity.uuid) for item in status or []: # Don't iterate through the list if item['contentLabel'] not in self.overrides: self.overrides[item['contentLabel']] = {} self.overrides[item['contentLabel']][ item['name']] = item['value'] message = "Release API is not supported by the server. Using default." try: result = self.uep.getRelease(self.identity.uuid) self.release = result['releaseVer'] except RemoteServerException, e: log.debug(message) except RestlibException, e: if e.code == 404: log.debug(message) else: raise