diff --git a/resources/sql/autopatches/20151030.harbormaster.initiator.sql b/resources/sql/autopatches/20151030.harbormaster.initiator.sql new file mode 100644 index 0000000000..f8ba3f6757 --- /dev/null +++ b/resources/sql/autopatches/20151030.harbormaster.initiator.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_build + ADD COLUMN initiatorPHID VARBINARY(64); diff --git a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php index 6655fd6806..fb664dc084 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php @@ -1,101 +1,101 @@ getViewer(); $plan_id = $request->getURIData('id'); $plan = id(new HarbormasterBuildPlanQuery()) ->setViewer($viewer) ->withIDs(array($plan_id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$plan) { return new Aphront404Response(); } $cancel_uri = $this->getApplicationURI("plan/{$plan_id}/"); if (!$plan->canRunManually()) { return $this->newDialog() ->setTitle(pht('Can Not Run Plan')) ->appendParagraph(pht('This plan can not be run manually.')) ->addCancelButton($cancel_uri); } $e_name = true; $v_name = null; $errors = array(); if ($request->isFormPost()) { $buildable = HarbormasterBuildable::initializeNewBuildable($viewer) ->setIsManualBuildable(true); $v_name = $request->getStr('buildablePHID'); if ($v_name) { $object = id(new PhabricatorObjectQuery()) ->setViewer($viewer) ->withNames(array($v_name)) ->executeOne(); if ($object instanceof HarbormasterBuildableInterface) { $buildable ->setBuildablePHID($object->getHarbormasterBuildablePHID()) ->setContainerPHID($object->getHarbormasterContainerPHID()); } else { $e_name = pht('Invalid'); $errors[] = pht('Enter the name of a revision or commit.'); } } else { $e_name = pht('Required'); $errors[] = pht('You must choose a revision or commit to build.'); } if (!$errors) { $buildable->save(); - $buildable->applyPlan($plan, array()); + $buildable->applyPlan($plan, array(), $viewer->getPHID()); $buildable_uri = '/B'.$buildable->getID(); return id(new AphrontRedirectResponse())->setURI($buildable_uri); } } if ($errors) { $errors = id(new PHUIInfoView())->setErrors($errors); } $title = pht('Run Build Plan Manually'); $save_button = pht('Run Plan Manually'); $form = id(new PHUIFormLayoutView()) ->setUser($viewer) ->appendRemarkupInstructions( pht( "Enter the name of a commit or revision to run this plan on (for ". "example, `rX123456` or `D123`).\n\n". "For more detailed output, you can also run manual builds from ". "the command line:\n\n". " phabricator/ $ ./bin/harbormaster build --plan %s", $plan->getID())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Buildable Name')) ->setName('buildablePHID') ->setError($e_name) ->setValue($v_name)); return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FULL) ->setTitle($title) ->appendChild($form) ->addCancelButton($cancel_uri) ->addSubmitButton($save_button); } } diff --git a/src/applications/harbormaster/engine/HarbormasterBuildRequest.php b/src/applications/harbormaster/engine/HarbormasterBuildRequest.php index 1874f08b27..71721fdaf2 100644 --- a/src/applications/harbormaster/engine/HarbormasterBuildRequest.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildRequest.php @@ -1,37 +1,47 @@ buildPlanPHID = $build_plan_phid; return $this; } public function getBuildPlanPHID() { return $this->buildPlanPHID; } public function setBuildParameters(array $build_parameters) { $this->buildParameters = $build_parameters; return $this; } public function getBuildParameters() { return $this->buildParameters; } + public function setInitiatorPHID($phid) { + $this->initiatorPHID = $phid; + return $this; + } + + public function getInitiatorPHID() { + return $this->initiatorPHID; + } + } diff --git a/src/applications/harbormaster/engine/HarbormasterTargetEngine.php b/src/applications/harbormaster/engine/HarbormasterTargetEngine.php index b201891b6b..d9efb8b9fd 100644 --- a/src/applications/harbormaster/engine/HarbormasterTargetEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterTargetEngine.php @@ -1,251 +1,255 @@ viewer = $viewer; return $this; } public function getViewer() { return $this->viewer; } public function setObject(HarbormasterBuildableInterface $object) { $this->object = $object; return $this; } public function getObject() { return $this->object; } public function setAutoTargetKeys(array $auto_keys) { $this->autoTargetKeys = $auto_keys; return $this; } public function getAutoTargetKeys() { return $this->autoTargetKeys; } public function buildTargets() { $object = $this->getObject(); $viewer = $this->getViewer(); $step_map = $this->generateBuildStepMap($this->getAutoTargetKeys()); $buildable = HarbormasterBuildable::createOrLoadExisting( $viewer, $object->getHarbormasterBuildablePHID(), $object->getHarbormasterContainerPHID()); $target_map = $this->generateBuildTargetMap($buildable, $step_map); return $target_map; } /** * Get a map of the @{class:HarbormasterBuildStep} objects for a list of * autotarget keys. * * This method creates the steps if they do not yet exist. * * @param list Autotarget keys, like `"core.arc.lint"`. * @return map Map of keys to step objects. */ private function generateBuildStepMap(array $autotargets) { $viewer = $this->getViewer(); $autosteps = $this->getAutosteps($autotargets); $autosteps = mgroup($autosteps, 'getBuildStepAutotargetPlanKey'); $plans = id(new HarbormasterBuildPlanQuery()) ->setViewer($viewer) ->withPlanAutoKeys(array_keys($autosteps)) ->needBuildSteps(true) ->execute(); $plans = mpull($plans, null, 'getPlanAutoKey'); // NOTE: When creating the plan and steps, we save the autokeys as the // names. These won't actually be shown in the UI, but make the data more // consistent for secondary consumers like typeaheads. $step_map = array(); foreach ($autosteps as $plan_key => $steps) { $plan = idx($plans, $plan_key); if (!$plan) { $plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer) ->setName($plan_key) ->setPlanAutoKey($plan_key); } $current = $plan->getBuildSteps(); $current = mpull($current, null, 'getStepAutoKey'); $new_steps = array(); foreach ($steps as $step_key => $step) { if (isset($current[$step_key])) { $step_map[$step_key] = $current[$step_key]; continue; } $new_step = HarbormasterBuildStep::initializeNewStep($viewer) ->setName($step_key) ->setClassName(get_class($step)) ->setStepAutoKey($step_key); $new_steps[$step_key] = $new_step; } if ($new_steps) { $plan->openTransaction(); if (!$plan->getPHID()) { $plan->save(); } foreach ($new_steps as $step_key => $step) { $step->setBuildPlanPHID($plan->getPHID()); $step->save(); $step->attachBuildPlan($plan); $step_map[$step_key] = $step; } $plan->saveTransaction(); } } return $step_map; } /** * Get all of the @{class:HarbormasterBuildStepImplementation} objects for * a list of autotarget keys. * * @param list Autotarget keys, like `"core.arc.lint"`. * @return map Map of keys to implementations. */ private function getAutosteps(array $autotargets) { $all_steps = HarbormasterBuildStepImplementation::getImplementations(); $all_steps = mpull($all_steps, null, 'getBuildStepAutotargetStepKey'); // Make sure all the targets really exist. foreach ($autotargets as $autotarget) { if (empty($all_steps[$autotarget])) { throw new Exception( pht( 'No build step provides autotarget "%s"!', $autotarget)); } } return array_select_keys($all_steps, $autotargets); } /** * Get a list of @{class:HarbormasterBuildTarget} objects for a list of * autotarget keys. * * If some targets or builds do not exist, they are created. * * @param HarbormasterBuildable A buildable. * @param map Map of keys to steps. * @return map Map of keys to targets. */ private function generateBuildTargetMap( HarbormasterBuildable $buildable, array $step_map) { $viewer = $this->getViewer(); + $initiator_phid = null; + if (!$viewer->isOmnipotent()) { + $initiator_phid = $viewer->getPHID(); + } $plan_map = mgroup($step_map, 'getBuildPlanPHID'); $builds = id(new HarbormasterBuildQuery()) ->setViewer($viewer) ->withBuildablePHIDs(array($buildable->getPHID())) ->withBuildPlanPHIDs(array_keys($plan_map)) ->needBuildTargets(true) ->execute(); $autobuilds = array(); foreach ($builds as $build) { $plan_key = $build->getBuildPlan()->getPlanAutoKey(); $autobuilds[$plan_key] = $build; } $new_builds = array(); foreach ($plan_map as $plan_phid => $steps) { $plan = head($steps)->getBuildPlan(); $plan_key = $plan->getPlanAutoKey(); $build = idx($autobuilds, $plan_key); if ($build) { // We already have a build for this set of targets, so we don't need // to do any work. (It's possible the build is an older build that // doesn't have all of the right targets if new autotargets were // recently introduced, but we don't currently try to construct them.) continue; } // NOTE: Normally, `applyPlan()` does not actually generate targets. // We need to apply the plan in-process to perform target generation. // This is fine as long as autotargets are empty containers that don't // do any work, which they always should be. PhabricatorWorker::setRunAllTasksInProcess(true); try { // NOTE: We might race another process here to create the same build // with the same `planAutoKey`. The database will prevent this and // using autotargets only currently makes sense if you just created the // resource and "own" it, so we don't try to handle this, but may need // to be more careful here if use of autotargets expands. - $build = $buildable->applyPlan($plan, array()); + $build = $buildable->applyPlan($plan, array(), $initiator_phid); PhabricatorWorker::setRunAllTasksInProcess(false); } catch (Exception $ex) { PhabricatorWorker::setRunAllTasksInProcess(false); throw $ex; } $new_builds[] = $build; } if ($new_builds) { $all_targets = id(new HarbormasterBuildTargetQuery()) ->setViewer($viewer) ->withBuildPHIDs(mpull($new_builds, 'getPHID')) ->execute(); } else { $all_targets = array(); } foreach ($builds as $build) { foreach ($build->getBuildTargets() as $target) { $all_targets[] = $target; } } $target_map = array(); foreach ($all_targets as $target) { $target_key = $target ->getImplementation() ->getBuildStepAutotargetStepKey(); if (!$target_key) { continue; } $target_map[$target_key] = $target; } $target_map = array_select_keys($target_map, array_keys($step_map)); return $target_map; } } diff --git a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php index de399e5976..76bdf9ccc3 100644 --- a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php +++ b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php @@ -1,87 +1,88 @@ getAdapter(); return ($adapter instanceof HarbormasterBuildableAdapterInterface); } - protected function applyBuilds(array $phids) { + protected function applyBuilds(array $phids, HeraldRule $rule) { $adapter = $this->getAdapter(); $allowed_types = array( HarbormasterBuildPlanPHIDType::TYPECONST, ); $targets = $this->loadStandardTargets($phids, $allowed_types, array()); if (!$targets) { return; } $phids = array_fuse(array_keys($targets)); foreach ($phids as $phid) { $request = id(new HarbormasterBuildRequest()) - ->setBuildPlanPHID($phid); + ->setBuildPlanPHID($phid) + ->setInitiatorPHID($rule->getPHID()); $adapter->queueHarbormasterBuildRequest($request); } $this->logEffect(self::DO_BUILD, $phids); } protected function getActionEffectMap() { return array( self::DO_BUILD => array( 'icon' => 'fa-play', 'color' => 'green', 'name' => pht('Building'), ), ); } protected function renderActionEffectDescription($type, $data) { switch ($type) { case self::DO_BUILD: return pht( 'Started %s build(s): %s.', phutil_count($data), $this->renderHandleList($data)); } } public function getHeraldActionName() { return pht('Run build plans'); } public function supportsRuleType($rule_type) { return ($rule_type != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); } public function applyEffect($object, HeraldEffect $effect) { - return $this->applyBuilds($effect->getTarget()); + return $this->applyBuilds($effect->getTarget(), $effect->getRule()); } public function getHeraldActionStandardType() { return self::STANDARD_PHID_LIST; } protected function getDatasource() { return new HarbormasterBuildPlanDatasource(); } public function renderActionDescription($value) { return pht( 'Run build plans: %s.', $this->renderHandleList($value)); } } diff --git a/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php b/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php index 7110d98d6e..6fd3cf2ffd 100644 --- a/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php +++ b/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php @@ -1,108 +1,113 @@ setName('build') ->setExamples('**build** [__options__] __buildable__ --plan __id__') ->setSynopsis(pht('Run plan __id__ on __buildable__.')) ->setArguments( array( array( 'name' => 'plan', 'param' => 'id', 'help' => pht('ID of build plan to run.'), ), array( 'name' => 'background', 'help' => pht( 'Submit builds into the build queue normally instead of '. 'running them in the foreground.'), ), array( 'name' => 'buildable', 'wildcard' => true, ), )); } public function execute(PhutilArgumentParser $args) { $viewer = $this->getViewer(); $names = $args->getArg('buildable'); if (count($names) != 1) { throw new PhutilArgumentUsageException( pht('Specify exactly one buildable object, by object name.')); } $name = head($names); $buildable = id(new PhabricatorObjectQuery()) ->setViewer($viewer) ->withNames($names) ->executeOne(); if (!$buildable) { throw new PhutilArgumentUsageException( pht('No such buildable "%s"!', $name)); } if (!($buildable instanceof HarbormasterBuildableInterface)) { throw new PhutilArgumentUsageException( pht('Object "%s" is not a buildable!', $name)); } $plan_id = $args->getArg('plan'); if (!$plan_id) { throw new PhutilArgumentUsageException( pht( 'Use %s to specify a build plan to run.', '--plan')); } $plan = id(new HarbormasterBuildPlanQuery()) ->setViewer($viewer) ->withIDs(array($plan_id)) ->executeOne(); if (!$plan) { throw new PhutilArgumentUsageException( pht('Build plan "%s" does not exist.', $plan_id)); } if (!$plan->canRunManually()) { throw new PhutilArgumentUsageException( pht('This build plan can not be run manually.')); } $console = PhutilConsole::getConsole(); $buildable = HarbormasterBuildable::initializeNewBuildable($viewer) ->setIsManualBuildable(true) ->setBuildablePHID($buildable->getHarbormasterBuildablePHID()) ->setContainerPHID($buildable->getHarbormasterContainerPHID()) ->save(); $console->writeOut( "%s\n", pht( 'Applying plan %s to new buildable %s...', $plan->getID(), 'B'.$buildable->getID())); $console->writeOut( "\n %s\n\n", PhabricatorEnv::getProductionURI('/B'.$buildable->getID())); if (!$args->getArg('background')) { PhabricatorWorker::setRunAllTasksInProcess(true); } - $buildable->applyPlan($plan, array()); + if ($viewer->isOmnipotent()) { + $initiator = id(new PhabricatorHarbormasterApplication())->getPHID(); + } else { + $initiator = $viewer->getPHID(); + } + $buildable->applyPlan($plan, array(), $initiator); $console->writeOut("%s\n", pht('Done.')); return 0; } } diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index e74471e879..3a7df73f52 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -1,332 +1,338 @@ setIsManualBuildable(0) ->setBuildableStatus(self::STATUS_BUILDING); } public function getMonogram() { return 'B'.$this->getID(); } /** * Returns an existing buildable for the object's PHID or creates a * new buildable implicitly if needed. */ public static function createOrLoadExisting( PhabricatorUser $actor, $buildable_object_phid, $container_object_phid) { $buildable = id(new HarbormasterBuildableQuery()) ->setViewer($actor) ->withBuildablePHIDs(array($buildable_object_phid)) ->withManualBuildables(false) ->setLimit(1) ->executeOne(); if ($buildable) { return $buildable; } $buildable = self::initializeNewBuildable($actor) ->setBuildablePHID($buildable_object_phid) ->setContainerPHID($container_object_phid); $buildable->save(); return $buildable; } /** * Start builds for a given buildable. * * @param phid PHID of the object to build. * @param phid Container PHID for the buildable. * @param list List of builds to perform. * @return void */ public static function applyBuildPlans( $phid, $container_phid, array $requests) { assert_instances_of($requests, 'HarbormasterBuildRequest'); if (!$requests) { return; } // Skip all of this logic if the Harbormaster application // isn't currently installed. $harbormaster_app = 'PhabricatorHarbormasterApplication'; if (!PhabricatorApplication::isClassInstalled($harbormaster_app)) { return; } $viewer = PhabricatorUser::getOmnipotentUser(); $buildable = self::createOrLoadExisting( $viewer, $phid, $container_phid); $plan_phids = mpull($requests, 'getBuildPlanPHID'); $plans = id(new HarbormasterBuildPlanQuery()) ->setViewer($viewer) ->withPHIDs($plan_phids) ->execute(); $plans = mpull($plans, null, 'getPHID'); foreach ($requests as $request) { $plan_phid = $request->getBuildPlanPHID(); $plan = idx($plans, $plan_phid); if (!$plan) { throw new Exception( pht( 'Failed to load build plan ("%s").', $plan_phid)); } if ($plan->isDisabled()) { // TODO: This should be communicated more clearly -- maybe we should // create the build but set the status to "disabled" or "derelict". continue; } $parameters = $request->getBuildParameters(); - $buildable->applyPlan($plan, $parameters); + $buildable->applyPlan($plan, $parameters, $request->getInitiatorPHID()); } } - public function applyPlan(HarbormasterBuildPlan $plan, array $parameters) { + public function applyPlan( + HarbormasterBuildPlan $plan, + array $parameters, + $initiator_phid) { $viewer = PhabricatorUser::getOmnipotentUser(); $build = HarbormasterBuild::initializeNewBuild($viewer) ->setBuildablePHID($this->getPHID()) ->setBuildPlanPHID($plan->getPHID()) ->setBuildParameters($parameters) ->setBuildStatus(HarbormasterBuild::STATUS_PENDING); + if ($initiator_phid) { + $build->setInitiatorPHID($initiator_phid); + } $auto_key = $plan->getPlanAutoKey(); if ($auto_key) { $build->setPlanAutoKey($auto_key); } $build->save(); PhabricatorWorker::scheduleTask( 'HarbormasterBuildWorker', array( 'buildID' => $build->getID(), )); return $build; } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'containerPHID' => 'phid?', 'buildableStatus' => 'text32', 'isManualBuildable' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_buildable' => array( 'columns' => array('buildablePHID'), ), 'key_container' => array( 'columns' => array('containerPHID'), ), 'key_manual' => array( 'columns' => array('isManualBuildable'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( HarbormasterBuildablePHIDType::TYPECONST); } public function attachBuildableObject($buildable_object) { $this->buildableObject = $buildable_object; return $this; } public function getBuildableObject() { return $this->assertAttached($this->buildableObject); } public function attachContainerObject($container_object) { $this->containerObject = $container_object; return $this; } public function getContainerObject() { return $this->assertAttached($this->containerObject); } public function attachContainerHandle($container_handle) { $this->containerHandle = $container_handle; return $this; } public function getContainerHandle() { return $this->assertAttached($this->containerHandle); } public function attachBuildableHandle($buildable_handle) { $this->buildableHandle = $buildable_handle; return $this; } public function getBuildableHandle() { return $this->assertAttached($this->buildableHandle); } public function attachBuilds(array $builds) { assert_instances_of($builds, 'HarbormasterBuild'); $this->builds = $builds; return $this; } public function getBuilds() { return $this->assertAttached($this->builds); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new HarbormasterBuildableTransactionEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new HarbormasterBuildableTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { return $this->getBuildableObject()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getBuildableObject()->hasAutomaticCapability( $capability, $viewer); } public function describeAutomaticCapability($capability) { return pht('A buildable inherits policies from the underlying object.'); } /* -( HarbormasterBuildableInterface )------------------------------------- */ public function getHarbormasterBuildablePHID() { // NOTE: This is essentially just for convenience, as it allows you create // a copy of a buildable by specifying `B123` without bothering to go // look up the underlying object. return $this->getBuildablePHID(); } public function getHarbormasterContainerPHID() { return $this->getContainerPHID(); } public function getBuildVariables() { return array(); } public function getAvailableBuildVariables() { return array(); } } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index 154681d93f..c96d0c2710 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -1,494 +1,501 @@ setBuildStatus(self::STATUS_INACTIVE) ->setBuildGeneration(0); } public function delete() { $this->openTransaction(); $this->deleteUnprocessedCommands(); $result = parent::delete(); $this->saveTransaction(); return $result; } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'buildParameters' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'buildStatus' => 'text32', 'buildGeneration' => 'uint32', 'planAutoKey' => 'text32?', + 'initiatorPHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_buildable' => array( 'columns' => array('buildablePHID'), ), 'key_plan' => array( 'columns' => array('buildPlanPHID'), ), 'key_status' => array( 'columns' => array('buildStatus'), ), 'key_planautokey' => array( 'columns' => array('buildablePHID', 'planAutoKey'), 'unique' => true, ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( HarbormasterBuildPHIDType::TYPECONST); } public function attachBuildable(HarbormasterBuildable $buildable) { $this->buildable = $buildable; return $this; } public function getBuildable() { return $this->assertAttached($this->buildable); } public function getName() { if ($this->getBuildPlan()) { return $this->getBuildPlan()->getName(); } return pht('Build'); } public function attachBuildPlan( HarbormasterBuildPlan $build_plan = null) { $this->buildPlan = $build_plan; return $this; } public function getBuildPlan() { return $this->assertAttached($this->buildPlan); } public function getBuildTargets() { return $this->assertAttached($this->buildTargets); } public function attachBuildTargets(array $targets) { $this->buildTargets = $targets; return $this; } public function isBuilding() { return $this->getBuildStatus() === self::STATUS_PENDING || $this->getBuildStatus() === self::STATUS_BUILDING; } public function isAutobuild() { return ($this->getPlanAutoKey() !== null); } public function createLog( HarbormasterBuildTarget $build_target, $log_source, $log_type) { $log_source = id(new PhutilUTF8StringTruncator()) ->setMaximumBytes(250) ->truncateString($log_source); $log = HarbormasterBuildLog::initializeNewBuildLog($build_target) ->setLogSource($log_source) ->setLogType($log_type) ->save(); return $log; } public function retrieveVariablesFromBuild() { $results = array( 'buildable.diff' => null, 'buildable.revision' => null, 'buildable.commit' => null, 'repository.callsign' => null, 'repository.phid' => null, 'repository.vcs' => null, 'repository.uri' => null, 'step.timestamp' => null, 'build.id' => null, + 'initiator.phid' => null, ); foreach ($this->getBuildParameters() as $key => $value) { $results['build/'.$key] = $value; } $buildable = $this->getBuildable(); $object = $buildable->getBuildableObject(); $object_variables = $object->getBuildVariables(); $results = $object_variables + $results; $results['step.timestamp'] = time(); $results['build.id'] = $this->getID(); + $results['initiator.phid'] = $this->getInitiatorPHID(); return $results; } public static function getAvailableBuildVariables() { $objects = id(new PhutilClassMapQuery()) ->setAncestorClass('HarbormasterBuildableInterface') ->execute(); $variables = array(); $variables[] = array( 'step.timestamp' => pht('The current UNIX timestamp.'), 'build.id' => pht('The ID of the current build.'), 'target.phid' => pht('The PHID of the current build target.'), + 'initiator.phid' => pht( + 'The PHID of the user or Object that initiated the build, '. + 'if applicable.'), ); foreach ($objects as $object) { $variables[] = $object->getAvailableBuildVariables(); } $variables = array_mergev($variables); return $variables; } public function isComplete() { switch ($this->getBuildStatus()) { case self::STATUS_PASSED: case self::STATUS_FAILED: case self::STATUS_ABORTED: case self::STATUS_ERROR: case self::STATUS_PAUSED: return true; } return false; } public function isPaused() { return ($this->getBuildStatus() == self::STATUS_PAUSED); } /* -( Build Commands )----------------------------------------------------- */ private function getUnprocessedCommands() { return $this->assertAttached($this->unprocessedCommands); } public function attachUnprocessedCommands(array $commands) { $this->unprocessedCommands = $commands; return $this; } public function canRestartBuild() { if ($this->isAutobuild()) { return false; } return !$this->isRestarting(); } public function canPauseBuild() { if ($this->isAutobuild()) { return false; } return !$this->isComplete() && !$this->isPaused() && !$this->isPausing(); } public function canAbortBuild() { if ($this->isAutobuild()) { return false; } return !$this->isComplete(); } public function canResumeBuild() { if ($this->isAutobuild()) { return false; } return $this->isPaused() && !$this->isResuming(); } public function isPausing() { $is_pausing = false; foreach ($this->getUnprocessedCommands() as $command_object) { $command = $command_object->getCommand(); switch ($command) { case HarbormasterBuildCommand::COMMAND_PAUSE: $is_pausing = true; break; case HarbormasterBuildCommand::COMMAND_RESUME: case HarbormasterBuildCommand::COMMAND_RESTART: $is_pausing = false; break; case HarbormasterBuildCommand::COMMAND_ABORT: $is_pausing = true; break; } } return $is_pausing; } public function isResuming() { $is_resuming = false; foreach ($this->getUnprocessedCommands() as $command_object) { $command = $command_object->getCommand(); switch ($command) { case HarbormasterBuildCommand::COMMAND_RESTART: case HarbormasterBuildCommand::COMMAND_RESUME: $is_resuming = true; break; case HarbormasterBuildCommand::COMMAND_PAUSE: $is_resuming = false; break; case HarbormasterBuildCommand::COMMAND_ABORT: $is_resuming = false; break; } } return $is_resuming; } public function isRestarting() { $is_restarting = false; foreach ($this->getUnprocessedCommands() as $command_object) { $command = $command_object->getCommand(); switch ($command) { case HarbormasterBuildCommand::COMMAND_RESTART: $is_restarting = true; break; } } return $is_restarting; } public function isAborting() { $is_aborting = false; foreach ($this->getUnprocessedCommands() as $command_object) { $command = $command_object->getCommand(); switch ($command) { case HarbormasterBuildCommand::COMMAND_ABORT: $is_aborting = true; break; } } return $is_aborting; } public function deleteUnprocessedCommands() { foreach ($this->getUnprocessedCommands() as $key => $command_object) { $command_object->delete(); unset($this->unprocessedCommands[$key]); } return $this; } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new HarbormasterBuildTransactionEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new HarbormasterBuildTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { return $this->getBuildable()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getBuildable()->hasAutomaticCapability( $capability, $viewer); } public function describeAutomaticCapability($capability) { return pht('A build inherits policies from its buildable.'); } }