Index: scripts/drydock/drydock_control.php =================================================================== --- scripts/drydock/drydock_control.php +++ scripts/drydock/drydock_control.php @@ -19,6 +19,7 @@ new DrydockManagementLeaseWorkflow(), new DrydockManagementCloseWorkflow(), new DrydockManagementReleaseWorkflow(), + new DrydockManagementCreateResourceWorkflow(), new PhutilHelpArgumentWorkflow(), ); Index: src/__phutil_library_map__.php =================================================================== --- src/__phutil_library_map__.php +++ src/__phutil_library_map__.php @@ -641,10 +641,12 @@ 'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php', 'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php', 'DrydockManagementCloseWorkflow' => 'applications/drydock/management/DrydockManagementCloseWorkflow.php', + 'DrydockManagementCreateResourceWorkflow' => 'applications/drydock/management/DrydockManagementCreateResourceWorkflow.php', 'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php', 'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php', 'DrydockManagementWaitForLeaseWorkflow' => 'applications/drydock/management/DrydockManagementWaitForLeaseWorkflow.php', 'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php', + 'DrydockPreallocatedHostBlueprint' => 'applications/drydock/blueprint/DrydockPreallocatedHostBlueprint.php', 'DrydockResource' => 'applications/drydock/storage/DrydockResource.php', 'DrydockResourceCloseController' => 'applications/drydock/controller/DrydockResourceCloseController.php', 'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php', @@ -2950,10 +2952,12 @@ 'DrydockLogController' => 'DrydockController', 'DrydockLogQuery' => 'PhabricatorOffsetPagedQuery', 'DrydockManagementCloseWorkflow' => 'DrydockManagementWorkflow', + 'DrydockManagementCreateResourceWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementWaitForLeaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementWorkflow' => 'PhutilArgumentWorkflow', + 'DrydockPreallocatedHostBlueprint' => 'DrydockBlueprint', 'DrydockResource' => 'DrydockDAO', 'DrydockResourceCloseController' => 'DrydockController', 'DrydockResourceListController' => 'DrydockController', Index: src/applications/drydock/blueprint/DrydockLocalHostBlueprint.php =================================================================== --- src/applications/drydock/blueprint/DrydockLocalHostBlueprint.php +++ src/applications/drydock/blueprint/DrydockLocalHostBlueprint.php @@ -3,8 +3,7 @@ final class DrydockLocalHostBlueprint extends DrydockBlueprint { public function isEnabled() { - // TODO: Figure this out. - return true; + return false; } public function canAllocateMoreResources(array $pool) { @@ -34,6 +33,8 @@ $resource = $this->newResourceTemplate('Host (localhost)'); $resource->setStatus(DrydockResourceStatus::STATUS_OPEN); $resource->setAttribute('path', $path); + $resource->setAttribute('remote', "false"); + $resource->setAttribute('preallocated', "false"); $resource->save(); return $resource; @@ -42,7 +43,7 @@ protected function canAllocateLease( DrydockResource $resource, DrydockLease $lease) { - return true; + return false; } protected function shouldAllocateLease( Index: src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprint.php =================================================================== --- /dev/null +++ src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprint.php @@ -0,0 +1,94 @@ +getAttribute('platform') === $resource->getAttribute('platform'); + } + + protected function shouldAllocateLease( + DrydockResource $resource, + DrydockLease $lease, + array $other_leases) { + return true; + } + + protected function executeAcquireLease( + DrydockResource $resource, + DrydockLease $lease) { + + // Similar to DrydockLocalHostBlueprint, we create a folder + // on the remote host that the lease can use. + + $lease_id = $lease->getID(); + + // Can't use DIRECTORY_SEPERATOR here because that is relevant to + // the platform we're currently running on, not the platform we are + // remoting to. + $separator = '/'; + if ($lease->getAttribute('platform') === 'windows') { + $separator = '\\'; + } + + // Clean up the directory path a little. + $base_path = rtrim($resource->getAttribute('path'), '/'); + $base_path = rtrim($base_path, '\\'); + $full_path = $base_path.$separator.$lease_id; + + $cmd = $lease->getInterface('command'); + + if ($lease->getAttribute('platform') !== 'windows') { + $cmd->execx('mkdir %s', $full_path); + } else { + // Windows is terrible. The mkdir command doesn't even support putting + // the path in quotes. IN QUOTES. ARGUHRGHUGHHGG!! Do some terribly + // inaccurate sanity checking since we can't safely escape the path. + if (preg_match('/^[A-Z]\\:\\\\[a-zA-Z0-9\\\\\\ ]/', $full_path) === 0) { + throw new Exception( + 'Unsafe path detected for Windows platform: "'.$full_path.'".'); + } + $cmd->execx('mkdir %C', $full_path); + } + + $lease->setAttribute('path', $full_path); + } + + public function getType() { + return 'host'; + } + + public function getInterface( + DrydockResource $resource, + DrydockLease $lease, + $type) { + + switch ($type) { + case 'command': + return id(new DrydockSSHCommandInterface()) + ->setConfiguration(array( + 'host' => $resource->getAttribute('host'), + 'port' => $resource->getAttribute('port'), + 'user' => $resource->getAttribute('user'), + 'ssh-keyfile' => $resource->getAttribute('ssh-keyfile'), + 'platform' => $resource->getAttribute('platform'))); + } + + throw new Exception("No interface of type '{$type}'."); + } + +} Index: src/applications/drydock/interface/command/DrydockSSHCommandInterface.php =================================================================== --- src/applications/drydock/interface/command/DrydockSSHCommandInterface.php +++ src/applications/drydock/interface/command/DrydockSSHCommandInterface.php @@ -4,19 +4,41 @@ public function getExecFuture($command) { $argv = func_get_args(); - $argv = $this->applyWorkingDirectoryToArgv($argv); + + // This assumes there's a UNIX shell living at the other + // end of the connection, which isn't the case for Windows machines. + if ($this->getConfig('platform') !== 'windows') { + $argv = $this->applyWorkingDirectoryToArgv($argv); + } $full_command = call_user_func_array('csprintf', $argv); + if ($this->getConfig('platform') === 'windows') { + // On Windows platforms we need to execute cmd.exe explicitly since + // most commands are not really executables. + $full_command = 'C:\\Windows\\system32\\cmd.exe /C '.$full_command; + } + // NOTE: The "-t -t" is for psuedo-tty allocation so we can "sudo" on some // systems, but maybe more trouble than it's worth? - return new ExecFuture( - 'ssh -t -t -o StrictHostKeyChecking=no -i %s %s@%s -- %s', - $this->getConfig('ssh-keyfile'), - $this->getConfig('user'), - $this->getConfig('host'), - $full_command); + $keyfile = $this->getConfig('ssh-keyfile'); + if (!empty($keyfile)) { + return new ExecFuture( + 'ssh -t -t -o StrictHostKeyChecking=no -p %s -i %s %s@%s -- %s', + $this->getConfig('port'), + $this->getConfig('ssh-keyfile'), + $this->getConfig('user'), + $this->getConfig('host'), + $full_command); + } else { + return new ExecFuture( + 'ssh -t -t -o StrictHostKeyChecking=no -p %s %s@%s -- %s', + $this->getConfig('port'), + $this->getConfig('user'), + $this->getConfig('host'), + $full_command); + } } } Index: src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php =================================================================== --- /dev/null +++ src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php @@ -0,0 +1,66 @@ +setName('create-resource') + ->setSynopsis('Create a resource manually.') + ->setArguments( + array( + array( + 'name' => 'name', + 'param' => 'resource_name', + 'help' => 'Resource name.', + ), + array( + 'name' => 'blueprint', + 'param' => 'blueprint_type', + 'help' => 'Blueprint type.', + ), + array( + 'name' => 'attributes', + 'param' => 'name=value,...', + 'help' => 'Resource attributes.', + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); + + $resource_name = $args->getArg('name'); + if (!$resource_name) { + throw new PhutilArgumentUsageException( + "Specify a resource name with `--name`."); + } + + $blueprint_type = $args->getArg('blueprint'); + if (!$blueprint_type) { + throw new PhutilArgumentUsageException( + "Specify a blueprint type with `--blueprint`."); + } + + $attributes = $args->getArg('attributes'); + if ($attributes) { + $options = new PhutilSimpleOptions(); + $options->setCaseSensitive(true); + $attributes = $options->parse($attributes); + } + + $resource = new DrydockResource(); + $resource->setBlueprintClass($blueprint_type); + $resource->setType(id(new $blueprint_type())->getType()); + $resource->setName($resource_name); + $resource->setStatus(DrydockResourceStatus::STATUS_OPEN); + if ($attributes) { + $resource->setAttributes($attributes); + } + $resource->save(); + + $console->writeOut("Created Resource %s\n", $resource->getID()); + return 0; + } + +}