1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 16:52:41 +01:00

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
This commit is contained in:
epriestley 2015-09-21 04:46:02 -07:00
parent 9a270efe8a
commit 6a0eb9d84b
10 changed files with 169 additions and 113 deletions

View file

@ -838,7 +838,6 @@ phutil_register_library_map(array(
'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php', 'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php',
'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php', 'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php',
'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php', 'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php',
'DrydockLocalCommandInterface' => 'applications/drydock/interface/command/DrydockLocalCommandInterface.php',
'DrydockLog' => 'applications/drydock/storage/DrydockLog.php', 'DrydockLog' => 'applications/drydock/storage/DrydockLog.php',
'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php', 'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php',
'DrydockLogListController' => 'applications/drydock/controller/DrydockLogListController.php', 'DrydockLogListController' => 'applications/drydock/controller/DrydockLogListController.php',
@ -846,6 +845,7 @@ phutil_register_library_map(array(
'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php', 'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php',
'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php', 'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php',
'DrydockManagementCloseWorkflow' => 'applications/drydock/management/DrydockManagementCloseWorkflow.php', 'DrydockManagementCloseWorkflow' => 'applications/drydock/management/DrydockManagementCloseWorkflow.php',
'DrydockManagementCommandWorkflow' => 'applications/drydock/management/DrydockManagementCommandWorkflow.php',
'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php', 'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php',
'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php', 'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php',
'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php', 'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php',
@ -4555,7 +4555,6 @@ phutil_register_library_map(array(
'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockLeaseStatus' => 'DrydockConstants', 'DrydockLeaseStatus' => 'DrydockConstants',
'DrydockLeaseViewController' => 'DrydockLeaseController', 'DrydockLeaseViewController' => 'DrydockLeaseController',
'DrydockLocalCommandInterface' => 'DrydockCommandInterface',
'DrydockLog' => array( 'DrydockLog' => array(
'DrydockDAO', 'DrydockDAO',
'PhabricatorPolicyInterface', 'PhabricatorPolicyInterface',
@ -4566,6 +4565,7 @@ phutil_register_library_map(array(
'DrydockLogQuery' => 'DrydockQuery', 'DrydockLogQuery' => 'DrydockQuery',
'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockManagementCloseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementCloseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementCommandWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow', 'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow',

View file

@ -119,11 +119,37 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
} }
public function getInterface( public function getInterface(
DrydockBlueprint $blueprint,
DrydockResource $resource, DrydockResource $resource,
DrydockLease $lease, DrydockLease $lease,
$type) { $type) {
// TODO: Actually do stuff here, this needs work and currently makes this
// entire exercise pointless. $viewer = PhabricatorUser::getOmnipotentUser();
switch ($type) {
case DrydockCommandInterface::INTERFACE_TYPE:
$credential_phid = $blueprint->getFieldValue('credentialPHID');
$binding_phid = $resource->getAttribute('almanacBindingPHID');
$binding = id(new AlmanacBindingQuery())
->setViewer($viewer)
->withPHIDs(array($binding_phid))
->executeOne();
if (!$binding) {
// TODO: This is probably a permanent failure, destroy this resource?
throw new Exception(
pht(
'Unable to load binding "%s" to create command interface.',
$binding_phid));
}
$interface = $binding->getInterface();
return id(new DrydockSSHCommandInterface())
->setConfig('credentialPHID', $credential_phid)
->setConfig('host', $interface->getAddress())
->setConfig('port', $interface->getPort());
}
} }
public function getFieldSpecifications() { public function getFieldSpecifications() {

View file

@ -3,59 +3,21 @@
/** /**
* @task lease Lease Acquisition * @task lease Lease Acquisition
* @task resource Resource Allocation * @task resource Resource Allocation
* @task interface Resource Interfaces
* @task log Logging * @task log Logging
*/ */
abstract class DrydockBlueprintImplementation extends Phobject { abstract class DrydockBlueprintImplementation extends Phobject {
private $activeResource; private $activeResource;
private $activeLease; private $activeLease;
private $instance;
abstract public function getType(); abstract public function getType();
abstract public function getInterface(
DrydockResource $resource,
DrydockLease $lease,
$type);
abstract public function isEnabled(); abstract public function isEnabled();
abstract public function getBlueprintName(); abstract public function getBlueprintName();
abstract public function getDescription(); abstract public function getDescription();
public function getBlueprintClass() {
return get_class($this);
}
protected function loadLease($lease_id) {
// TODO: Get rid of this?
$query = id(new DrydockLeaseQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIDs(array($lease_id))
->execute();
$lease = idx($query, $lease_id);
if (!$lease) {
throw new Exception(pht("No such lease '%d'!", $lease_id));
}
return $lease;
}
protected function getInstance() {
if (!$this->instance) {
throw new Exception(
pht('Attach the blueprint instance to the implementation.'));
}
return $this->instance;
}
public function attachInstance(DrydockBlueprint $instance) {
$this->instance = $instance;
return $this;
}
public function getFieldSpecifications() { public function getFieldSpecifications() {
return array(); return array();
} }
@ -105,6 +67,7 @@ abstract class DrydockBlueprintImplementation extends Phobject {
DrydockResource $resource, DrydockResource $resource,
DrydockLease $lease); DrydockLease $lease);
final public function releaseLease( final public function releaseLease(
DrydockBlueprint $blueprint, DrydockBlueprint $blueprint,
DrydockResource $resource, DrydockResource $resource,
@ -236,6 +199,16 @@ abstract class DrydockBlueprintImplementation extends Phobject {
DrydockLease $lease); DrydockLease $lease);
/* -( Resource Interfaces )------------------------------------------------ */
abstract public function getInterface(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease,
$type);
/* -( Logging )------------------------------------------------------------ */ /* -( Logging )------------------------------------------------------------ */
@ -308,7 +281,7 @@ abstract class DrydockBlueprintImplementation extends Phobject {
$this->log( $this->log(
pht( pht(
"Blueprint '%s': Created New Template", "Blueprint '%s': Created New Template",
$this->getBlueprintClass())); get_class($this)));
return $resource; return $resource;
} }

View file

@ -122,18 +122,11 @@ final class DrydockWorkingCopyBlueprintImplementation
} }
public function getInterface( public function getInterface(
DrydockBlueprint $blueprint,
DrydockResource $resource, DrydockResource $resource,
DrydockLease $lease, DrydockLease $lease,
$type) { $type) {
// TODO: This blueprint doesn't work at all.
switch ($type) {
case 'command':
return $this
->loadLease($resource->getAttribute('lease.host'))
->getInterface($type);
}
throw new Exception(pht("No interface of type '%s'.", $type));
} }
} }

View file

@ -2,12 +2,12 @@
abstract class DrydockInterface extends Phobject { abstract class DrydockInterface extends Phobject {
private $config; private $config = array();
abstract public function getInterfaceType(); abstract public function getInterfaceType();
final public function setConfiguration(array $config) { final public function setConfig($key, $value) {
$this->config = $config; $this->config[$key] = $value;
return $this; return $this;
} }

View file

@ -2,6 +2,8 @@
abstract class DrydockCommandInterface extends DrydockInterface { abstract class DrydockCommandInterface extends DrydockInterface {
const INTERFACE_TYPE = 'command';
private $workingDirectory; private $workingDirectory;
public function setWorkingDirectory($working_directory) { public function setWorkingDirectory($working_directory) {
@ -14,7 +16,7 @@ abstract class DrydockCommandInterface extends DrydockInterface {
} }
final public function getInterfaceType() { final public function getInterfaceType() {
return 'command'; return self::INTERFACE_TYPE;
} }
final public function exec($command) { final public function exec($command) {
@ -38,7 +40,7 @@ abstract class DrydockCommandInterface extends DrydockInterface {
protected function applyWorkingDirectoryToArgv(array $argv) { protected function applyWorkingDirectoryToArgv(array $argv) {
if ($this->getWorkingDirectory() !== null) { if ($this->getWorkingDirectory() !== null) {
$cmd = $argv[0]; $cmd = $argv[0];
$cmd = "(cd %s; {$cmd})"; $cmd = "(cd %s && {$cmd})";
$argv = array_merge( $argv = array_merge(
array($cmd), array($cmd),
array($this->getWorkingDirectory()), array($this->getWorkingDirectory()),

View file

@ -1,12 +0,0 @@
<?php
final class DrydockLocalCommandInterface extends DrydockCommandInterface {
public function getExecFuture($command) {
$argv = func_get_args();
$argv = $this->applyWorkingDirectoryToArgv($argv);
return newv('ExecFuture', $argv);
}
}

View file

@ -2,67 +2,57 @@
final class DrydockSSHCommandInterface extends DrydockCommandInterface { final class DrydockSSHCommandInterface extends DrydockCommandInterface {
private $passphraseSSHKey; private $credential;
private $connectTimeout; private $connectTimeout;
private function openCredentialsIfNotOpen() { private function loadCredential() {
if ($this->passphraseSSHKey !== null) { if ($this->credential === null) {
return; $credential_phid = $this->getConfig('credentialPHID');
}
$credential = id(new PassphraseCredentialQuery()) $this->credential = PassphraseSSHKey::loadFromPHID(
->setViewer(PhabricatorUser::getOmnipotentUser()) $credential_phid,
->withIDs(array($this->getConfig('credential')))
->needSecrets(true)
->executeOne();
if ($credential === null) {
throw new Exception(
pht(
'There is no credential with ID %d.',
$this->getConfig('credential')));
}
if ($credential->getProvidesType() !==
PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE) {
throw new Exception(pht('Only private key credentials are supported.'));
}
$this->passphraseSSHKey = PassphraseSSHKey::loadFromPHID(
$credential->getPHID(),
PhabricatorUser::getOmnipotentUser()); PhabricatorUser::getOmnipotentUser());
} }
return $this->credential;
}
public function setConnectTimeout($timeout) { public function setConnectTimeout($timeout) {
$this->connectTimeout = $timeout; $this->connectTimeout = $timeout;
return $this; return $this;
} }
public function getExecFuture($command) { public function getExecFuture($command) {
$this->openCredentialsIfNotOpen(); $credential = $this->loadCredential();
$argv = func_get_args(); $argv = func_get_args();
$argv = $this->applyWorkingDirectoryToArgv($argv); $argv = $this->applyWorkingDirectoryToArgv($argv);
$full_command = call_user_func_array('csprintf', $argv); $full_command = call_user_func_array('csprintf', $argv);
$command_timeout = ''; $flags = array();
if ($this->connectTimeout !== null) { $flags[] = '-o';
$command_timeout = csprintf( $flags[] = 'LogLevel=quiet';
'-o %s',
'ConnectTimeout='.$this->connectTimeout); $flags[] = '-o';
$flags[] = 'StrictHostKeyChecking=no';
$flags[] = '-o';
$flags[] = 'UserKnownHostsFile=/dev/null';
$flags[] = '-o';
$flags[] = 'BatchMode=yes';
if ($this->connectTimeout) {
$flags[] = '-o';
$flags[] = 'ConnectTimeout='.$this->connectTimeout;
} }
return new ExecFuture( return new ExecFuture(
'ssh '. 'ssh %Ls -l %P -p %s -i %P %s -- %s',
'-o LogLevel=quiet '. $flags,
'-o StrictHostKeyChecking=no '. $credential->getUsernameEnvelope(),
'-o UserKnownHostsFile=/dev/null '.
'-o BatchMode=yes '.
'%C -p %s -i %P %P@%s -- %s',
$command_timeout,
$this->getConfig('port'), $this->getConfig('port'),
$this->passphraseSSHKey->getKeyfileEnvelope(), $credential->getKeyfileEnvelope(),
$this->passphraseSSHKey->getUsernameEnvelope(),
$this->getConfig('host'), $this->getConfig('host'),
$full_command); $full_command);
} }

View file

@ -0,0 +1,66 @@
<?php
final class DrydockManagementCommandWorkflow
extends DrydockManagementWorkflow {
protected function didConstruct() {
$this
->setName('command')
->setSynopsis(pht('Run a command on a leased resource.'))
->setArguments(
array(
array(
'name' => 'lease',
'param' => 'id',
'help' => pht('Lease ID.'),
),
array(
'name' => 'argv',
'wildcard' => true,
'help' => pht('Command to execute.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$lease_id = $args->getArg('lease');
if (!$lease_id) {
throw new PhutilArgumentUsageException(
pht(
'Use %s to specify a lease.',
'--lease'));
}
$argv = $args->getArg('argv');
if (!$argv) {
throw new PhutilArgumentUsageException(
pht(
'Specify a command to run.'));
}
$lease = id(new DrydockLeaseQuery())
->setViewer($this->getViewer())
->withIDs(array($lease_id))
->executeOne();
if (!$lease) {
throw new Exception(
pht(
'Unable to load lease with ID "%s"!',
$lease_id));
}
// TODO: Check lease state, etc.
$interface = $lease->getInterface(DrydockCommandInterface::INTERFACE_TYPE);
list($stdout, $stderr) = call_user_func_array(
array($interface, 'execx'),
array('%Ls', $argv));
fprintf(STDOUT, $stdout);
fprintf(STDERR, $stderr);
return 0;
}
}

View file

@ -173,6 +173,24 @@ final class DrydockBlueprint extends DrydockDAO
return $this; return $this;
} }
public function getInterface(
DrydockResource $resource,
DrydockLease $lease,
$type) {
$interface = $this->getImplementation()
->getInterface($this, $resource, $lease, $type);
if (!$interface) {
throw new Exception(
pht(
'Unable to build resource interface of type "%s".',
$type));
}
return $interface;
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */ /* -( PhabricatorApplicationTransactionInterface )------------------------- */