def _drop_stanza_comments(stanza): n = {} for (key, value) in six.iteritems(stanza): if key.startswith("#"): continue n[key] = value return n
def walk(self, _prefix=()): if self._data: yield _prefix if self._children: for child_name, child in six.iteritems(self._children): # PY3: yield from for r in child.walk(_prefix=_prefix + (child_name, )): yield r
def reduce_stanza(stanza, keep_attrs): """ Pre-process a stanzas so that only a common set of keys will be compared. :param stanza: Stanzas containing attributes and values :type stanza: dict :param keep_attrs: Listing of :type keep_attrs: (list, set, tuple, dict) :return: a reduced copy of ``stanza``. """ return {attr: value for attr, value in six.iteritems(stanza) if attr in keep_attrs}
def find_op_by_location(diffs, type, **kwargs): for op in diffs: if op.location.type == type: match = True for (attr, value) in six.iteritems(kwargs): if getattr(op.location, attr, None) != value: match = False break if match: return op
def items(self, prefix=None): """ Helpful when rebuilding the input file. """ if prefix is None: prefix = () if self._data: yield prefix, self._data if self._children: for child_name, child in six.iteritems(self._children): # yield from (PY3) for r in child.items(prefix=prefix + (child_name, )): yield r
def show_value(value, stanza_, key, prefix_=""): with tc: tc.color(_diff_color_mapping.get(prefix_)) if isinstance(value, dict): if stanza_ is not GLOBAL_STANZA: stream.write("{0}[{1}]\n".format(prefix_, stanza_)) for x, y in sorted(six.iteritems(value)): write_key(x, y, prefix_) stream.write("\n") else: write_key(key, value, prefix_)
def explode_default_stanza(conf, default_stanza=None): """ Take the GLOBAL stanza, (aka [default]) and apply it's settings underneath ALL other stanzas. This is mostly only useful in minimizing and other comparison operations. """ if default_stanza is None: default_stanza = conf.get(GLOBAL_STANZA, conf.get("default")) if not default_stanza: return conf default_stanza = _drop_stanza_comments(default_stanza) n = {} for (stanza, content) in six.iteritems(conf): new_content = dict(default_stanza) new_content.update(content) n[stanza] = new_content return n
def _merge_conf_dicts(base, new_layer): """ Merge new_layer on top of base. It's up to the caller to deal with any necessary object copying to avoid odd referencing between the base and new_layer""" for (section, items) in six.iteritems(new_layer): if STANZA_MAGIC_KEY in items: magic_op = items[STANZA_MAGIC_KEY] if STANZA_OP_DROP in magic_op: # If this section exist in a parent (base), then drop it now if section in base: del base[section] continue # pragma: no cover (peephole optimization) if section in base: # TODO: Support other magic here... # Rip all the comments out of the new_layer, and prepend them (sequentially) to base comments = _extract_comments(items) if comments: inject_section_comments(base[section], prepend=comments) base[section].update(items) else: # TODO: Support other magic here too..., though with no parent info base[section] = items
def publish_conf(self, stanza_name, stanza_data, config_file): if self.meta: metadata = self.meta.get(config_file.name, stanza_name) owner = metadata.get("owner", None) app = config_file.service.namespace.app if metadata.get("export", None) == "system": sharing = "global" else: # Could still be "user" technically; but it seems unlikely that '--meta' would be given # in that case. Still, there's possible room for improvement. sharing = "app" else: metadata = {} owner = None sharing = None app = None res = {} # XXX: Move boolean comparison stuff to the core delta detection library.... self.make_boolean(stanza_data) try: stz = config_file[stanza_name] except KeyError: stz = None if stz is not None: ## print("Stanza {} already exists on server. Checking to see if update is needed.".format(stanza_name)) # When pulling do we need to specify this? (owner=owner, app=app, sharing=sharing); If meta is given and where these are different than the defaults on the CLI?... stz_data = stz.content # Diff printing really doesn't like 'None's... stz_data = {k: v or "" for k, v in six.iteritems(stz_data)} self.make_boolean(stz_data) res["path"] = stz.path try: res["updated"] = stz.state["updated"] except: pass ## print("VALUE NOW: (FROM SERVER) {}".format(stz.content)) ## VERY NOISY! data = reduce_stanza(stz_data, stanza_data) ## print("VALUE NOW: (FILTERED TO OUR ATTRS) {}".format(data)) delta = res["delta"] = compare_stanzas(stanza_data, data, stanza_name) if is_equal(delta): ## print("NO CHANGE NEEDED.") res["delta"] = [] action = "nochange" else: stz.update(**stanza_data) # Any need to call .refresh() here to grab the state from the server? action = "update" else: ## print("Stanza {} new -- publishing!".format(stanza_name)) stz = config_file.create(stanza_name, owner=owner, app=app, sharing=sharing, **stanza_data) res["delta"] = compare_stanzas({}, stanza_data, stanza_name) res["path"] = stz.path action = "new" # METADATA PUSH if not self.meta: return (action, res) if not int(stz.access["can_change_perms"]): res["meta"] = "Can't change meta according to 'can_change_perms'" return (action, res) # NOTE: We don't support attribute-level metadata here (Need it? 2 words: pull request) if not metadata: res["meta"] = "No metadata found for [{}/{}]".format( config_file.name, stanza_name) return (action, res) final_meta = {} if "access.read" in metadata: final_meta["perms.read"] = ",".join(metadata["access.read"]) if "access.write" in metadata: final_meta["perms.write"] = ",".join(metadata["access.write"]) if "owner" in metadata: final_meta["owner"] = metadata["owner"] else: final_meta["owner"] = "nobody" export = metadata.get("export", "") if export == "system": final_meta["sharing"] = "global" else: # Could still be "user" technically; but it seems unlikely that '--meta' would be given # in that case. Still, there's possible room for improvement. final_meta["sharing"] = "app" # Build access dict for comparison purpose access = {} for x in ("owner", "app", "sharing"): access[x] = stz.access[x] for x in ("read", "write"): try: access["perms." + x] = ",".join(stz.access["perms"][x]) except (KeyError, TypeError): access["perms." + x] = "" # print("[{}] fm={} access: {}".format(stanza_name, final_meta, access)) acl_delta = compare_stanzas(reduce_stanza(access, final_meta), final_meta, stanza_name + "/acl") if is_equal(acl_delta): res["acl_delta"] = [] return (action, res) else: res["acl_delta"] = acl_delta resource = None try: # Wonky workaround. See https://github.com/splunk/splunk-sdk-python/issues/207 # config_file.service.http.post() # response = Endpoint(config_file.service, stz.path + "acl/").post(**final_meta) svc = config_file.service all_headers = svc.additional_headers + svc._auth_headers resource = svc.authority + \ svc._abspath(stz.path + "acl", owner=svc.namespace.owner, app=svc.namespace.app, sharing=svc.namespace.sharing) #logger.debug("request to do the ACL THING! (Round trip debugging)") response = svc.http.post(resource, all_headers, **final_meta) res["meta_response"] = response except Exception: # Don't die on exceptions for ACLs... print the error and move on (too many things to go wrong here) print("Failed hitting: {} ARGS={}".format(resource, final_meta)) import traceback traceback.print_exc() # XXX: Do better return (action, res)