Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php
12256 views
1
<?php
2
3
final class HarbormasterBuildkiteBuildStepImplementation
4
extends HarbormasterBuildStepImplementation {
5
6
public function getName() {
7
return pht('Build with Buildkite');
8
}
9
10
public function getGenericDescription() {
11
return pht('Trigger a build in Buildkite.');
12
}
13
14
public function getBuildStepGroupKey() {
15
return HarbormasterExternalBuildStepGroup::GROUPKEY;
16
}
17
18
public function getDescription() {
19
return pht('Run a build in Buildkite.');
20
}
21
22
public function getEditInstructions() {
23
$hook_uri = '/harbormaster/hook/buildkite/';
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 Buildkite, they must:
30
31
- belong to a tracked repository;
32
- the repository must have a Staging Area configured;
33
- you must configure a Buildkite pipeline for that Staging Area; and
34
- you must configure the webhook described below.
35
36
To build **commits** with Buildkite, they must:
37
38
- belong to a tracked repository;
39
- you must configure a Buildkite pipeline for that repository; and
40
- you must configure the webhook described below.
41
42
Webhook Configuration
43
=====================
44
45
In {nav Settings} for your Organization in Buildkite, under
46
{nav Notification Services}, add a new **Webook Notification**.
47
48
Use these settings:
49
50
- **Webhook URL**: %s
51
- **Token**: The "Webhook Token" field below and the "Token" field in
52
Buildkite should both be set to the same nonempty value (any random
53
secret). You can use copy/paste the value Buildkite generates into
54
this form.
55
- **Events**: Only **build.finish** needs to be active.
56
57
Environment
58
===========
59
60
These variables will be available in the build environment:
61
62
| Variable | Description |
63
|----------|-------------|
64
| `HARBORMASTER_BUILD_TARGET_PHID` | PHID of the Build Target.
65
EOTEXT
66
,
67
$hook_uri);
68
}
69
70
public function execute(
71
HarbormasterBuild $build,
72
HarbormasterBuildTarget $build_target) {
73
$viewer = PhabricatorUser::getOmnipotentUser();
74
75
if (PhabricatorEnv::getEnvConfig('phabricator.silent')) {
76
$this->logSilencedCall($build, $build_target, pht('Buildkite'));
77
throw new HarbormasterBuildFailureException();
78
}
79
80
$buildable = $build->getBuildable();
81
82
$object = $buildable->getBuildableObject();
83
if (!($object instanceof HarbormasterBuildkiteBuildableInterface)) {
84
throw new Exception(
85
pht('This object does not support builds with Buildkite.'));
86
}
87
88
$organization = $this->getSetting('organization');
89
$pipeline = $this->getSetting('pipeline');
90
91
$uri = urisprintf(
92
'https://api.buildkite.com/v2/organizations/%s/pipelines/%s/builds',
93
$organization,
94
$pipeline);
95
96
$data_structure = array(
97
'commit' => $object->getBuildkiteCommit(),
98
'branch' => $object->getBuildkiteBranch(),
99
'message' => pht(
100
'Harbormaster Build %s ("%s") for %s',
101
$build->getID(),
102
$build->getName(),
103
$buildable->getMonogram()),
104
'env' => array(
105
'HARBORMASTER_BUILD_TARGET_PHID' => $build_target->getPHID(),
106
),
107
'meta_data' => array(
108
'buildTargetPHID' => $build_target->getPHID(),
109
110
// See PHI611. These are undocumented secret magic.
111
'phabricator:build:id' => (int)$build->getID(),
112
'phabricator:build:url' =>
113
PhabricatorEnv::getProductionURI($build->getURI()),
114
'phabricator:buildable:id' => (int)$buildable->getID(),
115
'phabricator:buildable:url' =>
116
PhabricatorEnv::getProductionURI($buildable->getURI()),
117
),
118
);
119
120
$engine = HarbormasterBuildableEngine::newForObject(
121
$object,
122
$viewer);
123
124
$author_identity = $engine->getAuthorIdentity();
125
if ($author_identity) {
126
$data_structure += array(
127
'author' => array(
128
'name' => $author_identity->getIdentityDisplayName(),
129
'email' => $author_identity->getIdentityEmailAddress(),
130
),
131
);
132
}
133
134
$json_data = phutil_json_encode($data_structure);
135
136
$credential_phid = $this->getSetting('token');
137
$api_token = id(new PassphraseCredentialQuery())
138
->setViewer($viewer)
139
->withPHIDs(array($credential_phid))
140
->needSecrets(true)
141
->executeOne();
142
if (!$api_token) {
143
throw new Exception(
144
pht(
145
'Unable to load API token ("%s")!',
146
$credential_phid));
147
}
148
149
$token = $api_token->getSecret()->openEnvelope();
150
151
$future = id(new HTTPSFuture($uri, $json_data))
152
->setMethod('POST')
153
->addHeader('Content-Type', 'application/json')
154
->addHeader('Accept', 'application/json')
155
->addHeader('Authorization', "Bearer {$token}")
156
->setTimeout(60);
157
158
$this->resolveFutures(
159
$build,
160
$build_target,
161
array($future));
162
163
$this->logHTTPResponse($build, $build_target, $future, pht('Buildkite'));
164
165
list($status, $body) = $future->resolve();
166
if ($status->isError()) {
167
throw new HarbormasterBuildFailureException();
168
}
169
170
$response = phutil_json_decode($body);
171
172
$uri_key = 'web_url';
173
$build_uri = idx($response, $uri_key);
174
if (!$build_uri) {
175
throw new Exception(
176
pht(
177
'Buildkite did not return a "%s"!',
178
$uri_key));
179
}
180
181
$target_phid = $build_target->getPHID();
182
183
$api_method = 'harbormaster.createartifact';
184
$api_params = array(
185
'buildTargetPHID' => $target_phid,
186
'artifactType' => HarbormasterURIArtifact::ARTIFACTCONST,
187
'artifactKey' => 'buildkite.uri',
188
'artifactData' => array(
189
'uri' => $build_uri,
190
'name' => pht('View in Buildkite'),
191
'ui.external' => true,
192
),
193
);
194
195
id(new ConduitCall($api_method, $api_params))
196
->setUser($viewer)
197
->execute();
198
}
199
200
public function getFieldSpecifications() {
201
return array(
202
'token' => array(
203
'name' => pht('API Token'),
204
'type' => 'credential',
205
'credential.type'
206
=> PassphraseTokenCredentialType::CREDENTIAL_TYPE,
207
'credential.provides'
208
=> PassphraseTokenCredentialType::PROVIDES_TYPE,
209
'required' => true,
210
),
211
'organization' => array(
212
'name' => pht('Organization Name'),
213
'type' => 'text',
214
'required' => true,
215
),
216
'pipeline' => array(
217
'name' => pht('Pipeline Name'),
218
'type' => 'text',
219
'required' => true,
220
),
221
'webhook.token' => array(
222
'name' => pht('Webhook Token'),
223
'type' => 'text',
224
'required' => true,
225
),
226
);
227
}
228
229
public function supportsWaitForMessage() {
230
return false;
231
}
232
233
public function shouldWaitForMessage(HarbormasterBuildTarget $target) {
234
return true;
235
}
236
237
}
238
239