From f9c6a69d9cc4068c048cb1f6e2614221704c9161 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 10 Apr 2018 05:50:49 -0700 Subject: [PATCH] Add skeleton code for Almanac Interfaces to have real transactions Summary: Depends on D19322. Ref T13120. Ref T12414. Currently, `AlmanacDevice` has a bit of a beast of a `TYPE_INTERFACE` transaction that fully creates a complex Interface object. This isn't very flexible or consistent, and Interfaces are complex enough to reasonably have their own object behaviors (for example, they have their own PHIDs). The complexity of this transaction makes modularizing `AlmanacDevice` transactions tricky. To simplify this, move Interface toward having its own set of normal transactions. This change just adds some reasonable-looking transactions; it doesn't actually hook them up in the UI or make them reachable. I'll test that they actually work as I swap the UI over. We may also have some code using the `TYPE_INTERFACE` transaction in Phacility support stuff, so that may need to wait a week to actually phase out. Test Plan: Ran `bin/storage upgrade` and `arc liberate`. This code isn't reachable yet. Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13120, T12414 Differential Revision: https://secure.phabricator.com/D19323 --- .../20180410.almanac.01.iface.xaction.sql | 19 ++++++ src/__phutil_library_map__.php | 15 +++++ .../almanac/editor/AlmanacInterfaceEditor.php | 22 +++++++ .../almanac/storage/AlmanacInterface.php | 25 +++++++- .../storage/AlmanacInterfaceTransaction.php | 22 +++++++ .../AlmanacInterfaceAddressTransaction.php | 44 ++++++++++++++ .../AlmanacInterfaceDeviceTransaction.php | 60 +++++++++++++++++++ .../AlmanacInterfaceNetworkTransaction.php | 53 ++++++++++++++++ .../AlmanacInterfacePortTransaction.php | 53 ++++++++++++++++ .../AlmanacInterfaceTransactionType.php | 4 ++ 10 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20180410.almanac.01.iface.xaction.sql create mode 100644 src/applications/almanac/editor/AlmanacInterfaceEditor.php create mode 100644 src/applications/almanac/storage/AlmanacInterfaceTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacInterfaceAddressTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacInterfacePortTransaction.php create mode 100644 src/applications/almanac/xaction/AlmanacInterfaceTransactionType.php diff --git a/resources/sql/autopatches/20180410.almanac.01.iface.xaction.sql b/resources/sql/autopatches/20180410.almanac.01.iface.xaction.sql new file mode 100644 index 0000000000..5f0dec18aa --- /dev/null +++ b/resources/sql/autopatches/20180410.almanac.01.iface.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_interfacetransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7cf2cbbf12..08806ab1b9 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -57,12 +57,19 @@ phutil_register_library_map(array( 'AlmanacDrydockPoolServiceType' => 'applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php', 'AlmanacEditor' => 'applications/almanac/editor/AlmanacEditor.php', 'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php', + 'AlmanacInterfaceAddressTransaction' => 'applications/almanac/xaction/AlmanacInterfaceAddressTransaction.php', 'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php', 'AlmanacInterfaceDeleteController' => 'applications/almanac/controller/AlmanacInterfaceDeleteController.php', + 'AlmanacInterfaceDeviceTransaction' => 'applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php', 'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php', + 'AlmanacInterfaceEditor' => 'applications/almanac/editor/AlmanacInterfaceEditor.php', + 'AlmanacInterfaceNetworkTransaction' => 'applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php', 'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php', + 'AlmanacInterfacePortTransaction' => 'applications/almanac/xaction/AlmanacInterfacePortTransaction.php', 'AlmanacInterfaceQuery' => 'applications/almanac/query/AlmanacInterfaceQuery.php', 'AlmanacInterfaceTableView' => 'applications/almanac/view/AlmanacInterfaceTableView.php', + 'AlmanacInterfaceTransaction' => 'applications/almanac/storage/AlmanacInterfaceTransaction.php', + 'AlmanacInterfaceTransactionType' => 'applications/almanac/xaction/AlmanacInterfaceTransactionType.php', 'AlmanacKeys' => 'applications/almanac/util/AlmanacKeys.php', 'AlmanacManageClusterServicesCapability' => 'applications/almanac/capability/AlmanacManageClusterServicesCapability.php', 'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php', @@ -5243,13 +5250,21 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', 'PhabricatorExtendedPolicyInterface', + 'PhabricatorApplicationTransactionInterface', ), + 'AlmanacInterfaceAddressTransaction' => 'AlmanacNetworkTransactionType', 'AlmanacInterfaceDatasource' => 'PhabricatorTypeaheadDatasource', 'AlmanacInterfaceDeleteController' => 'AlmanacDeviceController', + 'AlmanacInterfaceDeviceTransaction' => 'AlmanacNetworkTransactionType', 'AlmanacInterfaceEditController' => 'AlmanacDeviceController', + 'AlmanacInterfaceEditor' => 'PhabricatorApplicationTransactionEditor', + 'AlmanacInterfaceNetworkTransaction' => 'AlmanacNetworkTransactionType', 'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType', + 'AlmanacInterfacePortTransaction' => 'AlmanacNetworkTransactionType', 'AlmanacInterfaceQuery' => 'AlmanacQuery', 'AlmanacInterfaceTableView' => 'AphrontView', + 'AlmanacInterfaceTransaction' => 'PhabricatorModularTransaction', + 'AlmanacInterfaceTransactionType' => 'AlmanacTransactionType', 'AlmanacKeys' => 'Phobject', 'AlmanacManageClusterServicesCapability' => 'PhabricatorPolicyCapability', 'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow', diff --git a/src/applications/almanac/editor/AlmanacInterfaceEditor.php b/src/applications/almanac/editor/AlmanacInterfaceEditor.php new file mode 100644 index 0000000000..066b026dbd --- /dev/null +++ b/src/applications/almanac/editor/AlmanacInterfaceEditor.php @@ -0,0 +1,22 @@ +delete(); } + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new AlmanacInterfaceEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new AlmanacInterfaceTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + } diff --git a/src/applications/almanac/storage/AlmanacInterfaceTransaction.php b/src/applications/almanac/storage/AlmanacInterfaceTransaction.php new file mode 100644 index 0000000000..f469f926f7 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacInterfaceTransaction.php @@ -0,0 +1,22 @@ +getAddress(); + } + + public function applyInternalEffects($object, $value) { + $object->setAddress($value); + } + + public function getTitle() { + return pht( + '%s changed the address for this interface from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getAddress(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Interfaces must have an address.')); + } + + foreach ($xactions as $xaction) { + + // NOTE: For now, we don't validate addresses. We generally expect users + // to provide IPv4 addresses, but it's reasonable for them to provide + // IPv6 addresses, and some installs currently use DNS names. This is + // off-label but works today. + + } + + return $errors; + } + +} diff --git a/src/applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php b/src/applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php new file mode 100644 index 0000000000..3c4bf6045b --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php @@ -0,0 +1,60 @@ +getDevicePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setDevicePHID($value); + } + + public function getTitle() { + return pht( + '%s changed the device for this interface from %s to %s.', + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getAddress(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Interfaces must have a device.')); + } + + foreach ($xactions as $xaction) { + if (!$this->isNewObject()) { + $errors[] = $this->newInvalidError( + pht( + 'The device for an interface can not be changed once it has '. + 'been created.'), + $xaction); + continue; + } + + $device_phid = $xaction->getNewValue(); + $devices = id(new AlmanacDeviceQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($device_phid)) + ->execute(); + if (!$devices) { + $errors[] = $this->newInvalidError( + pht( + 'You can not attach an interface to a nonexistent or restricted '. + 'device.'), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php b/src/applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php new file mode 100644 index 0000000000..50353d904d --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php @@ -0,0 +1,53 @@ +getNetworkPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setNetworkPHID($value); + } + + public function getTitle() { + return pht( + '%s changed the network for this interface from %s to %s.', + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $network_phid = $object->getNetworkPHID(); + if ($this->isEmptyTextTransaction($network_phid, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Interfaces must have a network.')); + } + + foreach ($xactions as $xaction) { + $network_phid = $xaction->getNewValue(); + + $networks = id(new AlmanacNetworkQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($network_phid)) + ->execute(); + if (!$networks) { + $errors[] = $this->newInvalidError( + pht( + 'You can not put an interface on a nonexistent or restricted '. + 'network.'), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/almanac/xaction/AlmanacInterfacePortTransaction.php b/src/applications/almanac/xaction/AlmanacInterfacePortTransaction.php new file mode 100644 index 0000000000..c6a60dee61 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacInterfacePortTransaction.php @@ -0,0 +1,53 @@ +getPort(); + + if ($port !== null) { + $port = (int)$port; + } + + return $port; + } + + public function applyInternalEffects($object, $value) { + $object->setPort((int)$value); + } + + public function getTitle() { + return pht( + '%s changed the port for this interface from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Interfaces must have a port number.')); + } + + foreach ($xactions as $xaction) { + $port = $xaction->getNewValue(); + + $port = (int)$port; + if ($port < 1 || $port > 65535) { + $errors[] = $this->newInvalidError( + pht('Port numbers must be between 1 and 65535, inclusive.'), + $xaction); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/almanac/xaction/AlmanacInterfaceTransactionType.php b/src/applications/almanac/xaction/AlmanacInterfaceTransactionType.php new file mode 100644 index 0000000000..cf13663a18 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacInterfaceTransactionType.php @@ -0,0 +1,4 @@ +