From c2462e2ff31a06642354e0c0e653e55c123b691e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Gu=C3=A9rin?= Date: Wed, 31 Jul 2019 16:32:24 +0200 Subject: [PATCH] Add a flat output to the get-all command New output format where the keys of the yaml output are no more "merged" into a tree but are kept as full path. The new output is looks like: ```yaml my/nested/path: string my/nested/other_path: key1: val1 key2: val2 ``` --- CHANGELOG.md | 1 + tests/integration/test_integration.py | 7 +++++++ tests/unit/test_cli.py | 9 +++++++++ tests/unit/test_client_base.py | 12 ++++++++++++ vault_cli/cli.py | 9 +++++++-- vault_cli/client.py | 15 +++++++++++---- 6 files changed, 47 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9212aa..9a4a438 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG - Add implicit and explicit caching (#102 / #103) - Add `config_file=` in lib mode +- Add a `--flat` option to the `get-all` command (the new output uses full paths as dictionnary key) 0.9.0 (2019-07-11) ------------------ diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index c626c03..5c37ff4 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -55,6 +55,13 @@ def test_integration_cli(cli_runner, clean_vault): """ ) + assert call(cli_runner, ["get-all", "--flat", ""]).output == ( + """--- +a: b +c/d: e +""" + ) + call(cli_runner, ["delete", "a"]) assert call(cli_runner, ["list"]).output == "c/\n" diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 3e3bcb9..d6bc372 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -122,6 +122,15 @@ def test_get_all(cli_runner, vault_with_token): assert result.exit_code == 0 +def test_get_all_flat(cli_runner, vault_with_token): + + vault_with_token.db = {"a/baz": {"value": "bar"}, "a/foo": {"value": "yay"}} + result = cli_runner.invoke(cli.cli, ["get-all", "--flat", "a"]) + + assert yaml.safe_load(result.output) == {"a/baz": "bar", "a/foo": "yay"} + assert result.exit_code == 0 + + def test_set(cli_runner, vault_with_token): result = cli_runner.invoke(cli.cli, ["set", "a", "b"]) diff --git a/tests/unit/test_client_base.py b/tests/unit/test_client_base.py index e345cb9..d8376e5 100644 --- a/tests/unit/test_client_base.py +++ b/tests/unit/test_client_base.py @@ -138,6 +138,18 @@ def test_vault_client_base_get_all_secrets(vault): assert result == {"a": {"c": "secret-ac"}} +def test_vault_client_base_get_all_secrets_flat(vault): + vault.db = {"a/c": {"value": "secret-ac"}, "b": {"value": "secret-b"}} + + result = vault.get_all_secrets("a", "", flat=True) + + assert result == {"a/c": "secret-ac", "b": "secret-b"} + + result = vault.get_all_secrets("a", flat=True) + + assert result == {"a/c": "secret-ac"} + + @pytest.mark.parametrize( "input, expected", [("a", {"a/c": "secret-ac"}), ("b", {"b": "secret-b"})] ) diff --git a/vault_cli/cli.py b/vault_cli/cli.py index 5080314..74b941c 100644 --- a/vault_cli/cli.py +++ b/vault_cli/cli.py @@ -183,10 +183,15 @@ def list_(client_obj: client.VaultClientBase, path: str): @cli.command(name="get-all") +@click.option( + "--flat", + is_flag=True, + help=("Returns the full path as keys instead of merging paths into a tree"), +) @click.argument("path", required=False, nargs=-1) @click.pass_obj @handle_errors() -def get_all(client_obj: client.VaultClientBase, path: Sequence[str]): +def get_all(client_obj: client.VaultClientBase, path: Sequence[str], flat: bool): """ Return multiple secrets. Return a single yaml with all the secrets located at the given paths. Folders are recursively explored. Without a path, @@ -194,7 +199,7 @@ def get_all(client_obj: client.VaultClientBase, path: Sequence[str]): """ paths = list(path) or [""] - result = client_obj.get_all_secrets(*paths) + result = client_obj.get_all_secrets(*paths, flat=flat) click.echo( yaml.safe_dump(result, default_flow_style=False, explicit_start=True), nl=False diff --git a/vault_cli/client.py b/vault_cli/client.py index c0b2fd0..d50508d 100644 --- a/vault_cli/client.py +++ b/vault_cli/client.py @@ -210,7 +210,9 @@ def _browse_recursive_secrets( yield sub_path @caching - def get_all_secrets(self, *paths: str, render: bool = True) -> types.JSONDict: + def get_all_secrets( + self, *paths: str, render: bool = True, flat: bool = False + ) -> types.JSONDict: """ Takes several paths, return the nested dict of all secrets below those paths @@ -220,7 +222,10 @@ def get_all_secrets(self, *paths: str, render: bool = True) -> types.JSONDict: *paths : str Paths to read recursively render : bool, optional - Wether templated secrets should be rendered, by default True + Whether templated secrets should be rendered, by default True + flat : bool, optional + Whether to return flat structure with full path as keys or nested + structure that looks like a tree Returns ------- @@ -232,8 +237,10 @@ def get_all_secrets(self, *paths: str, render: bool = True) -> types.JSONDict: for path in paths: path_dict = self.get_secrets(path, render=render) - - result.update(utils.path_to_nested(path_dict)) + if flat: + result.update(path_dict) + else: + result.update(utils.path_to_nested(path_dict)) return result