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);
     }
 }
 public function execute(HarbormasterBuild $build, HarbormasterBuildTarget $build_target)
 {
     $viewer = PhabricatorUser::getOmnipotentUser();
     $settings = $this->getSettings();
     // TODO: We should probably have a separate temporary storage area for
     // execution stuff that doesn't step on configuration state?
     $lease_phid = $build_target->getDetail('exec.leasePHID');
     if ($lease_phid) {
         $lease = id(new DrydockLeaseQuery())->setViewer($viewer)->withPHIDs(array($lease_phid))->executeOne();
         if (!$lease) {
             throw new PhabricatorWorkerPermanentFailureException(pht('Lease "%s" could not be loaded.', $lease_phid));
         }
     } else {
         $working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation())->getType();
         $lease = id(new DrydockLease())->setResourceType($working_copy_type)->setOwnerPHID($build_target->getPHID());
         $map = $this->buildRepositoryMap($build_target);
         $lease->setAttribute('repositories.map', $map);
         $task_id = $this->getCurrentWorkerTaskID();
         if ($task_id) {
             $lease->setAwakenTaskIDs(array($task_id));
         }
         $lease->queueForActivation();
         $build_target->setDetail('exec.leasePHID', $lease->getPHID())->save();
     }
     if ($lease->isActivating()) {
         // TODO: Smart backoff?
         throw new PhabricatorWorkerYieldException(15);
     }
     if (!$lease->isActive()) {
         // TODO: We could just forget about this lease and retry?
         throw new PhabricatorWorkerPermanentFailureException(pht('Lease "%s" never activated.', $lease->getPHID()));
     }
     $artifact = $build_target->createArtifact($viewer, $settings['name'], HarbormasterWorkingCopyArtifact::ARTIFACTCONST, array('drydockLeasePHID' => $lease->getPHID()));
 }
 public function execute(HarbormasterBuild $build, HarbormasterBuildTarget $build_target)
 {
     $settings = $this->getSettings();
     // Create the lease.
     $lease = id(new DrydockLease())->setResourceType('host')->setOwnerPHID($build_target->getPHID())->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_target->createArtifact(PhabricatorUser::getOmnipotentUser(), $settings['name'], HarbormasterHostArtifact::ARTIFACTCONST, array('drydockLeasePHID' => $lease->getPHID()));
 }
 private function updateTarget(HarbormasterBuildTarget $target, array $payload)
 {
     $step = $target->getBuildStep();
     $impl = $step->getStepImplementation();
     if (!$impl instanceof HarbormasterCircleCIBuildStepImplementation) {
         throw new Exception(pht('Build target ("%s") has the wrong type of build step. Only ' . 'CircleCI build steps may be updated via the CircleCI webhook.', $target->getPHID()));
     }
     switch (idx($payload, 'status')) {
         case 'success':
         case 'fixed':
             $message_type = HarbormasterMessageType::MESSAGE_PASS;
             break;
         default:
             $message_type = HarbormasterMessageType::MESSAGE_FAIL;
             break;
     }
     $viewer = PhabricatorUser::getOmnipotentUser();
     $api_method = 'harbormaster.sendmessage';
     $api_params = array('buildTargetPHID' => $target->getPHID(), 'type' => $message_type);
     id(new ConduitCall($api_method, $api_params))->setUser($viewer)->execute();
 }
 public static function initializeNewBuildLog(HarbormasterBuildTarget $build_target)
 {
     return id(new HarbormasterBuildLog())->setBuildTargetPHID($build_target->getPHID())->setDuration(null)->setLive(0);
 }
 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();
 }
 public static function initializeNewBuildArtifact(HarbormasterBuildTarget $build_target)
 {
     return id(new HarbormasterBuildArtifact())->setBuildTargetPHID($build_target->getPHID());
 }
 private function buildLog(HarbormasterBuild $build, HarbormasterBuildTarget $build_target)
 {
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $limit = $request->getInt('l', 25);
     $logs = id(new HarbormasterBuildLogQuery())->setViewer($viewer)->withBuildTargetPHIDs(array($build_target->getPHID()))->execute();
     $empty_logs = array();
     $log_boxes = array();
     foreach ($logs as $log) {
         $start = 1;
         $lines = preg_split("/\r\n|\r|\n/", $log->getLogText());
         if ($limit !== 0) {
             $start = count($lines) - $limit;
             if ($start >= 1) {
                 $lines = array_slice($lines, -$limit, $limit);
             } else {
                 $start = 1;
             }
         }
         $id = null;
         $is_empty = false;
         if (count($lines) === 1 && trim($lines[0]) === '') {
             // Prevent Harbormaster from showing empty build logs.
             $id = celerity_generate_unique_node_id();
             $empty_logs[] = $id;
             $is_empty = true;
         }
         $log_view = new ShellLogView();
         $log_view->setLines($lines);
         $log_view->setStart($start);
         $header = id(new PHUIHeaderView())->setHeader(pht('Build Log %d (%s - %s)', $log->getID(), $log->getLogSource(), $log->getLogType()))->setSubheader($this->createLogHeader($build, $log))->setUser($viewer);
         $log_box = id(new PHUIObjectBoxView())->setHeader($header)->setForm($log_view);
         if ($is_empty) {
             $log_box = phutil_tag('div', array('style' => 'display: none', 'id' => $id), $log_box);
         }
         $log_boxes[] = $log_box;
     }
     if ($empty_logs) {
         $hide_id = celerity_generate_unique_node_id();
         Javelin::initBehavior('phabricator-reveal-content');
         $expand = phutil_tag('div', array('id' => $hide_id, 'class' => 'harbormaster-empty-logs-are-hidden mlr mlt mll'), array(pht('%s empty logs are hidden.', new PhutilNumber(count($empty_logs))), ' ', javelin_tag('a', array('href' => '#', 'sigil' => 'reveal-content', 'meta' => array('showIDs' => $empty_logs, 'hideIDs' => array($hide_id))), pht('Show all logs.'))));
         array_unshift($log_boxes, $expand);
     }
     return $log_boxes;
 }
 public static function initializeNewLintMessage(HarbormasterBuildTarget $build_target)
 {
     return id(new HarbormasterBuildLintMessage())->setBuildTargetPHID($build_target->getPHID());
 }