Exemplo n.º 1
0
 def register_args(self, parser):
     parser.add_argument(
         "conf",
         metavar="FILE",
         nargs="+",
         type=ConfFileType("r", "load", parse_profile=PARSECONF_MID),
         help="The source configuration file to pull changes from."
     ).completer = conf_files_completer
     parser.add_argument("--target",
                         "-t",
                         metavar="FILE",
                         type=ConfFileType("r+",
                                           "none",
                                           parse_profile=PARSECONF_STRICT),
                         default=ConfFileProxy("<stdout>", "w",
                                               self.stdout),
                         help="""
         Save the merged configuration files to this target file.
         If not provided. the the merged conf is written to standard output."""
                         ).completer = conf_files_completer
     parser.add_argument("--dry-run",
                         "-D",
                         default=False,
                         action="store_true",
                         help="""
         Enable dry-run mode.
         Instead of writing to TARGET, preview changes in 'diff' format.
         If TARGET doesn't exist, then show the merged file.""")
     parser.add_argument("--banner",
                         "-b",
                         default="",
                         help="""
         A banner or warning comment added to the top of the TARGET file.
         This is pften used to warn Splunk admins from editing an auto-generated file."""
                         )
Exemplo n.º 2
0
    def register_args(self, parser):
        parser.add_argument(
            "conf",
            metavar="FILE",
            nargs="+",
            help="The source configuration file(s) to collect settings from."
        ).completer = conf_files_completer
        parser.add_argument("--target",
                            "-t",
                            metavar="FILE",
                            type=ConfFileType("r+",
                                              "none",
                                              parse_profile=PARSECONF_STRICT),
                            default=ConfFileProxy("<stdout>", "w",
                                                  self.stdout),
                            help=dedent("""\
            Save the merged configuration files to this target file.
            If not provided, the merged conf is written to standard output.""")
                            ).completer = conf_files_completer

        # This is helpful when writing bash expressions like MyApp/{default,local}/props.conf;
        # when either default or local may not be present.
        parser.add_argument("--ignore-missing",
                            "-s",
                            default=False,
                            action="store_true",
                            help="Silently ignore any missing CONF files.")

        parser.add_argument("--dry-run",
                            "-D",
                            default=False,
                            action="store_true",
                            help=dedent("""\
            Enable dry-run mode.
            Instead of writing to TARGET, preview changes in 'diff' format.
            If TARGET doesn't exist, then show the merged file."""))
        parser.add_argument("--banner",
                            "-b",
                            default="",
                            help=dedent("""\
            A banner or warning comment added to the top of the TARGET file.
            Used to discourage Splunk admins from editing an auto-generated file."""
                                        ))
Exemplo n.º 3
0
    def run(self, args):
        # Ignores case sensitivity.  If you're on Windows, name your files right.
        conf_file_re = re.compile("([a-z]+\.conf|(default|local)\.meta)$")

        if args.target is None:
            self.stderr.write("Must provide the '--target' directory.\n")
            return EXIT_CODE_MISSING_ARG

            self.stderr.write("Combining conf files into {}\n".format(args.target))
        args.source = list(_expand_glob_list(args.source))
        for src in args.source:
            self.stderr.write("Reading conf files from {}\n".format(src))

        marker_file = os.path.join(args.target, CONTROLLED_DIR_MARKER)
        if os.path.isdir(args.target):
            if not os.path.isfile(os.path.join(args.target, CONTROLLED_DIR_MARKER)):
                self.stderr.write("Target directory already exists, but it appears to have been"
                                  "created by some other means.  Marker file missing.\n")
                return EXIT_CODE_COMBINE_MARKER_MISSING
        elif args.dry_run:
            self.stderr.write(
                "Skipping creating destination folder {0} (dry-run)\n".format(args.target))
        else:
            self.stderr.write("Creating destination folder {0}\n".format(args.target))
            os.mkdir(args.target)
            open(marker_file, "w").write("This directory is managed by KSCONF.  Don't touch\n")

        # Build a common tree of all src files.
        src_file_index = defaultdict(list)
        for src_root in args.source:
            for (root, dirs, files) in relwalk(src_root):
                for fn in files:
                    # Todo: Add blacklist CLI support:  defaults to consider: *sw[po], .git*, .bak, .~
                    if fn.endswith(".swp") or fn.endswith("*.bak"):
                        continue  # pragma: no cover  (peephole optimization)
                    src_file = os.path.join(root, fn)
                    src_path = os.path.join(src_root, root, fn)
                    src_file_index[src_file].append(src_path)

        # Find a set of files that exist in the target folder, but in NO source folder (for cleanup)
        target_extra_files = set()
        for (root, dirs, files) in relwalk(args.target):
            for fn in files:
                tgt_file = os.path.join(root, fn)
                if tgt_file not in src_file_index:
                    # Todo:  Add support for additional blacklist wildcards (using fnmatch)
                    if fn == CONTROLLED_DIR_MARKER or fn.endswith(".bak"):
                        continue  # pragma: no cover (peephole optimization)
                    target_extra_files.add(tgt_file)

        for (dest_fn, src_files) in sorted(src_file_index.items()):
            dest_path = os.path.join(args.target, dest_fn)

            # Make missing destination folder, if missing
            dest_dir = os.path.dirname(dest_path)
            if not os.path.isdir(dest_dir) and not args.dry_run:
                os.makedirs(dest_dir)

            # Handle conf files and non-conf files separately
            if not conf_file_re.search(dest_fn):
                # self.stderr.write("Considering {0:50}  NON-CONF Copy from source:  {1!r}\n".format(dest_fn, src_files[-1]))
                # Always use the last file in the list (since last directory always wins)
                src_file = src_files[-1]
                if args.dry_run:
                    if os.path.isfile(dest_path):
                        if file_compare(src_file, dest_path):
                            smart_rc = SMART_NOCHANGE
                        else:
                            if (_is_binary_file(src_file) or _is_binary_file(dest_path)):
                                # Binary files.  Can't compare...
                                smart_rc = "DRY-RUN (NO-DIFF=BIN)"
                            else:
                                show_text_diff(self.stdout, dest_path, src_file)
                                smart_rc = "DRY-RUN (DIFF)"
                    else:
                        smart_rc = "DRY-RUN (NEW)"
                else:
                    smart_rc = smart_copy(src_file, dest_path)
                if smart_rc != SMART_NOCHANGE:
                    self.stderr.write(
                        "Copy <{0}>   {1:50}  from {2}\n".format(smart_rc, dest_path, src_file))
            else:
                # Handle merging conf files
                dest = ConfFileProxy(os.path.join(args.target, dest_fn), "r+",
                                     parse_profile=PARSECONF_MID)
                srcs = [ConfFileProxy(sf, "r", parse_profile=PARSECONF_STRICT) for sf in src_files]
                # self.stderr.write("Considering {0:50}  CONF MERGE from source:  {1!r}\n".format(dest_fn, src_files[0]))
                smart_rc = merge_conf_files(dest, srcs, dry_run=args.dry_run,
                                            banner_comment=args.banner)
                if smart_rc != SMART_NOCHANGE:
                    self.stderr.write(
                        "Merge <{0}>   {1:50}  from {2!r}\n".format(smart_rc, dest_path,
                                                                    src_files))

        if True and target_extra_files:  # Todo: Allow for cleanup to be disabled via CLI
            self.stderr.write("Cleaning up extra files not part of source tree(s):  {0} files.\n".
                format(len(target_extra_files)))
            for dest_fn in target_extra_files:
                self.stderr.write("Remove unwanted file {0}\n".format(dest_fn))
                os.unlink(os.path.join(args.target, dest_fn))