Path: blob/master/src/applications/diffusion/protocol/DiffusionCommandEngine.php
12242 views
<?php12abstract class DiffusionCommandEngine extends Phobject {34private $repository;5private $protocol;6private $credentialPHID;7private $argv;8private $passthru;9private $connectAsDevice;10private $sudoAsDaemon;11private $uri;1213public static function newCommandEngine(PhabricatorRepository $repository) {14$engines = self::newCommandEngines();1516foreach ($engines as $engine) {17if ($engine->canBuildForRepository($repository)) {18return id(clone $engine)19->setRepository($repository);20}21}2223throw new Exception(24pht(25'No registered command engine can build commands for this '.26'repository ("%s").',27$repository->getDisplayName()));28}2930private static function newCommandEngines() {31return id(new PhutilClassMapQuery())32->setAncestorClass(__CLASS__)33->execute();34}3536abstract protected function canBuildForRepository(37PhabricatorRepository $repository);3839abstract protected function newFormattedCommand($pattern, array $argv);40abstract protected function newCustomEnvironment();4142public function setRepository(PhabricatorRepository $repository) {43$this->repository = $repository;44return $this;45}4647public function getRepository() {48return $this->repository;49}5051public function setURI(PhutilURI $uri) {52$this->uri = $uri;53$this->setProtocol($uri->getProtocol());54return $this;55}5657public function getURI() {58return $this->uri;59}6061public function setProtocol($protocol) {62$this->protocol = $protocol;63return $this;64}6566public function getProtocol() {67return $this->protocol;68}6970public function getDisplayProtocol() {71return $this->getProtocol().'://';72}7374public function setCredentialPHID($credential_phid) {75$this->credentialPHID = $credential_phid;76return $this;77}7879public function getCredentialPHID() {80return $this->credentialPHID;81}8283public function setArgv(array $argv) {84$this->argv = $argv;85return $this;86}8788public function getArgv() {89return $this->argv;90}9192public function setPassthru($passthru) {93$this->passthru = $passthru;94return $this;95}9697public function getPassthru() {98return $this->passthru;99}100101public function setConnectAsDevice($connect_as_device) {102$this->connectAsDevice = $connect_as_device;103return $this;104}105106public function getConnectAsDevice() {107return $this->connectAsDevice;108}109110public function setSudoAsDaemon($sudo_as_daemon) {111$this->sudoAsDaemon = $sudo_as_daemon;112return $this;113}114115public function getSudoAsDaemon() {116return $this->sudoAsDaemon;117}118119protected function shouldAlwaysSudo() {120return false;121}122123public function newFuture() {124$argv = $this->newCommandArgv();125$env = $this->newCommandEnvironment();126$is_passthru = $this->getPassthru();127128if ($this->getSudoAsDaemon() || $this->shouldAlwaysSudo()) {129$command = call_user_func_array('csprintf', $argv);130$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);131$argv = array('%C', $command);132}133134if ($is_passthru) {135$future = newv('PhutilExecPassthru', $argv);136} else {137$future = newv('ExecFuture', $argv);138}139140$future->setEnv($env);141142// See T13108. By default, don't let any cluster command run indefinitely143// to try to avoid cases where `git fetch` hangs for some reason and we're144// left sitting with a held lock forever.145$repository = $this->getRepository();146if (!$is_passthru) {147$future->setTimeout($repository->getEffectiveCopyTimeLimit());148}149150return $future;151}152153private function newCommandArgv() {154$argv = $this->argv;155$pattern = $argv[0];156$argv = array_slice($argv, 1);157158list($pattern, $argv) = $this->newFormattedCommand($pattern, $argv);159160return array_merge(array($pattern), $argv);161}162163private function newCommandEnvironment() {164$env = $this->newCommonEnvironment() + $this->newCustomEnvironment();165foreach ($env as $key => $value) {166if ($value === null) {167unset($env[$key]);168}169}170return $env;171}172173private function newCommonEnvironment() {174$repository = $this->getRepository();175176$env = array();177// NOTE: Force the language to "en_US.UTF-8", which overrides locale178// settings. This makes stuff print in English instead of, e.g., French,179// so we can parse the output of some commands, error messages, etc.180$env['LANG'] = 'en_US.UTF-8';181182// Propagate PHABRICATOR_ENV explicitly. For discussion, see T4155.183$env['PHABRICATOR_ENV'] = PhabricatorEnv::getSelectedEnvironmentName();184185$as_device = $this->getConnectAsDevice();186$credential_phid = $this->getCredentialPHID();187188if ($as_device) {189$device = AlmanacKeys::getLiveDevice();190if (!$device) {191throw new Exception(192pht(193'Attempting to build a repository command (for repository "%s") '.194'as device, but this host ("%s") is not configured as a cluster '.195'device.',196$repository->getDisplayName(),197php_uname('n')));198}199200if ($credential_phid) {201throw new Exception(202pht(203'Attempting to build a repository command (for repository "%s"), '.204'but the CommandEngine is configured to connect as both the '.205'current cluster device ("%s") and with a specific credential '.206'("%s"). These options are mutually exclusive. Connections must '.207'authenticate as one or the other, not both.',208$repository->getDisplayName(),209$device->getName(),210$credential_phid));211}212}213214215if ($this->isAnySSHProtocol()) {216if ($credential_phid) {217$env['PHABRICATOR_CREDENTIAL'] = $credential_phid;218}219if ($as_device) {220$env['PHABRICATOR_AS_DEVICE'] = 1;221}222}223224$env += $repository->getPassthroughEnvironmentalVariables();225226return $env;227}228229public function isSSHProtocol() {230return ($this->getProtocol() == 'ssh');231}232233public function isSVNProtocol() {234return ($this->getProtocol() == 'svn');235}236237public function isSVNSSHProtocol() {238return ($this->getProtocol() == 'svn+ssh');239}240241public function isHTTPProtocol() {242return ($this->getProtocol() == 'http');243}244245public function isHTTPSProtocol() {246return ($this->getProtocol() == 'https');247}248249public function isAnyHTTPProtocol() {250return ($this->isHTTPProtocol() || $this->isHTTPSProtocol());251}252253public function isAnySSHProtocol() {254return ($this->isSSHProtocol() || $this->isSVNSSHProtocol());255}256257public function isCredentialSupported() {258return ($this->getPassphraseProvidesCredentialType() !== null);259}260261public function isCredentialOptional() {262if ($this->isAnySSHProtocol()) {263return false;264}265266return true;267}268269public function getPassphraseCredentialLabel() {270if ($this->isAnySSHProtocol()) {271return pht('SSH Key');272}273274if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) {275return pht('Password');276}277278return null;279}280281public function getPassphraseDefaultCredentialType() {282if ($this->isAnySSHProtocol()) {283return PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE;284}285286if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) {287return PassphrasePasswordCredentialType::CREDENTIAL_TYPE;288}289290return null;291}292293public function getPassphraseProvidesCredentialType() {294if ($this->isAnySSHProtocol()) {295return PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE;296}297298if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) {299return PassphrasePasswordCredentialType::PROVIDES_TYPE;300}301302return null;303}304305protected function getSSHWrapper() {306$root = dirname(phutil_get_library_root('phabricator'));307return $root.'/bin/ssh-connect';308}309310}311312313