Skip to content

Commit

Permalink
Add stash init example
Browse files Browse the repository at this point in the history
- Adds a flag that enables a sort of 'tutorial' mode where an example key is inserted to the stash for the user to try out and `get`
  • Loading branch information
David Ginzbourg authored and nir0s committed Nov 28, 2017
1 parent 79c496b commit d161323
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 16 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 26 additions & 4 deletions ghost.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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))

Expand Down Expand Up @@ -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')
Expand Down
66 changes: 54 additions & 12 deletions tests/test_ghost.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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'})
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1787,6 +1825,10 @@ def test_ssh_with_key_path_and_extension(self, test_cli_stash):
'ssh -i /path/to/key [email protected] -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
Expand Down

0 comments on commit d161323

Please sign in to comment.