@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.

at recaptime-dev/main 258 lines 7.3 kB view raw
1<?php 2 3final class HarbormasterCircleCIBuildStepImplementation 4 extends HarbormasterBuildStepImplementation { 5 6 public function getName() { 7 return pht('Build with CircleCI'); 8 } 9 10 public function getGenericDescription() { 11 return pht('Trigger a build in CircleCI.'); 12 } 13 14 public function getBuildStepGroupKey() { 15 return HarbormasterExternalBuildStepGroup::GROUPKEY; 16 } 17 18 public function getDescription() { 19 return pht('Run a build in CircleCI.'); 20 } 21 22 public function getEditInstructions() { 23 $hook_uri = '/harbormaster/hook/circleci/'; 24 $hook_uri = PhabricatorEnv::getProductionURI($hook_uri); 25 26 return pht(<<<EOTEXT 27WARNING: This build step is new and experimental! 28 29To build **revisions** with CircleCI, they must: 30 31 - belong to a tracked repository; 32 - the repository must have a Staging Area configured; 33 - the Staging Area must be hosted on GitHub; and 34 - you must configure the webhook described below. 35 36To build **commits** with CircleCI, they must: 37 38 - belong to a repository that is being imported from GitHub; and 39 - you must configure the webhook described below. 40 41Webhook Configuration 42===================== 43 44Add this webhook to your `circle.yml` file to make CircleCI report results 45to Harbormaster. Until you install this hook, builds will hang waiting for 46a response from CircleCI. 47 48```lang=yml 49notify: 50 webhooks: 51 - url: %s 52``` 53 54Environment 55=========== 56 57These variables will be available in the build environment: 58 59| Variable | Description | 60|----------|-------------| 61| `HARBORMASTER_BUILD_TARGET_PHID` | PHID of the Build Target. 62 63EOTEXT 64 , 65 $hook_uri); 66 } 67 68 public static function getGitHubPath($uri) { 69 $uri_object = new PhutilURI($uri); 70 $domain = $uri_object->getDomain(); 71 72 $domain = phutil_utf8_strtolower($domain); 73 switch ($domain) { 74 case 'github.com': 75 case 'www.github.com': 76 return $uri_object->getPath(); 77 default: 78 return null; 79 } 80 } 81 82 public function execute( 83 HarbormasterBuild $build, 84 HarbormasterBuildTarget $build_target) { 85 $viewer = PhabricatorUser::getOmnipotentUser(); 86 87 if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { 88 $this->logSilencedCall($build, $build_target, pht('CircleCI')); 89 throw new HarbormasterBuildFailureException(); 90 } 91 92 $buildable = $build->getBuildable(); 93 94 $object = $buildable->getBuildableObject(); 95 $object_phid = $object->getPHID(); 96 if (!($object instanceof HarbormasterCircleCIBuildableInterface)) { 97 throw new Exception( 98 pht( 99 'Object ("%s") does not implement interface "%s". Only objects '. 100 'which implement this interface can be built with CircleCI.', 101 $object_phid, 102 'HarbormasterCircleCIBuildableInterface')); 103 } 104 105 $github_uri = $object->getCircleCIGitHubRepositoryURI(); 106 $build_type = $object->getCircleCIBuildIdentifierType(); 107 $build_identifier = $object->getCircleCIBuildIdentifier(); 108 109 $path = self::getGitHubPath($github_uri); 110 if ($path === null) { 111 throw new Exception( 112 pht( 113 'Object ("%s") claims "%s" is a GitHub repository URI, but the '. 114 'domain does not appear to be GitHub.', 115 $object_phid, 116 $github_uri)); 117 } 118 119 $path_parts = trim($path, '/'); 120 $path_parts = explode('/', $path_parts); 121 if (count($path_parts) < 2) { 122 throw new Exception( 123 pht( 124 'Object ("%s") claims "%s" is a GitHub repository URI, but the '. 125 'path ("%s") does not have enough components (expected at least '. 126 'two).', 127 $object_phid, 128 $github_uri, 129 $path)); 130 } 131 132 list($github_namespace, $github_name) = $path_parts; 133 $github_name = preg_replace('(\\.git$)', '', $github_name); 134 135 $credential_phid = $this->getSetting('token'); 136 $api_token = id(new PassphraseCredentialQuery()) 137 ->setViewer($viewer) 138 ->withPHIDs(array($credential_phid)) 139 ->needSecrets(true) 140 ->executeOne(); 141 if (!$api_token) { 142 throw new Exception( 143 pht( 144 'Unable to load API token ("%s")!', 145 $credential_phid)); 146 } 147 148 // When we pass "revision", the branch is ignored (and does not even need 149 // to exist), and only shows up in the UI. Use a cute string which will 150 // certainly never break anything or cause any kind of problem. 151 $ship = "\xF0\x9F\x9A\xA2"; 152 $branch = "{$ship}Harbormaster"; 153 154 $token = $api_token->getSecret()->openEnvelope(); 155 $parts = array( 156 'https://circleci.com/api/v1/project', 157 phutil_escape_uri($github_namespace), 158 phutil_escape_uri($github_name)."?circle-token={$token}", 159 ); 160 161 $uri = implode('/', $parts); 162 163 $data_structure = array(); 164 switch ($build_type) { 165 case 'tag': 166 $data_structure['tag'] = $build_identifier; 167 break; 168 case 'revision': 169 $data_structure['revision'] = $build_identifier; 170 break; 171 default: 172 throw new Exception( 173 pht( 174 'Unknown CircleCI build type "%s". Expected "%s" or "%s".', 175 $build_type, 176 'tag', 177 'revision')); 178 } 179 180 $data_structure['build_parameters'] = array( 181 'HARBORMASTER_BUILD_TARGET_PHID' => $build_target->getPHID(), 182 ); 183 184 $json_data = phutil_json_encode($data_structure); 185 186 $future = id(new HTTPSFuture($uri, $json_data)) 187 ->setMethod('POST') 188 ->addHeader('Content-Type', 'application/json') 189 ->addHeader('Accept', 'application/json') 190 ->setTimeout(60); 191 192 $this->resolveFutures( 193 $build, 194 $build_target, 195 array($future)); 196 197 $this->logHTTPResponse($build, $build_target, $future, pht('CircleCI')); 198 199 list($status, $body) = $future->resolve(); 200 if ($status->isError()) { 201 throw new HarbormasterBuildFailureException(); 202 } 203 204 $response = phutil_json_decode($body); 205 $build_uri = idx($response, 'build_url'); 206 if (!$build_uri) { 207 throw new Exception( 208 pht( 209 'CircleCI did not return a "%s"!', 210 'build_url')); 211 } 212 213 $target_phid = $build_target->getPHID(); 214 215 // Write an artifact to create a link to the external build in CircleCI. 216 217 $api_method = 'harbormaster.createartifact'; 218 $api_params = array( 219 'buildTargetPHID' => $target_phid, 220 'artifactType' => HarbormasterURIArtifact::ARTIFACTCONST, 221 'artifactKey' => 'circleci.uri', 222 'artifactData' => array( 223 'uri' => $build_uri, 224 'name' => pht('View in CircleCI'), 225 'ui.external' => true, 226 ), 227 ); 228 229 id(new ConduitCall($api_method, $api_params)) 230 ->setUser($viewer) 231 ->execute(); 232 } 233 234 public function getFieldSpecifications() { 235 return array( 236 'token' => array( 237 'name' => pht('API Token'), 238 'type' => 'credential', 239 'credential.type' 240 => PassphraseTokenCredentialType::CREDENTIAL_TYPE, 241 'credential.provides' 242 => PassphraseTokenCredentialType::PROVIDES_TYPE, 243 'required' => true, 244 ), 245 ); 246 } 247 248 public function supportsWaitForMessage() { 249 // NOTE: We always wait for a message, but don't need to show the UI 250 // control since "Wait" is the only valid choice. 251 return false; 252 } 253 254 public function shouldWaitForMessage(HarbormasterBuildTarget $target) { 255 return true; 256 } 257 258}