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

Configure Feed

Select the types of activity you want to include in your feed.

Allow AlmanacHost blueprints to build a meaningful CommandInterface

Summary: Ref T9253. Provide a meaningful command interface for Almanac hosts.

Test Plan:
Configued and leased a real host (`sbuild001.phacility.net`) and ran a command on it.

```
$ ./bin/drydock command --lease 90 -- ls /
bin
boot
core
dev
etc
home
initrd.img
lib
lib64
lost+found
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
vmlinuz
```

Reviewers: chad, hach-que

Reviewed By: chad, hach-que

Maniphest Tasks: T9253

Differential Revision: https://secure.phabricator.com/D14126

+168 -112
+2 -2
src/__phutil_library_map__.php
··· 838 838 'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php', 839 839 'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php', 840 840 'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php', 841 - 'DrydockLocalCommandInterface' => 'applications/drydock/interface/command/DrydockLocalCommandInterface.php', 842 841 'DrydockLog' => 'applications/drydock/storage/DrydockLog.php', 843 842 'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php', 844 843 'DrydockLogListController' => 'applications/drydock/controller/DrydockLogListController.php', ··· 846 845 'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php', 847 846 'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php', 848 847 'DrydockManagementCloseWorkflow' => 'applications/drydock/management/DrydockManagementCloseWorkflow.php', 848 + 'DrydockManagementCommandWorkflow' => 'applications/drydock/management/DrydockManagementCommandWorkflow.php', 849 849 'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php', 850 850 'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php', 851 851 'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php', ··· 4555 4555 'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine', 4556 4556 'DrydockLeaseStatus' => 'DrydockConstants', 4557 4557 'DrydockLeaseViewController' => 'DrydockLeaseController', 4558 - 'DrydockLocalCommandInterface' => 'DrydockCommandInterface', 4559 4558 'DrydockLog' => array( 4560 4559 'DrydockDAO', 4561 4560 'PhabricatorPolicyInterface', ··· 4566 4565 'DrydockLogQuery' => 'DrydockQuery', 4567 4566 'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 4568 4567 'DrydockManagementCloseWorkflow' => 'DrydockManagementWorkflow', 4568 + 'DrydockManagementCommandWorkflow' => 'DrydockManagementWorkflow', 4569 4569 'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow', 4570 4570 'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow', 4571 4571 'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow',
+28 -2
src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php
··· 119 119 } 120 120 121 121 public function getInterface( 122 + DrydockBlueprint $blueprint, 122 123 DrydockResource $resource, 123 124 DrydockLease $lease, 124 125 $type) { 125 - // TODO: Actually do stuff here, this needs work and currently makes this 126 - // entire exercise pointless. 126 + 127 + $viewer = PhabricatorUser::getOmnipotentUser(); 128 + 129 + switch ($type) { 130 + case DrydockCommandInterface::INTERFACE_TYPE: 131 + $credential_phid = $blueprint->getFieldValue('credentialPHID'); 132 + $binding_phid = $resource->getAttribute('almanacBindingPHID'); 133 + 134 + $binding = id(new AlmanacBindingQuery()) 135 + ->setViewer($viewer) 136 + ->withPHIDs(array($binding_phid)) 137 + ->executeOne(); 138 + if (!$binding) { 139 + // TODO: This is probably a permanent failure, destroy this resource? 140 + throw new Exception( 141 + pht( 142 + 'Unable to load binding "%s" to create command interface.', 143 + $binding_phid)); 144 + } 145 + 146 + $interface = $binding->getInterface(); 147 + 148 + return id(new DrydockSSHCommandInterface()) 149 + ->setConfig('credentialPHID', $credential_phid) 150 + ->setConfig('host', $interface->getAddress()) 151 + ->setConfig('port', $interface->getPort()); 152 + } 127 153 } 128 154 129 155 public function getFieldSpecifications() {
+16 -43
src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
··· 1 1 <?php 2 2 3 3 /** 4 - * @task lease Lease Acquisition 5 - * @task resource Resource Allocation 6 - * @task log Logging 4 + * @task lease Lease Acquisition 5 + * @task resource Resource Allocation 6 + * @task interface Resource Interfaces 7 + * @task log Logging 7 8 */ 8 9 abstract class DrydockBlueprintImplementation extends Phobject { 9 10 10 11 private $activeResource; 11 12 private $activeLease; 12 - private $instance; 13 13 14 14 abstract public function getType(); 15 - abstract public function getInterface( 16 - DrydockResource $resource, 17 - DrydockLease $lease, 18 - $type); 19 15 20 16 abstract public function isEnabled(); 21 17 22 18 abstract public function getBlueprintName(); 23 19 abstract public function getDescription(); 24 20 25 - public function getBlueprintClass() { 26 - return get_class($this); 27 - } 28 - 29 - protected function loadLease($lease_id) { 30 - // TODO: Get rid of this? 31 - $query = id(new DrydockLeaseQuery()) 32 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 33 - ->withIDs(array($lease_id)) 34 - ->execute(); 35 - 36 - $lease = idx($query, $lease_id); 37 - 38 - if (!$lease) { 39 - throw new Exception(pht("No such lease '%d'!", $lease_id)); 40 - } 41 - 42 - return $lease; 43 - } 44 - 45 - protected function getInstance() { 46 - if (!$this->instance) { 47 - throw new Exception( 48 - pht('Attach the blueprint instance to the implementation.')); 49 - } 50 - 51 - return $this->instance; 52 - } 53 - 54 - public function attachInstance(DrydockBlueprint $instance) { 55 - $this->instance = $instance; 56 - return $this; 57 - } 58 - 59 21 public function getFieldSpecifications() { 60 22 return array(); 61 23 } ··· 104 66 DrydockBlueprint $blueprint, 105 67 DrydockResource $resource, 106 68 DrydockLease $lease); 69 + 107 70 108 71 final public function releaseLease( 109 72 DrydockBlueprint $blueprint, ··· 236 199 DrydockLease $lease); 237 200 238 201 202 + /* -( Resource Interfaces )------------------------------------------------ */ 203 + 204 + 205 + abstract public function getInterface( 206 + DrydockBlueprint $blueprint, 207 + DrydockResource $resource, 208 + DrydockLease $lease, 209 + $type); 210 + 211 + 239 212 /* -( Logging )------------------------------------------------------------ */ 240 213 241 214 ··· 308 281 $this->log( 309 282 pht( 310 283 "Blueprint '%s': Created New Template", 311 - $this->getBlueprintClass())); 284 + get_class($this))); 312 285 313 286 return $resource; 314 287 }
+2 -9
src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
··· 122 122 } 123 123 124 124 public function getInterface( 125 + DrydockBlueprint $blueprint, 125 126 DrydockResource $resource, 126 127 DrydockLease $lease, 127 128 $type) { 128 - 129 - switch ($type) { 130 - case 'command': 131 - return $this 132 - ->loadLease($resource->getAttribute('lease.host')) 133 - ->getInterface($type); 134 - } 135 - 136 - throw new Exception(pht("No interface of type '%s'.", $type)); 129 + // TODO: This blueprint doesn't work at all. 137 130 } 138 131 139 132 }
+3 -3
src/applications/drydock/interface/DrydockInterface.php
··· 2 2 3 3 abstract class DrydockInterface extends Phobject { 4 4 5 - private $config; 5 + private $config = array(); 6 6 7 7 abstract public function getInterfaceType(); 8 8 9 - final public function setConfiguration(array $config) { 10 - $this->config = $config; 9 + final public function setConfig($key, $value) { 10 + $this->config[$key] = $value; 11 11 return $this; 12 12 } 13 13
+4 -2
src/applications/drydock/interface/command/DrydockCommandInterface.php
··· 2 2 3 3 abstract class DrydockCommandInterface extends DrydockInterface { 4 4 5 + const INTERFACE_TYPE = 'command'; 6 + 5 7 private $workingDirectory; 6 8 7 9 public function setWorkingDirectory($working_directory) { ··· 14 16 } 15 17 16 18 final public function getInterfaceType() { 17 - return 'command'; 19 + return self::INTERFACE_TYPE; 18 20 } 19 21 20 22 final public function exec($command) { ··· 38 40 protected function applyWorkingDirectoryToArgv(array $argv) { 39 41 if ($this->getWorkingDirectory() !== null) { 40 42 $cmd = $argv[0]; 41 - $cmd = "(cd %s; {$cmd})"; 43 + $cmd = "(cd %s && {$cmd})"; 42 44 $argv = array_merge( 43 45 array($cmd), 44 46 array($this->getWorkingDirectory()),
-12
src/applications/drydock/interface/command/DrydockLocalCommandInterface.php
··· 1 - <?php 2 - 3 - final class DrydockLocalCommandInterface extends DrydockCommandInterface { 4 - 5 - public function getExecFuture($command) { 6 - $argv = func_get_args(); 7 - $argv = $this->applyWorkingDirectoryToArgv($argv); 8 - 9 - return newv('ExecFuture', $argv); 10 - } 11 - 12 - }
+29 -39
src/applications/drydock/interface/command/DrydockSSHCommandInterface.php
··· 2 2 3 3 final class DrydockSSHCommandInterface extends DrydockCommandInterface { 4 4 5 - private $passphraseSSHKey; 5 + private $credential; 6 6 private $connectTimeout; 7 7 8 - private function openCredentialsIfNotOpen() { 9 - if ($this->passphraseSSHKey !== null) { 10 - return; 11 - } 12 - 13 - $credential = id(new PassphraseCredentialQuery()) 14 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 15 - ->withIDs(array($this->getConfig('credential'))) 16 - ->needSecrets(true) 17 - ->executeOne(); 18 - 19 - if ($credential === null) { 20 - throw new Exception( 21 - pht( 22 - 'There is no credential with ID %d.', 23 - $this->getConfig('credential'))); 24 - } 8 + private function loadCredential() { 9 + if ($this->credential === null) { 10 + $credential_phid = $this->getConfig('credentialPHID'); 25 11 26 - if ($credential->getProvidesType() !== 27 - PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE) { 28 - throw new Exception(pht('Only private key credentials are supported.')); 12 + $this->credential = PassphraseSSHKey::loadFromPHID( 13 + $credential_phid, 14 + PhabricatorUser::getOmnipotentUser()); 29 15 } 30 16 31 - $this->passphraseSSHKey = PassphraseSSHKey::loadFromPHID( 32 - $credential->getPHID(), 33 - PhabricatorUser::getOmnipotentUser()); 17 + return $this->credential; 34 18 } 35 19 36 20 public function setConnectTimeout($timeout) { ··· 39 23 } 40 24 41 25 public function getExecFuture($command) { 42 - $this->openCredentialsIfNotOpen(); 26 + $credential = $this->loadCredential(); 43 27 44 28 $argv = func_get_args(); 45 29 $argv = $this->applyWorkingDirectoryToArgv($argv); 46 30 $full_command = call_user_func_array('csprintf', $argv); 47 31 48 - $command_timeout = ''; 49 - if ($this->connectTimeout !== null) { 50 - $command_timeout = csprintf( 51 - '-o %s', 52 - 'ConnectTimeout='.$this->connectTimeout); 32 + $flags = array(); 33 + $flags[] = '-o'; 34 + $flags[] = 'LogLevel=quiet'; 35 + 36 + $flags[] = '-o'; 37 + $flags[] = 'StrictHostKeyChecking=no'; 38 + 39 + $flags[] = '-o'; 40 + $flags[] = 'UserKnownHostsFile=/dev/null'; 41 + 42 + $flags[] = '-o'; 43 + $flags[] = 'BatchMode=yes'; 44 + 45 + if ($this->connectTimeout) { 46 + $flags[] = '-o'; 47 + $flags[] = 'ConnectTimeout='.$this->connectTimeout; 53 48 } 54 49 55 50 return new ExecFuture( 56 - 'ssh '. 57 - '-o LogLevel=quiet '. 58 - '-o StrictHostKeyChecking=no '. 59 - '-o UserKnownHostsFile=/dev/null '. 60 - '-o BatchMode=yes '. 61 - '%C -p %s -i %P %P@%s -- %s', 62 - $command_timeout, 51 + 'ssh %Ls -l %P -p %s -i %P %s -- %s', 52 + $flags, 53 + $credential->getUsernameEnvelope(), 63 54 $this->getConfig('port'), 64 - $this->passphraseSSHKey->getKeyfileEnvelope(), 65 - $this->passphraseSSHKey->getUsernameEnvelope(), 55 + $credential->getKeyfileEnvelope(), 66 56 $this->getConfig('host'), 67 57 $full_command); 68 58 }
+66
src/applications/drydock/management/DrydockManagementCommandWorkflow.php
··· 1 + <?php 2 + 3 + final class DrydockManagementCommandWorkflow 4 + extends DrydockManagementWorkflow { 5 + 6 + protected function didConstruct() { 7 + $this 8 + ->setName('command') 9 + ->setSynopsis(pht('Run a command on a leased resource.')) 10 + ->setArguments( 11 + array( 12 + array( 13 + 'name' => 'lease', 14 + 'param' => 'id', 15 + 'help' => pht('Lease ID.'), 16 + ), 17 + array( 18 + 'name' => 'argv', 19 + 'wildcard' => true, 20 + 'help' => pht('Command to execute.'), 21 + ), 22 + )); 23 + } 24 + 25 + public function execute(PhutilArgumentParser $args) { 26 + $lease_id = $args->getArg('lease'); 27 + if (!$lease_id) { 28 + throw new PhutilArgumentUsageException( 29 + pht( 30 + 'Use %s to specify a lease.', 31 + '--lease')); 32 + } 33 + 34 + $argv = $args->getArg('argv'); 35 + if (!$argv) { 36 + throw new PhutilArgumentUsageException( 37 + pht( 38 + 'Specify a command to run.')); 39 + } 40 + 41 + $lease = id(new DrydockLeaseQuery()) 42 + ->setViewer($this->getViewer()) 43 + ->withIDs(array($lease_id)) 44 + ->executeOne(); 45 + if (!$lease) { 46 + throw new Exception( 47 + pht( 48 + 'Unable to load lease with ID "%s"!', 49 + $lease_id)); 50 + } 51 + 52 + // TODO: Check lease state, etc. 53 + 54 + $interface = $lease->getInterface(DrydockCommandInterface::INTERFACE_TYPE); 55 + 56 + list($stdout, $stderr) = call_user_func_array( 57 + array($interface, 'execx'), 58 + array('%Ls', $argv)); 59 + 60 + fprintf(STDOUT, $stdout); 61 + fprintf(STDERR, $stderr); 62 + 63 + return 0; 64 + } 65 + 66 + }
+18
src/applications/drydock/storage/DrydockBlueprint.php
··· 173 173 return $this; 174 174 } 175 175 176 + public function getInterface( 177 + DrydockResource $resource, 178 + DrydockLease $lease, 179 + $type) { 180 + 181 + $interface = $this->getImplementation() 182 + ->getInterface($this, $resource, $lease, $type); 183 + 184 + if (!$interface) { 185 + throw new Exception( 186 + pht( 187 + 'Unable to build resource interface of type "%s".', 188 + $type)); 189 + } 190 + 191 + return $interface; 192 + } 193 + 176 194 177 195 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 178 196