@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at upstream/main 313 lines 8.1 kB view raw
1<?php 2 3abstract class DiffusionCommandEngine extends Phobject { 4 5 private $repository; 6 private $protocol; 7 private $credentialPHID; 8 private $argv; 9 private $passthru; 10 private $connectAsDevice; 11 private $sudoAsDaemon; 12 private $uri; 13 14 public static function newCommandEngine(PhabricatorRepository $repository) { 15 $engines = self::newCommandEngines(); 16 17 foreach ($engines as $engine) { 18 if ($engine->canBuildForRepository($repository)) { 19 return id(clone $engine) 20 ->setRepository($repository); 21 } 22 } 23 24 throw new Exception( 25 pht( 26 'No registered command engine can build commands for this '. 27 'repository ("%s").', 28 $repository->getDisplayName())); 29 } 30 31 private static function newCommandEngines() { 32 return id(new PhutilClassMapQuery()) 33 ->setAncestorClass(self::class) 34 ->execute(); 35 } 36 37 abstract protected function canBuildForRepository( 38 PhabricatorRepository $repository); 39 40 abstract protected function newFormattedCommand($pattern, array $argv); 41 abstract protected function newCustomEnvironment(); 42 43 public function setRepository(PhabricatorRepository $repository) { 44 $this->repository = $repository; 45 return $this; 46 } 47 48 public function getRepository() { 49 return $this->repository; 50 } 51 52 public function setURI(PhutilURI $uri) { 53 $this->uri = $uri; 54 $this->setProtocol($uri->getProtocol()); 55 return $this; 56 } 57 58 public function getURI() { 59 return $this->uri; 60 } 61 62 public function setProtocol($protocol) { 63 $this->protocol = $protocol; 64 return $this; 65 } 66 67 public function getProtocol() { 68 return $this->protocol; 69 } 70 71 public function getDisplayProtocol() { 72 return $this->getProtocol().'://'; 73 } 74 75 public function setCredentialPHID($credential_phid) { 76 $this->credentialPHID = $credential_phid; 77 return $this; 78 } 79 80 public function getCredentialPHID() { 81 return $this->credentialPHID; 82 } 83 84 public function setArgv(array $argv) { 85 $this->argv = $argv; 86 return $this; 87 } 88 89 public function getArgv() { 90 return $this->argv; 91 } 92 93 public function setPassthru($passthru) { 94 $this->passthru = $passthru; 95 return $this; 96 } 97 98 public function getPassthru() { 99 return $this->passthru; 100 } 101 102 public function setConnectAsDevice($connect_as_device) { 103 $this->connectAsDevice = $connect_as_device; 104 return $this; 105 } 106 107 public function getConnectAsDevice() { 108 return $this->connectAsDevice; 109 } 110 111 public function setSudoAsDaemon($sudo_as_daemon) { 112 $this->sudoAsDaemon = $sudo_as_daemon; 113 return $this; 114 } 115 116 public function getSudoAsDaemon() { 117 return $this->sudoAsDaemon; 118 } 119 120 protected function shouldAlwaysSudo() { 121 return false; 122 } 123 124 public function newFuture() { 125 $argv = $this->newCommandArgv(); 126 $env = $this->newCommandEnvironment(); 127 $is_passthru = $this->getPassthru(); 128 129 if ($this->getSudoAsDaemon() || $this->shouldAlwaysSudo()) { 130 $command = call_user_func_array('csprintf', $argv); 131 $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); 132 $argv = array('%C', $command); 133 } 134 135 if ($is_passthru) { 136 $future = newv('PhutilExecPassthru', $argv); 137 } else { 138 $future = newv('ExecFuture', $argv); 139 } 140 141 $future->setEnv($env); 142 143 // See T13108. By default, don't let any cluster command run indefinitely 144 // to try to avoid cases where `git fetch` hangs for some reason and we're 145 // left sitting with a held lock forever. 146 $repository = $this->getRepository(); 147 if (!$is_passthru) { 148 $future->setTimeout($repository->getEffectiveCopyTimeLimit()); 149 } 150 151 return $future; 152 } 153 154 private function newCommandArgv() { 155 $argv = $this->argv; 156 $pattern = $argv[0]; 157 $argv = array_slice($argv, 1); 158 159 list($pattern, $argv) = $this->newFormattedCommand($pattern, $argv); 160 161 return array_merge(array($pattern), $argv); 162 } 163 164 private function newCommandEnvironment() { 165 $env = $this->newCommonEnvironment() + $this->newCustomEnvironment(); 166 foreach ($env as $key => $value) { 167 if ($value === null) { 168 unset($env[$key]); 169 } 170 } 171 return $env; 172 } 173 174 private function newCommonEnvironment() { 175 $repository = $this->getRepository(); 176 177 $env = array(); 178 // NOTE: Force the language to "C", which overrides locale 179 // settings. This makes stuff print in English instead of, e.g., French, 180 // so we can parse the output of some commands, error messages, etc. 181 // Note that LANG can be ignored if there is LANGUAGE. 182 // https://we.phorge.it/T15872 183 $env['LC_ALL'] = 'C'; 184 185 // Propagate PHABRICATOR_ENV explicitly. For discussion, see T4155. 186 $env['PHABRICATOR_ENV'] = PhabricatorEnv::getSelectedEnvironmentName(); 187 188 $as_device = $this->getConnectAsDevice(); 189 $credential_phid = $this->getCredentialPHID(); 190 191 if ($as_device) { 192 $device = AlmanacKeys::getLiveDevice(); 193 if (!$device) { 194 throw new Exception( 195 pht( 196 'Attempting to build a repository command (for repository "%s") '. 197 'as device, but this host ("%s") is not configured as a cluster '. 198 'device.', 199 $repository->getDisplayName(), 200 php_uname('n'))); 201 } 202 203 if ($credential_phid) { 204 throw new Exception( 205 pht( 206 'Attempting to build a repository command (for repository "%s"), '. 207 'but the CommandEngine is configured to connect as both the '. 208 'current cluster device ("%s") and with a specific credential '. 209 '("%s"). These options are mutually exclusive. Connections must '. 210 'authenticate as one or the other, not both.', 211 $repository->getDisplayName(), 212 $device->getName(), 213 $credential_phid)); 214 } 215 } 216 217 218 if ($this->isAnySSHProtocol()) { 219 if ($credential_phid) { 220 $env['PHABRICATOR_CREDENTIAL'] = $credential_phid; 221 } 222 if ($as_device) { 223 $env['PHABRICATOR_AS_DEVICE'] = 1; 224 } 225 } 226 227 $env += $repository->getPassthroughEnvironmentalVariables(); 228 229 return $env; 230 } 231 232 public function isSSHProtocol() { 233 return ($this->getProtocol() == 'ssh'); 234 } 235 236 public function isSVNProtocol() { 237 return ($this->getProtocol() == 'svn'); 238 } 239 240 public function isSVNSSHProtocol() { 241 return ($this->getProtocol() == 'svn+ssh'); 242 } 243 244 public function isHTTPProtocol() { 245 return ($this->getProtocol() == 'http'); 246 } 247 248 public function isHTTPSProtocol() { 249 return ($this->getProtocol() == 'https'); 250 } 251 252 public function isAnyHTTPProtocol() { 253 return ($this->isHTTPProtocol() || $this->isHTTPSProtocol()); 254 } 255 256 public function isAnySSHProtocol() { 257 return ($this->isSSHProtocol() || $this->isSVNSSHProtocol()); 258 } 259 260 public function isCredentialSupported() { 261 return ($this->getPassphraseProvidesCredentialType() !== null); 262 } 263 264 public function isCredentialOptional() { 265 if ($this->isAnySSHProtocol()) { 266 return false; 267 } 268 269 return true; 270 } 271 272 public function getPassphraseCredentialLabel() { 273 if ($this->isAnySSHProtocol()) { 274 return pht('SSH Key'); 275 } 276 277 if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) { 278 return pht('Password'); 279 } 280 281 return null; 282 } 283 284 public function getPassphraseDefaultCredentialType() { 285 if ($this->isAnySSHProtocol()) { 286 return PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE; 287 } 288 289 if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) { 290 return PassphrasePasswordCredentialType::CREDENTIAL_TYPE; 291 } 292 293 return null; 294 } 295 296 public function getPassphraseProvidesCredentialType() { 297 if ($this->isAnySSHProtocol()) { 298 return PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE; 299 } 300 301 if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) { 302 return PassphrasePasswordCredentialType::PROVIDES_TYPE; 303 } 304 305 return null; 306 } 307 308 protected function getSSHWrapper() { 309 $root = dirname(phutil_get_library_root('phabricator')); 310 return $root.'/bin/ssh-connect'; 311 } 312 313}