Merge lp:~frankban/lpsetup/ssh-keys into lp:lpsetup
- ssh-keys
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Benji York (community) | code | Approve | |
Review via email: mp+98175@code.launchpad.net |
Commit message
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
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 |
Looks good.