Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15420315
D10395.id25015.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
25 KB
Referenced Files
None
Subscribers
None
D10395.id25015.diff
View Options
diff --git a/resources/windows/zeroconf.ps1 b/resources/windows/zeroconf.ps1
new file mode 100644
--- /dev/null
+++ b/resources/windows/zeroconf.ps1
@@ -0,0 +1,111 @@
+# Allow running scripts on this system
+Set-ExecutionPolicy -Force Bypass
+
+# Download Cygwin64
+wget https://cygwin.com/setup-x86_64.exe `
+ -OutFile C:\setup-x86_64.exe
+
+# Run Cygwin64 install
+C:\setup-x86_64.exe -s http://mirrors.kernel.org/sourceware/cygwin/ -P openssh `
+ -q -a x86_64 -B -X -n -N -d
+
+# Wait for Cygwin install to finish
+while ((Get-Process setup-x86_64 -ErrorAction SilentlyContinue) -ne $null) {
+ Write-Host "Waiting for Cygwin to finish installation..."
+ Sleep 5
+}
+
+# Setup SSHD
+$env:PATH = $env:PATH + ";c:\cygwin64\bin"
+C:\cygwin64\bin\chmod.exe +r /etc/passwd
+C:\cygwin64\bin\chmod.exe +r /etc/group
+C:\cygwin64\bin\chmod.exe a+x /var
+C:\cygwin64\bin\bash.exe -c "mkpasswd > /etc/passwd"
+C:\cygwin64\bin\bash.exe C:\cygwin64\bin\ssh-host-config -u $username -y -w `
+ SSHPRIV1@
+
+# Set up cmd.exe wrapping script
+$cmdwrap = @"
+#!/bin/bash
+
+cmd=$*
+cmd=`${cmd:3}
+
+/cygdrive/c/Windows/system32/cmd.exe /C `$cmd
+"@
+$cmdwrap = $cmdwrap.Replace("`r`n", "`n")
+Set-Content -Path C:\cygwin64\bin\cmdwrap.sh -Value $cmdwrap
+
+C:\cygwin64\bin\bash.exe -c "mkpasswd > /etc/passwd"
+
+if (!(Test-Path C:\cygwin64\home\$username\.ssh)) {
+ mkdir C:\cygwin64\home\$username\.ssh
+}
+
+$keyline = "$publickey Automatically Defined Key"
+
+Set-Content `
+ -Path C:\cygwin64\home\$username\.ssh\authorized_keys `
+ -Value $keyline
+
+New-NetFirewallRule -DisplayName "SSHD" `
+ -Direction Inbound -Protocol TCP -LocalPort 22 -Action allow
+
+Set-Service sshd -StartupType Automatic
+
+# Change privilege mode.
+$secpasswd = ConvertTo-SecureString "SSHPRIV1@" -AsPlainText -Force
+$targetCred = New-Object System.Management.Automation.PSCredential($username, `
+ $secpasswd)
+Invoke-Command -ComputerName localhost -Credential $targetCred `
+ -ScriptBlock {
+ $sshd_config = Get-Content -Path C:\cygwin64\etc\sshd_config
+ $sshd_config = $sshd_config.Replace( `
+ "UsePrivilegeSeparation sandbox", `
+ "UsePrivilegeSeparation no")
+ Set-Content -Path C:\cygwin64\etc\sshd_config -Value $sshd_config
+ }
+
+# Use cmd.exe as the initial prompt on SSH.
+$passwd_config = Get-Content -Path C:\cygwin64\etc\passwd
+$search = $null;
+for ($i = 0; $i -lt $passwd_config.Count; $i++) {
+ if ($passwd_config[$i].Contains($username)) {
+ $passwd_config[$i] = $passwd_config[$i].Replace( `
+ "/var/empty", `
+ "/home/$username")
+ $passwd_config[$i] = $passwd_config[$i].Replace( `
+ "/bin/bash", `
+ "/bin/cmdwrap.sh")
+ write-host ("Looking at " + $passwd_config[$i])
+ }
+}
+Set-Content -Path C:\cygwin64\etc\passwd -Value $passwd_config
+
+# Prevent Powershell from using stupid defaults.
+Set-Item -ErrorAction SilentlyContinue `
+ WSMAN:localhost\Shell\MaxMemoryPerShellMB 100000000
+Set-Item -ErrorAction SilentlyContinue `
+ WSMAN:localhost\Shell\MaxShellsPerUser 10000
+
+Push-Location WSMAN:localhost\Plugin\
+foreach ($dir in Get-ChildItem) {
+ $name = $dir.Name
+ try {
+ Set-Item -ErrorAction SilentlyContinue `
+ -Path "$name\Quotas\MaxConcurrentCommandsPerShell" 100000000
+ } catch [System.InvalidOperationException] {
+ }
+ try {
+ Set-Item -ErrorAction SilentlyContinue `
+ -Path "$name\Quotas\MaxMemoryPerShellMB" 10000
+ } catch [System.InvalidOperationException] {
+ }
+}
+Pop-Location
+
+# Reload remoting configuration.
+Restart-Service winrm
+
+# Start SSH.
+Start-Service sshd
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -572,6 +572,7 @@
'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php',
'DrydockAllocationContext' => 'applications/drydock/util/DrydockAllocationContext.php',
'DrydockAllocatorWorker' => 'applications/drydock/worker/DrydockAllocatorWorker.php',
+ 'DrydockAmazonEC2HostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAmazonEC2HostBlueprintImplementation.php',
'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php',
'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php',
'DrydockBlueprintController' => 'applications/drydock/controller/DrydockBlueprintController.php',
@@ -2762,6 +2763,7 @@
'UserQueryConduitAPIMethod' => 'applications/people/conduit/UserQueryConduitAPIMethod.php',
'UserRemoveStatusConduitAPIMethod' => 'applications/people/conduit/UserRemoveStatusConduitAPIMethod.php',
'UserWhoAmIConduitAPIMethod' => 'applications/people/conduit/UserWhoAmIConduitAPIMethod.php',
+ 'WindowsZeroConf' => 'applications/drydock/blueprint/windows/WindowsZeroConf.php',
),
'function' => array(
'_phabricator_time_format' => 'view/viewutils.php',
@@ -3328,6 +3330,7 @@
'DoorkeeperTagsController' => 'PhabricatorController',
'DrydockAllocationContext' => 'Phobject',
'DrydockAllocatorWorker' => 'PhabricatorWorker',
+ 'DrydockAmazonEC2HostBlueprintImplementation' => 'DrydockMinMaxExpiryBlueprintImplementation',
'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface',
'DrydockBlueprint' => array(
'DrydockDAO',
@@ -5760,5 +5763,6 @@
'UserQueryConduitAPIMethod' => 'UserConduitAPIMethod',
'UserRemoveStatusConduitAPIMethod' => 'UserConduitAPIMethod',
'UserWhoAmIConduitAPIMethod' => 'UserConduitAPIMethod',
+ 'WindowsZeroConf' => 'Phobject',
),
));
diff --git a/src/applications/drydock/blueprint/DrydockAmazonEC2HostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAmazonEC2HostBlueprintImplementation.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/blueprint/DrydockAmazonEC2HostBlueprintImplementation.php
@@ -0,0 +1,499 @@
+<?php
+
+final class DrydockAmazonEC2HostBlueprintImplementation
+ extends DrydockMinMaxExpiryBlueprintImplementation {
+
+ public function isEnabled() {
+ // This blueprint is only available if the Amazon EC2 keys are configured.
+ return
+ PhabricatorEnv::getEnvConfig('amazon-ec2.access-key') &&
+ PhabricatorEnv::getEnvConfig('amazon-ec2.secret-key');
+ }
+
+ public function getBlueprintName() {
+ return pht('Amazon EC2 Remote Hosts');
+ }
+
+ public function getDescription() {
+ return pht(
+ 'Allows Drydock to allocate and execute commands on '.
+ 'Amazon EC2 remote hosts.');
+ }
+
+ private function getAWSEC2Future() {
+ return id(new PhutilAWSEC2Future())
+ ->setAWSKeys(
+ PhabricatorEnv::getEnvConfig('amazon-ec2.access-key'),
+ PhabricatorEnv::getEnvConfig('amazon-ec2.secret-key'))
+ ->setAWSRegion($this->getDetail('region'));
+ }
+
+ private function getAWSKeyPairName() {
+ return 'phabricator-'.$this->getDetail('keypair');
+ }
+
+ public function canAllocateResourceForLease(DrydockLease $lease) {
+ return
+ $lease->getAttribute('platform') === $this->getDetail('platform');
+ }
+
+ protected function canAllocateLease(
+ DrydockResource $resource,
+ DrydockLease $lease) {
+ return
+ $lease->getAttribute('platform') === $resource->getAttribute('platform');
+ }
+
+ protected function executeAllocateResource(
+ DrydockResource $resource,
+ DrydockLease $lease) {
+
+ // We need to retrieve this as we need to use it for both importing the
+ // key and looking up the ID for the resource attributes.
+ $credential = id(new PassphraseCredentialQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withPHIDs(array($this->getDetail('keypair')))
+ ->executeOne();
+
+ try {
+ $existing_keys = $this->getAWSEC2Future()
+ ->setRawAWSQuery(
+ 'DescribeKeyPairs',
+ array(
+ 'KeyName.0' => $this->getAWSKeyPairName()))
+ ->resolve();
+ } catch (PhutilAWSException $ex) {
+ // The key pair does not exist, so we need to import it.
+
+ $type = PassphraseCredentialType::getTypeByConstant(
+ $credential->getCredentialType());
+ if (!$type) {
+ throw new Exception(pht('Credential has invalid type "%s"!', $type));
+ }
+
+ if (!$type->hasPublicKey()) {
+ throw new Exception(pht('Credential has no public key!'));
+ }
+
+ $public_key = $type->getPublicKey(
+ PhabricatorUser::getOmnipotentUser(),
+ $credential);
+
+ $this->getAWSEC2Future()
+ ->setRawAWSQuery(
+ 'ImportKeyPair',
+ array(
+ 'KeyName' => $this->getAWSKeyPairName(),
+ 'PublicKeyMaterial' => base64_encode($public_key)))
+ ->resolve();
+ }
+
+ $settings = array(
+ 'ImageId' => $this->getDetail('ami'),
+ 'MinCount' => 1,
+ 'MaxCount' => 1,
+ 'KeyName' => $this->getAWSKeyPairName(),
+ 'InstanceType' => $this->getDetail('size'),
+ 'SubnetId' => $this->getDetail('subnet-id')
+ );
+
+ $i = 0;
+ $security_groups = explode(',', $this->getDetail('security-group-ids'));
+ foreach ($security_groups as $security_group) {
+ $settings['SecurityGroupId.'.$i] = $security_group;
+ $i++;
+ }
+
+ if (!$this->getDetail('skip-ssh-setup-windows')) {
+ if ($this->getDetail('platform') === 'windows') {
+ $settings['UserData'] = id(new WindowsZeroConf())
+ ->getEncodedUserData($credential);
+ }
+ }
+
+ $result = $this->getAWSEC2Future()
+ ->setRawAWSQuery(
+ 'RunInstances',
+ $settings)
+ ->resolve();
+
+ $instance = $result->instancesSet->item[0];
+ $instance_id = (string)$instance->instanceId;
+
+ // Allocate the resource and place it into Pending status while
+ // we wait for the instance to start.
+ $blueprint = $this->getInstance();
+ $resource
+ ->setName($instance_id)
+ ->setStatus(DrydockResourceStatus::STATUS_PENDING)
+ ->setAttributes(array(
+ 'instance-id' => $instance_id,
+ 'platform' => $this->getDetail('platform'),
+ 'path' => $this->getDetail('storage-path'),
+ 'credential' => $credential->getID()))
+ ->save();
+
+ // Wait until the instance has started.
+ while (true) {
+ try {
+ $result = $this->getAWSEC2Future()
+ ->setRawAWSQuery(
+ 'DescribeInstances',
+ array(
+ 'InstanceId.0' => $instance_id))
+ ->resolve();
+
+ $reservation = $result->reservationSet->item[0];
+ $instance = $reservation->instancesSet->item[0];
+ $instance_state = (string)$instance->instanceState->name;
+
+ if ($instance_state === 'pending') {
+ sleep(5);
+ continue;
+ } else if ($instance_state === 'running') {
+ break;
+ } else {
+ // Instance is shutting down or is otherwise terminated.
+ throw new Exception(
+ 'Allocated instance, but ended up in unexpected state \''.
+ $instance_state.'\'!');
+ }
+ } catch (PhutilAWSException $ex) {
+ // TODO: This can happen because the instance doesn't exist yet, but
+ // we should check specifically for that error.
+ sleep(5);
+ continue;
+ }
+ }
+
+ // Calculate the IP address of the instance.
+ $address = '';
+ if ($this->getDetail('allocate-elastic-ip')) {
+ $resource->setAttribute('eip-allocated', true);
+
+ try {
+ // Allocate, assign and use a public IP address.
+ $result = $this->getAWSEC2Future()
+ ->setRawAWSQuery(
+ 'AllocateAddress',
+ array(
+ 'Domain' => 'vpc'))
+ ->resolve();
+
+ $public_ip = (string)$result->publicIp;
+ $allocation_id = (string)$result->allocationId;
+
+ $resource->setAttribute('eip-allocation-id', $allocation_id);
+
+ $result = $this->getAWSEC2Future()
+ ->setRawAWSQuery(
+ 'AssociateAddress',
+ array(
+ 'InstanceId' => $instance_id,
+ 'AllocationId' => $allocation_id))
+ ->resolve();
+
+ $association_id = (string)$result->associationId;
+ $resource->setAttribute('eip-association-id', $association_id);
+
+ $address = $public_ip;
+ } catch (PhutilAWSException $ex) {
+ // We can't allocate an Elastic IP (probably because we've reached
+ // the maximum allowed on the account). Terminate the EC2 instance
+ // we just started and fail the resource allocation.
+ $this->getAWSEC2Future()
+ ->setRawAWSQuery(
+ 'TerminateInstances',
+ array(
+ 'InstanceId.0' => $instance_id))
+ ->resolve();
+
+ throw new Exception(
+ 'Unable to allocate an elastic IP for the new EC2 instance. '.
+ 'Check your AWS account limits and ensure your limit on '.
+ 'elastic IP addresses is high enough to complete the '.
+ 'resource allocation');
+ }
+ } else {
+ $resource->setAttribute('eip-allocated', false);
+
+ // Use the private IP address.
+ $result = $this->getAWSEC2Future()
+ ->setRawAWSQuery(
+ 'DescribeInstances',
+ array(
+ 'InstanceId.0' => $instance_id))
+ ->resolve();
+
+ $reservation = $result->reservationSet->item[0];
+ $instance = $reservation->instancesSet->item[0];
+
+ $address = (string)$instance->privateIpAddress;
+ }
+
+ // Update address and port attributes.
+ $resource->setAttribute('host', $address);
+ $resource->setAttribute('port', 22);
+ $resource->save();
+
+ // Wait until we get a successful SSH connection.
+ $ssh = id(new DrydockSSHCommandInterface())
+ ->setConfiguration(array(
+ 'host' => $resource->getAttribute('host'),
+ 'port' => $resource->getAttribute('port'),
+ 'credential' => $resource->getAttribute('credential'),
+ 'platform' => $resource->getAttribute('platform')));
+ $ssh->setConnectTimeout(60);
+
+ while (true) {
+ try {
+ $ssh->getExecFuture('echo "test"')->resolvex();
+ break;
+ } catch (Exception $ex) {
+
+ // Make sure the instance hasn't been terminated or shutdown while
+ // we've been trying to connect.
+ $result = $this->getAWSEC2Future()
+ ->setRawAWSQuery(
+ 'DescribeInstances',
+ array(
+ 'InstanceId.0' => $instance_id))
+ ->resolve();
+
+ $reservation = $result->reservationSet->item[0];
+ $instance = $reservation->instancesSet->item[0];
+ $instance_state = (string)$instance->instanceState->name;
+
+ if ($instance_state === 'shutting-down' ||
+ $instance_state === 'terminated') {
+
+ // Deallocate and release the public IP address if we allocated one.
+ if ($resource->getAttribute('eip-allocated')) {
+ try {
+ $this->getAWSEC2Future()
+ ->setRawAWSQuery(
+ 'DisassociateAddress',
+ array(
+ 'AssociationId' =>
+ $resource->getAttribute('eip-association-id')))
+ ->resolve();
+ } catch (PhutilAWSException $ex) {
+ }
+
+ try {
+ $this->getAWSEC2Future()
+ ->setRawAWSQuery(
+ 'ReleaseAddress',
+ array(
+ 'AllocationId' =>
+ $resource->getAttribute('eip-allocation-id')))
+ ->resolve();
+ } catch (PhutilAWSException $ex) {
+ }
+ }
+
+ throw new Exception(
+ 'Allocated instance, but ended up in unexpected state \''.
+ $instance_state.'\'!');
+ }
+
+ continue;
+ }
+ }
+
+ // Update the resource into open status.
+ $resource->setStatus(DrydockResourceStatus::STATUS_OPEN);
+ $resource->save();
+ return $resource;
+ }
+
+ protected function executeCloseResource(DrydockResource $resource) {
+
+ // Deallocate and release the public IP address if we allocated one.
+ if ($resource->getAttribute('eip-allocated')) {
+ $this->getAWSEC2Future()
+ ->setRawAWSQuery(
+ 'DisassociateAddress',
+ array(
+ 'AssociationId' => $resource->getAttribute('eip-association-id')))
+ ->resolve();
+
+ $this->getAWSEC2Future()
+ ->setRawAWSQuery(
+ 'ReleaseAddress',
+ array(
+ 'AllocationId' => $resource->getAttribute('eip-allocation-id')))
+ ->resolve();
+ }
+
+ // Terminate the EC2 instance.
+ $this->getAWSEC2Future()
+ ->setRawAWSQuery(
+ 'TerminateInstances',
+ array(
+ 'InstanceId.0' => $resource->getAttribute('instance-id')))
+ ->resolve();
+
+ }
+
+ protected function executeAcquireLease(
+ DrydockResource $resource,
+ DrydockLease $lease) {
+
+ while ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {
+ // This resource is still being set up by another allocator, wait until
+ // it is set to open.
+ sleep(5);
+ $resource->reload();
+ }
+
+ $platform = $resource->getAttribute('platform');
+ $path = $resource->getAttribute('path');
+
+ $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 ($platform === 'windows') {
+ $separator = '\\';
+ }
+
+ // Clean up the directory path a little.
+ $base_path = rtrim($path, '/');
+ $base_path = rtrim($base_path, '\\');
+ $full_path = $base_path.$separator.$lease_id;
+
+ $cmd = $lease->getInterface('command');
+
+ $cmd->execx('mkdir %s', $full_path);
+
+ $lease->setAttribute('path', $full_path);
+ }
+
+ protected function executeReleaseLease(
+ DrydockResource $resource,
+ DrydockLease $lease) {
+
+ $cmd = $lease->getInterface('command');
+ $path = $lease->getAttribute('path');
+
+ if ($resource->getAttribute('platform') !== 'windows') {
+ $cmd->execx('rm -rf %s', $path);
+ } else {
+ $cmd->execx('rm -Recurse -Force %s', $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'),
+ 'credential' => $resource->getAttribute('credential'),
+ 'platform' => $resource->getAttribute('platform')))
+ ->setWorkingDirectory($lease->getAttribute('path'));
+ case 'filesystem':
+ return id(new DrydockSFTPFilesystemInterface())
+ ->setConfiguration(array(
+ 'host' => $resource->getAttribute('host'),
+ 'port' => $resource->getAttribute('port'),
+ 'credential' => $resource->getAttribute('credential')));
+ }
+
+ throw new Exception("No interface of type '{$type}'.");
+ }
+
+ public function getFieldSpecifications() {
+ return array(
+ 'amazon' => array(
+ 'name' => pht('Amazon Configuration'),
+ 'type' => 'header'
+ ),
+ 'region' => array(
+ 'name' => pht('Region'),
+ 'type' => 'text',
+ 'required' => true,
+ 'caption' => pht('e.g. %s', 'us-west-1')
+ ),
+ 'ami' => array(
+ 'name' => pht('AMI (Amazon Image)'),
+ 'type' => 'text',
+ 'required' => true,
+ 'caption' => pht('e.g. %s', 'ami-7fd3ae4f')
+ ),
+ 'keypair' => array(
+ 'name' => pht('Key Pair'),
+ 'type' => 'credential',
+ 'required' => true,
+ 'credential.provides'
+ => PassphraseCredentialTypeSSHPrivateKey::PROVIDES_TYPE,
+ 'caption' => pht(
+ 'Only the public key component is transmitted to Amazon.')
+ ),
+ 'size' => array(
+ 'name' => pht('Instance Size'),
+ 'type' => 'text',
+ 'required' => true,
+ 'caption' => pht('e.g. %s', 't2.micro')
+ ),
+ 'platform' => array(
+ 'name' => pht('Platform Name'),
+ 'type' => 'text',
+ 'required' => true,
+ 'caption' => pht('e.g. %s or %s', 'windows', 'linux')
+ ),
+ 'subnet-id' => array(
+ 'name' => pht('VPC Subnet'),
+ 'type' => 'text',
+ 'required' => true,
+ 'caption' => pht('e.g. %s', 'subnet-2a67439b')
+ ),
+ 'security-group-ids' => array(
+ 'name' => pht('VPC Security Groups'),
+ 'type' => 'text',
+ 'required' => true,
+ 'caption' => pht('e.g. %s', 'sg-3fa3491f,sg-bg18dea2')
+ ),
+ 'storage-path' => array(
+ 'name' => pht('Storage Path'),
+ 'type' => 'text',
+ 'required' => true,
+ 'caption' => pht(
+ 'A writable location on the instance where new directories / files '.
+ 'can be created and data can be stored in.')
+ ),
+ 'allocate-elastic-ip' => array(
+ 'name' => pht('Allocate Public IP'),
+ 'type' => 'bool',
+ 'caption' => pht(
+ 'If Phabricator is running in the same subnet as the allocated '.
+ 'machines, then you do not need to turn this option on. If '.
+ 'phabricator is hosted externally to Amazon EC2, then enable this '.
+ 'option to automatically allocate and assign elastic IP addresses '.
+ 'to instances so that Phabricator can SSH to them from the '.
+ 'internet (instances are still only accessible by SSH key pairs)')
+ ),
+ 'skip-ssh-setup-windows' => array(
+ 'name' => pht('Skip SSH setup on Windows'),
+ 'type' => 'bool',
+ 'caption' => pht(
+ 'If SSH is already configured on a Windows AMI, check this option. '.
+ 'By default, Phabricator will automatically install and configure '.
+ 'SSH on the Windows image.')
+ ),
+ ) + parent::getFieldSpecifications();
+ }
+
+}
diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
--- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
@@ -342,6 +342,10 @@
return true;
}
+ public function canAllocateResourceForLease(DrydockLease $lease) {
+ return true;
+ }
+
abstract protected function executeAllocateResource(
DrydockResource $resource,
DrydockLease $lease);
diff --git a/src/applications/drydock/blueprint/windows/WindowsZeroConf.php b/src/applications/drydock/blueprint/windows/WindowsZeroConf.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/blueprint/windows/WindowsZeroConf.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Responsible for configuring and automatically installing SSH on Windows
+ * EC2 instances when they start.
+ */
+final class WindowsZeroConf extends Phobject {
+
+ public function getEncodedUserData(PassphraseCredential $credential) {
+ return base64_encode($this->getUserData($credential));
+ }
+
+ private function getZeroConfScript() {
+ $file =
+ dirname(phutil_get_library_root('phabricator')).
+ '/resources/windows/zeroconf.ps1';
+ return Filesystem::readFile($file);
+ }
+
+ private function getUserData(PassphraseCredential $credential) {
+
+ $type = PassphraseCredentialType::getTypeByConstant(
+ $credential->getCredentialType());
+ if (!$type) {
+ throw new Exception(pht('Credential has invalid type "%s"!', $type));
+ }
+
+ if (!$type->hasPublicKey()) {
+ throw new Exception(pht('Credential has no public key!'));
+ }
+
+ $username = $credential->getUsername();
+ $publickey = $type->getPublicKey(
+ PhabricatorUser::getOmnipotentUser(),
+ $credential);
+ $publickey = trim($publickey);
+
+ $username = str_replace('"', '`"', $username);
+ $publickey = str_replace('"', '`"', $publickey);
+
+ $start = <<<EOF
+<powershell>
+\$username = "$username";
+\$publickey = "$publickey";
+
+EOF;
+
+ $script = $this->getZeroConfScript();
+
+ $end = <<<EOF
+
+</powershell>
+EOF;
+
+ return $start.$script.$end;
+ }
+
+}
diff --git a/src/applications/drydock/worker/DrydockAllocatorWorker.php b/src/applications/drydock/worker/DrydockAllocatorWorker.php
--- a/src/applications/drydock/worker/DrydockAllocatorWorker.php
+++ b/src/applications/drydock/worker/DrydockAllocatorWorker.php
@@ -173,6 +173,12 @@
unset($blueprints[$key]);
continue;
}
+
+ if ($candidate_blueprint->getType() !==
+ $lease->getResourceType()) {
+ unset($blueprints[$key]);
+ continue;
+ }
}
$this->logToDrydock(
@@ -196,6 +202,11 @@
unset($blueprints[$key]);
continue;
}
+
+ if (!$candidate_blueprint->canAllocateResourceForLease($lease)) {
+ unset($blueprints[$key]);
+ continue;
+ }
}
$this->logToDrydock(
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Mar 22, 1:02 PM (1 d, 19 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7709395
Default Alt Text
D10395.id25015.diff (25 KB)
Attached To
Mode
D10395: [drydock/aws] Implement Amazon EC2 blueprint for Drydock
Attached
Detach File
Event Timeline
Log In to Comment