diff --git a/README.md b/README.md index b9ba2c2..2e30497 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,35 @@ Commands: ssh Use a key to SSH-connect to a machine unlock Unlock a key +----- (Optional) ----- + +# Initializing a stash with a tutorial +$ ghost init --tutorial +Stash: tinydb at ~/.ghost/ghost.json +Initializing stash... +Initialized stash at: ~/.ghost/ghost.json +Your passphrase can be found under the `passphrase.ghost` file in the current directory. +Make sure you save your passphrase somewhere safe. If lost, you will lose access to your stash. +TUTORIAL: +For your convenience we've stored an example key named 'example'. Usually this is done with 'ghost put example first_key=first_value [second_key=second_value [...]]'. You may retrieve it with 'ghost get example'. You may also delete it with 'ghost delete example'. + +$ export GHOST_PASSPHRASE=$(cat passphrase.ghost) + +$ ghost get example +Stash: tinydb at ~/.ghost/ghost.json +Retrieving key... + +Description: This is the key description +Lock: False +Created_At: 2017-05-29 13:49:56 +Modified_At: 2017-05-29 13:49:56 +Value: key=value; +Uid: f7e236f7-450c-400b-9328-95e18d5fac52 +Metadata: some_key=some_value; +Type: secret +Name: example + +----- (Optional) ----- # Initializing a stash $ ghost init diff --git a/ghost.py b/ghost.py index 7f25a6b..616060b 100644 --- a/ghost.py +++ b/ghost.py @@ -111,6 +111,13 @@ PASSPHRASE_FILENAME = 'passphrase.ghost' +EXAMPLE_KEY = { + 'name': 'example', + 'value': {'key': 'value'}, + 'metadata': {'some_key': 'some_value'}, + 'description': 'This is the key description' +} + POTENTIAL_PASSPHRASE_LOCATIONS = [ os.path.abspath(PASSPHRASE_FILENAME), os.path.join(GHOST_HOME, PASSPHRASE_FILENAME), @@ -185,7 +192,8 @@ def init(self): @property def is_initialized(self): if self._storage.is_initialized: - self.passphrase = get_passphrase(self.passphrase) + if not self.passphrase: + self.passphrase = get_passphrase(self.passphrase) if self.get('stored_passphrase'): return True return False @@ -1263,7 +1271,6 @@ def main(): @main.command(name='init', short_help='Initialize a stash') -@click.argument('STASH_PATH', required=False, type=click.STRING) @click.option('-p', '--passphrase', default=None, @@ -1275,7 +1282,14 @@ def main(): default='tinydb', type=click.Choice(STORAGE_MAPPING.keys()), help='Storage backend for the stash') -def init_stash(stash_path, passphrase, passphrase_size, backend): +@click.option('-t', + '--tutorial', + is_flag=True, + help='Whether to create example key') +@stash_option +@passphrase_option +@backend_option +def init_stash(stash, passphrase, passphrase_size, backend, tutorial): r"""Init a stash `STASH_PATH` is the path to the storage endpoint. If this isn't supplied, @@ -1293,7 +1307,7 @@ def init_stash(stash_path, passphrase, passphrase_size, backend): export GHOST_BACKEND='tinydb' """ - stash_path = stash_path or STORAGE_DEFAULT_PATH_MAPPING[backend] + stash_path = stash or STORAGE_DEFAULT_PATH_MAPPING[backend] click.echo('Stash: {0} at {1}'.format(backend, stash_path)) storage = STORAGE_MAPPING[backend](**_parse_path_string(stash_path)) @@ -1338,6 +1352,14 @@ def init_stash(stash_path, passphrase, passphrase_size, backend): 'Make sure you save your passphrase somewhere safe. ' 'If lost, you will lose access to your stash.') + if tutorial: + stash.put(**EXAMPLE_KEY) + click.echo("TUTORIAL: \nFor your convenience we've stored an example " + "key named 'example'. Usually this is done with 'ghost put " + "example first_key=first_value [second_key=second_value " + "[...]]'. You may retrieve it with 'ghost get example'. You" + " may also delete it with 'ghost delete example'.") + @main.command(name='put', short_help='Insert a new key') @click.argument('KEY_NAME') diff --git a/tests/test_ghost.py b/tests/test_ghost.py index 3322679..384105a 100644 --- a/tests/test_ghost.py +++ b/tests/test_ghost.py @@ -50,6 +50,13 @@ def _invoke(command): return cfy.invoke(getattr(ghost, func), params) +def _make_temp_passphrase_file(): + fd, temp_file_path = tempfile.mkstemp() + os.close(fd) + os.remove(temp_file_path) + return temp_file_path + + class TestGeneral: def test_get_current_time(self): assert len(ghost._get_current_time()) == 19 @@ -110,12 +117,6 @@ def test_prettify_list_input_not_list(self): ghost._prettify_list('') def test_get_passphrase(self): - def _make_temp_passphrase_file(): - fd, temp_file_path = tempfile.mkstemp() - os.close(fd) - os.remove(temp_file_path) - return temp_file_path - tempfile1 = _make_temp_passphrase_file() tempfile2 = _make_temp_passphrase_file() @@ -995,6 +996,13 @@ def assert_stash_initialized(stash_path): assert len(db) == 1 +def assert_stash_initialized_tutorial(stash_path): + db = get_tinydb(stash_path) + assert '2' in db + assert db['2']['name'] == 'example' + assert len(db) == 2 + + def assert_key_put(db, dont_verify_value=False): key = db['2'] assert key['name'] == 'aws' @@ -1326,6 +1334,36 @@ def test_list_locked_filtered_matches(self, test_stash): assert len(result) == 1 assert 'aws-2' in result + def test_is_initialized_passphrase_overridden(self): + prev_dir = os.getcwd() + stash_dir = tempfile.mkdtemp() + os.chdir(stash_dir) + stash_path = os.path.join(stash_dir, 'stash.json') + try: + tempfile1 = _make_temp_passphrase_file() + + passphrase = '123' + ghost.POTENTIAL_PASSPHRASE_LOCATIONS = [tempfile1] + with open(ghost.POTENTIAL_PASSPHRASE_LOCATIONS[0], 'w') \ + as passphrase_file: + passphrase_file.write(passphrase) + + storage = ghost.TinyDBStorage(stash_path) + stash = ghost.Stash(storage, 'some_passphrase') + stash.init() + stash.is_initialized + assert stash.passphrase == 'some_passphrase' + stash.passphrase = None + try: + stash.is_initialized + except ghost.GhostError: + pass + assert stash.passphrase == passphrase + finally: + os.remove(ghost.POTENTIAL_PASSPHRASE_LOCATIONS[0]) + os.chdir(prev_dir) + shutil.rmtree(stash_dir, ignore_errors=True) + def _create_migration_env(test_stash, temp_file_path): test_stash.put('aws', {'a': 'b'}) @@ -1360,7 +1398,7 @@ def test_cli_stash(stash_path): os.close(fd) os.remove(passphrase_file_path) ghost.PASSPHRASE_FILENAME = passphrase_file_path - _invoke('init_stash "{0}"'.format(stash_path)) + _invoke('init_stash -s "{0}"'.format(stash_path)) os.environ['GHOST_STASH_PATH'] = stash_path with open(passphrase_file_path) as passphrase_file: passphrase = passphrase_file.read() @@ -1404,7 +1442,7 @@ def test_init(self, test_cli_stash): assert_stash_initialized(test_cli_stash._storage.db_path) def test_init_already_initialized(self, test_cli_stash): - result = _invoke('init_stash "{0}" -p {1}'.format( + result = _invoke('init_stash -s "{0}" -p {1}'.format( os.environ['GHOST_STASH_PATH'], test_cli_stash.passphrase)) assert 'Stash already initialized' in result.output assert result.exit_code == 0 @@ -1415,7 +1453,7 @@ def test_init_permission_denied_on_passphrase(self): fd, temp_file = tempfile.mkstemp() os.close(fd) os.remove(temp_file) - result = _invoke('init_stash "{0}" -p whatever'.format(temp_file)) + result = _invoke('init_stash -s "{0}" -p whatever'.format(temp_file)) assert 'Expected OSError' in str(result.exception) assert 'Removing stale stash and passphrase' in str(result.output) assert type(result.exception) == SystemExit @@ -1428,7 +1466,7 @@ def test_init_permission_denied_on_passphrase(self): @pytest.mark.skipif(os.name == 'nt', reason='Irrelevant on Windows') def test_init_permission_denied_on_stash(self, test_cli_stash): - result = _invoke('init_stash "/x" -p {0}'.format( + result = _invoke('init_stash -s "/x" -p {0}'.format( test_cli_stash.passphrase)) assert 'Permission denied' in str(result.exception) assert type(result.exception) == SystemExit @@ -1642,8 +1680,8 @@ def test_load(self, test_cli_stash, temp_file_path): def test_fail_init_two_stashes_passphrase_file_exists(self, stash_path, temp_file_path): - _invoke('init_stash "{0}"'.format(stash_path)) - result = _invoke('init_stash "{0}" -b sqlalchemy'.format( + _invoke('init_stash -s "{0}"'.format(stash_path)) + result = _invoke('init_stash -s "{0}" -b sqlalchemy'.format( temp_file_path)) assert 'Overwriting might prevent you' in result.output @@ -1787,6 +1825,10 @@ def test_ssh_with_key_path_and_extension(self, test_cli_stash): 'ssh -i /path/to/key ubuntu@10.10.1.10 -o Key="Value"' assert expected_command in str(result.exception) + def test_tutorial(self, stash_path): + _invoke('init_stash -s "{0}" -t'.format(stash_path)) + assert_stash_initialized_tutorial(stash_path) + class TestMultiStash: # TODO: Test that migrate works when using multi-stash mode