Merge lp:~makyo/juju-quickstart/ssh-2-create-keys into lp:juju-quickstart

Proposed by Madison Scott-Clary
Status: Merged
Merged at revision: 31
Proposed branch: lp:~makyo/juju-quickstart/ssh-2-create-keys
Merge into: lp:juju-quickstart
Diff against target: 174 lines (+104/-26)
2 files modified
quickstart/app.py (+43/-12)
quickstart/tests/test_app.py (+61/-14)
To merge this branch: bzr merge lp:~makyo/juju-quickstart/ssh-2-create-keys
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+198135@code.launchpad.net

Description of the change

Generate SSH keys if none exist.

This is step 2: provide the user with the option to let Quickstart generate SSH keys.

Note: this is now an incremental branch, with better checks on ssh identities in the future; there's a card to represent this on the kanban board.

To test: make check

to QA:
1. mv ~/.ssh ~/.ssh.old
2a. .venv/bin/python juju-quickstart # should ask if you want to generate; say no, should exit
2b. .venv/bin/python juju-quickstart # should ask if you want to generate; say yes, shoud prompt for password, then continue, generating a new key
3. mv ~/.ssh.old ~/.ssh
4. ssh-agent
5. ssh-add
6. .venv/bin/python juju-quickstart # should bootstrap

https://codereview.appspot.com/36080044/

To post a comment you must log in.
Revision history for this message
Madison Scott-Clary (makyo) wrote :

Reviewers: mp+198135_code.launchpad.net,

Message:
Please take a look.

Description:
Generate SSH keys if none exist.

This is step 2: provide the user with the option to let Quickstart
generate SSH keys.

To test: make check

to QA:
1. mv ~/.ssh ~/.ssh.old
2a. .venv/bin/python juju-quickstart # should ask if you want to
generate; say no, should exit
2b. .venv/bin/python juju-quickstart # should ask if you want to
generate; say yes, shoud prompt for password, then continue, generating
a new key
3. mv ~/.ssh.old ~/.ssh
4. ssh-agent
5. ssh-add
6. .venv/bin/python juju-quickstart # should bootstrap

https://code.launchpad.net/~makyo/juju-quickstart/ssh-2-create-keys/+merge/198135

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/36080044/

Affected files (+108, -20 lines):
   A [revision details]
   M quickstart/app.py
   M quickstart/tests/test_app.py

Revision history for this message
Francesco Banconi (frankban) wrote :

LGTM with (possible) changes, thank you!

https://codereview.appspot.com/36080044/diff/1/quickstart/app.py
File quickstart/app.py (right):

https://codereview.appspot.com/36080044/diff/1/quickstart/app.py#newcode101
quickstart/app.py:101: if len(create_keys) > 0 and
create_keys[0].lower() == 'y':
This can be written just as:
if create_keys.lower() == 'y':

In general, for sequences (like strings), use the fact that if they are
empty they are also False, e.g.:
use "if seq" instead of "if len(seq) [> 0]".

https://codereview.appspot.com/36080044/diff/1/quickstart/app.py#newcode109
quickstart/app.py:109: 'ssh-add')
Thinking about this in retrospective, I am not sure we need the user to
have an ssh agent set up. It seems all we need are ssh keys, so IIUC the
return codes you described, a 130 could still be ok for us. What do you
think? Feel free to disagree.

