Ejemplo n.º 1
0
 def test_lint(self):
     output = start_lint(fail_on_warning=False,
                         skip_class_checks=False,
                         inventory_path="./tests/test_resources/inventory",
                         search_secrets=True,
                         secrets_path="./tests/test_resources/secrets",
                         compiled_path="./tests/test_resources/compiled")
     desired_output = 2
     self.assertEqual(output, desired_output)
Ejemplo n.º 2
0
 def test_lint(self):
     num_issues_found = start_lint(
         fail_on_warning=False,
         skip_class_checks=False,
         skip_yamllint=False,
         inventory_path="./tests/test_resources/inventory",
         search_secrets=True,
         secrets_path="./tests/test_resources/secrets",
         compiled_path="./tests/test_resources/compiled")
     desired_output = 3
     self.assertEqual(num_issues_found, desired_output)
Ejemplo n.º 3
0
    def test_lint(self):

        args = Object()
        args.fail_on_warning = False
        args.skip_class_checks = False
        args.skip_yamllint = False
        args.inventory_path = "./tests/test_resources/inventory"
        args.search_secrets = True
        args.refs_path = "./tests/test_resources/secrets"
        args.compiled_path = "./tests/test_resources/compiled"

        num_issues_found = start_lint(args)
        desired_output = 3
        self.assertEqual(num_issues_found, desired_output)
