diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'aced76a5', + 'core.pkg.css' => '203c9d69', 'core.pkg.js' => 'a590b451', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -129,7 +129,7 @@ 'rsrc/css/phui/phui-document.css' => '0267054b', 'rsrc/css/phui/phui-feed-story.css' => 'b7b26d23', 'rsrc/css/phui/phui-fontkit.css' => 'cb8ae7ad', - 'rsrc/css/phui/phui-form-view.css' => '621b21c5', + 'rsrc/css/phui/phui-form-view.css' => '07bafa88', 'rsrc/css/phui/phui-form.css' => 'afdb2c6e', 'rsrc/css/phui/phui-header-view.css' => '55bb32dd', 'rsrc/css/phui/phui-icon.css' => 'b0a6b1b6', @@ -782,7 +782,7 @@ 'phui-font-icon-base-css' => '3dad2ae3', 'phui-fontkit-css' => 'cb8ae7ad', 'phui-form-css' => 'afdb2c6e', - 'phui-form-view-css' => '621b21c5', + 'phui-form-view-css' => '07bafa88', 'phui-header-view-css' => '55bb32dd', 'phui-icon-view-css' => 'b0a6b1b6', 'phui-image-mask-css' => '5a8b09c8', 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 @@ -816,6 +816,8 @@ 'DrydockConstants' => 'applications/drydock/constants/DrydockConstants.php', 'DrydockController' => 'applications/drydock/controller/DrydockController.php', 'DrydockCreateBlueprintsCapability' => 'applications/drydock/capability/DrydockCreateBlueprintsCapability.php', + 'DrydockCustomAttributes' => 'applications/drydock/util/DrydockCustomAttributes.php', + 'DrydockCustomAttributesTestCase' => 'applications/drydock/util/__tests__/DrydockCustomAttributesTestCase.php', 'DrydockDAO' => 'applications/drydock/storage/DrydockDAO.php', 'DrydockDefaultEditCapability' => 'applications/drydock/capability/DrydockDefaultEditCapability.php', 'DrydockDefaultViewCapability' => 'applications/drydock/capability/DrydockDefaultViewCapability.php', @@ -2901,6 +2903,7 @@ 'PhabricatorStandardCustomFieldRemarkup' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php', 'PhabricatorStandardCustomFieldSelect' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php', 'PhabricatorStandardCustomFieldText' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php', + 'PhabricatorStandardCustomFieldTextarea' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldTextarea.php', 'PhabricatorStandardCustomFieldUsers' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php', 'PhabricatorStandardPageView' => 'view/page/PhabricatorStandardPageView.php', 'PhabricatorStandardSelectCustomFieldDatasource' => 'infrastructure/customfield/datasource/PhabricatorStandardSelectCustomFieldDatasource.php', @@ -4500,6 +4503,8 @@ 'DrydockConstants' => 'Phobject', 'DrydockController' => 'PhabricatorController', 'DrydockCreateBlueprintsCapability' => 'PhabricatorPolicyCapability', + 'DrydockCustomAttributes' => 'Phobject', + 'DrydockCustomAttributesTestCase' => 'PhabricatorTestCase', 'DrydockDAO' => 'PhabricatorLiskDAO', 'DrydockDefaultEditCapability' => 'PhabricatorPolicyCapability', 'DrydockDefaultViewCapability' => 'PhabricatorPolicyCapability', @@ -6976,6 +6981,7 @@ 'PhabricatorStandardCustomFieldRemarkup' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldSelect' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldText' => 'PhabricatorStandardCustomField', + 'PhabricatorStandardCustomFieldTextarea' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldUsers' => 'PhabricatorStandardCustomFieldPHIDs', 'PhabricatorStandardPageView' => 'PhabricatorBarePageView', 'PhabricatorStandardSelectCustomFieldDatasource' => 'PhabricatorTypeaheadDatasource', 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 @@ -60,8 +60,30 @@ return $this; } + public function supportsAutomaticCustomAttributes() { + return true; + } + public function getFieldSpecifications() { - return array(); + if ($this->supportsAutomaticCustomAttributes()) { + return array( + 'attr-header' => array( + 'name' => pht('Custom Attributes'), + 'type' => 'header', + ), + 'attributes' => array( + 'name' => pht('Attributes'), + 'type' => 'textarea', + 'caption' => pht( + 'A newline separated list of custom blueprint '. + 'attributes. Each attribute should be specified in '. + 'a key=value format.'), + 'monospace' => true, + ), + ); + } else { + return array(); + } } public function getDetail($key, $default = null) { @@ -81,6 +103,16 @@ $scope = $this->pushActiveScope($resource, $lease); + if ($this->supportsAutomaticCustomAttributes()) { + $custom_match = DrydockCustomAttributes::hasRequirements( + $lease->getAttributes(), + $this->getDetail('attributes', '')); + + if (!$custom_match) { + return false; + } + } + return $this->canAllocateLease($resource, $lease); } diff --git a/src/applications/drydock/blueprint/DrydockMinMaxBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockMinMaxBlueprintImplementation.php --- a/src/applications/drydock/blueprint/DrydockMinMaxBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockMinMaxBlueprintImplementation.php @@ -94,6 +94,6 @@ 'overallocate the lease to an existing resource and '. 'exceed the limit specified here.'), ), - ); + ) + parent::getFieldSpecifications(); } } diff --git a/src/applications/drydock/util/DrydockCustomAttributes.php b/src/applications/drydock/util/DrydockCustomAttributes.php new file mode 100644 --- /dev/null +++ b/src/applications/drydock/util/DrydockCustomAttributes.php @@ -0,0 +1,48 @@ + $value) { + // Only compare custom attributes. + if (substr($key, 0, 5) === 'attr_') { + if ($value === true) { + if (!array_key_exists($provided, $key)) { + return false; + } + } else { + if (idx($provided, $key) !== $value) { + return false; + } + } + } + } + + return true; + } + +} diff --git a/src/applications/drydock/util/__tests__/DrydockCustomAttributesTestCase.php b/src/applications/drydock/util/__tests__/DrydockCustomAttributesTestCase.php new file mode 100644 --- /dev/null +++ b/src/applications/drydock/util/__tests__/DrydockCustomAttributesTestCase.php @@ -0,0 +1,27 @@ +assertEqual(array(), $result); + } + + public function testEmptyAttributes() { + $result = DrydockCustomAttributes::parse(''); + $this->assertEqual(array(), $result); + } + + public function testSingleAttribute() { + $result = DrydockCustomAttributes::parse('abc=123'); + $this->assertEqual(array('attr_abc' => '123'), $result); + } + + public function testMultipleAttributes() { + $result = DrydockCustomAttributes::parse("abc=123\nxyz=456"); + $this->assertEqual(array( + 'attr_abc' => '123', + 'attr_xyz' => '456', + ), $result); + } +} 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 @@ -220,6 +220,16 @@ continue; } + if ($candidate_blueprint->supportsAutomaticCustomAttributes()) { + $custom_match = DrydockCustomAttributes::hasRequirements( + $lease->getAttributes(), + $candidate_blueprint->getDetail('attributes', '')); + if (!$custom_match) { + unset($blueprints[$key]); + continue; + } + } + if (!$candidate_blueprint->canAllocateResourceForLease($lease)) { unset($blueprints[$key]); continue; diff --git a/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php @@ -32,13 +32,16 @@ $artifact = count($artifacts) > 0 ? head($artifacts) : null; if ($artifact === null) { + $custom_attributes = DrydockCustomAttributes::parse( + idx($settings, 'attributes', '')); + // Create the lease. $lease = id(new DrydockLease()) ->setResourceType('host') ->setAttributes( array( 'platform' => $settings['platform'], - )) + ) + $custom_attributes) ->queueForActivation(); // Create the associated artifact. @@ -87,6 +90,14 @@ 'type' => 'text', 'required' => true, ), + 'attributes' => array( + 'name' => pht('Required Attributes'), + 'type' => 'textarea', + 'caption' => pht( + 'A newline separated list of required host attributes. Each '. + 'attribute should be specified in a key=value format.'), + 'monospace' => true, + ), ); } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldTextarea.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldTextarea.php new file mode 100644 --- /dev/null +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldTextarea.php @@ -0,0 +1,74 @@ +getFieldConfigValue('monospace', false); + $custom_class = + $monospace ? 'aphront-form-control-textarea-monospace' : null; + + return id(new AphrontFormTextAreaControl()) + ->setLabel($this->getFieldName()) + ->setName($this->getFieldKey()) + ->setCaption($this->getCaption()) + ->setValue($this->getFieldValue()) + ->setCustomClass($custom_class); + } + + public function getStyleForPropertyView() { + return 'block'; + } + + public function getApplicationTransactionRemarkupBlocks( + PhabricatorApplicationTransaction $xaction) { + return array( + $xaction->getNewValue(), + ); + } + + public function renderPropertyViewValue(array $handles) { + $value = $this->getFieldValue(); + + if (!strlen($value)) { + return null; + } + + return phutil_tag( + 'pre', + array(), + $value); + } + + public function getApplicationTransactionTitle( + PhabricatorApplicationTransaction $xaction) { + $author_phid = $xaction->getAuthorPHID(); + + // TODO: Expose fancy transactions. + + return pht( + '%s edited %s.', + $xaction->renderHandleLink($author_phid), + $this->getFieldName()); + } + + public function shouldAppearInHerald() { + return true; + } + + public function getHeraldFieldConditions() { + return array( + HeraldAdapter::CONDITION_CONTAINS, + HeraldAdapter::CONDITION_NOT_CONTAINS, + HeraldAdapter::CONDITION_IS, + HeraldAdapter::CONDITION_IS_NOT, + HeraldAdapter::CONDITION_REGEXP, + ); + } + + +} diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -154,6 +154,10 @@ height: 24em; } +.aphront-form-control-textarea-monospace { + font-family: monospace; +} + .aphront-form-control-select .aphront-form-input { padding-top: 2px; }