Merge lp:~frankban/lpsetup/ssh-keys into lp:lpsetup

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 6
Proposed branch: lp:~frankban/lpsetup/ssh-keys
Merge into: lp:lpsetup
Diff against target: 341 lines (+79/-49)
6 files modified
lpsetup/handlers.py (+17/-8)
lpsetup/settings.py (+1/-0)
lpsetup/subcommands/install.py (+21/-18)
lpsetup/subcommands/lxcinstall.py (+22/-18)
lpsetup/subcommands/update.py (+1/-2)
lpsetup/tests/test_handlers.py (+17/-3)
To merge this branch: bzr merge lp:~frankban/lpsetup/ssh-keys
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Review via email: mp+98175@code.launchpad.net

Description of the change

== Changes ==

- Updated the install and lxc-install subcommans to support a custom ssh key name.
- The root ssh key is no longer needed, so it is not created.

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

Looks good.

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

Thanks Benji.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lpsetup/handlers.py'
2--- lpsetup/handlers.py 2012-03-16 10:27:22 +0000
3+++ lpsetup/handlers.py 2012-03-19 10:05:30 +0000
4@@ -139,7 +139,8 @@
5 >>> private = r'PRIVATE\nKEY'
6 >>> public = r'PUBLIC\nKEY'
7 >>> namespace = argparse.Namespace(
8- ... private_key=private, public_key=public)
9+ ... private_key=private, public_key=public,
10+ ... ssh_key_name='id_rsa', home_dir='/tmp/')
11 >>> handle_ssh_keys(namespace)
12 >>> namespace.private_key == private.decode('string-escape')
13 True
14@@ -148,11 +149,18 @@
15 >>> namespace.valid_ssh_keys
16 True
17
18+ After this handler is called, the ssh key path is present as an attribute
19+ of the namespace::
20+
21+ >>> namespace.ssh_key_path
22+ '/tmp/.ssh/id_rsa'
23+
24 Keys are None if they are not provided and can not be found in the
25 current home directory::
26
27 >>> namespace = argparse.Namespace(
28- ... private_key=None, home_dir='/tmp/__does_not_exist__')
29+ ... private_key=None, public_key=None, ssh_key_name='id_rsa',
30+ ... home_dir='/tmp/__does_not_exists__')
31 >>> handle_ssh_keys(namespace) # doctest: +ELLIPSIS
32 >>> print namespace.private_key
33 None
34@@ -165,21 +173,22 @@
35 ValidationError will be raised.
36
37 >>> namespace = argparse.Namespace(
38- ... private_key=private, public_key=None,
39- ... home_dir='/tmp/__does_not_exist__')
40+ ... private_key=private, public_key=None, ssh_key_name='id_rsa',
41+ ... home_dir='/tmp/__does_not_exists__')
42 >>> handle_ssh_keys(namespace) # doctest: +ELLIPSIS
43 Traceback (most recent call last):
44 ValidationError: arguments private-key...
45 """
46 namespace.valid_ssh_keys = True
47- for attr, filename in (
48- ('private_key', 'id_rsa'),
49- ('public_key', 'id_rsa.pub')):
50+ namespace.ssh_key_path = os.path.join(
51+ namespace.home_dir, '.ssh', namespace.ssh_key_name)
52+ for attr, path in (
53+ ('private_key', namespace.ssh_key_path),
54+ ('public_key', namespace.ssh_key_path + '.pub')):
55 value = getattr(namespace, attr, None)
56 if value:
57 setattr(namespace, attr, value.decode('string-escape'))
58 else:
59- path = os.path.join(namespace.home_dir, '.ssh', filename)
60 try:
61 value = open(path).read()
62 except IOError:
63
64=== modified file 'lpsetup/settings.py'
65--- lpsetup/settings.py 2012-03-16 14:49:20 +0000
66+++ lpsetup/settings.py 2012-03-19 10:05:30 +0000
67@@ -69,3 +69,4 @@
68 LXC_PACKAGES = ['lxc', 'libvirt-bin']
69 LXC_PATH = '/var/lib/lxc/'
70 RESOLV_FILE = '/etc/resolv.conf'
71+SSH_KEY_NAME = 'id_rsa'
72
73=== modified file 'lpsetup/subcommands/install.py'
74--- lpsetup/subcommands/install.py 2012-03-16 14:49:20 +0000
75+++ lpsetup/subcommands/install.py 2012-03-19 10:05:30 +0000
76@@ -50,6 +50,7 @@
77 LP_PACKAGES,
78 LP_REPOS,
79 LP_SOURCE_DEPS,
80+ SSH_KEY_NAME,
81 )
82 from lpsetup.utils import call
83
84@@ -111,10 +112,9 @@
85 call('make', '-C', checkout_dir, 'install')
86
87
88-
89 def initialize(
90 user, full_name, email, lpuser, private_key, public_key, valid_ssh_keys,
91- dependencies_dir, directory):
92+ ssh_key_path, dependencies_dir, directory):
93 """Initialize host machine."""
94 # Install necessary deb packages. This requires Oneiric or later.
95 call('apt-get', 'update')
96@@ -122,35 +122,31 @@
97 # Create the user (if he does not exist).
98 if not user_exists(user):
99 call('useradd', '-m', '-s', '/bin/bash', '-U', user)
100- # Generate root ssh keys if they do not exist.
101- if not os.path.exists('/root/.ssh/id_rsa.pub'):
102- generate_ssh_keys('/root/.ssh/')
103 with su(user) as env:
104 # Set up the user's ssh directory. The ssh key must be associated
105 # with the lpuser's Launchpad account.
106 ssh_dir = os.path.join(env.home, '.ssh')
107 mkdirs(ssh_dir)
108 # Generate user ssh keys if none are supplied.
109+ pub_key_path = ssh_key_path + '.pub'
110 if not valid_ssh_keys:
111- generate_ssh_keys(ssh_dir)
112- private_key = open(os.path.join(ssh_dir, 'id_rsa')).read()
113- public_key = open(os.path.join(ssh_dir, 'id_rsa.pub')).read()
114- priv_file = os.path.join(ssh_dir, 'id_rsa')
115- pub_file = os.path.join(ssh_dir, 'id_rsa.pub')
116+ generate_ssh_keys(ssh_key_path)
117+ private_key = open(ssh_key_path).read()
118+ public_key = open(pub_key_path).read()
119 auth_file = os.path.join(ssh_dir, 'authorized_keys')
120 known_hosts = os.path.join(ssh_dir, 'known_hosts')
121- known_host_content = run(
122- 'ssh-keyscan', '-t', 'rsa', 'bazaar.launchpad.net')
123+ known_host_content = subprocess.check_output([
124+ 'ssh-keyscan', '-t', 'rsa', 'bazaar.launchpad.net'])
125 for filename, contents, mode in [
126- (priv_file, private_key, 'w'),
127- (pub_file, public_key, 'w'),
128+ (ssh_key_path, private_key, 'w'),
129+ (pub_key_path, public_key, 'w'),
130 (auth_file, public_key, 'a'),
131 (known_hosts, known_host_content, 'a'),
132 ]:
133 with open(filename, mode) as f:
134 f.write(contents + '\n')
135 os.chmod(filename, 0644)
136- os.chmod(priv_file, 0600)
137+ os.chmod(ssh_key_path, 0600)
138 # Set up bzr and Launchpad authentication.
139 call('bzr', 'whoami', formataddr([full_name, email]))
140 if valid_ssh_keys:
141@@ -220,14 +216,17 @@
142
143 actions = (
144 (initialize,
145- 'user', 'full_name', 'email', 'lpuser', 'private_key',
146- 'public_key', 'valid_ssh_keys', 'dependencies_dir', 'directory'),
147- (setup_apt, 'no_repositories'),
148+ 'user', 'full_name', 'email', 'lpuser',
149+ 'private_key', 'public_key', 'valid_ssh_keys', 'ssh_key_path',
150+ 'dependencies_dir', 'directory'),
151+ (setup_apt,
152+ 'no_repositories'),
153 (setup_launchpad,
154 'user', 'dependencies_dir', 'directory', 'valid_ssh_keys'),
155 )
156 help = __doc__
157 needs_root = True
158+ ssh_key_name_help = 'The ssh key name used to connect to Launchpad.'
159 validators = (
160 handlers.handle_user,
161 handlers.handle_lpuser,
162@@ -284,5 +283,9 @@
163 'given user (see -u argument). '
164 '[DEFAULT={0}]'.format(CHECKOUT_DIR))
165 parser.add_argument(
166+ '-s', '--ssh-key-name', default=SSH_KEY_NAME,
167+ help='{0} [DEFAULT={1}]'.format(
168+ self.ssh_key_name_help, SSH_KEY_NAME))
169+ parser.add_argument(
170 '-N', '--no-repositories', action='store_true',
171 help='Do not add APT repositories.')
172
173=== modified file 'lpsetup/subcommands/lxcinstall.py'
174--- lpsetup/subcommands/lxcinstall.py 2012-03-09 10:14:32 +0000
175+++ lpsetup/subcommands/lxcinstall.py 2012-03-19 10:05:30 +0000
176@@ -91,8 +91,6 @@
177 # Set up root ssh key.
178 user_authorized_keys = os.path.expanduser(
179 '~' + user + '/.ssh/authorized_keys')
180- with open(user_authorized_keys, 'a') as f:
181- f.write(open('/root/.ssh/id_rsa.pub').read())
182 dst = get_container_path(lxc_name, '/root/.ssh/')
183 mkdirs(dst)
184 shutil.copy(user_authorized_keys, dst)
185@@ -103,9 +101,9 @@
186 call('lxc-start', '-n', lxc_name, '-d')
187
188
189-def wait_for_lxc(lxc_name, trials=60, sleep_seconds=1):
190+def wait_for_lxc(lxc_name, ssh_key_path, trials=60, sleep_seconds=1):
191 """Try to ssh as `user` into the LXC container named `lxc_name`."""
192- sshcall = ssh(lxc_name)
193+ sshcall = ssh(lxc_name, key=ssh_key_path)
194 while True:
195 trials -= 1
196 try:
197@@ -118,19 +116,20 @@
198 break
199
200
201-def initialize_lxc(lxc_name, lxc_os):
202+def initialize_lxc(lxc_name, lxc_os, ssh_key_path):
203 """Initialize LXC container."""
204 base_packages = list(BASE_PACKAGES)
205 if lxc_os == 'lucid':
206 # Install argparse to be able to run this script inside a lucid lxc.
207 base_packages.append('python-argparse')
208- ssh(lxc_name)(
209+ sshcall = ssh(lxc_name, key=ssh_key_path)
210+ sshcall(
211 'DEBIAN_FRONTEND=noninteractive '
212 'apt-get install -y ' + ' '.join(base_packages))
213
214
215 def setup_launchpad_lxc(
216- user, dependencies_dir, directory, valid_ssh_keys, lxc_name):
217+ user, dependencies_dir, directory, valid_ssh_keys, ssh_key_path, lxc_name):
218 """Set up the Launchpad environment inside an LXC."""
219 # Use ssh to call this script from inside the container.
220 args = [
221@@ -138,12 +137,12 @@
222 '-d', dependencies_dir, '-c', directory
223 ]
224 cmd = this_command(directory, args)
225- ssh(lxc_name)(cmd)
226-
227-
228-def stop_lxc(lxc_name):
229+ ssh(lxc_name, key=ssh_key_path)(cmd)
230+
231+
232+def stop_lxc(lxc_name, ssh_key_path):
233 """Stop the lxc instance named `lxc_name`."""
234- ssh(lxc_name)('poweroff')
235+ ssh(lxc_name, key=ssh_key_path)('poweroff')
236 if not lxc_stopped(lxc_name):
237 subprocess.call(['lxc-stop', '-n', lxc_name])
238
239@@ -157,16 +156,21 @@
240 'public_key', 'valid_ssh_keys', 'dependencies_dir', 'directory'),
241 (create_lxc,
242 'user', 'lxc_name', 'lxc_arch', 'lxc_os'),
243- (start_lxc, 'lxc_name'),
244- (wait_for_lxc, 'lxc_name'),
245+ (start_lxc,
246+ 'lxc_name'),
247+ (wait_for_lxc,
248+ 'lxc_name', 'ssh_key_path'),
249 (initialize_lxc,
250- 'lxc_name', 'lxc_os'),
251+ 'lxc_name', 'lxc_os', 'ssh_key_path'),
252 (setup_launchpad_lxc,
253- 'user', 'dependencies_dir', 'directory', 'valid_ssh_keys',
254- 'lxc_name'),
255- (stop_lxc, 'lxc_name'),
256+ 'user', 'dependencies_dir', 'directory',
257+ 'valid_ssh_keys', 'ssh_key_path', 'lxc_name'),
258+ (stop_lxc,
259+ 'lxc_name', 'ssh_key_path'),
260 )
261 help = __doc__
262+ ssh_key_name_help = ('The ssh key name used to connect to Launchpad '
263+ 'and to to the LXC container.')
264
265 def add_arguments(self, parser):
266 super(SubCommand, self).add_arguments(parser)
267
268=== modified file 'lpsetup/subcommands/update.py'
269--- lpsetup/subcommands/update.py 2012-03-09 10:14:32 +0000
270+++ lpsetup/subcommands/update.py 2012-03-19 10:05:30 +0000
271@@ -56,7 +56,6 @@
272 call(cmd, '--parent', dependencies_dir, '--target', branch)
273
274
275-
276 class SubCommand(argparser.ActionsBasedSubCommand):
277 """Update the Launchpad environment to latest version."""
278
279@@ -64,7 +63,7 @@
280 (update_launchpad,
281 'user', 'valid_ssh_keys', 'dependencies_dir', 'directory', 'apt'),
282 (link_sourcecode_in_branches,
283- 'user', 'dependencies_dir', 'directory'),
284+ 'user', 'dependencies_dir', 'directory'),
285 )
286 help = __doc__
287 validators = (
288
289=== modified file 'lpsetup/tests/test_handlers.py'
290--- lpsetup/tests/test_handlers.py 2012-03-16 14:06:12 +0000
291+++ lpsetup/tests/test_handlers.py 2012-03-19 10:05:30 +0000
292@@ -101,13 +101,16 @@
293
294 class HandleSSHKeysTest(HandlersTestMixin, unittest.TestCase):
295
296+ home_dir = '/tmp/__does_not_exist__'
297 private = r'PRIVATE\nKEY'
298 public = r'PUBLIC\nKEY'
299+ ssh_key_name = 'id_rsa'
300
301 def test_key_escaping(self):
302 # Ensure the keys contained in the namespace are correctly escaped.
303 namespace = argparse.Namespace(
304- private_key=self.private, public_key=self.public)
305+ private_key=self.private, public_key=self.public,
306+ ssh_key_name=self.ssh_key_name, home_dir=self.home_dir)
307 handle_ssh_keys(namespace)
308 self.assertEqual(
309 self.private.decode('string-escape'),
310@@ -117,11 +120,22 @@
311 namespace.public_key)
312 self.assertTrue(namespace.valid_ssh_keys)
313
314+ def test_ssh_key_path_in_namespace(self):
315+ # After the handler is called, the ssh key path is present
316+ # as an attribute of the namespace.
317+ namespace = argparse.Namespace(
318+ private_key=self.private, public_key=self.public,
319+ ssh_key_name=self.ssh_key_name, home_dir=self.home_dir)
320+ handle_ssh_keys(namespace)
321+ expected = self.home_dir + '/.ssh/id_rsa'
322+ self.assertEqual(expected, namespace.ssh_key_path)
323+
324 def test_no_keys(self):
325 # Keys are None if they are not provided and can not be found in the
326 # current home directory.
327 namespace = argparse.Namespace(
328- private_key=None, home_dir='/tmp/__does_not_exist__')
329+ private_key=None, ssh_key_name=self.ssh_key_name,
330+ home_dir=self.home_dir)
331 handle_ssh_keys(namespace)
332 self.assertIsNone(namespace.private_key)
333 self.assertIsNone(namespace.public_key)
334@@ -131,7 +145,7 @@
335 # Ensure a `ValidationError` is raised if only one key is provided.
336 namespace = argparse.Namespace(
337 private_key=self.private, public_key=None,
338- home_dir='/tmp/__does_not_exist__')
339+ ssh_key_name=self.ssh_key_name, home_dir=self.home_dir)
340 with self.assertNotValid('private-key'):
341 handle_ssh_keys(namespace)
342

Subscribers

People subscribed via source and target branches

to all changes: