Page MenuHomePhabricator

D10395.id25015.diff
No OneTemporary

D10395.id25015.diff

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

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)

Event Timeline