Merge lp:~dooferlad/offspring/passing-ssh-key-to-slave2 into lp:offspring

Proposed by James Tunnicliffe
Status: Merged
Approved by: Kevin McDermott
Approved revision: 100
Merge reported by: Kevin McDermott
Merged at revision: not available
Proposed branch: lp:~dooferlad/offspring/passing-ssh-key-to-slave2
Merge into: lp:offspring
Diff against target: 632 lines (+423/-17)
12 files modified
lib/offspring/build/bin/offspring-build (+4/-0)
lib/offspring/build/functions/ssh.sh (+23/-0)
lib/offspring/build/tests/offspring_test_key (+27/-0)
lib/offspring/build/tests/offspring_test_key.pub (+1/-0)
lib/offspring/build/tests/test.sh (+27/-0)
lib/offspring/build/wrappers/test.sh (+52/-0)
lib/offspring/master/models.py (+12/-3)
lib/offspring/master/tests/helpers.py (+2/-0)
lib/offspring/slave/slave.py (+67/-12)
lib/offspring/slave/tests/test_slave.py (+197/-2)
lib/offspring/web/queuemanager/models.py (+9/-0)
migration/002-bzr-ssh-user-key.sql (+2/-0)
To merge this branch: bzr merge lp:~dooferlad/offspring/passing-ssh-key-to-slave2
Reviewer Review Type Date Requested Status
Kevin McDermott Approve
Review via email: mp+85165@code.launchpad.net

This proposal supersedes a proposal from 2011-12-08.

Description of the change

Merged on top of trunk, conflicts resolved.

---

Replaces the merge lp:~dooferlad/offspring/passing-ssh-key-to-slave2 now it contains changes from lp:~bigkevmcd/offspring/passing-ssh-key-to-slave and fixes as a result of code review.

To post a comment you must log in.
Revision history for this message
Kevin McDermott (bigkevmcd) wrote : Posted in a previous version of this proposal

Some minor fixes needed and it's good to go...

#1

+ def test_currentBuildIsActive(self):

This is actually four tests hidden in one...can you split them out, your comments are really the docstrings for the other tests...

#2
+ def test_addUserToBzrSshUrl(self):

This is cool, but I'd prefer to see the comparison made more explicit, you know what the result should be, so it might be better to be explicit in the test...

expected_url = "bzr+ssh://<email address hidden>/~dooferlad/offspring/config"

Using the same technique as the implementation is no guarantee that the result is actually correct :-)

#3

+ def test_currentBuildIsActive(self):

Doesn't fit with the test naming conventions (pep8)

#4

There are a couple of minor conflicts with trunk, can merge trunk and resolve?

Other than these minor issues, looks good, nice branch :-)

review: Needs Fixing
Revision history for this message
James Tunnicliffe (dooferlad) wrote : Posted in a previous version of this proposal

On 9 December 2011 07:42, Kevin McDermott <email address hidden> wrote:

> #3
>
> +    def test_currentBuildIsActive(self):
>
> Doesn't fit with the test naming conventions (pep8)

Please clarify. It fits with the naming convention of all the other
tests doesn't it?

Thanks,

--
James Tunnicliffe

Revision history for this message
Kevin McDermott (bigkevmcd) wrote : Posted in a previous version of this proposal

Hi James,

    def test_currentBuildIsActive(self):

compared with...

    def test_slave_run_starts_server_and_logs(self):
    def test_slave_stop(self):
    def test_slave_stop_build_no_current_build(self):
    def test_slave_stop_build_with_current_build(self):
    def test_slave_start_build(self):
    def test_currentBuildIsActive(self):
    def test_addUserToBzrSshUrl(self):
    def test_start_build_with_custom_ssh_key(self):
    def test_check_ssh_key_deleted_when_build_finished(self):
    def test__check_ssh_agent(self):

Also (and I think this was my fault), test__check_ssh_agent, can you rename that to test_check_ssh_agent ?

thanks,

Kevin :-)

Revision history for this message
James Tunnicliffe (dooferlad) wrote : Posted in a previous version of this proposal

I was mostly thinking about test_addUserToBzrSshUrl(self): the other
example :-) Would you suggest test_add_user-_to_bzr_ssh_url then?

I have already fixed up the __ problem.

James

