Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php
12256 views
1
<?php
2
3
final 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
27
WARNING: This build step is new and experimental!
28
29
To 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
36
To 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
41
Webhook Configuration
42
=====================
43
44
Add this webhook to your `circle.yml` file to make CircleCI report results
45
to Harbormaster. Until you install this hook, builds will hang waiting for
46
a response from CircleCI.
47
48
```lang=yml
49
notify:
50
webhooks:
51
- url: %s
52
```
53
54
Environment
55
===========
56
57
These variables will be available in the build environment:
58
59
| Variable | Description |
60
|----------|-------------|
61
| `HARBORMASTER_BUILD_TARGET_PHID` | PHID of the Build Target.
62
63
EOTEXT
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
}
259
260