Example #1
0
def _drop_stanza_comments(stanza):
    n = {}
    for (key, value) in six.iteritems(stanza):
        if key.startswith("#"):
            continue
        n[key] = value
    return n
Example #2
0
 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
Example #3
0
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}
Example #4
0
 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
Example #5
0
 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
Example #6
0
 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_)
Example #7
0
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
Example #8
0
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
Example #9
0
    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)