Ejemplo n.º 4
0
def main():
    """main function for command line usage"""
    parser = argparse.ArgumentParser(prog=PROJECT_NAME,
                                     description=DESCRIPTION)
    parser.add_argument("--version", action="version", version=VERSION)
    subparser = parser.add_subparsers(help="commands")

    eval_parser = subparser.add_parser("eval", help="evaluate jsonnet file")
    eval_parser.add_argument("jsonnet_file", type=str)
    eval_parser.add_argument(
        "--output",
        type=str,
        choices=("yaml", "json"),
        default=from_dot_kapitan("eval", "output", "yaml"),
        help='set output format, default is "yaml"',
    )
    eval_parser.add_argument(
        "--vars",
        type=str,
        default=from_dot_kapitan("eval", "vars", []),
        nargs="*",
        metavar="VAR",
        help="set variables",
    )
    eval_parser.add_argument(
        "--search-paths",
        "-J",
        type=str,
        nargs="+",
        default=from_dot_kapitan("eval", "search-paths", ["."]),
        metavar="JPATH",
        help='set search paths, default is ["."]',
    )

    compile_parser = subparser.add_parser("compile", help="compile targets")
    compile_parser.add_argument(
        "--search-paths",
        "-J",
        type=str,
        nargs="+",
        default=from_dot_kapitan("compile", "search-paths", [".", "lib"]),
        metavar="JPATH",
        help='set search paths, default is ["."]',
    )
    compile_parser.add_argument(
        "--jinja2-filters",
        "-J2F",
        type=str,
        default=from_dot_kapitan("compile", "jinja2-filters",
                                 defaults.DEFAULT_JINJA2_FILTERS_PATH),
        metavar="FPATH",
        help="load custom jinja2 filters from any file, default is to put\
                                them inside lib/jinja2_filters.py",
    )
    compile_parser.add_argument(
        "--verbose",
        "-v",
        help="set verbose mode",
        action="store_true",
        default=from_dot_kapitan("compile", "verbose", False),
    )
    compile_parser.add_argument(
        "--prune",
        help="prune jsonnet output",
        action="store_true",
        default=from_dot_kapitan("compile", "prune", False),
    )
    compile_parser.add_argument(
        "--quiet",
        help="set quiet mode, only critical output",
        action="store_true",
        default=from_dot_kapitan("compile", "quiet", False),
    )
    compile_parser.add_argument(
        "--output-path",
        type=str,
        default=from_dot_kapitan("compile", "output-path", "."),
        metavar="PATH",
        help='set output path, default is "."',
    )
    compile_parser.add_argument(
        "--fetch",
        help="fetches external dependencies",
        action="store_true",
        default=from_dot_kapitan("compile", "fetch", False),
    )
    compile_parser.add_argument(
        "--validate",
        help=
        "validate compile output against schemas as specified in inventory",
        action="store_true",
        default=from_dot_kapitan("compile", "validate", False),
    )
    compile_parser.add_argument(
        "--parallelism",
        "-p",
        type=int,
        default=from_dot_kapitan("compile", "parallelism", 4),
        metavar="INT",
        help="Number of concurrent compile processes, default is 4",
    )
    compile_parser.add_argument(
        "--indent",
        "-i",
        type=int,
        default=from_dot_kapitan("compile", "indent", 2),
        metavar="INT",
        help="Indentation spaces for YAML/JSON, default is 2",
    )
    compile_parser.add_argument(
        "--refs-path",
        help='set refs path, default is "./refs"',
        default=from_dot_kapitan("compile", "refs-path", "./refs"),
    )
    compile_parser.add_argument(
        "--reveal",
        help=
        "reveal refs (warning: this will potentially write sensitive data)",
        action="store_true",
        default=from_dot_kapitan("compile", "reveal", False),
    )
    compile_parser.add_argument(
        "--inventory-path",
        default=from_dot_kapitan("compile", "inventory-path", "./inventory"),
        help='set inventory path, default is "./inventory"',
    )
    compile_parser.add_argument(
        "--cache",
        "-c",
        help="enable compilation caching to .kapitan_cache, default is False",
        action="store_true",
        default=from_dot_kapitan("compile", "cache", False),
    )
    compile_parser.add_argument(
        "--cache-paths",
        type=str,
        nargs="+",
        default=from_dot_kapitan("compile", "cache-paths", []),
        metavar="PATH",
        help="cache additional paths to .kapitan_cache, default is []",
    )
    compile_parser.add_argument(
        "--ignore-version-check",
        help="ignore the version from .kapitan",
        action="store_true",
        default=from_dot_kapitan("compile", "ignore-version-check", False),
    )
    compile_parser.add_argument(
        "--schemas-path",
        default=from_dot_kapitan("validate", "schemas-path", "./schemas"),
        help='set schema cache path, default is "./schemas"',
    )

    compile_selector_parser = compile_parser.add_mutually_exclusive_group()
    compile_selector_parser.add_argument(
        "--targets",
        "-t",
        help="targets to compile, default is all",
        type=str,
        nargs="+",
        default=from_dot_kapitan("compile", "targets", []),
        metavar="TARGET",
    )
    compile_selector_parser.add_argument(
        "--labels",
        "-l",
        help="compile targets matching the labels, default is all",
        type=str,
        nargs="*",
        default=from_dot_kapitan("compile", "labels", []),
        metavar="key=value",
    )

    inventory_parser = subparser.add_parser("inventory", help="show inventory")
    inventory_parser.add_argument(
        "--target-name",
        "-t",
        default=from_dot_kapitan("inventory", "target-name", ""),
        help="set target name, default is all targets",
    )
    inventory_parser.add_argument(
        "--inventory-path",
        default=from_dot_kapitan("inventory", "inventory-path", "./inventory"),
        help='set inventory path, default is "./inventory"',
    )
    inventory_parser.add_argument(
        "--flat",
        "-F",
        help="flatten nested inventory variables",
        action="store_true",
        default=from_dot_kapitan("inventory", "flat", False),
    )
    inventory_parser.add_argument(
        "--pattern",
        "-p",
        default=from_dot_kapitan("inventory", "pattern", ""),
        help=
        "filter pattern (e.g. parameters.mysql.storage_class, or storage_class,"
        + ' or storage_*), default is ""',
    )
    inventory_parser.add_argument(
        "--verbose",
        "-v",
        help="set verbose mode",
        action="store_true",
        default=from_dot_kapitan("inventory", "verbose", False),
    )

    searchvar_parser = subparser.add_parser(
        "searchvar", help="show all inventory files where var is declared")
    searchvar_parser.add_argument(
        "searchvar",
        type=str,
        metavar="VARNAME",
        help=
        "e.g. parameters.mysql.storage_class, or storage_class, or storage_*",
    )
    searchvar_parser.add_argument(
        "--inventory-path",
        default=from_dot_kapitan("searchvar", "inventory-path", "./inventory"),
        help='set inventory path, default is "./inventory"',
    )
    searchvar_parser.add_argument(
        "--verbose",
        "-v",
        help="set verbose mode",
        action="store_true",
        default=from_dot_kapitan("searchvar", "verbose", False),
    )
    searchvar_parser.add_argument(
        "--pretty-print",
        "-p",
        help="Pretty print content of var",
        action="store_true",
        default=from_dot_kapitan("searchvar", "pretty-print", False),
    )

    subparser.add_parser("secrets", help="(DEPRECATED) please use refs")

    refs_parser = subparser.add_parser("refs", help="manage refs")
    refs_parser.add_argument(
        "--write",
        "-w",
        help="write ref token",
        metavar="TOKENNAME",
    )
    refs_parser.add_argument(
        "--update",
        help="update GPG recipients for ref token",
        metavar="TOKENNAME",
    )
    refs_parser.add_argument(
        "--update-targets",
        action="store_true",
        default=from_dot_kapitan("refs", "update-targets", False),
        help="update target secret refs",
    )
    refs_parser.add_argument(
        "--validate-targets",
        action="store_true",
        default=from_dot_kapitan("refs", "validate-targets", False),
        help="validate target secret refs",
    )
    refs_parser.add_argument(
        "--base64",
        "-b64",
        help="base64 encode file content",
        action="store_true",
        default=from_dot_kapitan("refs", "base64", False),
    )
    refs_parser.add_argument(
        "--reveal",
        "-r",
        help="reveal refs",
        action="store_true",
        default=from_dot_kapitan("refs", "reveal", False),
    )
    refs_parser.add_argument("--file",
                             "-f",
                             help='read file or directory, set "-" for stdin',
                             metavar="FILENAME")
    refs_parser.add_argument("--target-name",
                             "-t",
                             help="grab recipients from target name")
    refs_parser.add_argument(
        "--inventory-path",
        default=from_dot_kapitan("refs", "inventory-path", "./inventory"),
        help='set inventory path, default is "./inventory"',
    )
    refs_parser.add_argument(
        "--recipients",
        "-R",
        help="set GPG recipients",
        type=str,
        nargs="+",
        default=from_dot_kapitan("refs", "recipients", []),
        metavar="RECIPIENT",
    )
    refs_parser.add_argument("--key",
                             "-K",
                             help="set KMS key",
                             default=from_dot_kapitan("refs", "key", ""),
                             metavar="KEY")
    refs_parser.add_argument(
        "--vault-auth",
        help="set authentication type for vaultkv secrets",
        default=from_dot_kapitan("refs", "vault-auth", ""),
        metavar="AUTH",
    )
    refs_parser.add_argument(
        "--refs-path",
        help='set refs path, default is "./refs"',
        default=from_dot_kapitan("refs", "refs-path", "./refs"),
    )
    refs_parser.add_argument(
        "--verbose",
        "-v",
        help=
        "set verbose mode (warning: this will potentially show sensitive data)",
        action="store_true",
        default=from_dot_kapitan("refs", "verbose", False),
    )

    lint_parser = subparser.add_parser("lint",
                                       help="linter for inventory and refs")
    lint_parser.add_argument(
        "--fail-on-warning",
        default=from_dot_kapitan("lint", "fail-on-warning", False),
        action="store_true",
        help="exit with failure code if warnings exist, default is False",
    )
    lint_parser.add_argument(
        "--skip-class-checks",
        action="store_true",
        help="skip checking for unused classes, default is False",
        default=from_dot_kapitan("lint", "skip-class-checks", False),
    )
    lint_parser.add_argument(
        "--skip-yamllint",
        action="store_true",
        help="skip running yamllint on inventory, default is False",
        default=from_dot_kapitan("lint", "skip-yamllint", False),
    )
    lint_parser.add_argument(
        "--search-secrets",
        default=from_dot_kapitan("lint", "search-secrets", False),
        action="store_true",
        help="searches for plaintext secrets in inventory, default is False",
    )
    lint_parser.add_argument(
        "--refs-path",
        help='set refs path, default is "./refs"',
        default=from_dot_kapitan("lint", "refs-path", "./refs"),
    )
    lint_parser.add_argument(
        "--compiled-path",
        default=from_dot_kapitan("lint", "compiled-path", "./compiled"),
        help='set compiled path, default is "./compiled"',
    )
    lint_parser.add_argument(
        "--inventory-path",
        default=from_dot_kapitan("lint", "inventory-path", "./inventory"),
        help='set inventory path, default is "./inventory"',
    )

    init_parser = subparser.add_parser(
        "init",
        help=
        "initialize a directory with the recommended kapitan project skeleton."
    )
    init_parser.add_argument(
        "--directory",
        default=from_dot_kapitan("init", "directory", "."),
        help="set path, in which to generate the project skeleton,"
        'assumes directory already exists. default is "./"',
    )

    validate_parser = subparser.add_parser(
        "validate",
        help=
        "validates the compile output against schemas as specified in inventory"
    )
    validate_parser.add_argument(
        "--compiled-path",
        default=from_dot_kapitan("compile", "compiled-path", "./compiled"),
        help='set compiled path, default is "./compiled',
    )
    validate_parser.add_argument(
        "--inventory-path",
        default=from_dot_kapitan("compile", "inventory-path", "./inventory"),
        help='set inventory path, default is "./inventory"',
    )
    validate_parser.add_argument(
        "--targets",
        "-t",
        help="targets to validate, default is all",
        type=str,
        nargs="+",
        default=from_dot_kapitan("compile", "targets", []),
        metavar="TARGET",
    ),
    validate_parser.add_argument(
        "--schemas-path",
        default=from_dot_kapitan("validate", "schemas-path", "./schemas"),
        help='set schema cache path, default is "./schemas"',
    )
    validate_parser.add_argument(
        "--parallelism",
        "-p",
        type=int,
        default=from_dot_kapitan("validate", "parallelism", 4),
        metavar="INT",
        help="Number of concurrent validate processes, default is 4",
    )
    args = parser.parse_args()

    logger.debug("Running with args: %s", args)

    try:
        cmd = sys.argv[1]
    except IndexError:
        parser.print_help()
        sys.exit(1)

    # cache args where key is subcommand
    cached.args[sys.argv[1]] = args

    if hasattr(args, "verbose") and args.verbose:
        logging.basicConfig(
            level=logging.DEBUG,
            format="%(asctime)s %(name)-12s %(levelname)-8s %(message)s")
    elif hasattr(args, "quiet") and args.quiet:
        logging.basicConfig(level=logging.CRITICAL, format="%(message)s")
    else:
        logging.basicConfig(level=logging.INFO, format="%(message)s")

    if cmd == "eval":
        file_path = args.jsonnet_file
        search_paths = [os.path.abspath(path) for path in args.search_paths]
        ext_vars = {}
        if args.vars:
            ext_vars = dict(var.split("=") for var in args.vars)
        json_output = None

        def _search_imports(cwd, imp):
            return search_imports(cwd, imp, search_paths)

        json_output = jsonnet_file(
            file_path,
            import_callback=_search_imports,
            native_callbacks=resource_callbacks(search_paths),
            ext_vars=ext_vars,
        )
        if args.output == "yaml":
            json_obj = json.loads(json_output)
            yaml.safe_dump(json_obj, sys.stdout, default_flow_style=False)
        elif json_output:
            print(json_output)

    elif cmd == "compile":
        search_paths = [os.path.abspath(path) for path in args.search_paths]

        if not args.ignore_version_check:
            check_version()

        ref_controller = RefController(args.refs_path)
        # cache controller for use in reveal_maybe jinja2 filter
        cached.ref_controller_obj = ref_controller
        cached.revealer_obj = Revealer(ref_controller)

        compile_targets(
            args.inventory_path,
            search_paths,
            args.output_path,
            args.parallelism,
            args.targets,
            args.labels,
            ref_controller,
            prune=(args.prune),
            indent=args.indent,
            reveal=args.reveal,
            cache=args.cache,
            cache_paths=args.cache_paths,
            fetch_dependencies=args.fetch,
            validate=args.validate,
            schemas_path=args.schemas_path,
            jinja2_filters=args.jinja2_filters,
        )

    elif cmd == "inventory":
        if args.pattern and args.target_name == "":
            parser.error("--pattern requires --target_name")
        try:
            inv = inventory_reclass(args.inventory_path)
            if args.target_name != "":
                inv = inv["nodes"][args.target_name]
                if args.pattern != "":
                    pattern = args.pattern.split(".")
                    inv = deep_get(inv, pattern)
            if args.flat:
                inv = flatten_dict(inv)
                yaml.dump(inv,
                          sys.stdout,
                          width=10000,
                          default_flow_style=False)
            else:
                yaml.dump(inv,
                          sys.stdout,
                          Dumper=PrettyDumper,
                          default_flow_style=False)
        except Exception as e:
            if not isinstance(e, KapitanError):
                logger.exception("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
            sys.exit(1)

    elif cmd == "searchvar":
        searchvar(args.searchvar, args.inventory_path, args.pretty_print)

    elif cmd == "lint":
        start_lint(
            args.fail_on_warning,
            args.skip_class_checks,
            args.skip_yamllint,
            args.inventory_path,
            args.search_secrets,
            args.refs_path,
            args.compiled_path,
        )

    elif cmd == "init":
        initialise_skeleton(args.directory)

    elif cmd == "secrets":
        logger.error(
            "Secrets have been renamed to refs, please refer to: '$ kapitan refs --help'"
        )
        sys.exit(1)

    elif cmd == "refs":
        ref_controller = RefController(args.refs_path)

        if args.write is not None:
            ref_write(args, ref_controller)
        elif args.reveal:
            ref_reveal(args, ref_controller)
        elif args.update:
            secret_update(args, ref_controller)
        elif args.update_targets or args.validate_targets:
            secret_update_validate(args, ref_controller)

    elif cmd == "validate":
        schema_validate_compiled(
            args.targets,
            inventory_path=args.inventory_path,
            compiled_path=args.compiled_path,
            schema_cache_path=args.schemas_path,
            parallel=args.parallelism,
        )
Ejemplo n.º 5
0
def main():
    """main function for command line usage"""
    parser = argparse.ArgumentParser(prog=PROJECT_NAME,
                                     description=DESCRIPTION)
    parser.add_argument('--version', action='version', version=VERSION)
    subparser = parser.add_subparsers(help="commands")

    eval_parser = subparser.add_parser('eval', help='evaluate jsonnet file')
    eval_parser.add_argument('jsonnet_file', type=str)
    eval_parser.add_argument('--output',
                             type=str,
                             choices=('yaml', 'json'),
                             default=from_dot_kapitan('eval', 'output',
                                                      'yaml'),
                             help='set output format, default is "yaml"')
    eval_parser.add_argument('--vars',
                             type=str,
                             default=from_dot_kapitan('eval', 'vars', []),
                             nargs='*',
                             metavar='VAR',
                             help='set variables')
    eval_parser.add_argument('--search-paths',
                             '-J',
                             type=str,
                             nargs='+',
                             default=from_dot_kapitan('eval', 'search-paths',
                                                      ['.']),
                             metavar='JPATH',
                             help='set search paths, default is ["."]')

    compile_parser = subparser.add_parser('compile', help='compile targets')
    compile_parser.add_argument('--search-paths',
                                '-J',
                                type=str,
                                nargs='+',
                                default=from_dot_kapitan(
                                    'compile', 'search-paths', ['.', 'lib']),
                                metavar='JPATH',
                                help='set search paths, default is ["."]')
    compile_parser.add_argument(
        '--jinja2-filters',
        '-J2F',
        type=str,
        default=from_dot_kapitan('compile', 'jinja2-filters',
                                 default_jinja2_filters_path),
        metavar='FPATH',
        help='load custom jinja2 filters from any file, default is to put\
                                them inside lib/jinja2_filters.py')
    compile_parser.add_argument('--verbose',
                                '-v',
                                help='set verbose mode',
                                action='store_true',
                                default=from_dot_kapitan(
                                    'compile', 'verbose', False))
    compile_parser.add_argument('--prune',
                                help='prune jsonnet output',
                                action='store_true',
                                default=from_dot_kapitan(
                                    'compile', 'prune', False))
    compile_parser.add_argument('--quiet',
                                help='set quiet mode, only critical output',
                                action='store_true',
                                default=from_dot_kapitan(
                                    'compile', 'quiet', False))
    compile_parser.add_argument('--output-path',
                                type=str,
                                default=from_dot_kapitan(
                                    'compile', 'output-path', '.'),
                                metavar='PATH',
                                help='set output path, default is "."')
    compile_parser.add_argument('--fetch',
                                help='fetches external dependencies',
                                action='store_true',
                                default=from_dot_kapitan(
                                    'compile', 'fetch', False))
    compile_parser.add_argument(
        '--validate',
        help=
        'validate compile output against schemas as specified in inventory',
        action='store_true',
        default=from_dot_kapitan('compile', 'validate', False))
    compile_parser.add_argument('--targets',
                                '-t',
                                help='targets to compile, default is all',
                                type=str,
                                nargs='+',
                                default=from_dot_kapitan(
                                    'compile', 'targets', []),
                                metavar='TARGET')
    compile_parser.add_argument(
        '--parallelism',
        '-p',
        type=int,
        default=from_dot_kapitan('compile', 'parallelism', 4),
        metavar='INT',
        help='Number of concurrent compile processes, default is 4')
    compile_parser.add_argument(
        '--indent',
        '-i',
        type=int,
        default=from_dot_kapitan('compile', 'indent', 2),
        metavar='INT',
        help='Indentation spaces for YAML/JSON, default is 2')
    compile_parser.add_argument(
        '--secrets-path',
        help='set secrets path, default is "./secrets"',
        default=from_dot_kapitan('compile', 'secrets-path', './secrets'))
    compile_parser.add_argument(
        '--reveal',
        help='reveal secrets (warning: this will write sensitive data)',
        action='store_true',
        default=from_dot_kapitan('compile', 'reveal', False))
    compile_parser.add_argument(
        '--inventory-path',
        default=from_dot_kapitan('compile', 'inventory-path', './inventory'),
        help='set inventory path, default is "./inventory"')
    compile_parser.add_argument(
        '--cache',
        '-c',
        help='enable compilation caching to .kapitan_cache, default is False',
        action='store_true',
        default=from_dot_kapitan('compile', 'cache', False))
    compile_parser.add_argument(
        '--cache-paths',
        type=str,
        nargs='+',
        default=from_dot_kapitan('compile', 'cache-paths', []),
        metavar='PATH',
        help='cache additional paths to .kapitan_cache, default is []')
    compile_parser.add_argument('--ignore-version-check',
                                help='ignore the version from .kapitan',
                                action='store_true',
                                default=from_dot_kapitan(
                                    'compile', 'ignore-version-check', False))
    compile_parser.add_argument(
        '--schemas-path',
        default=from_dot_kapitan('validate', 'schemas-path', './schemas'),
        help='set schema cache path, default is "./schemas"')

    inventory_parser = subparser.add_parser('inventory', help='show inventory')
    inventory_parser.add_argument(
        '--target-name',
        '-t',
        default=from_dot_kapitan('inventory', 'target-name', ''),
        help='set target name, default is all targets')
    inventory_parser.add_argument(
        '--inventory-path',
        default=from_dot_kapitan('inventory', 'inventory-path', './inventory'),
        help='set inventory path, default is "./inventory"')
    inventory_parser.add_argument('--flat',
                                  '-F',
                                  help='flatten nested inventory variables',
                                  action='store_true',
                                  default=from_dot_kapitan(
                                      'inventory', 'flat', False))
    inventory_parser.add_argument(
        '--pattern',
        '-p',
        default=from_dot_kapitan('inventory', 'pattern', ''),
        help=
        'filter pattern (e.g. parameters.mysql.storage_class, or storage_class,'
        + ' or storage_*), default is ""')
    inventory_parser.add_argument('--verbose',
                                  '-v',
                                  help='set verbose mode',
                                  action='store_true',
                                  default=from_dot_kapitan(
                                      'inventory', 'verbose', False))

    searchvar_parser = subparser.add_parser(
        'searchvar', help='show all inventory files where var is declared')
    searchvar_parser.add_argument(
        'searchvar',
        type=str,
        metavar='VARNAME',
        help=
        'e.g. parameters.mysql.storage_class, or storage_class, or storage_*')
    searchvar_parser.add_argument(
        '--inventory-path',
        default=from_dot_kapitan('searchvar', 'inventory-path', './inventory'),
        help='set inventory path, default is "./inventory"')
    searchvar_parser.add_argument('--verbose',
                                  '-v',
                                  help='set verbose mode',
                                  action='store_true',
                                  default=from_dot_kapitan(
                                      'searchvar', 'verbose', False))
    searchvar_parser.add_argument('--pretty-print',
                                  '-p',
                                  help='Pretty print content of var',
                                  action='store_true',
                                  default=from_dot_kapitan(
                                      'searchvar', 'pretty-print', False))

    secrets_parser = subparser.add_parser('secrets', help='manage secrets')
    secrets_parser.add_argument(
        '--write',
        '-w',
        help='write secret token',
        metavar='TOKENNAME',
    )
    secrets_parser.add_argument(
        '--update',
        help='update recipients for secret token',
        metavar='TOKENNAME',
    )
    secrets_parser.add_argument('--update-targets',
                                action='store_true',
                                default=from_dot_kapitan(
                                    'secrets', 'update-targets', False),
                                help='update target secrets')
    secrets_parser.add_argument('--validate-targets',
                                action='store_true',
                                default=from_dot_kapitan(
                                    'secrets', 'validate-targets', False),
                                help='validate target secrets')
    secrets_parser.add_argument('--base64',
                                '-b64',
                                help='base64 encode file content',
                                action='store_true',
                                default=from_dot_kapitan(
                                    'secrets', 'base64', False))
    secrets_parser.add_argument('--reveal',
                                '-r',
                                help='reveal secrets',
                                action='store_true',
                                default=from_dot_kapitan(
                                    'secrets', 'reveal', False))
    secrets_parser.add_argument(
        '--file',
        '-f',
        help='read file or directory, set "-" for stdin',
        metavar='FILENAME')
    secrets_parser.add_argument('--target-name',
                                '-t',
                                help='grab recipients from target name')
    secrets_parser.add_argument(
        '--inventory-path',
        default=from_dot_kapitan('secrets', 'inventory-path', './inventory'),
        help='set inventory path, default is "./inventory"')
    secrets_parser.add_argument('--recipients',
                                '-R',
                                help='set GPG recipients',
                                type=str,
                                nargs='+',
                                default=from_dot_kapitan(
                                    'secrets', 'recipients', []),
                                metavar='RECIPIENT')
    secrets_parser.add_argument('--key',
                                '-K',
                                help='set KMS key',
                                default=from_dot_kapitan('secrets', 'key', ''),
                                metavar='KEY')
    secrets_parser.add_argument(
        '--secrets-path',
        help='set secrets path, default is "./secrets"',
        default=from_dot_kapitan('secrets', 'secrets-path', './secrets'))
    secrets_parser.add_argument(
        '--verbose',
        '-v',
        help='set verbose mode (warning: this will show sensitive data)',
        action='store_true',
        default=from_dot_kapitan('secrets', 'verbose', False))

    lint_parser = subparser.add_parser('lint',
                                       help='linter for inventory and secrets')
    lint_parser.add_argument(
        '--fail-on-warning',
        default=from_dot_kapitan('lint', 'fail-on-warning', False),
        action='store_true',
        help='exit with failure code if warnings exist, default is False')
    lint_parser.add_argument(
        '--skip-class-checks',
        action='store_true',
        help='skip checking for unused classes, default is False',
        default=from_dot_kapitan('lint', 'skip-class-checks', False))
    lint_parser.add_argument(
        '--skip-yamllint',
        action='store_true',
        help='skip running yamllint on inventory, default is False',
        default=from_dot_kapitan('lint', 'skip-yamllint', False))
    lint_parser.add_argument(
        '--search-secrets',
        default=from_dot_kapitan('lint', 'search-secrets', False),
        action='store_true',
        help='searches for plaintext secrets in inventory, default is False')
    lint_parser.add_argument('--secrets-path',
                             help='set secrets path, default is "./secrets"',
                             default=from_dot_kapitan('lint', 'secrets-path',
                                                      './secrets'))
    lint_parser.add_argument('--compiled-path',
                             default=from_dot_kapitan('lint', 'compiled-path',
                                                      './compiled'),
                             help='set compiled path, default is "./compiled"')
    lint_parser.add_argument(
        '--inventory-path',
        default=from_dot_kapitan('lint', 'inventory-path', './inventory'),
        help='set inventory path, default is "./inventory"')

    init_parser = subparser.add_parser(
        'init',
        help=
        'initialize a directory with the recommended kapitan project skeleton.'
    )
    init_parser.add_argument(
        '--directory',
        default=from_dot_kapitan('init', 'directory', '.'),
        help=
        'set path, in which to generate the project skeleton, assumes directory already exists. default is "./"'
    )

    validate_parser = subparser.add_parser(
        'validate',
        help=
        'validates the compile output against schemas as specified in inventory'
    )
    validate_parser.add_argument(
        '--compiled-path',
        default=from_dot_kapitan('compile', 'compiled-path', './compiled'),
        help='set compiled path, default is "./compiled')
    validate_parser.add_argument(
        '--inventory-path',
        default=from_dot_kapitan('compile', 'inventory-path', './inventory'),
        help='set inventory path, default is "./inventory"')
    validate_parser.add_argument('--targets',
                                 '-t',
                                 help='targets to validate, default is all',
                                 type=str,
                                 nargs='+',
                                 default=from_dot_kapitan(
                                     'compile', 'targets', []),
                                 metavar='TARGET'),
    validate_parser.add_argument(
        '--schemas-path',
        default=from_dot_kapitan('validate', 'schemas-path', './schemas'),
        help='set schema cache path, default is "./schemas"')
    validate_parser.add_argument(
        '--parallelism',
        '-p',
        type=int,
        default=from_dot_kapitan('validate', 'parallelism', 4),
        metavar='INT',
        help='Number of concurrent validate processes, default is 4')
    args = parser.parse_args()

    logger.debug('Running with args: %s', args)

    try:
        cmd = sys.argv[1]
    except IndexError:
        parser.print_help()
        sys.exit(1)

    if hasattr(args, 'verbose') and args.verbose:
        logging.basicConfig(
            level=logging.DEBUG,
            format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
    elif hasattr(args, 'quiet') and args.quiet:
        logging.basicConfig(level=logging.CRITICAL, format="%(message)s")
    else:
        logging.basicConfig(level=logging.INFO, format="%(message)s")

    if cmd == 'eval':
        file_path = args.jsonnet_file
        search_paths = [os.path.abspath(path) for path in args.search_paths]
        ext_vars = {}
        if args.vars:
            ext_vars = dict(var.split('=') for var in args.vars)
        json_output = None

        def _search_imports(cwd, imp):
            return search_imports(cwd, imp, search_paths)

        json_output = jsonnet_file(
            file_path,
            import_callback=_search_imports,
            native_callbacks=resource_callbacks(search_paths),
            ext_vars=ext_vars)
        if args.output == 'yaml':
            json_obj = json.loads(json_output)
            yaml.safe_dump(json_obj, sys.stdout, default_flow_style=False)
        elif json_output:
            print(json_output)

    elif cmd == 'compile':
        search_paths = [os.path.abspath(path) for path in args.search_paths]

        if not args.ignore_version_check:
            check_version()

        ref_controller = RefController(args.secrets_path)

        compile_targets(args.inventory_path,
                        search_paths,
                        args.output_path,
                        args.parallelism,
                        args.targets,
                        ref_controller,
                        prune=(args.prune),
                        indent=args.indent,
                        reveal=args.reveal,
                        cache=args.cache,
                        cache_paths=args.cache_paths,
                        fetch_dependencies=args.fetch,
                        validate=args.validate,
                        schemas_path=args.schemas_path,
                        jinja2_filters=args.jinja2_filters)

    elif cmd == 'inventory':
        if args.pattern and args.target_name == '':
            parser.error("--pattern requires --target_name")
        try:
            inv = inventory_reclass(args.inventory_path)
            if args.target_name != '':
                inv = inv['nodes'][args.target_name]
                if args.pattern != '':
                    pattern = args.pattern.split(".")
                    inv = deep_get(inv, pattern)
            if args.flat:
                inv = flatten_dict(inv)
                yaml.dump(inv,
                          sys.stdout,
                          width=10000,
                          default_flow_style=False)
            else:
                yaml.dump(inv,
                          sys.stdout,
                          Dumper=PrettyDumper,
                          default_flow_style=False)
        except Exception as e:
            if not isinstance(e, KapitanError):
                logger.exception("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
            sys.exit(1)

    elif cmd == 'searchvar':
        searchvar(args.searchvar, args.inventory_path, args.pretty_print)

    elif cmd == 'lint':
        start_lint(args.fail_on_warning, args.skip_class_checks,
                   args.skip_yamllint, args.inventory_path,
                   args.search_secrets, args.secrets_path, args.compiled_path)

    elif cmd == 'init':
        initialise_skeleton(args.directory)

    elif cmd == 'secrets':
        ref_controller = RefController(args.secrets_path)

        if args.write is not None:
            secret_write(args, ref_controller)
        elif args.reveal:
            secret_reveal(args, ref_controller)
        elif args.update:
            secret_update(args, ref_controller)
        elif args.update_targets or args.validate_targets:
            secret_update_validate(args, ref_controller)

    elif cmd == 'validate':
        schema_validate_compiled(args.targets,
                                 inventory_path=args.inventory_path,
                                 compiled_path=args.compiled_path,
                                 schema_cache_path=args.schemas_path,
                                 parallel=args.parallelism)