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