From 37f922a07e52ab4f59c29cfbfad173b21868e17c Mon Sep 17 00:00:00 2001 From: Lev Morgan Date: Wed, 17 Apr 2019 11:58:48 -0500 Subject: [PATCH] Improve files.write, add decrypt output to file Add an option, -s, to write decrypted files to a file rather than stdout. Decryptyed files have their mode set to 600. Also adds a few improvements to files.write. Change-Id: Ia1a6de78d401afbea6ee261652f4650071f54b60 --- doc/source/cli/cli.rst | 7 +++++++ pegleg/cli.py | 19 +++++++++++++++--- pegleg/engine/util/files.py | 30 +++++++++++++++++++--------- tests/unit/engine/util/test_files.py | 22 ++++++++++++++++++++ 4 files changed, 66 insertions(+), 12 deletions(-) diff --git a/doc/source/cli/cli.rst b/doc/source/cli/cli.rst index 9e753930..8f73ec20 100644 --- a/doc/source/cli/cli.rst +++ b/doc/source/cli/cli.rst @@ -670,11 +670,18 @@ documents in the ``filename``. The absolute path to the pegleg managed encrypted secrets file. + +**-s / save-location** (Optional). + +The desired output path for the decrypted file. If not specified, it will be +printed to stdout. + Usage: :: ./pegleg.sh site secrets decrypt -f + [-s ] Examples """""""" diff --git a/pegleg/cli.py b/pegleg/cli.py index 4fdd7661..e9c21da9 100644 --- a/pegleg/cli.py +++ b/pegleg/cli.py @@ -24,6 +24,7 @@ from pegleg import engine from pegleg.engine import bundle from pegleg.engine import catalog from pegleg.engine.secrets import wrap_secret +from pegleg.engine.util import files from pegleg.engine.util.pegleg_secret_management import PeglegSecretManagement from pegleg.engine.util.shipyard_helper import ShipyardHelper @@ -652,12 +653,24 @@ def encrypt(*, save_location, author, site_name): '-f', '--filename', 'file_name', - help='The file name to decrypt and print out to stdout') + help='The file to decrypt') +@click.option( + '-s', + '--save-location', + 'save_location', + default=None, + help='The destination where the decrypted file should be saved. ' + 'If not specified, it will be printed to stdout.') @click.argument('site_name') -def decrypt(*, file_name, site_name): +def decrypt(*, file_name, save_location, site_name): engine.repository.process_repositories(site_name) - click.echo(engine.secrets.decrypt(file_name, site_name)) + decrypted = engine.secrets.decrypt(file_name, site_name) + if save_location is None: + click.echo(decrypted) + else: + files.write(save_location, decrypted) + os.chmod(save_location, 0o600) @main.group(help="Miscellaneous generate commands") diff --git a/pegleg/engine/util/files.py b/pegleg/engine/util/files.py index 54ea38e9..333b5a06 100644 --- a/pegleg/engine/util/files.py +++ b/pegleg/engine/util/files.py @@ -296,18 +296,30 @@ def write(file_path, data): :param file_path: Destination file for the written data file :type file_path: str :param data: data to be written to the destination file - :type data: dict or a list of dicts + :type data: str, dict, or a list of dicts """ - os.makedirs(os.path.dirname(file_path), exist_ok=True) + try: + os.makedirs(os.path.dirname(file_path), exist_ok=True) - with open(file_path, 'w') as stream: - yaml.safe_dump_all( - data, - stream, - explicit_start=True, - explicit_end=True, - default_flow_style=False) + with open(file_path, 'w') as stream: + if isinstance(data, str): + stream.write(data) + elif isinstance(data, (dict, collections.abc.Iterable)): + if isinstance(data, dict): + data = [data] + yaml.safe_dump_all( + data, + stream, + explicit_start=True, + explicit_end=True, + default_flow_style=False) + else: + raise ValueError('data must be str or dict, ' + 'not {}'.format(type(data))) + except EnvironmentError as e: + raise click.ClickError( + "Couldn't write data to {}: {}".format(file_path, e)) def _recurse_subdirs(search_path, depth): diff --git a/tests/unit/engine/util/test_files.py b/tests/unit/engine/util/test_files.py index 5a9e696c..5f13dd4b 100644 --- a/tests/unit/engine/util/test_files.py +++ b/tests/unit/engine/util/test_files.py @@ -14,6 +14,9 @@ import os +import pytest +import yaml + from pegleg import config from pegleg.engine.util import files from tests.unit.fixtures import create_tmp_deployment_files @@ -37,6 +40,25 @@ class TestFileHelpers(object): assert not documents, ("Documents returned should be empty for " "site-definition.yaml") + def test_write(self, create_tmp_deployment_files): + path = os.path.join(config.get_site_repo(), 'site', 'cicd', + 'test_out.yaml') + files.write(path, "test text") + with open(path, "r") as out_fi: + assert out_fi.read() == "test text" + + files.write(path, {"a": 1}) + with open(path, "r") as out_fi: + assert yaml.safe_load(out_fi) == {"a": 1} + + files.write(path, [{"a": 1}]) + with open(path, "r") as out_fi: + assert list(yaml.safe_load_all(out_fi)) == [{"a": 1}] + + with pytest.raises(ValueError) as _: + files.write(path, object()) + + def test_file_in_subdir(): assert files.file_in_subdir("aaa/bbb/ccc.txt", "aaa") assert files.file_in_subdir("aaa/bbb/ccc.txt", "bbb")