Merge lp:~benji/launchpad/add-sudoers-to-lxcsetup into lp:launchpad

Proposed by Benji York on 2012-03-01
Status: Merged
Approved by: Gary Poster on 2012-03-01
Approved revision: no longer in the source branch.
Merged at revision: 14890
Proposed branch: lp:~benji/launchpad/add-sudoers-to-lxcsetup
Merge into: lp:launchpad
Diff against target: 199 lines (+64/-27)
1 file modified
utilities/setuplxc.py (+64/-27)
To merge this branch: bzr merge lp:~benji/launchpad/add-sudoers-to-lxcsetup
Reviewer Review Type Date Requested Status
Gary Poster (community) 2012-03-01 Approve on 2012-03-01
Review via email: mp+95373@code.launchpad.net

Commit Message

[r=gary][no-qa] add sudo wrapper scripts for building/testing uxing LXC

Description of the Change

This branch is primarily about adding two wrapper scripts
/usr/local/bin/launchpad-lxc-build and
/usr/local/bin/launchpad-lxc-test) that run the LP build and tests in an
LXC container and a sudoers.d entry to let the buildbot user run those
scripts as root.

Other changes:

- small cleanup to some docstrings (made them raw strings so backslashes
  wouldn't have to be escaped).
- added a couple of packages to the host, one that was missing
  (testrepository) and one that is a new requirement (sshpass)
- fix a bug in the way bzr's whoami was invoked which resulted in
  error-producing double-quoting of the value (we use formataddr now)

The /usr/local/bin test command does not yet work because of a bug in
overlayfs used by LXC. We'll address that in a subsequent branch.

Lint: the make lint report is clean

Tests: we have no way doing automated testing of this at the moment

To post a comment you must log in.
Gary Poster (gary) wrote :

- testrepository and sshpass are only necessary for the parallel testing stuff; this script can do more, and it would be nice if these dependencies were only added in the proper case; but we can maybe wait for lpsetup to do (or consider) that kind of division.

- As we agreed on IRC, the test script you have here is not the newest one we tried. Neither this older one nor the newer one works perfectly, but the newer one has the advantage of not requiring/using sshpass. We agreed that you would switch to that newer version. Then you would remove sshpass as a dependency, of course.

- Have you verified that this script can be run again, replacing the older root/sudo scripts with newer versions? That will be important for the use case of running this repeatedly in the data center, as we fix stuff. It looks like it does the right thing, but confidence would be nice.

- "sleep 30 # aparently RUNNING isn't quite enough": you could steal the ssh reattempt logic that gmb did for lxc-start-ephemeral here. See lines 144ff of the script currently in Precise.

- sudoers_file = '/etc/sudoers.d/lauchpad-buildbot' lauchpad -> launchpad

- Your change to install language-pack-en will conflict with a later branch of Francesco's that removes that entirely (Robert told us that LANG=C when installing postgres is sufficient). Consider removing the change here so it is easier later, but it will be an easy conflict resolution, so do what you think.

review: Approve
Benji York (benji) wrote :

> - testrepository and sshpass are only necessary for the parallel testing
> stuff; this script can do more, and it would be nice if these dependencies
> were only added in the proper case; but we can maybe wait for lpsetup to do
> (or consider) that kind of division.
>
> - As we agreed on IRC, the test script you have here is not the newest one we
> tried. Neither this older one nor the newer one works perfectly, but the
> newer one has the advantage of not requiring/using sshpass. We agreed that
> you would switch to that newer version. Then you would remove sshpass as a
> dependency, of course.

Done.

> - Have you verified that this script can be run again, replacing the older
> root/sudo scripts with newer versions? That will be important for the use
> case of running this repeatedly in the data center, as we fix stuff. It looks
> like it does the right thing, but confidence would be nice.

I have not verified that it can be run again recently, but I did fix at
least one bug keeping it from being run again during the course of this
branch. I've created a card for the task.

> - "sleep 30 # aparently RUNNING isn't quite enough": you could steal the ssh
> reattempt logic that gmb did for lxc-start-ephemeral here. See lines 144ff of
> the script currently in Precise.

To keep from having to do time-intensive test runs, I've left this out
of this branch but I have a copied and tweaked version of the code in
question that may work that we can include in a subsequent branch.

> - sudoers_file = '/etc/sudoers.d/lauchpad-buildbot' lauchpad -> launchpad

Fixed.

> - Your change to install language-pack-en will conflict with a later branch of
> Francesco's that removes that entirely (Robert told us that LANG=C when
> installing postgres is sufficient). Consider removing the change here so it
> is easier later, but it will be an easy conflict resolution, so do what you
> think.

