1 # This file is part of Buildbot. Buildbot is free software: you can
2 # redistribute it and/or modify it under the terms of the GNU General Public
3 # License as published by the Free Software Foundation, version 2.
5 # This program is distributed in the hope that it will be useful, but WITHOUT
6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10 # You should have received a copy of the GNU General Public License along with
11 # this program; if not, write to the Free Software Foundation, Inc., 51
12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
14 # Copyright Buildbot Team Members
18 from twisted
.internet
import defer
19 from twisted
.python
import log
, runtime
20 from twisted
.protocols
import basic
22 from buildbot
import pbutil
23 from buildbot
.sourcestamp
import SourceStamp
24 from buildbot
.changes
.maildir
import MaildirService
25 from buildbot
.process
.properties
import Properties
26 from buildbot
.schedulers
import base
27 from buildbot
.status
.builder
import BuildSetStatus
30 class TryBase(base
.BaseScheduler
):
33 # triggered by external events, not DB changes or timers
36 def filterBuilderList(self
, builderNames
):
37 # self.builderNames is the configured list of builders
38 # available for try. If the user supplies a list of builders,
39 # it must be restricted to the configured list. If not, build
40 # on all of the configured builders.
42 for b
in builderNames
:
43 if not b
in self
.builderNames
:
44 log
.msg("%s got with builder %s" % (self
, b
))
45 log
.msg(" but that wasn't in our list: %s"
46 % (self
.builderNames
,))
49 builderNames
= self
.builderNames
52 class BadJobfile(Exception):
55 class JobFileScanner(basic
.NetstringReceiver
):
58 self
.transport
= self
# so transport.loseConnection works
61 def stringReceived(self
, s
):
62 self
.strings
.append(s
)
64 def loseConnection(self
):
67 class Try_Jobdir(TryBase
):
68 compare_attrs
= ( 'name', 'builderNames', 'jobdir', 'properties' )
70 def __init__(self
, name
, builderNames
, jobdir
,
72 base
.BaseScheduler
.__init
__(self
, name
, builderNames
, properties
)
74 self
.watcher
= MaildirService()
75 self
.watcher
.setServiceParent(self
)
77 def setServiceParent(self
, parent
):
80 self
.watcher
.setBasedir(os
.path
.join(m
.basedir
, self
.jobdir
))
81 TryBase
.setServiceParent(self
, parent
)
83 def parseJob(self
, f
):
84 # jobfiles are serialized build requests. Each is a list of
85 # serialized netstrings, in the following order:
86 # "1", the version number of this format
87 # buildsetID, arbitrary string, used to find the buildSet later
88 # branch name, "" for default-branch
89 # base revision, "" for HEAD
90 # patchlevel, usually "1"
94 p
.dataReceived(f
.read())
96 raise BadJobfile("unable to parse netstrings")
100 buildsetID
, branch
, baserev
, patchlevel
, diff
= s
[:5]
106 patchlevel
= int(patchlevel
)
107 patch
= (patchlevel
, diff
)
108 ss
= SourceStamp("Old client", branch
, baserev
, patch
)
109 elif ver
== "2": # introduced the repository and project property
110 buildsetID
, branch
, baserev
, patchlevel
, diff
, repository
, project
= s
[:7]
116 patchlevel
= int(patchlevel
)
117 patch
= (patchlevel
, diff
)
118 ss
= SourceStamp(branch
, baserev
, patch
, repository
=repository
,
121 raise BadJobfile("unknown version '%s'" % ver
)
122 return builderNames
, ss
, buildsetID
124 def messageReceived(self
, filename
):
125 md
= os
.path
.join(self
.parent
.parent
.basedir
, self
.jobdir
)
126 if runtime
.platformType
== "posix":
127 # open the file before moving it, because I'm afraid that once
128 # it's in cur/, someone might delete it at any moment
129 path
= os
.path
.join(md
, "new", filename
)
131 os
.rename(os
.path
.join(md
, "new", filename
),
132 os
.path
.join(md
, "cur", filename
))
134 # do this backwards under windows, because you can't move a file
135 # that somebody is holding open. This was causing a Permission
136 # Denied error on bear's win32-twisted1.3 buildslave.
137 os
.rename(os
.path
.join(md
, "new", filename
),
138 os
.path
.join(md
, "cur", filename
))
139 path
= os
.path
.join(md
, "cur", filename
)
143 builderNames
, ss
, jobid
= self
.parseJob(f
)
145 log
.msg("%s reports a bad jobfile in %s" % (self
, filename
))
148 # Validate/fixup the builder names.
149 builderNames
= self
.filterBuilderList(builderNames
)
153 d
= self
.parent
.db
.runInteraction(self
._try
, ss
, builderNames
, reason
)
155 self
.parent
.loop_done() # so it will notify builder loop
159 def _try(self
, t
, ss
, builderNames
, reason
):
161 ssid
= db
.get_sourcestampid(ss
, t
)
162 bsid
= self
.create_buildset(ssid
, reason
, t
, builderNames
=builderNames
)
166 class Try_Userpass(TryBase
):
167 compare_attrs
= ( 'name', 'builderNames', 'port', 'userpass', 'properties' )
169 def __init__(self
, name
, builderNames
, port
, userpass
,
171 base
.BaseScheduler
.__init
__(self
, name
, builderNames
, properties
)
173 self
.userpass
= userpass
174 self
.properties
= properties
176 def startService(self
):
177 TryBase
.startService(self
)
178 master
= self
.parent
.parent
180 # register each user/passwd with the pbmanager
181 def factory(mind
, username
):
182 return Try_Userpass_Perspective(self
, username
)
183 self
.registrations
= []
184 for user
, passwd
in self
.userpass
:
185 self
.registrations
.append(
186 master
.pbmanager
.register(self
.port
, user
, passwd
, factory
))
188 def stopService(self
):
189 d
= defer
.maybeDeferred(TryBase
.stopService
, self
)
191 return defer
.gatherResults(
192 [ reg
.unregister() for reg
in self
.registrations
])
196 class Try_Userpass_Perspective(pbutil
.NewCredPerspective
):
197 def __init__(self
, parent
, username
):
199 self
.username
= username
201 def perspective_try(self
, branch
, revision
, patch
, repository
, project
,
202 builderNames
, properties
={}, ):
203 log
.msg("user %s requesting build on builders %s" % (self
.username
,
205 # build the intersection of the request and our configured list
206 builderNames
= self
.parent
.filterBuilderList(builderNames
)
209 ss
= SourceStamp(branch
, revision
, patch
, repository
=repository
,
211 reason
= "'try' job from user %s" % self
.username
213 # roll the specified props in with our inherited props
214 combined_props
= Properties()
215 combined_props
.updateFromProperties(self
.parent
.properties
)
216 combined_props
.update(properties
, "try build")
218 status
= self
.parent
.parent
.parent
.status
219 db
= self
.parent
.parent
.db
220 d
= db
.runInteraction(self
._try
, ss
, builderNames
, reason
,
223 # return a remotely-usable BuildSetStatus object
224 bss
= BuildSetStatus(bsid
, status
, db
)
225 from buildbot
.status
.client
import makeRemote
227 #self.parent.parent.loop_done() # so it will notify builder loop
232 def _try(self
, t
, ss
, builderNames
, reason
, combined_props
, db
):
233 ssid
= db
.get_sourcestampid(ss
, t
)
234 bsid
= self
.parent
.create_buildset(ssid
, reason
, t
,
235 props
=combined_props
,
236 builderNames
=builderNames
)
239 def perspective_getAvailableBuilderNames(self
):
240 # Return a list of builder names that are configured
241 # for the try service
242 # This is mostly intended for integrating try services
243 # into other applications
244 return self
.parent
.listBuilderNames()