@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add `harbormaster.createartifact`

Summary:
Ref T8659. In the general case, this eventually allows build processes to do things like:

- Upload build results (like a ".app" or ".exe" or other binary).
- Pass complex results between build steps (e.g., build step A does something hard and build step B uses it to do something else).

Today, we're a long way away from having the infrastructure for that. However, it is useful to let third party build processes (like Jenkins) upload URIs that link back to the external build results.

This adds `harbormaster.createartifact` so they can do that. The only useful thing to do with this method today is have your Jenkins build do this:

params = array(
"uri": "https://jenkins.mycompany.com/build/23923/details/",
"name": "View Build Results in Jenkins",
"ui.external": true,
);
harbormaster.createartifact(target, 'uri', params);

Then (after the next diff) we'll show a link in Differential and a prominent link in Harbormaster. I didn't actually do the UI stuff in this diff since it's already pretty big.

This change moves a lot of code around, too:

- Adds PHIDs to artifacts.
- It modularizes build artifact types (currently "file", "host" and "URI").
- It formalizes build artifact parameters and construction:
- This lets me generate usable documentation about how to create artifacts.
- This prevents users from doing dangerous or policy-violating things.
- It does some other general modernization.

Test Plan:
{F715633}

{F715634}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T8659

Differential Revision: https://secure.phabricator.com/D13900

