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. 4# 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 8# details. 9# 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. 13# 14# Copyright Buildbot Team Members 15 16from twisted.internet import defer 17from twisted.python import log 18 19from buildbot import config 20from buildbot.changes import base 21from buildbot.pbutil import NewCredPerspective 22 23 24class ChangePerspective(NewCredPerspective): 25 26 def __init__(self, master, prefix): 27 self.master = master 28 self.prefix = prefix 29 30 def attached(self, mind): 31 return self 32 33 def detached(self, mind): 34 pass 35 36 def perspective_addChange(self, changedict): 37 log.msg("perspective_addChange called") 38 39 if 'revlink' in changedict and not changedict['revlink']: 40 changedict['revlink'] = '' 41 if 'repository' in changedict and not changedict['repository']: 42 changedict['repository'] = '' 43 if 'project' in changedict and not changedict['project']: 44 changedict['project'] = '' 45 if 'files' not in changedict or not changedict['files']: 46 changedict['files'] = [] 47 if 'committer' in changedict and not changedict['committer']: 48 changedict['committer'] = None 49 50 # rename arguments to new names. Note that the client still uses the 51 # "old" names (who, when, and isdir), as they are not deprecated yet, 52 # although the master will accept the new names (author, 53 # when_timestamp). After a few revisions have passed, we 54 # can switch the client to use the new names. 55 if 'who' in changedict: 56 changedict['author'] = changedict['who'] 57 del changedict['who'] 58 if 'when' in changedict: 59 changedict['when_timestamp'] = changedict['when'] 60 del changedict['when'] 61 62 # turn any bytestring keys into unicode, assuming utf8 but just 63 # replacing unknown characters. Ideally client would send us unicode 64 # in the first place, but older clients do not, so this fallback is 65 # useful. 66 for key in changedict: 67 if isinstance(changedict[key], bytes): 68 changedict[key] = changedict[key].decode('utf8', 'replace') 69 changedict['files'] = list(changedict['files']) 70 for i, file in enumerate(changedict.get('files', [])): 71 if isinstance(file, bytes): 72 changedict['files'][i] = file.decode('utf8', 'replace') 73 74 files = [] 75 for path in changedict['files']: 76 if self.prefix: 77 if not path.startswith(self.prefix): 78 # this file does not start with the prefix, so ignore it 79 continue 80 path = path[len(self.prefix):] 81 files.append(path) 82 changedict['files'] = files 83 84 if not files: 85 log.msg("No files listed in change... bit strange, but not fatal.") 86 87 if "links" in changedict: 88 log.msg("Found links: " + repr(changedict['links'])) 89 del changedict['links'] 90 91 d = self.master.data.updates.addChange(**changedict) 92 93 # set the return value to None, so we don't get users depending on 94 # getting a changeid 95 d.addCallback(lambda _: None) 96 return d 97 98 99class PBChangeSource(base.ChangeSource): 100 compare_attrs = ("user", "passwd", "port", "prefix", "port") 101 102 def __init__(self, user="change", passwd="changepw", port=None, 103 prefix=None, name=None): 104 105 if name is None: 106 if prefix: 107 name = "PBChangeSource:{}:{}".format(prefix, port) 108 else: 109 name = "PBChangeSource:{}".format(port) 110 111 super().__init__(name=name) 112 113 self.user = user 114 self.passwd = passwd 115 self.port = port 116 self.prefix = prefix 117 self.registration = None 118 self.registered_port = None 119 120 def describe(self): 121 portname = self.registered_port 122 d = "PBChangeSource listener on " + str(portname) 123 if self.prefix is not None: 124 d += " (prefix '{}')".format(self.prefix) 125 return d 126 127 def _calculatePort(self, cfg): 128 # calculate the new port, defaulting to the worker's PB port if 129 # none was specified 130 port = self.port 131 if port is None: 132 port = cfg.protocols.get('pb', {}).get('port') 133 return port 134 135 @defer.inlineCallbacks 136 def reconfigServiceWithBuildbotConfig(self, new_config): 137 port = self._calculatePort(new_config) 138 if not port: 139 config.error("No port specified for PBChangeSource, and no " 140 "worker port configured") 141 142 # and, if it's changed, re-register 143 if port != self.registered_port and self.isActive(): 144 yield self._unregister() 145 yield self._register(port) 146 147 yield super().reconfigServiceWithBuildbotConfig(new_config) 148 149 @defer.inlineCallbacks 150 def activate(self): 151 port = self._calculatePort(self.master.config) 152 yield self._register(port) 153 154 def deactivate(self): 155 return self._unregister() 156 157 @defer.inlineCallbacks 158 def _register(self, port): 159 if not port: 160 return 161 self.registered_port = port 162 self.registration = yield self.master.pbmanager.register(port, self.user, self.passwd, 163 self.getPerspective) 164 165 def _unregister(self): 166 self.registered_port = None 167 if self.registration: 168 reg = self.registration 169 self.registration = None 170 return reg.unregister() 171 return defer.succeed(None) 172 173 def getPerspective(self, mind, username): 174 assert username == self.user 175 return ChangePerspective(self.master, self.prefix) 176