https://codereview.appspot.com/36080044/diff/1/quickstart/app.py#newcode121
quickstart/app.py:121: retcode, _, error =
utils.call('/usr/bin/ssh-keygen',
Here, if the user decides to continue, we just generate ssh keys without
starting the agent. This is ok IMHO, but then we don't want to require
the agent on the next quickstart run.

https://codereview.appspot.com/36080044/diff/1/quickstart/app.py#newcode128
quickstart/app.py:128: raise ProgramExit('Error generating ssh key!
{}'.format(error))
All the other quickstart messages are lower cased: we might want to
change this, but for now let's follow that pattern.

https://codereview.appspot.com/36080044/diff/1/quickstart/app.py#newcode129
quickstart/app.py:129: print('A new SSH key was generated in
{}'.format(key_file))
Ditto.

https://codereview.appspot.com/36080044/diff/1/quickstart/tests/test_app.py
File quickstart/tests/test_app.py (right):

https://codereview.appspot.com/36080044/diff/1/quickstart/tests/test_app.py#newcode197
quickstart/tests/test_app.py:197: mock_call.assert_has_calls([
assert_called_with? <shrug>

https://codereview.appspot.com/36080044/diff/1/quickstart/tests/test_app.py#newcode239
quickstart/tests/test_app.py:239:
mock_call.assert_called_with('/usr/bin/ssh-keygen',
assert_called_once_with? Cheap double check.

https://codereview.appspot.com/36080044/

Revision history for this message
Madison Scott-Clary (makyo) wrote :

Thanks for the review; need to switch check for keys as noted below, but
everything else is done.

https://codereview.appspot.com/36080044/diff/1/quickstart/app.py
File quickstart/app.py (right):

https://codereview.appspot.com/36080044/diff/1/quickstart/app.py#newcode101
quickstart/app.py:101: if len(create_keys) > 0 and
create_keys[0].lower() == 'y':
On 2013/12/09 10:45:41, frankban wrote:
> This can be written just as:
> if create_keys.lower() == 'y':

> In general, for sequences (like strings), use the fact that if they
are empty
> they are also False, e.g.:
> use "if seq" instead of "if len(seq) [> 0]".

Done.

https://codereview.appspot.com/36080044/diff/1/quickstart/app.py#newcode109
quickstart/app.py:109: 'ssh-add')
On 2013/12/09 10:45:41, frankban wrote:
> Thinking about this in retrospective, I am not sure we need the user
to have an
> ssh agent set up. It seems all we need are ssh keys, so IIUC the
return codes
> you described, a 130 could still be ok for us. What do you think? Feel
free to
> disagree.

I ran into that with this branch, and agree with you. I don't think we
need to start the agent. The problem I was running into in the original
branch is that an agent was already running as part of working with bzr.
  Going to switch to just keygen and use a different test, not `ssh-add
-l`; I was under the impression that would list keys regardless of agent
status, but testing from a tty, that's not the case.

https://codereview.appspot.com/36080044/diff/1/quickstart/app.py#newcode121
quickstart/app.py:121: retcode, _, error =
utils.call('/usr/bin/ssh-keygen',
On 2013/12/09 10:45:41, frankban wrote:
> Here, if the user decides to continue, we just generate ssh keys
without
> starting the agent. This is ok IMHO, but then we don't want to require
the agent
> on the next quickstart run.

Agreed, but see note about the current ssh-add -l test.

https://codereview.appspot.com/36080044/diff/1/quickstart/app.py#newcode128
quickstart/app.py:128: raise ProgramExit('Error generating ssh key!
{}'.format(error))
On 2013/12/09 10:45:41, frankban wrote:
> All the other quickstart messages are lower cased: we might want to
change this,
> but for now let's follow that pattern.

Done.

https://codereview.appspot.com/36080044/diff/1/quickstart/app.py#newcode129
quickstart/app.py:129: print('A new SSH key was generated in
{}'.format(key_file))
On 2013/12/09 10:45:41, frankban wrote:
> Ditto.

Done.

https://codereview.appspot.com/36080044/diff/1/quickstart/tests/test_app.py
File quickstart/tests/test_app.py (right):

https://codereview.appspot.com/36080044/diff/1/quickstart/tests/test_app.py#newcode197
quickstart/tests/test_app.py:197: mock_call.assert_has_calls([
On 2013/12/09 10:45:41, frankban wrote:
> assert_called_with? <shrug>

Done.

https://codereview.appspot.com/36080044/diff/1/quickstart/tests/test_app.py#newcode239
quickstart/tests/test_app.py:239:
mock_call.assert_called_with('/usr/bin/ssh-keygen',
On 2013/12/09 10:45:41, frankban wrote:
> assert_called_once_with? Cheap double check.

Done.

https://codereview.appspot.com/36080044/

31. By Madison Scott-Clary

Responding to review.

32. By Madison Scott-Clary

Switching to find from ssh-add

Revision history for this message
Madison Scott-Clary (makyo) wrote :
Revision history for this message
Francesco Banconi (frankban) wrote :
Revision history for this message
Madison Scott-Clary (makyo) wrote :

*** Submitted:

Generate SSH keys if none exist.

This is step 2: provide the user with the option to let Quickstart
generate SSH keys.

Note: this is now an incremental branch, with better checks on ssh
identities in the future; there's a card to represent this on the kanban
board.

To test: make check

to QA:
1. mv ~/.ssh ~/.ssh.old
2a. .venv/bin/python juju-quickstart # should ask if you want to
generate; say no, should exit
2b. .venv/bin/python juju-quickstart # should ask if you want to
generate; say yes, shoud prompt for password, then continue, generating
a new key
3. mv ~/.ssh.old ~/.ssh
4. ssh-agent
5. ssh-add
6. .venv/bin/python juju-quickstart # should bootstrap

R=frankban
CC=
https://codereview.appspot.com/36080044

https://codereview.appspot.com/36080044/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'quickstart/app.py'
2--- quickstart/app.py 2013-12-06 17:18:22 +0000
3+++ quickstart/app.py 2013-12-09 18:12:20 +0000
4@@ -24,6 +24,7 @@
5 import functools
6 import json
7 import logging
8+import os
9 import time
10
11 import jujuclient
12@@ -88,18 +89,48 @@
13
14
15 def ensure_ssh_keys():
16- retcode = utils.call('/usr/bin/ssh-add', '-l')[0]
17- if retcode > 0:
18- # The return code will be 130 if no agent was found; it will be 127 if
19- # no keys were found. The return code will be 0 (with an output of
20- # keys) if everything was successful.
21- raise ProgramExit('No ssh keys were found.\n'
22- 'Please run these commands and follow the '
23- 'instructions they produce before running '
24- 'quickstart again:\n\n'
25- 'ssh-keygen -t rsa\n'
26- 'ssh-agent\n'
27- 'ssh-add')
28+ """Ensure that SSH keys are available.
29+
30+ Allow the user to let quickstart create SSH keys, or quit by raising a
31+ ProgramExit if they would like to create the key themselves.
32+ """
33+ ssh_dir = os.path.join(os.path.expanduser('~'), '.ssh')
34+ retcode = utils.call('/usr/bin/find', ssh_dir, '-name', 'id_*.pub')[0]
35+ if retcode > 0:
36+ print('No SSH keys were found in ~/.ssh\n\n'
37+ 'Would you like quickstart to create them for you? Note that '
38+ 'this will create a new SSH key with the passphrase you provide '
39+ 'available to your SSH agent. ssh-keygen will ask for a '
40+ 'passphrase, but juju quickstart will not have access to it.\n'
41+ 'Alternatively, you may create the keys on your own.\n')
42+ create_keys = raw_input('Continue [y/N]? ')
43+ if create_keys.lower() == 'y':
44+ create_ssh_keys()
45+ else:
46+ raise ProgramExit('Please run these commands and follow the '
47+ 'instructions they produce before running '
48+ 'quickstart again:\n\n'
49+ 'ssh-keygen -b 4096 -t rsa')
50+
51+
52+def create_ssh_keys():
53+ """Create SSH keys for the user
54+
55+ Raises ProgramExit if the keys were not created successfully.
56+
57+ NB: this involves user interaction for entering the passphrase; this may
58+ have to change if creating SSH keys takes place in the urwid interface.
59+ """
60+ key_file = os.path.join(os.path.expanduser('~'), '.ssh', 'id_juju_rsa')
61+ retcode, _, error = utils.call('/usr/bin/ssh-keygen',
62+ '-q', # silent
63+ '-b', '4096', # 4096 bytes
64+ '-t', 'rsa', # RSA type
65+ '-C', 'Generated with Juju Quickstart',
66+ '-f', key_file)
67+ if retcode > 0:
68+ raise ProgramExit('error generating ssh key! {}'.format(error))
69+ print('a new ssh key was generated in {}'.format(key_file))
70
71
72 def ensure_environments():
73
74=== modified file 'quickstart/tests/test_app.py'
75--- quickstart/tests/test_app.py 2013-12-06 17:18:22 +0000
76+++ quickstart/tests/test_app.py 2013-12-09 18:12:20 +0000
77@@ -20,6 +20,7 @@
78
79 from contextlib import contextmanager
80 import json
81+import os
82 import unittest
83
84 import jujuclient
85@@ -176,29 +177,75 @@
86 self.assertEqual(3, mock_call.call_count)
87
88
89+@helpers.mock_print
90 class TestEnsureSSHKeys(
91 helpers.CallTestsMixin, ProgramExitTestsMixin, unittest.TestCase):
92
93- def test_success(self):
94+ print_msg = 'No SSH keys were found in ~/.ssh\n\n' \
95+ 'Would you like quickstart to create them for you? Note ' \
96+ 'that this will create a new SSH key with the passphrase ' \
97+ 'you provide available to your SSH agent. ssh-keygen will ' \
98+ 'ask for a passphrase, but juju quickstart will not have ' \
99+ 'access to it.\nAlternatively, you may create the keys on ' \
100+ 'your own.\n'
101+ ssh_key_find = ('/usr/bin/find',
102+ os.path.join(os.path.expanduser('~'), '.ssh'),
103+ '-name', 'id_*.pub')
104+
105+ def patch_raw_input(self, return_value='N'):
106+ mock_raw_input = mock.Mock(return_value=return_value)
107+ return mock.patch('__builtin__.raw_input', mock_raw_input)
108+
109+ def test_success(self, mock_print):
110 with self.patch_call(0) as mock_call:
111 app.ensure_ssh_keys()
112- mock_call.assert_has_calls([
113- mock.call('/usr/bin/ssh-add', '-l')
114- ])
115+ mock_call.assert_called_with(*self.ssh_key_find)
116
117- def test_failure(self):
118- msg = 'No ssh keys were found.\n' \
119- 'Please run these commands and follow the instructions they ' \
120+ def test_failure_no_keygen(self, mock_print):
121+ msg = 'Please run these commands and follow the instructions they ' \
122 'produce before running quickstart again:\n\n' \
123- 'ssh-keygen -t rsa\n' \
124- 'ssh-agent\n' \
125- 'ssh-add'
126+ 'ssh-keygen -b 4096 -t rsa'
127 with self.assert_program_exit(msg):
128 with self.patch_call(130) as mock_call:
129- app.ensure_ssh_keys()
130- mock_call.assert_has_calls([
131- mock.call('/usr/bin/ssh-add', '-l')
132- ])
133+ with self.patch_raw_input() as mock_raw_input:
134+ app.ensure_ssh_keys()
135+ mock_call.assert_called_with(*self.ssh_key_find)
136+ mock_print.assert_has_calls([mock.call(self.print_msg)])
137+ self.assertTrue(mock_raw_input.called)
138+
139+ def test_failure_with_keygen(self, mock_print):
140+ with self.patch_call(130) as mock_call:
141+ with self.patch_raw_input(return_value='Y') as mock_raw_input:
142+ with mock.patch('quickstart.app.create_ssh_keys') \
143+ as mock_create_ssh_keys:
144+ app.ensure_ssh_keys()
145+ mock_call.assert_called_with(*self.ssh_key_find)
146+ mock_print.assert_has_calls([mock.call(self.print_msg)])
147+ self.assertTrue(mock_raw_input.called)
148+ self.assertTrue(mock_create_ssh_keys.called)
149+
150+
151+@helpers.mock_print
152+class TestCreateSSHKeys(
153+ helpers.CallTestsMixin, ProgramExitTestsMixin, unittest.TestCase):
154+
155+ def test_success(self, mock_print):
156+ key_file = os.path.join(os.path.expanduser('~'), '.ssh', 'id_juju_rsa')
157+ with self.patch_call(0) as mock_call:
158+ app.create_ssh_keys()
159+ mock_call.assert_called_once_with('/usr/bin/ssh-keygen',
160+ '-q', '-b', '4096', '-t', 'rsa',
161+ '-C',
162+ 'Generated with Juju Quickstart',
163+ '-f', key_file)
164+ mock_print.assert_called_with('a new ssh key was generated in {}'
165+ .format(key_file))
166+
167+ def test_failure(self, mock_print):
168+ with self.assert_program_exit('error generating ssh key! Oh no!'):
169+ with self.patch_call(1, error='Oh no!') as mock_call:
170+ app.create_ssh_keys()
171+ self.assertTrue(mock_call.called)
172
173
174 @helpers.mock_print

Subscribers

People subscribed via source and target branches