+714 -241
+5
resources/sql/autopatches/20150814.harbormater.artifact.phid.sql
··· 1 + ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildartifact 2 + ADD phid VARBINARY(64) NOT NULL AFTER id; 3 + 4 + UPDATE {$NAMESPACE}_harbormaster.harbormaster_buildartifact 5 + SET phid = CONCAT('PHID-HMBA-', id) WHERE phid = '';
+12
src/__phutil_library_map__.php
··· 915 915 'FundSchemaSpec' => 'applications/fund/storage/FundSchemaSpec.php', 916 916 'HarbormasterArcLintBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php', 917 917 'HarbormasterArcUnitBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php', 918 + 'HarbormasterArtifact' => 'applications/harbormaster/artifact/HarbormasterArtifact.php', 918 919 'HarbormasterAutotargetsTestCase' => 'applications/harbormaster/__tests__/HarbormasterAutotargetsTestCase.php', 919 920 'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php', 920 921 'HarbormasterBuildAbortedException' => 'applications/harbormaster/exception/HarbormasterBuildAbortedException.php', 921 922 'HarbormasterBuildActionController' => 'applications/harbormaster/controller/HarbormasterBuildActionController.php', 922 923 'HarbormasterBuildArcanistAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildArcanistAutoplan.php', 923 924 'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php', 925 + 'HarbormasterBuildArtifactPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php', 924 926 'HarbormasterBuildArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildArtifactQuery.php', 925 927 'HarbormasterBuildAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildAutoplan.php', 926 928 'HarbormasterBuildCommand' => 'applications/harbormaster/storage/HarbormasterBuildCommand.php', ··· 980 982 'HarbormasterCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php', 981 983 'HarbormasterConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php', 982 984 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 985 + 'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php', 983 986 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', 984 987 'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php', 988 + 'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php', 985 989 'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php', 990 + 'HarbormasterHostArtifact' => 'applications/harbormaster/artifact/HarbormasterHostArtifact.php', 986 991 'HarbormasterLeaseHostBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php', 987 992 'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php', 988 993 'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php', ··· 1018 1023 'HarbormasterTestBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterTestBuildStepGroup.php', 1019 1024 'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php', 1020 1025 'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php', 1026 + 'HarbormasterURIArtifact' => 'applications/harbormaster/artifact/HarbormasterURIArtifact.php', 1021 1027 'HarbormasterUnitMessagesController' => 'applications/harbormaster/controller/HarbormasterUnitMessagesController.php', 1022 1028 'HarbormasterUnitPropertyView' => 'applications/harbormaster/view/HarbormasterUnitPropertyView.php', 1023 1029 'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php', ··· 4603 4609 'FundSchemaSpec' => 'PhabricatorConfigSchemaSpec', 4604 4610 'HarbormasterArcLintBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 4605 4611 'HarbormasterArcUnitBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 4612 + 'HarbormasterArtifact' => 'Phobject', 4606 4613 'HarbormasterAutotargetsTestCase' => 'PhabricatorTestCase', 4607 4614 'HarbormasterBuild' => array( 4608 4615 'HarbormasterDAO', ··· 4616 4623 'HarbormasterDAO', 4617 4624 'PhabricatorPolicyInterface', 4618 4625 ), 4626 + 'HarbormasterBuildArtifactPHIDType' => 'PhabricatorPHIDType', 4619 4627 'HarbormasterBuildArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4620 4628 'HarbormasterBuildAutoplan' => 'Phobject', 4621 4629 'HarbormasterBuildCommand' => 'HarbormasterDAO', ··· 4700 4708 'HarbormasterCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 4701 4709 'HarbormasterConduitAPIMethod' => 'ConduitAPIMethod', 4702 4710 'HarbormasterController' => 'PhabricatorController', 4711 + 'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 4703 4712 'HarbormasterDAO' => 'PhabricatorLiskDAO', 4704 4713 'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup', 4714 + 'HarbormasterFileArtifact' => 'HarbormasterArtifact', 4705 4715 'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 4716 + 'HarbormasterHostArtifact' => 'HarbormasterArtifact', 4706 4717 'HarbormasterLeaseHostBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 4707 4718 'HarbormasterLintMessagesController' => 'HarbormasterController', 4708 4719 'HarbormasterLintPropertyView' => 'AphrontView', ··· 4738 4749 'HarbormasterTestBuildStepGroup' => 'HarbormasterBuildStepGroup', 4739 4750 'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation', 4740 4751 'HarbormasterUIEventListener' => 'PhabricatorEventListener', 4752 + 'HarbormasterURIArtifact' => 'HarbormasterArtifact', 4741 4753 'HarbormasterUnitMessagesController' => 'HarbormasterController', 4742 4754 'HarbormasterUnitPropertyView' => 'AphrontView', 4743 4755 'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
+16 -25
src/applications/drydock/query/DrydockLeaseQuery.php
··· 27 27 return $this; 28 28 } 29 29 30 + public function newResultObject() { 31 + return new DrydockLease(); 32 + } 33 + 30 34 protected function loadPage() { 31 - $table = new DrydockLease(); 32 - $conn_r = $table->establishConnection('r'); 33 - 34 - $data = queryfx_all( 35 - $conn_r, 36 - 'SELECT lease.* FROM %T lease %Q %Q %Q', 37 - $table->getTableName(), 38 - $this->buildWhereClause($conn_r), 39 - $this->buildOrderClause($conn_r), 40 - $this->buildLimitClause($conn_r)); 41 - 42 - return $table->loadAllFromArray($data); 35 + return $this->loadStandardPage($this->newResultObject()); 43 36 } 44 37 45 38 protected function willFilterPage(array $leases) { ··· 69 62 return $leases; 70 63 } 71 64 72 - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { 73 - $where = array(); 65 + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { 66 + $where = parent::buildWhereClauseParts($conn); 74 67 75 - if ($this->resourceIDs) { 68 + if ($this->resourceIDs !== null) { 76 69 $where[] = qsprintf( 77 - $conn_r, 70 + $conn, 78 71 'resourceID IN (%Ld)', 79 72 $this->resourceIDs); 80 73 } 81 74 82 - if ($this->ids) { 75 + if ($this->ids !== null) { 83 76 $where[] = qsprintf( 84 - $conn_r, 77 + $conn, 85 78 'id IN (%Ld)', 86 79 $this->ids); 87 80 } 88 81 89 - if ($this->phids) { 82 + if ($this->phids !== null) { 90 83 $where[] = qsprintf( 91 - $conn_r, 84 + $conn, 92 85 'phid IN (%Ls)', 93 86 $this->phids); 94 87 } 95 88 96 - if ($this->statuses) { 89 + if ($this->statuses !== null) { 97 90 $where[] = qsprintf( 98 - $conn_r, 91 + $conn, 99 92 'status IN (%Ld)', 100 93 $this->statuses); 101 94 } 102 95 103 - $where[] = $this->buildPagingClause($conn_r); 104 - 105 - return $this->formatWhereClause($where); 96 + return $where; 106 97 } 107 98 108 99 }
+88
src/applications/harbormaster/artifact/HarbormasterArtifact.php
··· 1 + <?php 2 + 3 + 4 + abstract class HarbormasterArtifact extends Phobject { 5 + 6 + private $buildArtifact; 7 + 8 + abstract public function getArtifactTypeName(); 9 + 10 + public function getArtifactTypeSummary() { 11 + return $this->getArtifactTypeDescription(); 12 + } 13 + 14 + abstract public function getArtifactTypeDescription(); 15 + abstract public function getArtifactParameterSpecification(); 16 + abstract public function getArtifactParameterDescriptions(); 17 + abstract public function willCreateArtifact(PhabricatorUser $actor); 18 + 19 + public function validateArtifactData(array $artifact_data) { 20 + $artifact_spec = $this->getArtifactParameterSpecification(); 21 + PhutilTypeSpec::checkMap($artifact_data, $artifact_spec); 22 + } 23 + 24 + public function renderArtifactSummary(PhabricatorUser $viewer) { 25 + return null; 26 + } 27 + 28 + public function releaseArtifact(PhabricatorUser $actor) { 29 + return; 30 + } 31 + 32 + public function getArtifactDataExample() { 33 + return null; 34 + } 35 + 36 + public function setBuildArtifact(HarbormasterBuildArtifact $build_artifact) { 37 + $this->buildArtifact = $build_artifact; 38 + return $this; 39 + } 40 + 41 + public function getBuildArtifact() { 42 + return $this->buildArtifact; 43 + } 44 + 45 + final public function getArtifactConstant() { 46 + $class = new ReflectionClass($this); 47 + 48 + $const = $class->getConstant('ARTIFACTCONST'); 49 + if ($const === false) { 50 + throw new Exception( 51 + pht( 52 + '"%s" class "%s" must define a "%s" property.', 53 + __CLASS__, 54 + get_class($this), 55 + 'ARTIFACTCONST')); 56 + } 57 + 58 + $limit = self::getArtifactConstantByteLimit(); 59 + if (!is_string($const) || (strlen($const) > $limit)) { 60 + throw new Exception( 61 + pht( 62 + '"%s" class "%s" has an invalid "%s" property. Action constants '. 63 + 'must be strings and no more than %s bytes in length.', 64 + __CLASS__, 65 + get_class($this), 66 + 'ARTIFACTCONST', 67 + new PhutilNumber($limit))); 68 + } 69 + 70 + return $const; 71 + } 72 + 73 + final public static function getArtifactConstantByteLimit() { 74 + return 32; 75 + } 76 + 77 + final public static function getAllArtifactTypes() { 78 + return id(new PhutilClassMapQuery()) 79 + ->setAncestorClass(__CLASS__) 80 + ->setUniqueMethod('getArtifactConstant') 81 + ->execute(); 82 + } 83 + 84 + final public static function getArtifactType($type) { 85 + return idx(self::getAllArtifactTypes(), $type); 86 + } 87 + 88 + }
+66
src/applications/harbormaster/artifact/HarbormasterFileArtifact.php
··· 1 + <?php 2 + 3 + final class HarbormasterFileArtifact extends HarbormasterArtifact { 4 + 5 + const ARTIFACTCONST = 'file'; 6 + 7 + public function getArtifactTypeName() { 8 + return pht('File'); 9 + } 10 + 11 + public function getArtifactTypeDescription() { 12 + return pht( 13 + 'Stores a reference to file data which has been uploaded to '. 14 + 'Phabricator.'); 15 + } 16 + 17 + public function getArtifactParameterSpecification() { 18 + return array( 19 + 'filePHID' => 'string', 20 + ); 21 + } 22 + 23 + public function getArtifactParameterDescriptions() { 24 + return array( 25 + 'filePHID' => pht('File to create an artifact from.'), 26 + ); 27 + } 28 + 29 + public function getArtifactDataExample() { 30 + return array( 31 + 'filePHID' => 'PHID-FILE-abcdefghijklmnopqrst', 32 + ); 33 + } 34 + 35 + public function renderArtifactSummary(PhabricatorUser $viewer) { 36 + $artifact = $this->getBuildArtifact(); 37 + $file_phid = $artifact->getProperty('filePHID'); 38 + return $viewer->renderHandle($file_phid); 39 + } 40 + 41 + public function willCreateArtifact(PhabricatorUser $actor) { 42 + // NOTE: This is primarily making sure the actor has permission to view the 43 + // file. We don't want to let you run builds using files you don't have 44 + // permission to see, since this could let you violate permissions. 45 + $this->loadArtifactFile($actor); 46 + } 47 + 48 + public function loadArtifactFile(PhabricatorUser $viewer) { 49 + $artifact = $this->getBuildArtifact(); 50 + $file_phid = $artifact->getProperty('filePHID'); 51 + 52 + $file = id(new PhabricatorFileQuery()) 53 + ->setViewer($viewer) 54 + ->withPHIDs(array($file_phid)) 55 + ->executeOne(); 56 + if (!$file) { 57 + throw new Exception( 58 + pht( 59 + 'File PHID "%s" does not correspond to a valid file.', 60 + $file_phid)); 61 + } 62 + 63 + return $file; 64 + } 65 + 66 + }
+74
src/applications/harbormaster/artifact/HarbormasterHostArtifact.php
··· 1 + <?php 2 + 3 + final class HarbormasterHostArtifact extends HarbormasterArtifact { 4 + 5 + const ARTIFACTCONST = 'host'; 6 + 7 + public function getArtifactTypeName() { 8 + return pht('Drydock Host'); 9 + } 10 + 11 + public function getArtifactTypeDescription() { 12 + return pht('References a host lease from Drydock.'); 13 + } 14 + 15 + 16 + public function getArtifactParameterSpecification() { 17 + return array( 18 + 'drydockLeasePHID' => 'string', 19 + ); 20 + } 21 + 22 + public function getArtifactParameterDescriptions() { 23 + return array( 24 + 'drydockLeasePHID' => pht( 25 + 'Drydock host lease to create an artifact from.'), 26 + ); 27 + } 28 + 29 + public function getArtifactDataExample() { 30 + return array( 31 + 'drydockLeasePHID' => 'PHID-DRYL-abcdefghijklmnopqrst', 32 + ); 33 + } 34 + 35 + public function renderArtifactSummary(PhabricatorUser $viewer) { 36 + $artifact = $this->getBuildArtifact(); 37 + $file_phid = $artifact->getProperty('drydockLeasePHID'); 38 + return $viewer->renderHandle($file_phid); 39 + } 40 + 41 + public function willCreateArtifact(PhabricatorUser $actor) { 42 + $this->loadArtifactLease($actor); 43 + } 44 + 45 + public function loadArtifactLease(PhabricatorUser $viewer) { 46 + $artifact = $this->getBuildArtifact(); 47 + $lease_phid = $artifact->getProperty('drydockLeasePHID'); 48 + 49 + $lease = id(new DrydockLeaseQuery()) 50 + ->setViewer($viewer) 51 + ->withPHIDs(array($lease_phid)) 52 + ->executeOne(); 53 + if (!$lease) { 54 + throw new Exception( 55 + pht( 56 + 'Drydock lease PHID "%s" does not correspond to a valid lease.', 57 + $lease_phid)); 58 + } 59 + 60 + return $lease; 61 + } 62 + 63 + public function releaseArtifact(PhabricatorUser $actor) { 64 + $lease = $this->loadArtifactLease($actor); 65 + $resource = $lease->getResource(); 66 + $blueprint = $resource->getBlueprint(); 67 + 68 + if ($lease->isActive()) { 69 + $blueprint->releaseLease($resource, $lease); 70 + } 71 + } 72 + 73 + 74 + }
+100
src/applications/harbormaster/artifact/HarbormasterURIArtifact.php
··· 1 + <?php 2 + 3 + final class HarbormasterURIArtifact extends HarbormasterArtifact { 4 + 5 + const ARTIFACTCONST = 'uri'; 6 + 7 + public function getArtifactTypeName() { 8 + return pht('URI'); 9 + } 10 + 11 + public function getArtifactTypeSummary() { 12 + return pht('Stores a URI.'); 13 + } 14 + 15 + public function getArtifactTypeDescription() { 16 + return pht( 17 + "Stores a URI.\n\n". 18 + "With `ui.external`, you can use this artifact type to add links to ". 19 + "build results in an external build system."); 20 + } 21 + 22 + public function getArtifactParameterSpecification() { 23 + return array( 24 + 'uri' => 'string', 25 + 'name' => 'optional string', 26 + 'ui.external' => 'optional bool', 27 + ); 28 + } 29 + 30 + public function getArtifactParameterDescriptions() { 31 + return array( 32 + 'uri' => pht('The URI to store.'), 33 + 'name' => pht('Optional label for this URI.'), 34 + 'ui.external' => pht( 35 + 'If true, display this URI in the UI as an link to '. 36 + 'additional build details in an external build system.'), 37 + ); 38 + } 39 + 40 + public function getArtifactDataExample() { 41 + return array( 42 + 'uri' => 'https://buildserver.mycompany.com/build/123/', 43 + 'name' => pht('View External Build Results'), 44 + 'ui.external' => true, 45 + ); 46 + } 47 + 48 + public function renderArtifactSummary(PhabricatorUser $viewer) { 49 + $artifact = $this->getBuildArtifact(); 50 + $uri = $artifact->getProperty('uri'); 51 + 52 + try { 53 + $this->validateURI($uri); 54 + } catch (Exception $ex) { 55 + return pht('<Invalid URI>'); 56 + } 57 + 58 + $name = $artifact->getProperty('name', $uri); 59 + 60 + return phutil_tag( 61 + 'a', 62 + array( 63 + 'href' => $uri, 64 + 'target' => '_blank', 65 + ), 66 + $name); 67 + } 68 + 69 + public function willCreateArtifact(PhabricatorUser $actor) { 70 + $artifact = $this->getBuildArtifact(); 71 + $uri = $artifact->getProperty('uri'); 72 + $this->validateURI($uri); 73 + } 74 + 75 + private function validateURI($raw_uri) { 76 + $uri = new PhutilURI($raw_uri); 77 + 78 + $protocol = $uri->getProtocol(); 79 + if (!strlen($protocol)) { 80 + throw new Exception( 81 + pht( 82 + 'Unable to identify the protocol for URI "%s". URIs must be '. 83 + 'fully qualified and have an identifiable protocol.', 84 + $raw_uri)); 85 + } 86 + 87 + $protocol_key = 'uri.allowed-protocols'; 88 + $protocols = PhabricatorEnv::getEnvConfig($protocol_key); 89 + if (empty($protocols[$protocol])) { 90 + throw new Exception( 91 + pht( 92 + 'URI "%s" does not have an allowable protocol. Configure '. 93 + 'protocols in `%s`. Allowed protocols are: %s.', 94 + $raw_uri, 95 + $protocol_key, 96 + implode(', ', array_keys($protocols)))); 97 + } 98 + } 99 + 100 + }
+12
src/applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php
··· 15 15 return pht('All Harbormaster APIs are new and subject to change.'); 16 16 } 17 17 18 + protected function returnArtifactList(array $artifacts) { 19 + $list = array(); 20 + 21 + foreach ($artifacts as $artifact) { 22 + $list[] = array( 23 + 'phid' => $artifact->getPHID(), 24 + ); 25 + } 26 + 27 + return $list; 28 + } 29 + 18 30 }
+129
src/applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php
··· 1 + <?php 2 + 3 + final class HarbormasterCreateArtifactConduitAPIMethod 4 + extends HarbormasterConduitAPIMethod { 5 + 6 + public function getAPIMethodName() { 7 + return 'harbormaster.createartifact'; 8 + } 9 + 10 + public function getMethodSummary() { 11 + return pht('Create a build artifact.'); 12 + } 13 + 14 + public function getMethodDescription() { 15 + $types = HarbormasterArtifact::getAllArtifactTypes(); 16 + $types = msort($types, 'getArtifactTypeName'); 17 + 18 + $head_key = pht('Key'); 19 + $head_type = pht('Type'); 20 + $head_desc = pht('Description'); 21 + $head_atype = pht('Artifact Type'); 22 + $head_name = pht('Name'); 23 + $head_summary = pht('Summary'); 24 + 25 + $out = array(); 26 + $out[] = pht( 27 + 'Use this method to attach artifacts to build targets while running '. 28 + 'builds. Artifacts can be used to carry data through a complex build '. 29 + 'workflow, provide extra information to users, or store build results.'); 30 + $out[] = null; 31 + $out[] = pht( 32 + 'When creating an artifact, you will choose an `artifactType` from '. 33 + 'this table. These types of artifacts are supported:'); 34 + 35 + $out[] = "| {$head_atype} | {$head_name} | {$head_summary} |"; 36 + $out[] = '|-------------|--------------|--------------|'; 37 + foreach ($types as $type) { 38 + $type_name = $type->getArtifactTypeName(); 39 + $type_const = $type->getArtifactConstant(); 40 + $type_summary = $type->getArtifactTypeSummary(); 41 + $out[] = "| `{$type_const}` | **{$type_name}** | {$type_summary} |"; 42 + } 43 + 44 + $out[] = null; 45 + $out[] = pht( 46 + 'Each artifact also needs an `artifactKey`, which names the artifact. '. 47 + 'Finally, you will provide some `artifactData` to fill in the content '. 48 + 'of the artifact. The data you provide depends on what type of artifact '. 49 + 'you are creating.'); 50 + 51 + foreach ($types as $type) { 52 + $type_name = $type->getArtifactTypeName(); 53 + $type_const = $type->getArtifactConstant(); 54 + 55 + $out[] = $type_name; 56 + $out[] = '--------------------------'; 57 + $out[] = null; 58 + $out[] = $type->getArtifactTypeDescription(); 59 + $out[] = null; 60 + $out[] = pht( 61 + 'Create an artifact of this type by passing `%s` as the '. 62 + '`artifactType`. When creating an artifact of this type, provide '. 63 + 'these parameters as a dictionary to `artifactData`:', 64 + $type_const); 65 + 66 + $spec = $type->getArtifactParameterSpecification(); 67 + $desc = $type->getArtifactParameterDescriptions(); 68 + $out[] = "| {$head_key} | {$head_type} | {$head_desc} |"; 69 + $out[] = '|-------------|--------------|--------------|'; 70 + foreach ($spec as $key => $key_type) { 71 + $key_desc = idx($desc, $key); 72 + $out[] = "| `{$key}` | //{$key_type}// | {$key_desc} |"; 73 + } 74 + 75 + $example = $type->getArtifactDataExample(); 76 + if ($example !== null) { 77 + $json = new PhutilJSON(); 78 + $rendered = $json->encodeFormatted($example); 79 + 80 + $out[] = pht('For example:'); 81 + $out[] = '```lang=json'; 82 + $out[] = $rendered; 83 + $out[] = '```'; 84 + } 85 + } 86 + 87 + return implode("\n", $out); 88 + } 89 + 90 + protected function defineParamTypes() { 91 + return array( 92 + 'buildTargetPHID' => 'phid', 93 + 'artifactKey' => 'string', 94 + 'artifactType' => 'string', 95 + 'artifactData' => 'map<string, wild>', 96 + ); 97 + } 98 + 99 + protected function defineReturnType() { 100 + return 'wild'; 101 + } 102 + 103 + protected function execute(ConduitAPIRequest $request) { 104 + $viewer = $request->getUser(); 105 + 106 + $build_target_phid = $request->getValue('buildTargetPHID'); 107 + $build_target = id(new HarbormasterBuildTargetQuery()) 108 + ->setViewer($viewer) 109 + ->withPHIDs(array($build_target_phid)) 110 + ->executeOne(); 111 + if (!$build_target) { 112 + throw new Exception( 113 + pht( 114 + 'No such build target "%s"!', 115 + $build_target_phid)); 116 + } 117 + 118 + $artifact = $build_target->createArtifact( 119 + $viewer, 120 + $request->getValue('artifactKey'), 121 + $request->getValue('artifactType'), 122 + $request->getValue('artifactData')); 123 + 124 + return array( 125 + 'data' => $this->returnArtifactList(array($artifact)), 126 + ); 127 + } 128 + 129 + }
+36 -20
src/applications/harbormaster/controller/HarbormasterBuildViewController.php
··· 3 3 final class HarbormasterBuildViewController 4 4 extends HarbormasterController { 5 5 6 - private $id; 7 - 8 - public function willProcessRequest(array $data) { 9 - $this->id = $data['id']; 10 - } 11 - 12 - public function processRequest() { 6 + public function handleRequest(AphrontRequest $request) { 13 7 $request = $this->getRequest(); 14 8 $viewer = $request->getUser(); 15 9 16 - $id = $this->id; 10 + $id = $request->getURIData('id'); 17 11 $generation = $request->getInt('g'); 18 12 19 13 $build = id(new HarbormasterBuildQuery()) ··· 224 218 )); 225 219 } 226 220 227 - private function buildArtifacts( 228 - HarbormasterBuildTarget $build_target) { 229 - 230 - $request = $this->getRequest(); 231 - $viewer = $request->getUser(); 221 + private function buildArtifacts(HarbormasterBuildTarget $build_target) { 222 + $viewer = $this->getViewer(); 232 223 233 224 $artifacts = id(new HarbormasterBuildArtifactQuery()) 234 225 ->setViewer($viewer) 235 226 ->withBuildTargetPHIDs(array($build_target->getPHID())) 236 227 ->execute(); 237 228 238 - $list = id(new PHUIObjectItemListView()) 239 - ->setNoDataString(pht('This target has no associated artifacts.')) 240 - ->setFlush(true); 229 + $artifacts = msort($artifacts, 'getArtifactKey'); 241 230 231 + $rows = array(); 242 232 foreach ($artifacts as $artifact) { 243 - $item = $artifact->getObjectItemView($viewer); 244 - if ($item !== null) { 245 - $list->addItem($item); 233 + $impl = $artifact->getArtifactImplementation(); 234 + 235 + if ($impl) { 236 + $summary = $impl->renderArtifactSummary($viewer); 237 + $type_name = $impl->getArtifactTypeName(); 238 + } else { 239 + $summary = pht('<Unknown Artifact Type>'); 240 + $type_name = $artifact->getType(); 246 241 } 242 + 243 + $rows[] = array( 244 + $artifact->getArtifactKey(), 245 + $type_name, 246 + $summary, 247 + ); 247 248 } 248 249 249 - return $list; 250 + $table = id(new AphrontTableView($rows)) 251 + ->setNoDataString(pht('This target has no associated artifacts.')) 252 + ->setHeaders( 253 + array( 254 + pht('Key'), 255 + pht('Type'), 256 + pht('Summary'), 257 + )) 258 + ->setColumnClasses( 259 + array( 260 + 'pri', 261 + '', 262 + 'wide', 263 + )); 264 + 265 + return $table; 250 266 } 251 267 252 268 private function buildLog(
+1 -1
src/applications/harbormaster/engine/HarbormasterBuildEngine.php
··· 483 483 ->execute(); 484 484 485 485 foreach ($artifacts as $artifact) { 486 - $artifact->release(); 486 + $artifact->releaseArtifact(); 487 487 } 488 488 489 489 }
+35
src/applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php
··· 1 + <?php 2 + 3 + final class HarbormasterBuildArtifactPHIDType extends PhabricatorPHIDType { 4 + 5 + const TYPECONST = 'HMBA'; 6 + 7 + public function getTypeName() { 8 + return pht('Build Artifact'); 9 + } 10 + 11 + public function newObject() { 12 + return new HarbormasterBuildArtifact(); 13 + } 14 + 15 + protected function buildQueryForObjects( 16 + PhabricatorObjectQuery $query, 17 + array $phids) { 18 + 19 + return id(new HarbormasterBuildArtifactQuery()) 20 + ->withPHIDs($phids); 21 + } 22 + 23 + public function loadHandles( 24 + PhabricatorHandleQuery $query, 25 + array $handles, 26 + array $objects) { 27 + 28 + foreach ($handles as $phid => $handle) { 29 + $artifact = $objects[$phid]; 30 + $artifact_id = $artifact->getID(); 31 + $handle->setName(pht('Build Artifact %d', $artifact_id)); 32 + } 33 + } 34 + 35 + }
+20 -40
src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php
··· 6 6 private $ids; 7 7 private $buildTargetPHIDs; 8 8 private $artifactTypes; 9 - private $artifactKeys; 9 + private $artifactIndexes; 10 10 private $keyBuildPHID; 11 11 private $keyBuildGeneration; 12 12 ··· 25 25 return $this; 26 26 } 27 27 28 - public function withArtifactKeys( 29 - $build_phid, 30 - $build_gen, 31 - array $artifact_keys) { 32 - $this->keyBuildPHID = $build_phid; 33 - $this->keyBuildGeneration = $build_gen; 34 - $this->artifactKeys = $artifact_keys; 28 + public function withArtifactIndexes(array $artifact_indexes) { 29 + $this->artifactIndexes = $artifact_indexes; 35 30 return $this; 36 31 } 37 32 38 - protected function loadPage() { 39 - $table = new HarbormasterBuildArtifact(); 40 - $conn_r = $table->establishConnection('r'); 41 - 42 - $data = queryfx_all( 43 - $conn_r, 44 - 'SELECT * FROM %T %Q %Q %Q', 45 - $table->getTableName(), 46 - $this->buildWhereClause($conn_r), 47 - $this->buildOrderClause($conn_r), 48 - $this->buildLimitClause($conn_r)); 33 + public function newResultObject() { 34 + return new HarbormasterBuildArtifact(); 35 + } 49 36 50 - return $table->loadAllFromArray($data); 37 + protected function loadPage() { 38 + return $this->loadStandardPage($this->newResultObject()); 51 39 } 52 40 53 41 protected function willFilterPage(array $page) { ··· 75 63 return $page; 76 64 } 77 65 78 - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { 79 - $where = array(); 66 + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { 67 + $where = parent::buildWhereClauseParts($conn); 80 68 81 - if ($this->ids) { 69 + if ($this->ids !== null) { 82 70 $where[] = qsprintf( 83 - $conn_r, 71 + $conn, 84 72 'id IN (%Ld)', 85 73 $this->ids); 86 74 } 87 75 88 - if ($this->buildTargetPHIDs) { 76 + if ($this->buildTargetPHIDs !== null) { 89 77 $where[] = qsprintf( 90 - $conn_r, 78 + $conn, 91 79 'buildTargetPHID IN (%Ls)', 92 80 $this->buildTargetPHIDs); 93 81 } 94 82 95 - if ($this->artifactTypes) { 83 + if ($this->artifactTypes !== null) { 96 84 $where[] = qsprintf( 97 - $conn_r, 85 + $conn, 98 86 'artifactType in (%Ls)', 99 87 $this->artifactTypes); 100 88 } 101 89 102 - if ($this->artifactKeys) { 103 - $indexes = array(); 104 - foreach ($this->artifactKeys as $key) { 105 - $indexes[] = PhabricatorHash::digestForIndex( 106 - $this->keyBuildPHID.$this->keyBuildGeneration.$key); 107 - } 108 - 90 + if ($this->artifactIndexes !== null) { 109 91 $where[] = qsprintf( 110 - $conn_r, 92 + $conn, 111 93 'artifactIndex IN (%Ls)', 112 - $indexes); 94 + $this->artifactIndexes); 113 95 } 114 96 115 - $where[] = $this->buildPagingClause($conn_r); 116 - 117 - return $this->formatWhereClause($where); 97 + return $where; 118 98 } 119 99 120 100 public function getQueryApplicationClass() {
+6 -6
src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php
··· 43 43 $settings = $this->getSettings(); 44 44 $variables = $build_target->getVariables(); 45 45 46 - $artifact = $build->loadArtifact($settings['hostartifact']); 47 - 48 - $lease = $artifact->loadDrydockLease(); 46 + $artifact = $build_target->loadArtifact($settings['hostartifact']); 47 + $impl = $artifact->getArtifactImplementation(); 48 + $lease = $impl->loadArtifactLease(); 49 49 50 50 $this->platform = $lease->getAttribute('platform'); 51 51 ··· 120 120 public function getArtifactInputs() { 121 121 return array( 122 122 array( 123 - 'name' => pht('Run on Host'), 124 - 'key' => $this->getSetting('hostartifact'), 125 - 'type' => HarbormasterBuildArtifact::TYPE_HOST, 123 + 'name' => pht('Run on Host'), 124 + 'key' => $this->getSetting('hostartifact'), 125 + 'type' => HarbormasterHostArtifact::ARTIFACTCONST, 126 126 ), 127 127 ); 128 128 }
+7 -8
src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php
··· 36 36 $lease->waitUntilActive(); 37 37 38 38 // Create the associated artifact. 39 - $artifact = $build->createArtifact( 40 - $build_target, 39 + $artifact = $build_target->createArtifact( 40 + PhabricatorUser::getOmnipotentUser(), 41 41 $settings['name'], 42 - HarbormasterBuildArtifact::TYPE_HOST); 43 - $artifact->setArtifactData(array( 44 - 'drydock-lease' => $lease->getID(), 45 - )); 46 - $artifact->save(); 42 + HarbormasterHostArtifact::ARTIFACTCONST, 43 + array( 44 + 'drydockLeasePHID' => $lease->getPHID(), 45 + )); 47 46 } 48 47 49 48 public function getArtifactOutputs() { ··· 51 50 array( 52 51 'name' => pht('Leased Host'), 53 52 'key' => $this->getSetting('name'), 54 - 'type' => HarbormasterBuildArtifact::TYPE_HOST, 53 + 'type' => HarbormasterHostArtifact::ARTIFACTCONST, 55 54 ), 56 55 ); 57 56 }
+9 -8
src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php
··· 29 29 30 30 $settings = $this->getSettings(); 31 31 $variables = $build_target->getVariables(); 32 + $viewer = PhabricatorUser::getOmnipotentUser(); 32 33 33 34 $path = $this->mergeVariables( 34 35 'vsprintf', 35 36 $settings['path'], 36 37 $variables); 37 38 38 - $artifact = $build->loadArtifact($settings['artifact']); 39 - 40 - $file = $artifact->loadPhabricatorFile(); 39 + $artifact = $build_target->loadArtifact($settings['artifact']); 40 + $impl = $artifact->getArtifactImplementation(); 41 + $file = $impl->loadArtifactFile($viewer); 41 42 42 43 $fragment = id(new PhragmentFragmentQuery()) 43 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 44 + ->setViewer($viewer) 44 45 ->withPaths(array($path)) 45 46 ->executeOne(); 46 47 47 48 if ($fragment === null) { 48 49 PhragmentFragment::createFromFile( 49 - PhabricatorUser::getOmnipotentUser(), 50 + $viewer, 50 51 $file, 51 52 $path, 52 53 PhabricatorPolicies::getMostOpenPolicy(), 53 54 PhabricatorPolicies::POLICY_USER); 54 55 } else { 55 56 if ($file->getMimeType() === 'application/zip') { 56 - $fragment->updateFromZIP(PhabricatorUser::getOmnipotentUser(), $file); 57 + $fragment->updateFromZIP($viewer, $file); 57 58 } else { 58 - $fragment->updateFromFile(PhabricatorUser::getOmnipotentUser(), $file); 59 + $fragment->updateFromFile($viewer, $file); 59 60 } 60 61 } 61 62 } ··· 65 66 array( 66 67 'name' => pht('Publishes File'), 67 68 'key' => $this->getSetting('artifact'), 68 - 'type' => HarbormasterBuildArtifact::TYPE_FILE, 69 + 'type' => HarbormasterFileArtifact::ARTIFACTCONST, 69 70 ), 70 71 ); 71 72 }
+9 -11
src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php
··· 34 34 $settings['path'], 35 35 $variables); 36 36 37 - $artifact = $build->loadArtifact($settings['hostartifact']); 38 - 37 + $artifact = $build_target->loadArtifact($settings['hostartifact']); 39 38 $lease = $artifact->loadDrydockLease(); 40 39 41 40 $interface = $lease->getInterface('filesystem'); ··· 44 43 $file = $interface->saveFile($path, $settings['name']); 45 44 46 45 // Insert the artifact record. 47 - $artifact = $build->createArtifact( 48 - $build_target, 46 + $artifact = $build_target->createArtifact( 47 + PhabricatorUser::getOmnipotentUser(), 49 48 $settings['name'], 50 - HarbormasterBuildArtifact::TYPE_FILE); 51 - $artifact->setArtifactData(array( 52 - 'filePHID' => $file->getPHID(), 53 - )); 54 - $artifact->save(); 49 + HarbormasterFileArtifact::ARTIFACTCONST, 50 + array( 51 + 'filePHID' => $file->getPHID(), 52 + )); 55 53 } 56 54 57 55 public function getArtifactInputs() { ··· 59 57 array( 60 58 'name' => pht('Upload From Host'), 61 59 'key' => $this->getSetting('hostartifact'), 62 - 'type' => HarbormasterBuildArtifact::TYPE_HOST, 60 + 'type' => HarbormasterHostArtifact::ARTIFACTCONST, 63 61 ), 64 62 ); 65 63 } ··· 69 67 array( 70 68 'name' => pht('Uploaded File'), 71 69 'key' => $this->getSetting('name'), 72 - 'type' => HarbormasterBuildArtifact::TYPE_FILE, 70 + 'type' => HarbormasterHostArtifact::ARTIFACTCONST, 73 71 ), 74 72 ); 75 73 }
-30
src/applications/harbormaster/storage/build/HarbormasterBuild.php
··· 235 235 return $log; 236 236 } 237 237 238 - public function createArtifact( 239 - HarbormasterBuildTarget $build_target, 240 - $artifact_key, 241 - $artifact_type) { 242 - 243 - $artifact = 244 - HarbormasterBuildArtifact::initializeNewBuildArtifact($build_target); 245 - $artifact->setArtifactKey( 246 - $this->getPHID(), 247 - $this->getBuildGeneration(), 248 - $artifact_key); 249 - $artifact->setArtifactType($artifact_type); 250 - $artifact->save(); 251 - return $artifact; 252 - } 253 - 254 - public function loadArtifact($name) { 255 - $artifact = id(new HarbormasterBuildArtifactQuery()) 256 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 257 - ->withArtifactKeys( 258 - $this->getPHID(), 259 - $this->getBuildGeneration(), 260 - array($name)) 261 - ->executeOne(); 262 - if ($artifact === null) { 263 - throw new Exception(pht('Artifact not found!')); 264 - } 265 - return $artifact; 266 - } 267 - 268 238 public function retrieveVariablesFromBuild() { 269 239 $results = array( 270 240 'buildable.diff' => null,
+41 -92
src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php
··· 10 10 protected $artifactData = array(); 11 11 12 12 private $buildTarget = self::ATTACHABLE; 13 - 14 - const TYPE_FILE = 'file'; 15 - const TYPE_HOST = 'host'; 16 - const TYPE_URI = 'uri'; 13 + private $artifactImplementation; 17 14 18 15 public static function initializeNewBuildArtifact( 19 16 HarbormasterBuildTarget $build_target) { 20 17 return id(new HarbormasterBuildArtifact()) 18 + ->attachBuildTarget($build_target) 21 19 ->setBuildTargetPHID($build_target->getPHID()); 22 20 } 23 21 24 22 protected function getConfiguration() { 25 23 return array( 24 + self::CONFIG_AUX_PHID => true, 26 25 self::CONFIG_SERIALIZATION => array( 27 26 'artifactData' => self::SERIALIZATION_JSON, 28 27 ), ··· 43 42 ) + parent::getConfiguration(); 44 43 } 45 44 45 + public function generatePHID() { 46 + return PhabricatorPHID::generateNewPHID( 47 + HarbormasterBuildArtifactPHIDType::TYPECONST); 48 + } 49 + 46 50 public function attachBuildTarget(HarbormasterBuildTarget $build_target) { 47 51 $this->buildTarget = $build_target; 48 52 return $this; ··· 52 56 return $this->assertAttached($this->buildTarget); 53 57 } 54 58 55 - public function setArtifactKey($build_phid, $build_gen, $key) { 56 - $this->artifactIndex = 57 - PhabricatorHash::digestForIndex($build_phid.$build_gen.$key); 59 + public function setArtifactKey($key) { 60 + $target = $this->getBuildTarget(); 61 + $this->artifactIndex = self::getArtifactIndex($target, $key); 58 62 $this->artifactKey = $key; 59 63 return $this; 60 64 } 61 65 62 - public function getObjectItemView(PhabricatorUser $viewer) { 63 - $data = $this->getArtifactData(); 64 - switch ($this->getArtifactType()) { 65 - case self::TYPE_FILE: 66 - $handle = id(new PhabricatorHandleQuery()) 67 - ->setViewer($viewer) 68 - ->withPHIDs($data) 69 - ->executeOne(); 66 + public static function getArtifactIndex( 67 + HarbormasterBuildTarget $target, 68 + $artifact_key) { 69 + 70 + $build = $target->getBuild(); 70 71 71 - return id(new PHUIObjectItemView()) 72 - ->setObjectName(pht('File')) 73 - ->setHeader($handle->getFullName()) 74 - ->setHref($handle->getURI()); 75 - case self::TYPE_HOST: 76 - $leases = id(new DrydockLeaseQuery()) 77 - ->setViewer($viewer) 78 - ->withIDs(array($data['drydock-lease'])) 79 - ->execute(); 80 - $lease = idx($leases, $data['drydock-lease']); 72 + $parts = array( 73 + $build->getPHID(), 74 + $target->getBuildGeneration(), 75 + $artifact_key, 76 + ); 77 + $parts = implode("\0", $parts); 81 78 82 - return id(new PHUIObjectItemView()) 83 - ->setObjectName(pht('Drydock Lease')) 84 - ->setHeader($lease->getID()) 85 - ->setHref('/drydock/lease/'.$lease->getID()); 86 - case self::TYPE_URI: 87 - return id(new PHUIObjectItemView()) 88 - ->setObjectName($data['name']) 89 - ->setHeader($data['uri']) 90 - ->setHref($data['uri']); 91 - default: 92 - return null; 93 - } 79 + return PhabricatorHash::digestForIndex($parts); 94 80 } 95 81 96 - public function loadDrydockLease() { 97 - if ($this->getArtifactType() !== self::TYPE_HOST) { 98 - throw new Exception( 99 - pht( 100 - '`%s` may only be called on host artifacts.', 101 - __FUNCTION__)); 102 - } 82 + public function releaseArtifact() { 83 + $impl = $this->getArtifactImplementation(); 103 84 104 - $data = $this->getArtifactData(); 105 - 106 - // FIXME: Is there a better way of doing this? 107 - // TODO: Policy stuff, etc. 108 - $lease = id(new DrydockLease())->load( 109 - $data['drydock-lease']); 110 - if ($lease === null) { 111 - throw new Exception(pht('Associated Drydock lease not found!')); 112 - } 113 - $resource = id(new DrydockResource())->load( 114 - $lease->getResourceID()); 115 - if ($resource === null) { 116 - throw new Exception(pht('Associated Drydock resource not found!')); 85 + if ($impl) { 86 + $impl->releaseArtifact(PhabricatorUser::getOmnipotentUser()); 117 87 } 118 - $lease->attachResource($resource); 119 88 120 - return $lease; 89 + return null; 121 90 } 122 91 123 - public function loadPhabricatorFile() { 124 - if ($this->getArtifactType() !== self::TYPE_FILE) { 125 - throw new Exception( 126 - pht( 127 - '`%s` may only be called on file artifacts.', 128 - __FUNCTION__)); 129 - } 130 - 131 - $data = $this->getArtifactData(); 132 - 133 - // The data for TYPE_FILE is an array with a single PHID in it. 134 - $phid = $data['filePHID']; 92 + public function getArtifactImplementation() { 93 + if ($this->artifactImplementation === null) { 94 + $type = $this->getArtifactType(); 95 + $impl = HarbormasterArtifact::getArtifactType($type); 96 + if (!$impl) { 97 + return null; 98 + } 135 99 136 - $file = id(new PhabricatorFileQuery()) 137 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 138 - ->withPHIDs(array($phid)) 139 - ->executeOne(); 140 - if ($file === null) { 141 - throw new Exception(pht('Associated file not found!')); 100 + $impl = clone $impl; 101 + $impl->setBuildArtifact($this); 102 + $this->artifactImplementation = $impl; 142 103 } 143 - return $file; 144 - } 145 104 146 - public function release() { 147 - switch ($this->getArtifactType()) { 148 - case self::TYPE_HOST: 149 - $this->releaseDrydockLease(); 150 - break; 151 - } 105 + return $this->artifactImplementation; 152 106 } 153 107 154 - public function releaseDrydockLease() { 155 - $lease = $this->loadDrydockLease(); 156 - $resource = $lease->getResource(); 157 - $blueprint = $resource->getBlueprint(); 158 108 159 - if ($lease->isActive()) { 160 - $blueprint->releaseLease($resource, $lease); 161 - } 109 + public function getProperty($key, $default = null) { 110 + return idx($this->artifactData, $key, $default); 162 111 } 163 112 164 113
+48
src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php
··· 201 201 ); 202 202 } 203 203 204 + public function createArtifact( 205 + PhabricatorUser $actor, 206 + $artifact_key, 207 + $artifact_type, 208 + array $artifact_data) { 209 + 210 + $impl = HarbormasterArtifact::getArtifactType($artifact_type); 211 + if (!$impl) { 212 + throw new Exception( 213 + pht( 214 + 'There is no implementation available for artifacts of type "%s".', 215 + $artifact_type)); 216 + } 217 + 218 + $impl->validateArtifactData($artifact_data); 219 + 220 + $artifact = HarbormasterBuildArtifact::initializeNewBuildArtifact($this) 221 + ->setArtifactKey($artifact_key) 222 + ->setArtifactType($artifact_type) 223 + ->setArtifactData($artifact_data); 224 + 225 + $impl = $artifact->getArtifactImplementation(); 226 + $impl->willCreateArtifact($actor); 227 + 228 + return $artifact->save(); 229 + } 230 + 231 + public function loadArtifact($artifact_key) { 232 + $indexes = array(); 233 + 234 + $indexes[] = HarbormasterBuildArtifact::getArtifactIndex( 235 + $this, 236 + $artifact_key); 237 + 238 + $artifact = id(new HarbormasterBuildArtifactQuery()) 239 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 240 + ->withArtifactIndexes($indexes) 241 + ->executeOne(); 242 + if ($artifact === null) { 243 + throw new Exception( 244 + pht( 245 + 'Artifact "%s" not found!', 246 + $artifact_key)); 247 + } 248 + 249 + return $artifact; 250 + } 251 + 204 252 205 253 /* -( Status )------------------------------------------------------------- */ 206 254