diff --git a/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php b/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php index dc0c759da6..6f8fe28c6e 100644 --- a/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php +++ b/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php @@ -1,79 +1,79 @@ getStepImplementation(); } catch (Exception $ex) { return array(); } $specs = $impl->getFieldSpecifications(); if ($impl->supportsWaitForMessage()) { $specs['builtin.next-steps-header'] = array( 'type' => 'header', 'name' => pht('Next Steps'), ); $specs['builtin.wait-for-message'] = array( 'type' => 'select', 'name' => pht('When Complete'), 'instructions' => pht( 'After completing this build step Harbormaster can continue the '. 'build normally, or it can pause the build and wait for a message. '. 'If you are using this build step to trigger some work in an '. 'external system, you may want to have Phabricator wait for that '. 'system to perform the work and report results back.'. "\n\n". 'If you select **Continue Build Normally**, the build plan will '. 'proceed once this step finishes.'. "\n\n". 'If you select **Wait For Message**, the build plan will pause '. 'indefinitely once this step finishes. To resume the build, an '. 'external system must call `harbormaster.sendmessage` with the '. 'build target PHID, and either `"pass"` or `"fail"` to indicate '. 'the result for this step. After the result is recorded, the build '. 'plan will resume.'), 'options' => array( - '' => pht('Continue Build Normally'), + 'continue' => pht('Continue Build Normally'), 'wait' => pht('Wait For Message'), ), ); } return PhabricatorStandardCustomField::buildStandardFields($this, $specs); } public function shouldUseStorage() { return false; } public function readValueFromObject(PhabricatorCustomFieldInterface $object) { $key = $this->getProxy()->getRawStandardFieldKey(); $this->setValueFromStorage($object->getDetail($key)); } public function applyApplicationTransactionInternalEffects( PhabricatorApplicationTransaction $xaction) { $object = $this->getObject(); $key = $this->getProxy()->getRawStandardFieldKey(); $this->setValueFromApplicationTransactions($xaction->getNewValue()); $value = $this->getValueForStorage(); $object->setDetail($key, $value); } public function getBuildTargetFieldValue() { return $this->getProxy()->getFieldValue(); } } diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php index 4dac15f7a0..ca6f30bd36 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -1,314 +1,315 @@ currentWorkerTaskID = $id; return $this; } public function getCurrentWorkerTaskID() { return $this->currentWorkerTaskID; } public static function getImplementations() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) ->execute(); } public static function getImplementation($class) { $base = idx(self::getImplementations(), $class); if ($base) { return (clone $base); } return null; } public static function requireImplementation($class) { if (!$class) { throw new Exception(pht('No implementation is specified!')); } $implementation = self::getImplementation($class); if (!$implementation) { throw new Exception(pht('No such implementation "%s" exists!', $class)); } return $implementation; } /** * The name of the implementation. */ abstract public function getName(); public function getBuildStepGroupKey() { return HarbormasterOtherBuildStepGroup::GROUPKEY; } /** * The generic description of the implementation. */ public function getGenericDescription() { return ''; } /** * The description of the implementation, based on the current settings. */ public function getDescription() { return $this->getGenericDescription(); } public function getEditInstructions() { return null; } /** * Run the build target against the specified build. */ abstract public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target); /** * Gets the settings for this build step. */ public function getSettings() { return $this->settings; } public function getSetting($key, $default = null) { return idx($this->settings, $key, $default); } /** * Loads the settings for this build step implementation from a build * step or target. */ final public function loadSettings($build_object) { $this->settings = $build_object->getDetails(); return $this; } /** * Return the name of artifacts produced by this command. * * Future steps will calculate all available artifact mappings * before them and filter on the type. * * @return array The mappings of artifact names to their types. */ public function getArtifactInputs() { return array(); } public function getArtifactOutputs() { return array(); } public function getDependencies(HarbormasterBuildStep $build_step) { $dependencies = $build_step->getDetail('dependsOn', array()); $inputs = $build_step->getStepImplementation()->getArtifactInputs(); $inputs = ipull($inputs, null, 'key'); $artifacts = $this->getAvailableArtifacts( $build_step->getBuildPlan(), $build_step, null); foreach ($artifacts as $key => $type) { if (!array_key_exists($key, $inputs)) { unset($artifacts[$key]); } } $artifact_steps = ipull($artifacts, 'step'); $artifact_steps = mpull($artifact_steps, 'getPHID'); $dependencies = array_merge($dependencies, $artifact_steps); return $dependencies; } /** * Returns a list of all artifacts made available in the build plan. */ public static function getAvailableArtifacts( HarbormasterBuildPlan $build_plan, $current_build_step, $artifact_type) { $steps = id(new HarbormasterBuildStepQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withBuildPlanPHIDs(array($build_plan->getPHID())) ->execute(); $artifacts = array(); $artifact_arrays = array(); foreach ($steps as $step) { if ($current_build_step !== null && $step->getPHID() === $current_build_step->getPHID()) { continue; } $implementation = $step->getStepImplementation(); $array = $implementation->getArtifactOutputs(); $array = ipull($array, 'type', 'key'); foreach ($array as $name => $type) { if ($type !== $artifact_type && $artifact_type !== null) { continue; } $artifacts[$name] = array('type' => $type, 'step' => $step); } } return $artifacts; } /** * Convert a user-provided string with variables in it, like: * * ls ${dirname} * * ...into a string with variables merged into it safely: * * ls 'dir with spaces' * * @param string Name of a `vxsprintf` function, like @{function:vcsprintf}. * @param string User-provided pattern string containing `${variables}`. * @param dict List of available replacement variables. * @return string String with variables replaced safely into it. */ protected function mergeVariables($function, $pattern, array $variables) { $regexp = '@\\$\\{(?P[a-z\\./-]+)\\}@'; $matches = null; preg_match_all($regexp, $pattern, $matches); $argv = array(); foreach ($matches['name'] as $name) { if (!array_key_exists($name, $variables)) { throw new Exception(pht("No such variable '%s'!", $name)); } $argv[] = $variables[$name]; } $pattern = str_replace('%', '%%', $pattern); $pattern = preg_replace($regexp, '%s', $pattern); return call_user_func($function, $pattern, $argv); } public function getFieldSpecifications() { return array(); } protected function formatSettingForDescription($key, $default = null) { return $this->formatValueForDescription($this->getSetting($key, $default)); } protected function formatValueForDescription($value) { if (strlen($value)) { return phutil_tag('strong', array(), $value); } else { return phutil_tag('em', array(), pht('(null)')); } } public function supportsWaitForMessage() { return false; } public function shouldWaitForMessage(HarbormasterBuildTarget $target) { if (!$this->supportsWaitForMessage()) { return false; } - return (bool)$target->getDetail('builtin.wait-for-message'); + $wait = $target->getDetail('builtin.wait-for-message'); + return ($wait == 'wait'); } protected function shouldAbort( HarbormasterBuild $build, HarbormasterBuildTarget $target) { return $build->getBuildGeneration() !== $target->getBuildGeneration(); } protected function resolveFutures( HarbormasterBuild $build, HarbormasterBuildTarget $target, array $futures) { $futures = new FutureIterator($futures); foreach ($futures->setUpdateInterval(5) as $key => $future) { if ($future === null) { $build->reload(); if ($this->shouldAbort($build, $target)) { throw new HarbormasterBuildAbortedException(); } } } } protected function logHTTPResponse( HarbormasterBuild $build, HarbormasterBuildTarget $build_target, BaseHTTPFuture $future, $label) { list($status, $body, $headers) = $future->resolve(); $header_lines = array(); // TODO: We don't currently preserve the entire "HTTP" response header, but // should. Once we do, reproduce it here faithfully. $status_code = $status->getStatusCode(); $header_lines[] = "HTTP {$status_code}"; foreach ($headers as $header) { list($head, $tail) = $header; $header_lines[] = "{$head}: {$tail}"; } $header_lines = implode("\n", $header_lines); $build_target ->newLog($label, 'http.head') ->append($header_lines); $build_target ->newLog($label, 'http.body') ->append($body); } /* -( Automatic Targets )-------------------------------------------------- */ public function getBuildStepAutotargetStepKey() { return null; } public function getBuildStepAutotargetPlanKey() { throw new PhutilMethodNotImplementedException(); } public function shouldRequireAutotargeting() { return false; } }