@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
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}