public function execute(HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { // We can only wait when building against commits. $buildable = $build->getBuildable(); $object = $buildable->getBuildableObject(); if (!$object instanceof PhabricatorRepositoryCommit) { return; } // Block until all previous builds of the same build plan have // finished. $plan = $build->getBuildPlan(); $existing_logs = id(new HarbormasterBuildLogQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withBuildTargetPHIDs(array($build_target->getPHID()))->execute(); if ($existing_logs) { $log = head($existing_logs); } else { $log = $build->createLog($build_target, 'waiting', 'blockers'); } $blockers = $this->getBlockers($object, $plan, $build); if ($blockers) { $log->start(); $log->append(pht("Blocked by: %s\n", implode(',', $blockers))); $log->finalize(); } if ($blockers) { throw new PhabricatorWorkerYieldException(15); } }
protected function loadPage() { $table = new HarbormasterBuild(); $conn_r = $table->establishConnection('r'); $data = queryfx_all($conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); }
private function buildActionView(HarbormasterBuildUnitMessage $message, HarbormasterBuild $build) { $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView())->setUser($viewer); $view->addAction(id(new PhabricatorActionView())->setName(pht('View Build'))->setHref($build->getURI())->setIcon('fa-wrench')); return $view; }
public function execute(HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { $viewer = PhabricatorUser::getOmnipotentUser(); $settings = $this->getSettings(); $variables = $build_target->getVariables(); $artifact = $build_target->loadArtifact($settings['hostartifact']); $impl = $artifact->getArtifactImplementation(); $lease = $impl->loadArtifactLease($viewer); $this->platform = $lease->getAttribute('platform'); $command = $this->mergeVariables(array($this, 'escapeCommand'), $settings['command'], $variables); $this->platform = null; $interface = $lease->getInterface('command'); $future = $interface->getExecFuture('%C', $command); $log_stdout = $build->createLog($build_target, 'remote', 'stdout'); $log_stderr = $build->createLog($build_target, 'remote', 'stderr'); $start_stdout = $log_stdout->start(); $start_stderr = $log_stderr->start(); $build_update = 5; // Read the next amount of available output every second. $futures = new FutureIterator(array($future)); foreach ($futures->setUpdateInterval(1) as $key => $future_iter) { if ($future_iter === null) { // Check to see if we should abort. if ($build_update <= 0) { $build->reload(); if ($this->shouldAbort($build, $build_target)) { $future->resolveKill(); throw new HarbormasterBuildAbortedException(); } else { $build_update = 5; } } else { $build_update -= 1; } // Command is still executing. // Read more data as it is available. list($stdout, $stderr) = $future->read(); $log_stdout->append($stdout); $log_stderr->append($stderr); $future->discardBuffers(); } else { // Command execution is complete. // Get the return value so we can log that as well. list($err) = $future->resolve(); // Retrieve the last few bits of information. list($stdout, $stderr) = $future->read(); $log_stdout->append($stdout); $log_stderr->append($stderr); $future->discardBuffers(); break; } } $log_stdout->finalize($start_stdout); $log_stderr->finalize($start_stderr); if ($err) { throw new HarbormasterBuildFailureException(); } }
private function executeBuildCommand(HarbormasterBuild $build, HarbormasterBuildTransaction $xaction) { $command = $xaction->getNewValue(); switch ($command) { case HarbormasterBuildCommand::COMMAND_RESTART: $issuable = $build->canRestartBuild(); break; case HarbormasterBuildCommand::COMMAND_PAUSE: $issuable = $build->canPauseBuild(); break; case HarbormasterBuildCommand::COMMAND_RESUME: $issuable = $build->canResumeBuild(); break; case HarbormasterBuildCommand::COMMAND_ABORT: $issuable = $build->canAbortBuild(); break; default: throw new Exception(pht('Unknown command %s', $command)); } if (!$issuable) { return; } $actor = $this->getActor(); if (!$build->canIssueCommand($actor, $command)) { return; } id(new HarbormasterBuildCommand())->setAuthorPHID($xaction->getAuthorPHID())->setTargetPHID($build->getPHID())->setCommand($command)->save(); PhabricatorWorker::scheduleTask('HarbormasterBuildWorker', array('buildID' => $build->getID()), array('objectPHID' => $build->getPHID())); }
public function execute(HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { $settings = $this->getSettings(); // Create the lease. $lease = id(new DrydockLease())->setResourceType('host')->setAttributes(array('platform' => $settings['platform']))->queueForActivation(); // Wait until the lease is fulfilled. // TODO: This will throw an exception if the lease can't be fulfilled; // we should treat that as build failure not build error. $lease->waitUntilActive(); // Create the associated artifact. $artifact = $build->createArtifact($build_target, $settings['name'], HarbormasterBuildArtifact::TYPE_HOST); $artifact->setArtifactData(array('drydock-lease' => $lease->getID())); $artifact->save(); }
public function execute(HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { $settings = $this->getSettings(); $variables = $build_target->getVariables(); $path = $this->mergeVariables('vsprintf', $settings['path'], $variables); $artifact = $build->loadArtifact($settings['hostartifact']); $lease = $artifact->loadDrydockLease(); $interface = $lease->getInterface('filesystem'); // TODO: Handle exceptions. $file = $interface->saveFile($path, $settings['name']); // Insert the artifact record. $artifact = $build->createArtifact($build_target, $settings['name'], HarbormasterBuildArtifact::TYPE_FILE); $artifact->setArtifactData(array('filePHID' => $file->getPHID())); $artifact->save(); }
public function execute(HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { // We can only wait when building against commits. $buildable = $build->getBuildable(); $object = $buildable->getBuildableObject(); if (!$object instanceof PhabricatorRepositoryCommit) { return; } // Block until all previous builds of the same build plan have // finished. $plan = $build->getBuildPlan(); $blockers = $this->getBlockers($object, $plan, $build); if ($blockers) { throw new PhabricatorWorkerYieldException(15); } }
public function execute(HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { $settings = $this->getSettings(); $variables = $build_target->getVariables(); $path = $this->mergeVariables('vsprintf', $settings['path'], $variables); $artifact = $build->loadArtifact($settings['artifact']); $file = $artifact->loadPhabricatorFile(); $fragment = id(new PhragmentFragmentQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withPaths(array($path))->executeOne(); if ($fragment === null) { PhragmentFragment::createFromFile(PhabricatorUser::getOmnipotentUser(), $file, $path, PhabricatorPolicies::getMostOpenPolicy(), PhabricatorPolicies::POLICY_USER); } else { if ($file->getMimeType() === 'application/zip') { $fragment->updateFromZIP(PhabricatorUser::getOmnipotentUser(), $file); } else { $fragment->updateFromFile(PhabricatorUser::getOmnipotentUser(), $file); } } }
private function handlePropertyEvent($ui_event) { $user = $ui_event->getUser(); $object = $ui_event->getValue('object'); if (!$object || !$object->getPHID()) { // No object, or the object has no PHID yet.. return; } if ($object instanceof HarbormasterBuildable) { // Although HarbormasterBuildable implements the correct interface, it // does not make sense to show a build's build status. In the best case // it is meaningless, and in the worst case it's confusing. return; } if ($object instanceof DifferentialRevision) { // TODO: This is a bit hacky and we could probably find a cleaner fix // eventually, but we show build status on each diff, immediately below // this property list, so it's redundant to show it on the revision view. return; } if (!$object instanceof HarbormasterBuildableInterface) { return; } $buildable_phid = $object->getHarbormasterBuildablePHID(); if (!$buildable_phid) { return; } if (!$this->canUseApplication($ui_event->getUser())) { return; } $buildable = id(new HarbormasterBuildableQuery())->setViewer($user)->withManualBuildables(false)->withBuildablePHIDs(array($buildable_phid))->needBuilds(true)->executeOne(); if (!$buildable) { return; } $builds = $buildable->getBuilds(); $build_handles = id(new PhabricatorHandleQuery())->setViewer($user)->withPHIDs(mpull($builds, 'getPHID'))->execute(); $status_view = new PHUIStatusListView(); $buildable_status = $buildable->getBuildableStatus(); $buildable_icon = HarbormasterBuildable::getBuildableStatusIcon($buildable_status); $buildable_color = HarbormasterBuildable::getBuildableStatusColor($buildable_status); $buildable_name = HarbormasterBuildable::getBuildableStatusName($buildable_status); $target = phutil_tag('a', array('href' => '/' . $buildable->getMonogram()), pht('Buildable %d', $buildable->getID())); $target = phutil_tag('strong', array(), $target); $status_view->addItem(id(new PHUIStatusItemView())->setIcon($buildable_icon, $buildable_color, $buildable_name)->setTarget($target)); foreach ($builds as $build) { $item = new PHUIStatusItemView(); $item->setTarget($build_handles[$build->getPHID()]->renderLink()); $status = $build->getBuildStatus(); $status_name = HarbormasterBuild::getBuildStatusName($status); $icon = HarbormasterBuild::getBuildStatusIcon($status); $color = HarbormasterBuild::getBuildStatusColor($status); $item->setIcon($icon, $color, $status_name); $status_view->addItem($item); } $view = $ui_event->getValue('view'); $view->addProperty(pht('Build Status'), $status_view); }
public function execute(HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { $settings = $this->getSettings(); $target = time() + $settings['seconds']; // Use $build_update so that we only reload every 5 seconds, but // the sleep mechanism remains accurate. $build_update = 5; while (time() < $target) { sleep(1); if ($build_update <= 0) { $build->reload(); $build_update = 5; if ($this->shouldAbort($build, $build_target)) { throw new HarbormasterBuildAbortedException(); } } else { $build_update -= 1; } } }
private function buildBuildList(HarbormasterBuildable $buildable) { $viewer = $this->getRequest()->getUser(); $build_list = id(new PHUIObjectItemListView())->setUser($viewer); foreach ($buildable->getBuilds() as $build) { $view_uri = $this->getApplicationURI('/build/' . $build->getID() . '/'); $item = id(new PHUIObjectItemView())->setObjectName(pht('Build %d', $build->getID()))->setHeader($build->getName())->setHref($view_uri); $status = $build->getBuildStatus(); $item->setStatusIcon('fa-dot-circle-o ' . HarbormasterBuild::getBuildStatusColor($status), HarbormasterBuild::getBuildStatusName($status)); $item->addAttribute(HarbormasterBuild::getBuildStatusName($status)); if ($build->isRestarting()) { $item->addIcon('fa-repeat', pht('Restarting')); } else { if ($build->isPausing()) { $item->addIcon('fa-pause', pht('Pausing')); } else { if ($build->isResuming()) { $item->addIcon('fa-play', pht('Resuming')); } } } $build_id = $build->getID(); $restart_uri = "build/restart/{$build_id}/buildable/"; $resume_uri = "build/resume/{$build_id}/buildable/"; $pause_uri = "build/pause/{$build_id}/buildable/"; $abort_uri = "build/abort/{$build_id}/buildable/"; $item->addAction(id(new PHUIListItemView())->setIcon('fa-repeat')->setName(pht('Restart'))->setHref($this->getApplicationURI($restart_uri))->setWorkflow(true)->setDisabled(!$build->canRestartBuild())); if ($build->canResumeBuild()) { $item->addAction(id(new PHUIListItemView())->setIcon('fa-play')->setName(pht('Resume'))->setHref($this->getApplicationURI($resume_uri))->setWorkflow(true)); } else { $item->addAction(id(new PHUIListItemView())->setIcon('fa-pause')->setName(pht('Pause'))->setHref($this->getApplicationURI($pause_uri))->setWorkflow(true)->setDisabled(!$build->canPauseBuild())); } $targets = $build->getBuildTargets(); if ($targets) { $target_list = id(new PHUIStatusListView()); foreach ($targets as $target) { $status = $target->getTargetStatus(); $icon = HarbormasterBuildTarget::getBuildTargetStatusIcon($status); $color = HarbormasterBuildTarget::getBuildTargetStatusColor($status); $status_name = HarbormasterBuildTarget::getBuildTargetStatusName($status); $name = $target->getName(); $target_list->addItem(id(new PHUIStatusItemView())->setIcon($icon, $color, $status_name)->setTarget(pht('Target %d', $target->getID()))->setNote($name)); } $target_box = id(new PHUIBoxView())->addPadding(PHUI::PADDING_SMALL)->appendChild($target_list); $item->appendChild($target_box); } $build_list->addItem($item); } $build_list->setFlush(true); $box = id(new PHUIObjectBoxView())->setHeaderText(pht('Builds'))->appendChild($build_list); return $box; }
public function execute(HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { $viewer = PhabricatorUser::getOmnipotentUser(); $settings = $this->getSettings(); $variables = $build_target->getVariables(); $uri = $this->mergeVariables('vurisprintf', $settings['uri'], $variables); $log_body = $build->createLog($build_target, $uri, 'http-body'); $start = $log_body->start(); $method = nonempty(idx($settings, 'method'), 'POST'); $future = id(new HTTPSFuture($uri))->setMethod($method)->setTimeout(60); $credential_phid = $this->getSetting('credential'); if ($credential_phid) { $key = PassphrasePasswordKey::loadFromPHID($credential_phid, $viewer); $future->setHTTPBasicAuthCredentials($key->getUsernameEnvelope()->openEnvelope(), $key->getPasswordEnvelope()); } list($status, $body, $headers) = $this->resolveFuture($build, $build_target, $future); $log_body->append($body); $log_body->finalize($start); if ($status->getStatusCode() != 200) { $build->setBuildStatus(HarbormasterBuild::STATUS_FAILED); } }
private function handlePropertyEvent($ui_event) { $user = $ui_event->getUser(); $object = $ui_event->getValue('object'); if (!$object || !$object->getPHID()) { // No object, or the object has no PHID yet.. return; } if ($object instanceof HarbormasterBuildable) { // Although HarbormasterBuildable implements the correct interface, it // does not make sense to show a build's build status. In the best case // it is meaningless, and in the worst case it's confusing. return; } if (!$object instanceof HarbormasterBuildableInterface) { return; } $buildable_phid = $object->getHarbormasterBuildablePHID(); if (!$buildable_phid) { return; } if (!$this->canUseApplication($ui_event->getUser())) { return; } $buildables = id(new HarbormasterBuildableQuery())->setViewer($user)->withManualBuildables(false)->withBuildablePHIDs(array($buildable_phid))->execute(); if (!$buildables) { return; } $builds = id(new HarbormasterBuildQuery())->setViewer($user)->withBuildablePHIDs(mpull($buildables, 'getPHID'))->execute(); if (!$builds) { return; } $build_handles = id(new PhabricatorHandleQuery())->setViewer($user)->withPHIDs(mpull($builds, 'getPHID'))->execute(); $status_view = new PHUIStatusListView(); foreach ($builds as $build) { $item = new PHUIStatusItemView(); $item->setTarget($build_handles[$build->getPHID()]->renderLink()); $status = $build->getBuildStatus(); $status_name = HarbormasterBuild::getBuildStatusName($status); $icon = HarbormasterBuild::getBuildStatusIcon($status); $color = HarbormasterBuild::getBuildStatusColor($status); $item->setIcon($icon, $color, $status_name); $status_view->addItem($item); } $view = $ui_event->getValue('view'); $view->addProperty(pht('Build Status'), $status_view); }
protected function execute(ConduitAPIRequest $request) { $viewer = $request->getUser(); $query = id(new HarbormasterBuildQuery())->setViewer($viewer); $ids = $request->getValue('ids'); if ($ids !== null) { $query->withIDs($ids); } $phids = $request->getValue('phids'); if ($phids !== null) { $query->withPHIDs($phids); } $statuses = $request->getValue('buildStatuses'); if ($statuses !== null) { $query->withBuildStatuses($statuses); } $buildable_phids = $request->getValue('buildablePHIDs'); if ($buildable_phids !== null) { $query->withBuildablePHIDs($buildable_phids); } $build_plan_phids = $request->getValue('buildPlanPHIDs'); if ($build_plan_phids !== null) { $query->withBuildPlanPHIDs($build_plan_phids); } $pager = $this->newPager($request); $builds = $query->executeWithCursorPager($pager); $data = array(); foreach ($builds as $build) { $id = $build->getID(); $uri = '/harbormaster/build/' . $id . '/'; $status = $build->getBuildStatus(); $data[] = array('id' => $id, 'phid' => $build->getPHID(), 'uri' => PhabricatorEnv::getProductionURI($uri), 'name' => $build->getBuildPlan()->getName(), 'buildablePHID' => $build->getBuildablePHID(), 'buildPlanPHID' => $build->getBuildPlanPHID(), 'buildStatus' => $status, 'buildStatusName' => HarbormasterBuild::getBuildStatusName($status)); } $results = array('data' => $data); $results = $this->addPagerResults($results, $pager); return $results; }
private function executeBuildCommand(HarbormasterBuild $build, HarbormasterBuildTransaction $xaction) { $command = $xaction->getNewValue(); switch ($command) { case HarbormasterBuildCommand::COMMAND_RESTART: $issuable = $build->canRestartBuild(); break; case HarbormasterBuildCommand::COMMAND_STOP: $issuable = $build->canStopBuild(); break; case HarbormasterBuildCommand::COMMAND_RESUME: $issuable = $build->canResumeBuild(); break; default: throw new Exception("Unknown command {$command}"); } if (!$issuable) { return; } id(new HarbormasterBuildCommand())->setAuthorPHID($xaction->getAuthorPHID())->setTargetPHID($build->getPHID())->setCommand($command)->save(); PhabricatorWorker::scheduleTask('HarbormasterBuildWorker', array('buildID' => $build->getID())); }
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(); } } } }
public function applyPlan(HarbormasterBuildPlan $plan) { $viewer = PhabricatorUser::getOmnipotentUser(); $build = HarbormasterBuild::initializeNewBuild($viewer)->setBuildablePHID($this->getPHID())->setBuildPlanPHID($plan->getPHID())->setBuildStatus(HarbormasterBuild::STATUS_PENDING)->save(); PhabricatorWorker::scheduleTask('HarbormasterBuildWorker', array('buildID' => $build->getID())); return $build; }
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(HarbormasterBuildStatus::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()), array('objectPHID' => $build->getPHID())); return $build; }
private function releaseAllArtifacts(HarbormasterBuild $build) { $targets = id(new HarbormasterBuildTargetQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withBuildPHIDs(array($build->getPHID()))->withBuildGenerations(array($build->getBuildGeneration()))->execute(); if (count($targets) === 0) { return; } $target_phids = mpull($targets, 'getPHID'); $artifacts = id(new HarbormasterBuildArtifactQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withBuildTargetPHIDs($target_phids)->execute(); foreach ($artifacts as $artifact) { $artifact->releaseArtifact(); } }
public static function initializeNewBuildTarget(HarbormasterBuild $build, HarbormasterBuildStep $build_step, array $variables) { return id(new HarbormasterBuildTarget())->setName($build_step->getName())->setBuildPHID($build->getPHID())->setBuildStepPHID($build_step->getPHID())->setClassName($build_step->getClassName())->setDetails($build_step->getDetails())->setTargetStatus(self::STATUS_PENDING)->setVariables($variables)->setBuildGeneration($build->getBuildGeneration()); }
private function renderBuildVariablesTable() { $viewer = $this->getRequest()->getUser(); $variables = HarbormasterBuild::getAvailableBuildVariables(); ksort($variables); $rows = array(); $rows[] = pht('The following variables can be used in most fields. To reference ' . 'a variable, use `${name}` in a field.'); $rows[] = pht('| Variable | Description |'); $rows[] = '|---|---|'; foreach ($variables as $name => $description) { $rows[] = '| `' . $name . '` | ' . $description . ' |'; } $rows = implode("\n", $rows); $form = id(new AphrontFormView())->setUser($viewer)->appendRemarkupInstructions($rows); return id(new PHUIObjectBoxView())->setHeaderText(pht('Build Variables'))->appendChild($form); }
private function loadHistoryDiffStatus(array $diffs) { assert_instances_of($diffs, 'DifferentialDiff'); $diff_phids = mpull($diffs, 'getPHID'); $bad_unit_status = array(ArcanistUnitTestResult::RESULT_FAIL, ArcanistUnitTestResult::RESULT_BROKEN); $message = new HarbormasterBuildUnitMessage(); $target = new HarbormasterBuildTarget(); $build = new HarbormasterBuild(); $buildable = new HarbormasterBuildable(); $broken_diffs = queryfx_all($message->establishConnection('r'), 'SELECT distinct a.buildablePHID FROM %T m JOIN %T t ON m.buildTargetPHID = t.phid JOIN %T b ON t.buildPHID = b.phid JOIN %T a ON b.buildablePHID = a.phid WHERE a.buildablePHID IN (%Ls) AND m.result in (%Ls)', $message->getTableName(), $target->getTableName(), $build->getTableName(), $buildable->getTableName(), $diff_phids, $bad_unit_status); $unit_status = array(); foreach ($broken_diffs as $broken) { $phid = $broken['buildablePHID']; $unit_status[$phid] = DifferentialUnitStatus::UNIT_FAIL; } return $unit_status; }
private function getStatus(HarbormasterBuild $build) { $status_view = new PHUIStatusListView(); $item = new PHUIStatusItemView(); if ($build->isStopping()) { $status_name = pht('Pausing'); $icon = PHUIStatusItemView::ICON_RIGHT; $color = 'dark'; } else { $status = $build->getBuildStatus(); $status_name = HarbormasterBuild::getBuildStatusName($status); $icon = HarbormasterBuild::getBuildStatusIcon($status); $color = HarbormasterBuild::getBuildStatusColor($status); } $item->setTarget($status_name); $item->setIcon($icon, $color); $status_view->addItem($item); return $status_view; }
protected function resolveFuture(HarbormasterBuild $build, HarbormasterBuildTarget $target, Future $future) { $futures = Futures(array($future)); foreach ($futures->setUpdateInterval(5) as $key => $future) { if ($future === null) { $build->reload(); if ($this->shouldAbort($build, $target)) { throw new HarbormasterBuildAbortedException(); } } else { return $future->resolve(); } } }
public function execute(HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { $viewer = PhabricatorUser::getOmnipotentUser(); $buildable = $build->getBuildable(); $object = $buildable->getBuildableObject(); $object_phid = $object->getPHID(); if (!$object instanceof HarbormasterCircleCIBuildableInterface) { throw new Exception(pht('Object ("%s") does not implement interface "%s". Only objects ' . 'which implement this interface can be built with CircleCI.', $object_phid, 'HarbormasterCircleCIBuildableInterface')); } $github_uri = $object->getCircleCIGitHubRepositoryURI(); $build_type = $object->getCircleCIBuildIdentifierType(); $build_identifier = $object->getCircleCIBuildIdentifier(); $path = self::getGitHubPath($github_uri); if ($path === null) { throw new Exception(pht('Object ("%s") claims "%s" is a GitHub repository URI, but the ' . 'domain does not appear to be GitHub.', $object_phid, $github_uri)); } $path_parts = trim($path, '/'); $path_parts = explode('/', $path_parts); if (count($path_parts) < 2) { throw new Exception(pht('Object ("%s") claims "%s" is a GitHub repository URI, but the ' . 'path ("%s") does not have enough components (expected at least ' . 'two).', $object_phid, $github_uri, $path)); } list($github_namespace, $github_name) = $path_parts; $github_name = preg_replace('(\\.git$)', '', $github_name); $credential_phid = $this->getSetting('token'); $api_token = id(new PassphraseCredentialQuery())->setViewer($viewer)->withPHIDs(array($credential_phid))->needSecrets(true)->executeOne(); if (!$api_token) { throw new Exception(pht('Unable to load API token ("%s")!', $credential_phid)); } // When we pass "revision", the branch is ignored (and does not even need // to exist), and only shows up in the UI. Use a cute string which will // certainly never break anything or cause any kind of problem. $ship = "🚢"; $branch = "{$ship}Harbormaster"; $token = $api_token->getSecret()->openEnvelope(); $parts = array('https://circleci.com/api/v1/project', phutil_escape_uri($github_namespace), phutil_escape_uri($github_name) . "?circle-token={$token}"); $uri = implode('/', $parts); $data_structure = array(); switch ($build_type) { case 'tag': $data_structure['tag'] = $build_identifier; break; case 'revision': $data_structure['revision'] = $build_identifier; break; default: throw new Exception(pht('Unknown CircleCI build type "%s". Expected "%s" or "%s".', $build_type, 'tag', 'revision')); } $data_structure['build_parameters'] = array('HARBORMASTER_BUILD_TARGET_PHID' => $build_target->getPHID()); $json_data = phutil_json_encode($data_structure); $future = id(new HTTPSFuture($uri, $json_data))->setMethod('POST')->addHeader('Content-Type', 'application/json')->addHeader('Accept', 'application/json')->setTimeout(60); $this->resolveFutures($build, $build_target, array($future)); $this->logHTTPResponse($build, $build_target, $future, pht('CircleCI')); list($status, $body) = $future->resolve(); if ($status->isError()) { throw new HarbormasterBuildFailureException(); } $response = phutil_json_decode($body); $build_uri = idx($response, 'build_url'); if (!$build_uri) { throw new Exception(pht('CircleCI did not return a "%s"!', 'build_url')); } $target_phid = $build_target->getPHID(); // Write an artifact to create a link to the external build in CircleCI. $api_method = 'harbormaster.createartifact'; $api_params = array('buildTargetPHID' => $target_phid, 'artifactType' => HarbormasterURIArtifact::ARTIFACTCONST, 'artifactKey' => 'circleci.uri', 'artifactData' => array('uri' => $build_uri, 'name' => pht('View in CircleCI'), 'ui.external' => true)); id(new ConduitCall($api_method, $api_params))->setUser($viewer)->execute(); }