Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/harbormaster/plan/HarbormasterBuildPlanBehavior.php
12256 views
1
<?php
2
3
final class HarbormasterBuildPlanBehavior
4
extends Phobject {
5
6
private $key;
7
private $name;
8
private $options;
9
private $defaultKey;
10
private $editInstructions;
11
12
const BEHAVIOR_RUNNABLE = 'runnable';
13
const RUNNABLE_IF_VIEWABLE = 'view';
14
const RUNNABLE_IF_EDITABLE = 'edit';
15
16
const BEHAVIOR_RESTARTABLE = 'restartable';
17
const RESTARTABLE_ALWAYS = 'always';
18
const RESTARTABLE_IF_FAILED = 'failed';
19
const RESTARTABLE_NEVER = 'never';
20
21
const BEHAVIOR_DRAFTS = 'hold-drafts';
22
const DRAFTS_ALWAYS = 'always';
23
const DRAFTS_IF_BUILDING = 'building';
24
const DRAFTS_NEVER = 'never';
25
26
const BEHAVIOR_BUILDABLE = 'buildable';
27
const BUILDABLE_ALWAYS = 'always';
28
const BUILDABLE_IF_BUILDING = 'building';
29
const BUILDABLE_NEVER = 'never';
30
31
const BEHAVIOR_LANDWARNING = 'arc-land';
32
const LANDWARNING_ALWAYS = 'always';
33
const LANDWARNING_IF_BUILDING = 'building';
34
const LANDWARNING_IF_COMPLETE = 'complete';
35
const LANDWARNING_NEVER = 'never';
36
37
public function setKey($key) {
38
$this->key = $key;
39
return $this;
40
}
41
42
public function getKey() {
43
return $this->key;
44
}
45
46
public function setName($name) {
47
$this->name = $name;
48
return $this;
49
}
50
51
public function getName() {
52
return $this->name;
53
}
54
55
public function setEditInstructions($edit_instructions) {
56
$this->editInstructions = $edit_instructions;
57
return $this;
58
}
59
60
public function getEditInstructions() {
61
return $this->editInstructions;
62
}
63
64
public function getOptionMap() {
65
return mpull($this->options, 'getName', 'getKey');
66
}
67
68
public function setOptions(array $options) {
69
assert_instances_of($options, 'HarbormasterBuildPlanBehaviorOption');
70
71
$key_map = array();
72
$default = null;
73
74
foreach ($options as $option) {
75
$key = $option->getKey();
76
77
if (isset($key_map[$key])) {
78
throw new Exception(
79
pht(
80
'Multiple behavior options (for behavior "%s") have the same '.
81
'key ("%s"). Each option must have a unique key.',
82
$this->getKey(),
83
$key));
84
}
85
$key_map[$key] = true;
86
87
if ($option->getIsDefault()) {
88
if ($default === null) {
89
$default = $key;
90
} else {
91
throw new Exception(
92
pht(
93
'Multiple behavior options (for behavior "%s") are marked as '.
94
'default options ("%s" and "%s"). Exactly one option must be '.
95
'marked as the default option.',
96
$this->getKey(),
97
$default,
98
$key));
99
}
100
}
101
}
102
103
if ($default === null) {
104
throw new Exception(
105
pht(
106
'No behavior option is marked as the default option (for '.
107
'behavior "%s"). Exactly one option must be marked as the '.
108
'default option.',
109
$this->getKey()));
110
}
111
112
$this->options = mpull($options, null, 'getKey');
113
$this->defaultKey = $default;
114
115
return $this;
116
}
117
118
public function getOptions() {
119
return $this->options;
120
}
121
122
public function getPlanOption(HarbormasterBuildPlan $plan) {
123
$behavior_key = $this->getKey();
124
$storage_key = self::getStorageKeyForBehaviorKey($behavior_key);
125
126
$plan_value = $plan->getPlanProperty($storage_key);
127
if (isset($this->options[$plan_value])) {
128
return $this->options[$plan_value];
129
}
130
131
return idx($this->options, $this->defaultKey);
132
}
133
134
public static function getTransactionMetadataKey() {
135
return 'behavior-key';
136
}
137
138
public static function getStorageKeyForBehaviorKey($behavior_key) {
139
return sprintf('behavior.%s', $behavior_key);
140
}
141
142
public static function getBehavior($key) {
143
$behaviors = self::newPlanBehaviors();
144
145
if (!isset($behaviors[$key])) {
146
throw new Exception(
147
pht(
148
'No build plan behavior with key "%s" exists.',
149
$key));
150
}
151
152
return $behaviors[$key];
153
}
154
155
public static function newPlanBehaviors() {
156
$draft_options = array(
157
id(new HarbormasterBuildPlanBehaviorOption())
158
->setKey(self::DRAFTS_ALWAYS)
159
->setIcon('fa-check-circle-o green')
160
->setName(pht('Always'))
161
->setIsDefault(true)
162
->setDescription(
163
pht(
164
'Revisions are not sent for review until the build completes, '.
165
'and are returned to the author for updates if the build fails.')),
166
id(new HarbormasterBuildPlanBehaviorOption())
167
->setKey(self::DRAFTS_IF_BUILDING)
168
->setIcon('fa-pause-circle-o yellow')
169
->setName(pht('If Building'))
170
->setDescription(
171
pht(
172
'Revisions are not sent for review until the build completes, '.
173
'but they will be sent for review even if it fails.')),
174
id(new HarbormasterBuildPlanBehaviorOption())
175
->setKey(self::DRAFTS_NEVER)
176
->setIcon('fa-circle-o red')
177
->setName(pht('Never'))
178
->setDescription(
179
pht(
180
'Revisions are sent for review regardless of the status of the '.
181
'build.')),
182
);
183
184
$land_options = array(
185
id(new HarbormasterBuildPlanBehaviorOption())
186
->setKey(self::LANDWARNING_ALWAYS)
187
->setIcon('fa-check-circle-o green')
188
->setName(pht('Always'))
189
->setIsDefault(true)
190
->setDescription(
191
pht(
192
'"arc land" warns if the build is still running or has '.
193
'failed.')),
194
id(new HarbormasterBuildPlanBehaviorOption())
195
->setKey(self::LANDWARNING_IF_BUILDING)
196
->setIcon('fa-pause-circle-o yellow')
197
->setName(pht('If Building'))
198
->setDescription(
199
pht(
200
'"arc land" warns if the build is still running, but ignores '.
201
'the build if it has failed.')),
202
id(new HarbormasterBuildPlanBehaviorOption())
203
->setKey(self::LANDWARNING_IF_COMPLETE)
204
->setIcon('fa-dot-circle-o yellow')
205
->setName(pht('If Complete'))
206
->setDescription(
207
pht(
208
'"arc land" warns if the build has failed, but ignores the '.
209
'build if it is still running.')),
210
id(new HarbormasterBuildPlanBehaviorOption())
211
->setKey(self::LANDWARNING_NEVER)
212
->setIcon('fa-circle-o red')
213
->setName(pht('Never'))
214
->setDescription(
215
pht(
216
'"arc land" never warns that the build is still running or '.
217
'has failed.')),
218
);
219
220
$aggregate_options = array(
221
id(new HarbormasterBuildPlanBehaviorOption())
222
->setKey(self::BUILDABLE_ALWAYS)
223
->setIcon('fa-check-circle-o green')
224
->setName(pht('Always'))
225
->setIsDefault(true)
226
->setDescription(
227
pht(
228
'The buildable waits for the build, and fails if the '.
229
'build fails.')),
230
id(new HarbormasterBuildPlanBehaviorOption())
231
->setKey(self::BUILDABLE_IF_BUILDING)
232
->setIcon('fa-pause-circle-o yellow')
233
->setName(pht('If Building'))
234
->setDescription(
235
pht(
236
'The buildable waits for the build, but does not fail '.
237
'if the build fails.')),
238
id(new HarbormasterBuildPlanBehaviorOption())
239
->setKey(self::BUILDABLE_NEVER)
240
->setIcon('fa-circle-o red')
241
->setName(pht('Never'))
242
->setDescription(
243
pht(
244
'The buildable does not wait for the build.')),
245
);
246
247
$restart_options = array(
248
id(new HarbormasterBuildPlanBehaviorOption())
249
->setKey(self::RESTARTABLE_ALWAYS)
250
->setIcon('fa-repeat green')
251
->setName(pht('Always'))
252
->setIsDefault(true)
253
->setDescription(
254
pht('The build may be restarted.')),
255
id(new HarbormasterBuildPlanBehaviorOption())
256
->setKey(self::RESTARTABLE_IF_FAILED)
257
->setIcon('fa-times-circle-o yellow')
258
->setName(pht('If Failed'))
259
->setDescription(
260
pht('The build may be restarted if it has failed.')),
261
id(new HarbormasterBuildPlanBehaviorOption())
262
->setKey(self::RESTARTABLE_NEVER)
263
->setIcon('fa-times red')
264
->setName(pht('Never'))
265
->setDescription(
266
pht('The build may not be restarted.')),
267
);
268
269
$run_options = array(
270
id(new HarbormasterBuildPlanBehaviorOption())
271
->setKey(self::RUNNABLE_IF_EDITABLE)
272
->setIcon('fa-pencil green')
273
->setName(pht('If Editable'))
274
->setIsDefault(true)
275
->setDescription(
276
pht('Only users who can edit the plan can run it manually.')),
277
id(new HarbormasterBuildPlanBehaviorOption())
278
->setKey(self::RUNNABLE_IF_VIEWABLE)
279
->setIcon('fa-exclamation-triangle yellow')
280
->setName(pht('If Viewable'))
281
->setDescription(
282
pht(
283
'Any user who can view the plan can run it manually.')),
284
);
285
286
$behaviors = array(
287
id(new self())
288
->setKey(self::BEHAVIOR_DRAFTS)
289
->setName(pht('Hold Drafts'))
290
->setEditInstructions(
291
pht(
292
'When users create revisions in Differential, the default '.
293
'behavior is to hold them in the "Draft" state until all builds '.
294
'pass. Once builds pass, the revisions promote and are sent for '.
295
'review, which notifies reviewers.'.
296
"\n\n".
297
'The general intent of this workflow is to make sure reviewers '.
298
'are only spending time on review once changes survive automated '.
299
'tests. If a change does not pass tests, it usually is not '.
300
'really ready for review.'.
301
"\n\n".
302
'If you want to promote revisions out of "Draft" before builds '.
303
'pass, or promote revisions even when builds fail, you can '.
304
'change the promotion behavior. This may be useful if you have '.
305
'very long-running builds, or some builds which are not very '.
306
'important.'.
307
"\n\n".
308
'Users may always use "Request Review" to promote a "Draft" '.
309
'revision, even if builds have failed or are still in progress.'))
310
->setOptions($draft_options),
311
id(new self())
312
->setKey(self::BEHAVIOR_LANDWARNING)
313
->setName(pht('Warn When Landing'))
314
->setEditInstructions(
315
pht(
316
'When a user attempts to `arc land` a revision and that revision '.
317
'has ongoing or failed builds, the default behavior of `arc` is '.
318
'to warn them about those builds and give them a chance to '.
319
'reconsider: they may want to wait for ongoing builds to '.
320
'complete, or fix failed builds before landing the change.'.
321
"\n\n".
322
'If you do not want to warn users about this build, you can '.
323
'change the warning behavior. This may be useful if the build '.
324
'takes a long time to run (so you do not expect users to wait '.
325
'for it) or the outcome is not important.'.
326
"\n\n".
327
'This warning is only advisory. Users may always elect to ignore '.
328
'this warning and continue, even if builds have failed.'.
329
"\n\n".
330
'This setting also affects the warning that is published to '.
331
'revisions when commits land with ongoing or failed builds.'))
332
->setOptions($land_options),
333
id(new self())
334
->setKey(self::BEHAVIOR_BUILDABLE)
335
->setEditInstructions(
336
pht(
337
'The overall state of a buildable (like a commit or revision) is '.
338
'normally the aggregation of the individual states of all builds '.
339
'that have run against it.'.
340
"\n\n".
341
'Buildables are "building" until all builds pass (which changes '.
342
'them to "pass"), or any build fails (which changes them to '.
343
'"fail").'.
344
"\n\n".
345
'You can change this behavior if you do not want to wait for this '.
346
'build, or do not care if it fails.'))
347
->setName(pht('Affects Buildable'))
348
->setOptions($aggregate_options),
349
id(new self())
350
->setKey(self::BEHAVIOR_RESTARTABLE)
351
->setEditInstructions(
352
pht(
353
'Usually, builds may be restarted by users who have permission '.
354
'to edit the related build plan. (You can change who is allowed '.
355
'to restart a build by adjusting the "Runnable" behavior.)'.
356
"\n\n".
357
'Restarting a build may be useful if you suspect it has failed '.
358
'for environmental or circumstantial reasons unrelated to the '.
359
'actual code, and want to give it another chance at glory.'.
360
"\n\n".
361
'If you want to prevent a build from being restarted, you can '.
362
'change when it may be restarted by adjusting this behavior. '.
363
'This may be useful to prevent accidents where a build with a '.
364
'dangerous side effect (like deployment) is restarted '.
365
'improperly.'))
366
->setName(pht('Restartable'))
367
->setOptions($restart_options),
368
id(new self())
369
->setKey(self::BEHAVIOR_RUNNABLE)
370
->setEditInstructions(
371
pht(
372
'To run a build manually, you normally must have permission to '.
373
'edit the related build plan. If you would prefer that anyone who '.
374
'can see the build plan be able to run and restart the build, you '.
375
'can change the behavior here.'.
376
"\n\n".
377
'Note that this controls access to all build management actions: '.
378
'"Run Plan Manually", "Restart", "Abort", "Pause", and "Resume".'.
379
"\n\n".
380
'WARNING: This may be unsafe, particularly if the build has '.
381
'side effects like deployment.'.
382
"\n\n".
383
'If you weaken this policy, an attacker with control of an '.
384
'account that has "Can View" permission but not "Can Edit" '.
385
'permission can manually run this build against any old version '.
386
'of the code, including versions with known security issues.'.
387
"\n\n".
388
'If running the build has a side effect like deploying code, '.
389
'they can force deployment of a vulnerable version and then '.
390
'escalate into an attack against the deployed service.'))
391
->setName(pht('Runnable'))
392
->setOptions($run_options),
393
);
394
395
return mpull($behaviors, null, 'getKey');
396
}
397
398
}
399
400