On 9 December 2011 12:03, Kevin McDermott <email address hidden> wrote:
> Hi James,
>
>    def test_currentBuildIsActive(self):
>
> compared with...
>
>    def test_slave_run_starts_server_and_logs(self):
>    def test_slave_stop(self):
>    def test_slave_stop_build_no_current_build(self):
>    def test_slave_stop_build_with_current_build(self):
>    def test_slave_start_build(self):
>    def test_currentBuildIsActive(self):
>    def test_addUserToBzrSshUrl(self):
>    def test_start_build_with_custom_ssh_key(self):
>    def test_check_ssh_key_deleted_when_build_finished(self):
>    def test__check_ssh_agent(self):
>
> Also (and I think this was my fault), test__check_ssh_agent, can you rename that to test_check_ssh_agent ?
>
> thanks,
>
> Kevin :-)
> --
> https://code.launchpad.net/~dooferlad/offspring/passing-ssh-key-to-slave2/+merge/84987
> You are the owner of lp:~dooferlad/offspring/passing-ssh-key-to-slave2.

--
James Tunnicliffe

Revision history for this message
Kevin McDermott (bigkevmcd) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/offspring/build/bin/offspring-build'
2--- lib/offspring/build/bin/offspring-build 2011-11-30 14:15:52 +0000
3+++ lib/offspring/build/bin/offspring-build 2011-12-09 17:18:46 +0000
4@@ -32,6 +32,7 @@
5 source "${IBS_FUNC_DIR}/reports.sh"
6 source "${IBS_FUNC_DIR}/logging.sh"
7 source "${IBS_FUNC_DIR}/files.sh"
8+ source "${IBS_FUNC_DIR}/ssh.sh"
9
10 [ -n "${IBS_PROXY}" ] && export http_proxy="${IBS_PROXY}"
11
12@@ -192,6 +193,9 @@
13 echo "Performing initialization..."
14 _Initialize
15
16+# If any SSH keys have been passed to us, load them into ssh-agent
17+AddKeys ${3}
18+
19 # Setup and start logging
20 _Setup
21
22
23=== added file 'lib/offspring/build/functions/ssh.sh'
24--- lib/offspring/build/functions/ssh.sh 1970-01-01 00:00:00 +0000
25+++ lib/offspring/build/functions/ssh.sh 2011-12-09 17:18:46 +0000
26@@ -0,0 +1,23 @@
27+#!/bin/bash
28+
29+# Copyright 2010 Canonical Ltd. This software is licensed under the
30+# GNU Affero General Public License version 3 (see the file LICENSE).
31+
32+AddKeys() {
33+ RUN_SSH_AGENT=1
34+
35+ if [ -z "${1}" ]; then
36+ let RUN_SSH_AGENT=-1
37+ fi
38+
39+ if [ $RUN_SSH_AGENT = 1 ]; then
40+ eval `ssh-agent`
41+ ssh-add $1
42+ fi
43+}
44+
45+StopSSHAgent() {
46+ if [ $RUN_SSH_AGENT = 1 ]; then
47+ kill $SSH_AGENT_PID
48+ fi
49+}
50\ No newline at end of file
51
52=== added directory 'lib/offspring/build/tests'
53=== added file 'lib/offspring/build/tests/offspring_test_key'
54--- lib/offspring/build/tests/offspring_test_key 1970-01-01 00:00:00 +0000
55+++ lib/offspring/build/tests/offspring_test_key 2011-12-09 17:18:46 +0000
56@@ -0,0 +1,27 @@
57+-----BEGIN RSA PRIVATE KEY-----
58+MIIEowIBAAKCAQEAuzjspIVMkDrI+1MkDCFVh9XBbkc55hCpI/RqMz594jDc86iM
59+4vRmq5R+HJszi/mbbklgsD8x0bGMz65u+yvp9AxScn0/d5GXfhZKHz3fROjT4WEf
60+1NB2gqAink3e9YJZ+04sAJ1Xz81w8wkoTh7roU35GBXSjbH1rMl17qLny8pUsuv6
61+gehemzYAcvM9mAASfAzNvPR87NjrbizYMOwWsQ6stEm4Aemdcio42ZZBHXVm/QV0
62+hOTXLfopOH622iIuEFU5j1blUY/NXyDBU+hNUmMeO9DpL06oDu0XIdwb3nPaEQ3n
63+U+I1VvTR8gz1sQgXYVGa3qGbAm01mcXWQ+Is8QIDAQABAoIBADFRnYT5WGHmGmua
64+SzSm01ElDf9u4+GnIedGy3MUUzTyikHldLeUijdItq/ycnG9HyS+T6od+5Gxo9ZR
65+rQqdVtPjKxTdyYpF4BJm7L+uHNKaQrZsT2ZQQ+fFJ1lsSf+ChxGcVhsTV7517/sV
66+vnhVzNyBHc0qcnzBFGaf62EhqM4V2ogsj3bLd6nwRAyvpbUMrWWv057teKooxBsk
67+j0QXP3WYEzAmdbphkG1qPCGJQNhYBbyjU7Cf4bJg/CbHENynM8zlbiFVUWgfwtNJ
68+ZcddrFs3Y1P6nDa/Jd2ifqx6V7M7LAqpKjqwiuCNPiXVWyxmELaKu8Yvmqsv5qXF
69+ixKcDAECgYEA9Ozj7r0PHkrrCcoGdIQTs1sZS0eV+yDKdOOlUeFpPMHi6PqROOwY
70+cKr4RtkJC/AgjfA3tUEBemVKYoCOVWYeHmlTd3wQpl7lqqGN431A18/Z0psKpFh+
71+wLfMZXBslYybY3b+VguICuyICN/hksCxKJNurKalPow/EYqo0dspOkkCgYEAw7AZ
72+QKCfnuYfGdGto8JFizkxS6uvTInXNTzRZPdkbgd/JhCmnXENg93QSo/GWe2dehQG
73+NZkCpVnZFKkubbxCc/OBAF+es7dVRc8vy8uwa2nOW9SG6yLNlNKdTwJxmRlV+53H
74+mzC0PZ4GyMnBFJ46+rem3siLLVMlkolbZzlnHWkCgYA8+e0VNsRYylYRrdZFk8xD
75+zt5RO5U/XD6LM1GpPPEySyLu1dLp1P2Qrz/4g3gZHMM+ExwLaA+yJR2LwG2vHSlK
76+cPZyvNR4Vw/elzH3/Orzz69vG2Je4BlOaXPdnUurP8I/1RQk3+IStih37ST/oDF6
77+5JmdKi/hjpD1EQxOkr2E4QKBgC9Y/3MsqhJ3WZUUr6/MxKjgCLZnbv3U6DZgZcXJ
78+OgqJU9Fw++9iOEPsuoYf7X06yfyMtcfoIsTBTY37NVml0Gpfw5nEiRCwzjga3lSw
79+DxqeOijr7k0cWaOlphxE2hmSEMTVs0MwcJvsDXYtosMLWffp0b1bxpkL4i5nf68l
80+K3bpAoGBAJR5uzQGl5pXg7QKYPv1Sdi6pBp1wGSpFzzPatlpnrO8zZi9c0O29h7R
81+3lMfBazjjNQmJF25uh+svgPCboNDh9dhqDBKf/ep0ju4wpXF2I0JM5xhYP2s242K
82+mqsBXi4JWa9GAjVqVA48M3Vsu4r1YTOxIqDjnAFpnTU2tzzp2OFZ
83+-----END RSA PRIVATE KEY-----
84
85=== added file 'lib/offspring/build/tests/offspring_test_key.pub'
86--- lib/offspring/build/tests/offspring_test_key.pub 1970-01-01 00:00:00 +0000
87+++ lib/offspring/build/tests/offspring_test_key.pub 2011-12-09 17:18:46 +0000
88@@ -0,0 +1,1 @@
89+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7OOykhUyQOsj7UyQMIVWH1cFuRznmEKkj9GozPn3iMNzzqIzi9GarlH4cmzOL+ZtuSWCwPzHRsYzPrm77K+n0DFJyfT93kZd+FkofPd9E6NPhYR/U0HaCoCKeTd71gln7TiwAnVfPzXDzCShOHuuhTfkYFdKNsfWsyXXuoufLylSy6/qB6F6bNgBy8z2YABJ8DM289Hzs2OtuLNgw7BaxDqy0SbgB6Z1yKjjZlkEddWb9BXSE5Nct+ik4frbaIi4QVTmPVuVRj81fIMFT6E1SYx470OkvTqgO7Rch3Bvec9oRDedT4jVW9NHyDPWxCBdhUZreoZsCbTWZxdZD4izx dooferlad@JT-Linaro-ThinkPad-T410
90
91=== added file 'lib/offspring/build/tests/test.sh'
92--- lib/offspring/build/tests/test.sh 1970-01-01 00:00:00 +0000
93+++ lib/offspring/build/tests/test.sh 2011-12-09 17:18:46 +0000
94@@ -0,0 +1,27 @@
95+#!/bin/bash
96+
97+# Copyright 2010 Canonical Ltd. This software is licensed under the
98+# GNU Affero General Public License version 3 (see the file LICENSE).
99+
100+# Get the full directory path of the test script
101+DIR="$( cd "$( dirname "$0" )" && pwd )"
102+
103+# Load functions under test
104+source "${DIR}/../functions/ssh.sh"
105+
106+# Force key permissions to 600 to keep SSH happy
107+chmod 600 "${DIR}/offspring_test_key"
108+
109+# Load test key
110+AddKeys "${DIR}/offspring_test_key"
111+
112+# See what keys are loaded (expect lib/offspring/build/tests/offspring_test_key)
113+echo After loading test key, keys are:
114+ssh-add -l
115+
116+# Stop the loaded ssh-agent process
117+StopSSHAgent
118+
119+# Check that no keys are listed: Expect "Could not open a connection to your authentication agent."
120+echo "After killing ssh agent, we shouldn't be able to find any keys..."
121+ssh-add -l
122
123=== added file 'lib/offspring/build/wrappers/test.sh'
124--- lib/offspring/build/wrappers/test.sh 1970-01-01 00:00:00 +0000
125+++ lib/offspring/build/wrappers/test.sh 2011-12-09 17:18:46 +0000
126@@ -0,0 +1,52 @@
127+#!/bin/bash
128+
129+# Copyright 2010 Canonical Ltd. This software is licensed under the
130+# GNU Affero General Public License version 3 (see the file LICENSE).
131+
132+Buildtool_setup() {
133+ Catch_faults permit_errors
134+
135+ TARGETFS="${IBS_WORKDIR}/${PROJECT}/chroot"
136+
137+ if [ -d "${IBS_WORKDIR}/${PROJECT}/config" ]; then
138+ rm -rf "${IBS_WORKDIR}/${PROJECT}/config" > /dev/null 2>&1 || true
139+ fi
140+
141+ Action "Copying project configuration directory into work directory for build"
142+ Export_project_config "${PROJECT}" "${IBS_WORKDIR}/${PROJECT}/config"
143+ Action_done
144+
145+ if [ "$?" != "0" ]; then
146+ Error "Unable to copy project build configuration to %s" "${IBS_WORKDIR}/${PROJECT}/config"
147+ Die
148+ fi
149+ Catch_faults disallow_errors
150+}
151+
152+Buildtool_build() {
153+ Action "Test Build - Buildtool_build"
154+ Action_done
155+}
156+
157+Buildtool_cleanup() {
158+ Action "Test Build - Buildtool_cleanup"
159+ Action_done
160+}
161+
162+_Check_directory() {
163+ if [ "${PWD}" != "${IBS_WORKDIR}/${PROJECT}" ]; then
164+ Error "Working directory is '%s'; expected '%s'" "{$PWD}" "${IBS_WORKDIR}/${PROJECT}"
165+ Die
166+ fi
167+ Debug "Directory check performed and was successful; PWD: %s" "${PWD}"
168+}
169+
170+_SaveConfig() {
171+ Action "Test Build - _SaveConfig"
172+ Action_done
173+}
174+
175+_SaveBuildWorkDirectory() {
176+ Action "Test Build - _SaveBuildWorkDirectory"
177+ Action_done
178+}
179
180=== modified file 'lib/offspring/master/models.py'
181--- lib/offspring/master/models.py 2011-12-01 16:02:08 +0000
182+++ lib/offspring/master/models.py 2011-12-09 17:18:46 +0000
183@@ -104,9 +104,12 @@
184 """
185 Pass BuildRequest to a slave server.
186 """
187- result = self.server_proxy.startBuild(request.project.name,
188- request.project.config_url)
189- if result == LexbuilderSlaveServer.REQUEST_OKAY:
190+ status = self.server_proxy.startBuild(request.project.name,
191+ request.project.config_url,
192+ request.project.lp_user,
193+ request.project.lp_ssh_key)
194+
195+ if status == LexbuilderSlaveServer.REQUEST_OKAY:
196 self.previous_state = self.current_state
197 self.current_state = LexbuilderSlave.STATE_BUILDING
198 self.current_job = BuildResult(request, self)
199@@ -174,6 +177,12 @@
200 config_url = Unicode()
201 notes = Unicode()
202
203+ # The Launchpad User and SSH key are stored per project. If we stored them
204+ # per LaunchpadProject, anyone who could create a Project referencing that
205+ # LaunchpadProject could get access to the private data in it.
206+ lp_user = Unicode()
207+ lp_ssh_key = Unicode()
208+
209 def __init__(self, name, priority=40):
210 self.title = unicode(name.capitalize())
211 self.name = unicode(name)
212
213=== modified file 'lib/offspring/master/tests/helpers.py'
214--- lib/offspring/master/tests/helpers.py 2011-11-30 12:31:01 +0000
215+++ lib/offspring/master/tests/helpers.py 2011-12-09 17:18:46 +0000
216@@ -128,6 +128,8 @@
217 "suite VARCHAR(200) NOT NULL, "
218 "status VARCHAR(200), "
219 "notes TEXT, "
220+ "lp_user TEXT, "
221+ "lp_ssh_key text, "
222 "is_active BOOLEAN NOT NULL DEFAULT TRUE)")
223
224 self.connection.execute(
225
226=== modified file 'lib/offspring/slave/slave.py'
227--- lib/offspring/slave/slave.py 2011-12-06 15:10:45 +0000
228+++ lib/offspring/slave/slave.py 2011-12-09 17:18:46 +0000
229@@ -2,6 +2,8 @@
230 # GNU Affero General Public License version 3 (see the file LICENSE).
231 import logging
232 import platform
233+import re
234+import tempfile
235 import time
236 import xmlrpclib
237
238@@ -55,13 +57,36 @@
239 self.server.stop()
240 self.server.join()
241
242- def startBuild(self, projectName, configBranchURL):
243+ def addUserToBzrSshUrl(self, configBranchURL, lpUser):
244+ """Adds lpUser to configBranchURL, returns updated configBranchURL."""
245+
246+ # check for lp: format URLs. Convert these to bzr+ssh ones
247+ configBranchURL = re.sub("lp:", "bzr+ssh://bazaar.launchpad.net/",
248+ configBranchURL)
249+
250+ # The assumption is that this function will only be called with a
251+ # bzr+ssh URL (hence the name!)
252+ assert(re.search("bzr\+ssh://", configBranchURL))
253+
254+ # Add user to URL
255+ configBranchURL = re.sub("bzr\+ssh://",
256+ "bzr+ssh://" + lpUser + "@", configBranchURL)
257+
258+ return configBranchURL
259+
260+ def startBuild(self, projectName, configBranchURL, lpUser=None,
261+ lpSSHKey=None):
262 """
263 Instigate a new build for projectName, using the configuration at
264 configBranchURL.
265 """
266+ if lpUser:
267+ # We don't want to pass the user separately, we want it to be
268+ # integrated into the configBranchURL.
269+ configBranchURL = self.addUserToBzrSshUrl(configBranchURL, lpUser)
270+
271 self.currentBuild = ProjectBuildProcess(
272- self, projectName, configBranchURL)
273+ self, projectName, configBranchURL, lpSSHKey)
274 logging.info("Starting build: %s %s %s" % (
275 self.currentBuild.getCommandPath(), projectName, configBranchURL))
276 self.currentBuild.start()
277@@ -75,9 +100,19 @@
278 if self.currentBuild.process is not None:
279 if self.currentBuild.process.poll() is None:
280 self.currentBuild.process.terminate()
281- # Sleep before sending kill.
282- time.sleep(self.config.getint("slave", "child_wait_timeout"))
283- self.currentBuild.process.kill()
284+ if self.currentBuild.process.poll() is None:
285+ # Sleep before sending kill.
286+ time.sleep(int(self.config.get("slave",
287+ "child_wait_timeout")))
288+ if self.currentBuild.process.poll() is None:
289+ self.currentBuild.process.kill()
290+ self.currentBuild.tidyUp()
291+
292+ def currentBuildIsActive(self):
293+ """Return True if current build is active, else False."""
294+ if self.currentBuild is not None:
295+ return self.currentBuild.state == ProjectBuildProcess.STATE_ACTIVE
296+ return False
297
298
299 class LexbuilderSlaveServer(LexbuilderXMLRPCServer):
300@@ -101,10 +136,8 @@
301 """
302 Start building the project on the LexbuilderSlave.
303 """
304- current_build = self.slave.currentBuild
305- if current_build is not None:
306- if current_build.state == ProjectBuildProcess.STATE_ACTIVE:
307- return LexbuilderSlaveServer.REQUEST_ERROR
308+ if self.slave.currentBuildIsActive():
309+ return LexbuilderSlaveServer.REQUEST_ERROR
310 self.slave.startBuild(projectName, configBranchURL)
311 return LexbuilderSlaveServer.REQUEST_OKAY
312
313@@ -229,11 +262,32 @@
314
315 class ProjectBuildProcess(MonitoredProcess):
316
317- def __init__(self, supervisor, projectName, configBranchURL):
318- MonitoredProcess.__init__(
319- self, supervisor, [projectName, configBranchURL])
320+ def __init__(self, supervisor, projectName, configBranchURL,
321+ lpSSHKey=None):
322+ args = [projectName, configBranchURL]
323+ self.ssh_key_file = None
324+
325+ if lpSSHKey:
326+ # Write SSH key to disk. tempfile will delete it after the build
327+ # when this class goes out of scope.
328+ # Need to have this here so we can pass the temp file name
329+ # containing the key to the builder process on its command line.
330+ self.ssh_key_file = tempfile.NamedTemporaryFile()
331+ args.append(self.ssh_key_file.name)
332+ self.ssh_key_file.write(lpSSHKey)
333+ self.ssh_key_file.flush()
334+
335+ MonitoredProcess.__init__(self, supervisor, args)
336 self.projectName = projectName
337
338+ def __del__(self):
339+ self.tidyUp()
340+
341+ def tidyUp(self):
342+ if self.ssh_key_file:
343+ self.ssh_key_file.close()
344+ self.ssh_key_file = None
345+
346 def setup(self):
347 self.buildName = None
348
349@@ -256,6 +310,7 @@
350 logging.debug(
351 "Setting state of slave to %s", LexbuilderSlave.STATE_IDLE)
352 self.master.state = LexbuilderSlave.STATE_IDLE
353+ self.tidyUp()
354
355 def getCommandPath(self):
356 return self.master.config.get("slave", "build_command")
357
358=== modified file 'lib/offspring/slave/tests/test_slave.py'
359--- lib/offspring/slave/tests/test_slave.py 2011-12-08 17:29:41 +0000
360+++ lib/offspring/slave/tests/test_slave.py 2011-12-09 17:18:46 +0000
361@@ -1,8 +1,12 @@
362 # Copyright 2011 Canonical Ltd. This software is licensed under the
363 # GNU Affero General Public License version 3 (see the file LICENSE).
364+
365+from ConfigParser import ConfigParser
366+from datetime import datetime
367+import os
368 import platform
369-from datetime import datetime
370-from ConfigParser import ConfigParser
371+import re
372+import subprocess
373 import xmlrpclib
374
375 from offspring.tests.helpers import CaptureLoggingTestCase
376@@ -56,6 +60,9 @@
377 def getBuildName(self):
378 return self.name
379
380+ def tidyUp(self):
381+ self.tidied = True
382+
383
384 class LexbuilderSlaveTestMixin(object):
385 """
386@@ -78,6 +85,10 @@
387 self.config.set("slave", "port", 8765)
388
389
390+class FakeProjectBuildProcess(object):
391+ state = ProjectBuildProcess.STATE_ACTIVE
392+
393+
394 class TestLexbuilderSlave(LexbuilderSlaveTestMixin, CaptureLoggingTestCase):
395 """
396 Tests for the LexbuilderSlave class.
397@@ -135,6 +146,7 @@
398
399 self.assertTrue(fake_build.process.terminated)
400 self.assertTrue(fake_build.process.killed)
401+ self.assertTrue(fake_build.tidied)
402 self.assertEqual("Killing active build.\n", log_file.getvalue())
403
404 def test_slave_start_build(self):
405@@ -148,10 +160,187 @@
406
407 fake_url = u"http://example.com/project"
408 self.slave.startBuild(u"testing", fake_url)
409+ self.assertEqual(["testing", fake_url],
410+ self.slave.currentBuild.arguments)
411 self.assertEqual(
412 "Starting build: /usr/bin/offspring-build testing %s\n" % fake_url,
413 log_file.getvalue())
414
415+ def test_current_build_is_active_no_current_build(self):
416+ """Test currentBuildIsActive returns False when currentBuild = False"""
417+
418+ # To start with, currentBuild should be None, so active == False
419+ self.assertEqual(self.slave.currentBuild, None)
420+ self.assertEqual(self.slave.currentBuildIsActive(), False)
421+
422+ def test_current_build_is_active_build_in_active_state(self):
423+ """Test currentBuildIsActive returns True when build is active"""
424+ # Set up a fake build process (starts off in STATE_ACTIVE)
425+ self.slave.currentBuild = FakeProjectBuildProcess()
426+
427+ # Now we have started a build, currentBuild has been assigned.
428+ self.assertNotEqual(self.slave.currentBuild, None)
429+ self.assertEqual(self.slave.currentBuild.state,
430+ ProjectBuildProcess.STATE_ACTIVE)
431+ self.assertEqual(self.slave.currentBuildIsActive(), True)
432+
433+ def test_current_build_is_active_build_state_not_active(self):
434+ """Test currentBuildIsActive when currentBuild state != STATE_ACTIVE
435+
436+ currentBuildIsActive should return False when
437+ slave.currentBuild.state != STATE_ACTIVE. Clearly, currentBuild needs
438+ to be valid for this test.
439+
440+ """
441+
442+ # Set up a fake build process (starts off in STATE_ACTIVE)
443+ self.slave.currentBuild = FakeProjectBuildProcess()
444+
445+ self.slave.currentBuild.state = ProjectBuildProcess.STATE_SUCCESS
446+ self.assertEqual(self.slave.currentBuildIsActive(), False)
447+
448+ self.slave.currentBuild.state = ProjectBuildProcess.STATE_FAILED
449+ self.assertEqual(self.slave.currentBuildIsActive(), False)
450+
451+ def test_add_user_to_bzr_ssh_url(self):
452+ """Test lp:project URL form converted into bzr+ssh form adding user."""
453+
454+ config_url = u"lp:~dooferlad/offspring/config"
455+ user = "user"
456+
457+ updated_url = self.slave.addUserToBzrSshUrl(config_url, user)
458+ expected_url = ("bzr+ssh://user@bazaar.launchpad.net/~dooferlad/" +
459+ "offspring/config")
460+ self.assertEqual(updated_url, expected_url)
461+
462+ def test_start_build_with_custom_ssh_key(self):
463+ """Test slave converts lp:project URL into bzr+ssh form adding user.
464+
465+ When sending a URL to the slave in the form lp:project, and
466+ a user and SSH key are sent as well, that the slave adds the user
467+ into the URL so a private config branch can be checked out (URL needs
468+ to be modified to send the user name through BZR to SSH).
469+ """
470+ log_file = self.capture_logging()
471+ build_process_mock = self.mocker.patch(ProjectBuildProcess)
472+ build_process_mock.start()
473+
474+ sshkey_filename = self.makeFile()
475+ tempfile_mock = self.mocker.replace("tempfile.NamedTemporaryFile")
476+ tempfile_mock()
477+ self.mocker.result(open(sshkey_filename, "w"))
478+
479+ self.mocker.replay()
480+
481+ config_url = u"lp:~dooferlad/offspring/config"
482+ args = ["project_name", config_url, "user", "key"]
483+
484+ self.slave.startBuild(*args)
485+
486+ new_url = re.sub("lp:",
487+ "bzr+ssh://" + args[2] + "@bazaar.launchpad.net/",
488+ args[1])
489+ self.assertEqual(["project_name", new_url, sshkey_filename],
490+ self.slave.currentBuild.arguments)
491+ self.assertEqual(
492+ "Starting build: /usr/bin/offspring-build project_name %s\n"
493+ % new_url, log_file.getvalue())
494+
495+ def test_check_ssh_key_deleted_when_build_finished(self):
496+ """Test SSH key file lifetime management.
497+
498+ The SSH key of a project is written to a temporary file when the
499+ build process starts, this is tested by the first assert. When the
500+ build has finished or is stopped, the key file is deleted. This is
501+ tested by the second assert.
502+ """
503+ log_file = self.capture_logging()
504+ build_process_mock = self.mocker.patch(ProjectBuildProcess)
505+ build_process_mock.start()
506+ self.mocker.replay()
507+
508+ config_url = u"lp:~dooferlad/offspring/config"
509+ args = ["project_name", config_url, "user", "key"]
510+
511+ self.slave.startBuild(*args)
512+
513+ file_path = self.slave.currentBuild.ssh_key_file.name
514+ read_key = open(file_path).read()
515+
516+ # We should have a key written to a file for the slave to read...
517+ self.assertEqual(read_key, args[3])
518+
519+ # Can't use XMLRPC interface here because it checks to see if a build
520+ # has actually started. We just want to test the clean up logic.
521+ self.slave.stopBuild()
522+ # Having stopped the slave, the key should have been erased
523+ self.assertFalse(os.path.exists(file_path))
524+
525+ def test_check_ssh_agent(self):
526+ """Check that SSH agent is started and killed by build.
527+
528+ In order to pass private keys to SSH, the build script starts
529+ ssh-agent if a private key file is passed to it. When the build has
530+ completed, the build script kills SSH agent.
531+
532+ This test uses the called shell script to test the functions used
533+ by the build script to start ssh-agent, load the specified key and
534+ kill the started ssh-agent process.
535+
536+ """
537+ # This is a wrapper around a shell script that runs the commands and
538+ # we just check the output.
539+
540+ dir_of_this_file = os.path.dirname(os.path.abspath(__file__))
541+ test_script = os.path.join(dir_of_this_file,
542+ "../../build/tests/test.sh")
543+
544+ test_script_process = subprocess.Popen(test_script,
545+ stdout=subprocess.PIPE,
546+ stderr=subprocess.STDOUT)
547+
548+ stdout = test_script_process.communicate()[0]
549+
550+ lines = stdout.split("\n")
551+
552+ # Sample output:
553+ # Agent pid 9839
554+ # Identity added: /bla/offspring_test_key (/bla/offspring_test_key)
555+ # After loading test key, keys are:
556+ # 2048 <fingerprint> /bla/offspring_test_key (RSA)
557+ # After killing ssh agent, we shouldn't be able to find any keys...
558+ # Could not open a connection to your authentication agent.
559+
560+ # Expect the first line to be of the form "Agent pid [PID]"
561+ self.assertTrue(re.search("Agent pid \d+", lines[0]),
562+ "Test build ssh-agent check failed: Expected to find" +
563+ "\nAgent pid <PID>\nfound:\n" + lines[0])
564+
565+ # Expect the second line to be of the form:
566+ # Identity added: <path to...>/offspring_test_key
567+ self.assertTrue(re.search("Identity added:.*?offspring_test_key",
568+ lines[1]),
569+ "Test build ssh-agent check failed: Expected to find" +
570+ "\nIdentity added: <path to...>/offspring_test_key" +
571+ "\nfound:\n" + lines[1])
572+
573+ # Ignore the third line, it is just a fixed message
574+ # Expect the fourth line to be:
575+ # <key fingerprint> /path/to/offspring_test_key (RSA)
576+ self.assertTrue(re.search("offspring_test_key \(RSA\)", lines[3]),
577+ "Test build ssh-agent check failed: Expected to find" +
578+ "\noffspring_test_key (RSA)\nfound:\n" + lines[3])
579+
580+ # Ignore the fifth line, it is just a fixed message
581+ # Expect the sixth line to be:
582+ # "Could not open a connection to your authentication agent."
583+ search_string = (
584+ "^Could not open a connection to your authentication agent\.$")
585+ message = ("Test build ssh-agent check failed: Expected to find" +
586+ "\nCould not open a connection to your authentication" +
587+ "agent\n, found:\n" + lines[5])
588+ self.assertTrue(re.search(search_string, lines[5]), message)
589+
590
591 class FakeLexbuilderSlave(object):
592 """Fake slave for use in tests."""
593@@ -169,6 +358,12 @@
594 def stopBuild(self):
595 self.stopped = True
596
597+ def currentBuildIsActive(self):
598+ """Return True if current build is active, else False."""
599+ if self.currentBuild is not None:
600+ return self.currentBuild.state == ProjectBuildProcess.STATE_ACTIVE
601+ return False
602+
603
604 class TestLexbuilderSlaveServer(LexbuilderSlaveTestMixin,
605 CaptureLoggingTestCase):
606
607=== modified file 'lib/offspring/web/queuemanager/models.py'
608--- lib/offspring/web/queuemanager/models.py 2011-11-30 14:15:52 +0000
609+++ lib/offspring/web/queuemanager/models.py 2011-12-09 17:18:46 +0000
610@@ -218,6 +218,15 @@
611 launchpad_project = models.ForeignKey("LaunchpadProject", blank=True, null=True)
612 status = models.CharField('status', max_length=200, null=True, choices=STATUS_CHOICES)
613 config_url = models.CharField('config URL', max_length=200) # Add some kind of validator here.
614+
615+ # The Launchpad User and SSH key are stored per project. If we stored them
616+ # per LaunchpadProject, anyone who could create a Project referencing that
617+ # LaunchpadProject could get access to the private data in it.
618+ lp_user = models.TextField("Launchpad User", null=True, editable=False,
619+ blank=True)
620+ lp_ssh_key = models.TextField("Launchpad User's SSH Key", blank=True,
621+ null=True, editable=False)
622+
623 notes = models.TextField(blank=True, null=True)
624
625 class Meta:
626
627=== added file 'migration/002-bzr-ssh-user-key.sql'
628--- migration/002-bzr-ssh-user-key.sql 1970-01-01 00:00:00 +0000
629+++ migration/002-bzr-ssh-user-key.sql 2011-12-09 17:18:46 +0000
630@@ -0,0 +1,2 @@
631+ALTER TABLE projects ADD COLUMN "lp_user" text;
632+ALTER TABLE projects ADD COLUMN "lp_ssh_key" text;

Subscribers

People subscribed via source and target branches