I didn't add any code to install language-pack-en, just edited a line
that also included it. I've removed language-pack-en from the list of
packages to be installed on the host in the hopes that a single-line
conflict will be slightly easier to understand and resolve.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'utilities/setuplxc.py'
2--- utilities/setuplxc.py 2012-02-16 15:03:36 +0000
3+++ utilities/setuplxc.py 2012-03-01 16:39:23 +0000
4@@ -29,7 +29,7 @@
5
6 from collections import namedtuple, OrderedDict
7 from contextlib import contextmanager
8-from email.Utils import parseaddr
9+from email.Utils import parseaddr, formataddr
10 import argparse
11 import os
12 import platform
13@@ -38,11 +38,12 @@
14 import subprocess
15 import sys
16 import time
17+import textwrap
18
19
20 DEPENDENCIES_DIR = '~/dependencies'
21 DHCP_FILE = '/etc/dhcp/dhclient.conf'
22-HOST_PACKAGES = ['ssh', 'lxc', 'libvirt-bin', 'bzr', 'language-pack-en']
23+HOST_PACKAGES = ['ssh', 'lxc', 'libvirt-bin', 'bzr', 'testrepository']
24 HOSTS_FILE = '/etc/hosts'
25 LP_APACHE_MODULES = 'proxy proxy_http rewrite ssl deflate headers'
26 LP_APACHE_ROOTS = (
27@@ -140,23 +141,23 @@
28
29
30 def file_append(filename, line):
31- """Append given `line`, if not present, at the end of `filename`.
32+ r"""Append given `line`, if not present, at the end of `filename`.
33
34 Usage example::
35
36 >>> import tempfile
37 >>> f = tempfile.NamedTemporaryFile('w', delete=False)
38- >>> f.write('line1\\n')
39+ >>> f.write('line1\n')
40 >>> f.close()
41- >>> file_append(f.name, 'new line\\n')
42+ >>> file_append(f.name, 'new line\n')
43 >>> open(f.name).read()
44- 'line1\\nnew line\\n'
45+ 'line1\nnew line\n'
46
47 Nothing happens if the file already contains the given `line`::
48
49- >>> file_append(f.name, 'new line\\n')
50+ >>> file_append(f.name, 'new line\n')
51 >>> open(f.name).read()
52- 'line1\\nnew line\\n'
53+ 'line1\nnew line\n'
54
55 A new line is automatically added before the given `line` if it is not
56 present at the end of current file content::
57@@ -165,9 +166,9 @@
58 >>> f = tempfile.NamedTemporaryFile('w', delete=False)
59 >>> f.write('line1')
60 >>> f.close()
61- >>> file_append(f.name, 'new line\\n')
62+ >>> file_append(f.name, 'new line\n')
63 >>> open(f.name).read()
64- 'line1\\nnew line\\n'
65+ 'line1\nnew line\n'
66 """
67 with open(filename, 'a+') as f:
68 content = f.read()
69@@ -179,30 +180,30 @@
70
71
72 def file_prepend(filename, line):
73- """Insert given `line`, if not present, at the beginning of `filename`.
74+ r"""Insert given `line`, if not present, at the beginning of `filename`.
75
76 Usage example::
77
78 >>> import tempfile
79 >>> f = tempfile.NamedTemporaryFile('w', delete=False)
80- >>> f.write('line1\\n')
81+ >>> f.write('line1\n')
82 >>> f.close()
83- >>> file_prepend(f.name, 'line0\\n')
84+ >>> file_prepend(f.name, 'line0\n')
85 >>> open(f.name).read()
86- 'line0\\nline1\\n'
87+ 'line0\nline1\n'
88
89 If the file starts with the given `line`, nothing happens::
90
91- >>> file_prepend(f.name, 'line0\\n')
92+ >>> file_prepend(f.name, 'line0\n')
93 >>> open(f.name).read()
94- 'line0\\nline1\\n'
95+ 'line0\nline1\n'
96
97 If the file contains the given `line`, but not at the beginning,
98 the line is moved on top::
99
100- >>> file_prepend(f.name, 'line1\\n')
101+ >>> file_prepend(f.name, 'line1\n')
102 >>> open(f.name).read()
103- 'line1\\nline0\\n'
104+ 'line1\nline0\n'
105 """
106 with open(filename, 'r+') as f:
107 lines = f.readlines()
108@@ -297,7 +298,7 @@
109 sshcmd = (
110 'ssh',
111 '-t',
112- '-t', # Yes, this second -t is deliberate. See `man ssh`.
113+ '-t', # Yes, this second -t is deliberate. See `man ssh`.
114 '-o', 'StrictHostKeyChecking=no',
115 '-o', 'UserKnownHostsFile=/dev/null',
116 location,
117@@ -499,12 +500,12 @@
118
119
120 def handle_ssh_keys(namespace):
121- """Handle private and public ssh keys.
122+ r"""Handle private and public ssh keys.
123
124 Keys contained in the namespace are escaped::
125
126- >>> private = r'PRIVATE\\nKEY'
127- >>> public = r'PUBLIC\\nKEY'
128+ >>> private = r'PRIVATE\nKEY'
129+ >>> public = r'PUBLIC\nKEY'
130 >>> namespace = argparse.Namespace(
131 ... private_key=private, public_key=public)
132 >>> handle_ssh_keys(namespace)
133@@ -703,8 +704,7 @@
134 os.chmod(filename, 0644)
135 os.chmod(priv_file, 0600)
136 # Set up bzr and Launchpad authentication.
137- subprocess.call([
138- 'bzr', 'whoami', '"{} <{}>"'.format(fullname, email)])
139+ subprocess.call(['bzr', 'whoami', formataddr((fullname, email))])
140 if valid_ssh_keys:
141 subprocess.call(['bzr', 'lp-login', lpuser])
142 # Set up the repository.
143@@ -720,7 +720,44 @@
144 with su(user) as env:
145 # Set up source dependencies.
146 for subdir in ('eggs', 'yui', 'sourcecode'):
147- os.makedirs(os.path.join(dependencies_dir, subdir))
148+ path = os.path.join(dependencies_dir, subdir)
149+ if not os.path.exists(path):
150+ os.makedirs(path)
151+ # We need a script that will run the LP build inside LXC. It is run as
152+ # root (see below) but drops root once inside the LXC container.
153+ build_script_file = '/usr/local/bin/launchpad-lxc-build'
154+ with open(build_script_file, 'w') as script:
155+ script.write(textwrap.dedent("""\
156+ #!/bin/sh
157+ set -uex
158+ lxc-start -n lptests -d
159+ lxc-wait -n lptests -s RUNNING
160+ sleep 30 # aparently RUNNING isn't quite enough
161+ su buildbot -c "/usr/bin/ssh -o StrictHostKeyChecking=no lptests \\
162+ make -C /var/lib/buildbot/lp schema"
163+ lxc-stop -n lptests
164+ lxc-wait -n lptests -s STOPPED
165+ """))
166+ os.chmod(build_script_file, 0555)
167+ test_script_file = '/usr/local/bin/launchpad-lxc-test'
168+ with open(test_script_file, 'w') as script:
169+ script.write(textwrap.dedent("""
170+ #!/bin/sh
171+ set -uex
172+ lxc-start-ephemeral -o lptests -b $PWD -- xvfb-run \\
173+ --error-file=/var/tmp/xvfb-errors.log \\
174+ --server-args='-screen 0 1024x768x24' \\
175+ -a $PWD/bin/test --subunit $@
176+ """))
177+ os.chmod(test_script_file, 0555)
178+ # Add a file to sudoers.d that will let the buildbot user run the above.
179+ sudoers_file = '/etc/sudoers.d/launchpad-buildbot'
180+ with open(sudoers_file, 'w') as sudoers:
181+ sudoers.write('{} ALL = (ALL) NOPASSWD:'.format(user))
182+ sudoers.write(' /usr/local/bin/launchpad-lxc-build,')
183+ sudoers.write(' /usr/local/bin/launchpad-lxc-test\n')
184+ # The sudoers must have this mode or it will be ignored.
185+ os.chmod(sudoers_file, 0440)
186 with cd(dependencies_dir):
187 with su(user) as env:
188 subprocess.call([
189@@ -826,8 +863,8 @@
190 # Set up Launchpad dependencies.
191 checkout_dir = os.path.join(directory, LP_CHECKOUT)
192 sshcall(
193- 'cd {} && utilities/update-sourcecode --use-http "{}/sourcecode"'.format(
194- checkout_dir, dependencies_dir))
195+ ('cd {} && utilities/update-sourcecode --use-http '
196+ '"{}/sourcecode"').format(checkout_dir, dependencies_dir))
197 sshcall(
198 'cd {} && utilities/link-external-sourcecode "{}"'.format(
199 checkout_dir, dependencies_dir))