Skip to content
833 linesCodeBlameRaw
1//! The actions service: GitHub Actions workflows, run on g1t as they are.
2//!
3//! A repository's `.g1t/workflows/*.yml`, in GitHub's format, are read
4//! from the commit an
5//! event is about (the default branch for issues, schedules and manual
6//! runs). Each workflow an event starts becomes a run; each job of the run
7//! (one per matrix combination) runs in a sandbox once the jobs it needs
8//! have finished. Jobs report their steps and logs back as they go, and a
9//! run on a pull request's head is a status on that pull request.
10//!
11//! Secrets and variables belong to a repository or to its workspace; a
12//! repository's override its workspace's of the same name. Secret values
13//! are sealed at rest and never returned.
14//!
15//! Mirrors `packages/contracts/src/actions.ts`.
16
17use serde::{Deserialize, Serialize};
18use serde_json::Value;
19
20use crate::repos::RepoPath;
21use crate::{User, Viewer};
22
23/// A note on something in a workflow that runs differently on g1t.
24#[derive(Clone, Debug, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct WorkflowNote {
27 /// `info`, `warning` or `unsupported`.
28 pub severity: String,
29 pub job: Option<String>,
30 pub message: String,
31}
32
33#[derive(Clone, Debug, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct Workflow {
36 pub id: String,
37 /// `.g1t/workflows/ci.yml`.
38 pub path: String,
39 pub name: String,
40 /// The events that start it, such as `push` and `pull_request`.
41 pub events: Vec<String>,
42 /// `active`, or `disabled` when a member turned it off.
43 pub state: String,
44 /// Why the file cannot be used, if it cannot.
45 pub error: Option<String>,
46 pub notes: Vec<WorkflowNote>,
47 /// `on.workflow_dispatch.inputs` as written, when it can be run by hand.
48 pub dispatch: Option<Value>,
49 pub last_run: Option<WorkflowRun>,
50}
51
52#[derive(Clone, Debug, Serialize, Deserialize)]
53#[serde(rename_all = "camelCase")]
54pub struct WorkflowRun {
55 pub id: String,
56 pub workflow_id: String,
57 pub path: String,
58 /// The workflow's name.
59 pub name: String,
60 /// `run-name`, or what started it: a commit's subject, a pull request's title.
61 pub title: String,
62 /// Counts the workflow's runs: 1, 2, 3…
63 pub number: u64,
64 pub attempt: u64,
65 /// The GitHub event: `push`, `pull_request`, `schedule`…
66 pub event: String,
67 #[serde(rename = "ref")]
68 pub git_ref: String,
69 pub sha: String,
70 /// The pull request it ran for, if any.
71 pub pull: Option<u32>,
72 /// `queued`, `in_progress` or `completed`.
73 pub status: String,
74 /// When completed: `success`, `failure`, `cancelled` or `skipped`.
75 pub conclusion: Option<String>,
76 /// Why it could not start, such as a workflow file that does not read.
77 pub error: Option<String>,
78 /// Username of whoever caused it.
79 pub actor: Option<String>,
80 pub created_at: String,
81 pub started_at: Option<String>,
82 pub finished_at: Option<String>,
83}
84
85#[derive(Clone, Debug, Default, Serialize, Deserialize)]
86#[serde(rename_all = "camelCase")]
87pub struct StepState {
88 /// From 1.
89 pub number: u32,
90 pub name: String,
91 /// `queued`, `in_progress` or `completed`.
92 pub status: String,
93 /// `success`, `failure`, `cancelled` or `skipped`.
94 pub conclusion: Option<String>,
95 pub started_at: Option<String>,
96 pub finished_at: Option<String>,
97}
98
99/// A message a step left with `::error::`, `::warning::` or `::notice::`.
100#[derive(Clone, Debug, Default, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct Annotation {
103 /// `error`, `warning` or `notice`.
104 pub level: String,
105 pub message: String,
106 pub title: Option<String>,
107 pub file: Option<String>,
108 pub line: Option<u32>,
109}
110
111#[derive(Clone, Debug, Serialize, Deserialize)]
112#[serde(rename_all = "camelCase")]
113pub struct Job {
114 pub id: String,
115 pub run_id: String,
116 /// Its key under `jobs:`.
117 pub key: String,
118 /// With its matrix combination: `test (ubuntu-latest, 20)`.
119 pub name: String,
120 pub needs: Vec<String>,
121 /// `queued`, `waiting` (for the jobs it needs), `in_progress` or `completed`.
122 pub status: String,
123 pub conclusion: Option<String>,
124 pub steps: Vec<StepState>,
125 pub annotations: Vec<Annotation>,
126 /// Why it did not run, what stopped it, or what it waits for.
127 pub reason: Option<String>,
128 pub started_at: Option<String>,
129 pub finished_at: Option<String>,
130 /// The environment it names, once its needs are done (an expression
131 /// read by then). A job held by the environment's protection rules is
132 /// `pending` until they let it through.
133 #[serde(default)]
134 pub environment: Option<String>,
135 /// Its `runs-on` names self-hosted runners (see `runners`).
136 #[serde(default)]
137 pub self_hosted: bool,
138 /// The self-hosted runner that took it, by name.
139 #[serde(default)]
140 pub runner: Option<String>,
141}
142
143#[derive(Clone, Debug, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct RunDetail {
146 pub run: WorkflowRun,
147 pub jobs: Vec<Job>,
148 /// The workflow's notes, as of the run's commit.
149 pub notes: Vec<WorkflowNote>,
150 /// For a run of a pull request from outside: whether it waits for, or
151 /// had, someone's approval (`status` is `action_required` while it waits).
152 #[serde(default)]
153 pub approval: Option<RunApproval>,
154 /// The environments whose protection rules hold its jobs, this attempt.
155 #[serde(default)]
156 pub pending_deployments: Vec<PendingDeployment>,
157}
158
159/// A run that needed approval before it started.
160#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
161#[serde(rename_all = "camelCase")]
162pub struct RunApproval {
163 /// `required` while it waits, then `approved`.
164 pub state: String,
165 /// Why it waits, in words.
166 pub reason: String,
167 /// Who approved it.
168 pub approved_by: Option<String>,
169}
170
171/// One person or team who may approve a job's deployment to an
172/// environment.
173#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
174pub struct EnvironmentReviewer {
175 /// `user` or `team`.
176 #[serde(rename = "type")]
177 pub kind: String,
178 /// A username, or a team's slug in the repository's workspace.
179 pub name: String,
180}
181
182/// A branch or tag pattern an environment lets deploy.
183#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
184pub struct BranchPattern {
185 /// fnmatch-style, as branch filters are: `main`, `release/*`, `v*`.
186 pub name: String,
187 /// `branch` or `tag`.
188 #[serde(rename = "type", default = "branch_kind")]
189 pub kind: String,
190}
191
192fn branch_kind() -> String {
193 "branch".to_owned()
194}
195
196/// The most reviewers an environment may have, as on GitHub.
197pub const MAX_ENVIRONMENT_REVIEWERS: usize = 6;
198/// The longest wait timer, in minutes: 30 days.
199pub const MAX_WAIT_MINUTES: u32 = 43_200;
200
201/// An environment and its protection rules. Jobs that name it with
202/// `environment:` wait until the rules let them through; only then does the
203/// job get the environment's secrets.
204#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
205#[serde(rename_all = "camelCase")]
206pub struct Environment {
207 /// Lowercase.
208 pub name: String,
209 /// Who may approve its jobs; none means no review is needed.
210 pub reviewers: Vec<EnvironmentReviewer>,
211 /// Whoever started a run may not approve its jobs, even as a reviewer.
212 pub prevent_self_review: bool,
213 /// Minutes each job waits before it may start.
214 pub wait_minutes: u32,
215 /// Which refs may deploy: `all`, `protected` (branches the rules
216 /// protect, the default branch included) or `selected` (`branch_patterns`).
217 pub branch_policy: String,
218 pub branch_patterns: Vec<BranchPattern>,
219 /// Admins may approve without being reviewers, which also skips the wait.
220 pub admins_bypass: bool,
221 /// Whether it has rules saved; false for one only named by a workflow,
222 /// a secret or a deployment.
223 pub protected: bool,
224 pub updated_at: Option<String>,
225 pub updated_by: Option<String>,
226}
227
228/// An environment holding a run's jobs, and where its rules stand.
229#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub struct PendingDeployment {
232 pub environment: String,
233 /// `waiting`, `approved` or `rejected`.
234 pub state: String,
235 /// Whether a reviewer must approve it before its jobs start.
236 pub needs_review: bool,
237 /// When its wait timer lets its jobs start, if it has one.
238 pub wait_until: Option<String>,
239 pub reviewers: Vec<EnvironmentReviewer>,
240 /// The jobs it holds, by name.
241 pub jobs: Vec<String>,
242 /// Whether the viewer may approve or reject it now.
243 #[serde(default)]
244 pub can_review: bool,
245 pub reviewed_by: Option<String>,
246 pub comment: Option<String>,
247 pub reviewed_at: Option<String>,
248}
249
250/// A repository's choices for its workflows.
251#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
252#[serde(rename_all = "camelCase")]
253pub struct ActionsSettings {
254 /// What a workflow without `permissions:` gets: `read` (contents and
255 /// packages read; the default) or `write` (every permission).
256 pub default_permissions: String,
257 /// Which pull requests' runs wait for approval: `first_time_contributors`,
258 /// `outside_contributors` (the default) or `all_external_contributors`.
259 pub approval_policy: String,
260}
261
262/// The approval policies, least strict first.
263pub const APPROVAL_POLICIES: [&str; 3] = ["first_time_contributors", "outside_contributors", "all_external_contributors"];
264
265/// `actions_settings`. Returns `Outcome<ActionsSettings>`; anyone who can
266/// read the repository may see them.
267#[derive(Debug, Serialize, Deserialize)]
268pub struct ActionsSettingsArgs {
269 pub viewer: Viewer,
270 pub repo: RepoPath,
271}
272
273/// `set_actions_settings`: Admins only. Fields left out stay as they are.
274/// Returns `Outcome<ActionsSettings>`.
275#[derive(Debug, Serialize, Deserialize)]
276#[serde(rename_all = "camelCase")]
277pub struct SetActionsSettingsArgs {
278 pub actor: User,
279 pub repo: RepoPath,
280 #[serde(default)]
281 pub default_permissions: Option<String>,
282 #[serde(default)]
283 pub approval_policy: Option<String>,
284}
285
286/// `environments`: every environment a repository's workflows, secrets,
287/// deployments or rules name, with its rules. `environment`: one, by
288/// `name`. Returns `Outcome<Vec<Environment>>` and `Outcome<Environment>`.
289#[derive(Debug, Serialize, Deserialize)]
290pub struct EnvironmentsArgs {
291 pub viewer: Viewer,
292 pub repo: RepoPath,
293 #[serde(default)]
294 pub name: Option<String>,
295}
296
297/// `set_environment`: create an environment's rules or change them. Fields
298/// left out stay as they are (none, for a new one). Admins only. Returns
299/// `Outcome<Environment>`.
300#[derive(Debug, Serialize, Deserialize)]
301#[serde(rename_all = "camelCase")]
302pub struct SetEnvironmentArgs {
303 pub actor: User,
304 pub repo: RepoPath,
305 pub name: String,
306 #[serde(default)]
307 pub reviewers: Option<Vec<EnvironmentReviewer>>,
308 #[serde(default)]
309 pub prevent_self_review: Option<bool>,
310 #[serde(default)]
311 pub wait_minutes: Option<u32>,
312 #[serde(default)]
313 pub branch_policy: Option<String>,
314 #[serde(default)]
315 pub branch_patterns: Option<Vec<BranchPattern>>,
316 #[serde(default)]
317 pub admins_bypass: Option<bool>,
318}
319
320/// `delete_environment`: its rules go; jobs naming it run without them.
321/// Its secrets' rows stay. Admins only. Returns `Outcome<bool>`.
322#[derive(Debug, Serialize, Deserialize)]
323pub struct DeleteEnvironmentArgs {
324 pub actor: User,
325 pub repo: RepoPath,
326 pub name: String,
327}
328
329/// `pending_deployments`: the environments holding a run's jobs. Returns
330/// `Outcome<Vec<PendingDeployment>>`.
331#[derive(Debug, Serialize, Deserialize)]
332pub struct PendingDeploymentsArgs {
333 pub viewer: Viewer,
334 pub repo: RepoPath,
335 pub id: String,
336}
337
338/// `review_deployments`: approve or reject a run's jobs for `environments`
339/// (every one waiting, if empty). Returns `Outcome<Vec<PendingDeployment>>`.
340#[derive(Debug, Serialize, Deserialize)]
341pub struct ReviewDeploymentsArgs {
342 pub actor: User,
343 pub repo: RepoPath,
344 pub id: String,
345 #[serde(default)]
346 pub environments: Vec<String>,
347 /// `approved` or `rejected`.
348 pub state: String,
349 #[serde(default)]
350 pub comment: Option<String>,
351}
352
353/// `repository_dispatch`: start the default branch's workflows that run
354/// `on: repository_dispatch` for `event_type`. Needs the Write role (a
355/// token's `code:write`). Returns `Outcome<u32>`: how many started.
356#[derive(Debug, Serialize, Deserialize)]
357#[serde(rename_all = "camelCase")]
358pub struct RepositoryDispatchArgs {
359 pub actor: User,
360 pub repo: RepoPath,
361 pub event_type: String,
362 #[serde(default)]
363 pub client_payload: Value,
364}
365
366#[derive(Clone, Debug, Serialize, Deserialize)]
367#[serde(rename_all = "camelCase")]
368pub struct LogChunk {
369 pub seq: u64,
370 /// The step it belongs to, from 1; 0 for the job's setup.
371 pub step: u32,
372 pub text: String,
373}
374
375#[derive(Clone, Debug, Serialize, Deserialize)]
376#[serde(rename_all = "camelCase")]
377pub struct JobLog {
378 pub chunks: Vec<LogChunk>,
379 /// Whether the job has finished, so no more will come.
380 pub done: bool,
381}
382
383/// Who may read a secret or variable: workflows (`secrets.*` and `vars.*`
384/// in GitHub Actions) and deployments (a deploy build's environment and the
385/// running app's bindings). Agents, checks and the merge queue read none.
386pub const CONSUMERS: [&str; 2] = ["workflows", "deployments"];
387
388/// One row of a repository's or workspace's secrets and variables, as
389/// Vercel lists environment variables: a key, its type, the environments
390/// it applies to and who reads it. A key may have one row per environment.
391/// Secrets' values are never returned.
392#[derive(Clone, Debug, Serialize, Deserialize)]
393#[serde(rename_all = "camelCase")]
394pub struct Setting {
395 #[serde(default)]
396 pub id: String,
397 pub name: String,
398 /// `secret`, or `variable` (shown as Config).
399 #[serde(default)]
400 pub kind: String,
401 /// A variable's value; secrets' are never returned.
402 pub value: Option<String>,
403 /// `project` (a repository's, which belong to its project) or
404 /// `workspace`.
405 pub scope: String,
406 pub updated_at: String,
407 /// `workflows` and/or `deployments`.
408 #[serde(default)]
409 pub available_to: Vec<String>,
410 /// The environments it applies to; empty is every environment.
411 #[serde(default)]
412 pub environments: Vec<String>,
413 /// A workspace's row: the projects it reaches, by slug; empty is every
414 /// project.
415 #[serde(default)]
416 pub projects: Vec<String>,
417 #[serde(default)]
418 pub note: Option<String>,
419 #[serde(default)]
420 pub updated_by: Option<String>,
421}
422
423// --- Methods ---------------------------------------------------------------
424
425/// `workflows`. Returns `Outcome<Vec<Workflow>>`.
426#[derive(Debug, Serialize, Deserialize)]
427pub struct WorkflowsArgs {
428 pub repo: RepoPath,
429 pub viewer: Viewer,
430}
431
432/// `runs`: newest first. Returns `Outcome<Vec<WorkflowRun>>`.
433#[derive(Debug, Serialize, Deserialize)]
434pub struct RunsArgs {
435 pub repo: RepoPath,
436 pub viewer: Viewer,
437 /// A workflow's id or file name.
438 #[serde(default)]
439 pub workflow: Option<String>,
440 #[serde(default)]
441 pub branch: Option<String>,
442 #[serde(default)]
443 pub event: Option<String>,
444 /// The pull request's number.
445 #[serde(default)]
446 pub pull: Option<u32>,
447 #[serde(default)]
448 pub sha: Option<String>,
449 #[serde(default)]
450 pub limit: Option<u32>,
451}
452
453/// `run`. Returns `Outcome<RunDetail>`.
454#[derive(Debug, Serialize, Deserialize)]
455pub struct RunArgs {
456 pub repo: RepoPath,
457 pub viewer: Viewer,
458 pub id: String,
459}
460
461/// `logs`: a job's log after `after`. Returns `Outcome<JobLog>`.
462#[derive(Debug, Serialize, Deserialize)]
463pub struct LogsArgs {
464 pub repo: RepoPath,
465 pub viewer: Viewer,
466 pub job: String,
467 #[serde(default)]
468 pub after: u64,
469}
470
471/// `dispatch`: run a workflow that has `workflow_dispatch`. Members only.
472/// Returns `Outcome<WorkflowRun>`.
473#[derive(Debug, Serialize, Deserialize)]
474pub struct DispatchArgs {
475 pub actor: User,
476 pub repo: RepoPath,
477 /// A workflow's id or file name.
478 pub workflow: String,
479 /// A branch or tag; the default branch when absent.
480 #[serde(default, rename = "ref")]
481 pub git_ref: Option<String>,
482 #[serde(default)]
483 pub inputs: serde_json::Map<String, Value>,
484}
485
486/// `cancel` and `rerun` (all jobs, or with `failed_only` the ones that did
487/// not succeed). Members only. Returns `Outcome<WorkflowRun>`.
488#[derive(Debug, Serialize, Deserialize)]
489pub struct RunActionArgs {
490 pub actor: User,
491 pub repo: RepoPath,
492 pub id: String,
493 #[serde(default)]
494 pub failed_only: bool,
495}
496
497/// `set_workflow_enabled`. Members only. Returns `Outcome<Workflow>`.
498#[derive(Debug, Serialize, Deserialize)]
499pub struct SetWorkflowEnabledArgs {
500 pub actor: User,
501 pub repo: RepoPath,
502 pub workflow: String,
503 pub enabled: bool,
504}
505
506/// Whose secrets or variables: a repository's, or with only `workspace`,
507/// a workspace's.
508#[derive(Clone, Debug, Serialize, Deserialize)]
509pub struct SettingsOwner {
510 #[serde(default)]
511 pub repo: Option<RepoPath>,
512 #[serde(default)]
513 pub workspace: Option<String>,
514}
515
516/// `settings`: the secrets (`kind: secret`) or variables (`kind: variable`)
517/// of a repository, with its workspace's, or of a workspace. Members only.
518/// Returns `Outcome<Vec<Setting>>`.
519#[derive(Debug, Serialize, Deserialize)]
520pub struct SettingsArgs {
521 pub actor: User,
522 #[serde(flatten)]
523 pub owner: SettingsOwner,
524 pub kind: String,
525}
526
527/// `set_setting`: add or replace one. A repository's need a member; a
528/// workspace's an owner. Returns `Outcome<Setting>`.
529#[derive(Debug, Serialize, Deserialize)]
530pub struct SetSettingArgs {
531 pub actor: User,
532 #[serde(flatten)]
533 pub owner: SettingsOwner,
534 /// `secret` or `variable`. Changing a variable's row to `secret` seals
535 /// it; a secret cannot become a variable.
536 pub kind: String,
537 pub name: String,
538 /// The row to change. Left out, the key's row for every environment, as
539 /// GitHub's API addresses a secret by name alone.
540 #[serde(default)]
541 pub id: Option<String>,
542 /// Needed for a new row; left out, an existing row keeps its value.
543 #[serde(default)]
544 pub value: Option<String>,
545 /// `workflows` and/or `deployments`; left out, unchanged (both, for a
546 /// new row).
547 // Named as callers send it: an `alias` is not honoured beside the
548 // flattened owner in the Worker's build.
549 #[serde(default, rename = "availableTo")]
550 pub available_to: Option<Vec<String>>,
551 /// The environments it applies to; empty is every one. Left out,
552 /// unchanged.
553 #[serde(default)]
554 pub environments: Option<Vec<String>>,
555 /// A workspace's row: project slugs; empty for every one.
556 #[serde(default)]
557 pub projects: Option<Vec<String>>,
558 #[serde(default)]
559 pub note: Option<String>,
560}
561
562/// `resolve_settings`: the secrets and variables one reader gets, for the
563/// services that hand them out (the deployments service). Returns
564/// `ResolvedSettings`.
565#[derive(Debug, Serialize, Deserialize)]
566#[serde(rename_all = "camelCase")]
567pub struct ResolveSettingsArgs {
568 pub repo_id: String,
569 pub repo: RepoPath,
570 /// The project being read for; its repository's primary project if left
571 /// out.
572 #[serde(default)]
573 pub project_id: Option<String>,
574 #[serde(default)]
575 pub project_slug: Option<String>,
576 /// `workflows` or `deployments`.
577 pub consumer: String,
578 /// The environment being read for, such as `production` or `preview`.
579 #[serde(default)]
580 pub environment: Option<String>,
581 /// Whether the run is trusted; an untrusted one gets no secrets.
582 pub trusted: bool,
583}
584
585#[derive(Debug, Default, Serialize, Deserialize)]
586pub struct ResolvedSettings {
587 pub secrets: serde_json::Map<String, serde_json::Value>,
588 pub variables: serde_json::Map<String, serde_json::Value>,
589}
590
591/// `delete_setting`. Returns `Outcome<bool>`.
592#[derive(Debug, Serialize, Deserialize)]
593pub struct DeleteSettingArgs {
594 pub actor: User,
595 #[serde(flatten)]
596 pub owner: SettingsOwner,
597 pub kind: String,
598 pub name: String,
599 /// One row; left out, every row of the key.
600 #[serde(default)]
601 pub id: Option<String>,
602}
603
604/// `job_spec` and `job_report`: the sandbox running a job, with the job's
605/// own token. `report` is one of:
606/// `{"kind": "step", "number", "status", "conclusion"}`,
607/// `{"kind": "log", "step", "text"}`,
608/// `{"kind": "annotation", "level", "message", "title", "file", "line"}`,
609/// `{"kind": "done", "conclusion", "outputs", "reason"}`.
610#[derive(Debug, Serialize, Deserialize)]
611pub struct JobCallArgs {
612 pub job: String,
613 pub token: String,
614 #[serde(default)]
615 pub report: Value,
616}
617
618/// What the runner needs to start a job's sandbox.
619#[derive(Debug, Serialize, Deserialize)]
620#[serde(rename_all = "camelCase")]
621pub struct StartJobArgs {
622 pub job: String,
623 pub token: String,
624 pub repo: RepoPath,
625 /// Minutes before the job is stopped.
626 pub timeout_minutes: u32,
627 /// The workflow file the job is in (`.g1t/workflows/deploy.yml`), for
628 /// the guardrails' workflow-only domains.
629 #[serde(default)]
630 pub workflow: Option<String>,
631 /// The environment the job names with `environment:`, when it names
632 /// one plainly (not with an expression).
633 #[serde(default)]
634 pub environment: Option<String>,
635 /// Whether its run is trusted: not a pull request from a fork. Only a
636 /// trusted run's jobs reach workflow-only domains.
637 #[serde(default)]
638 pub trusted: bool,
639 /// The machine its `runs-on` asked for, by label (`instance_for`):
640 /// `g1t-2core` or `g1t-4core`; absent, the standard one.
641 #[serde(default)]
642 pub instance: Option<String>,
643}
644
645/// A size of machine g1t runs workflow jobs on, asked for by a label in
646/// `runs-on`. Each is a Cloudflare Containers instance type; it costs what
647/// that instance costs g1t, plus the margin, like any sandbox time.
648#[derive(Clone, Copy, Debug, PartialEq)]
649pub struct InstanceType {
650 /// The `runs-on` label, or `standard` for the default.
651 pub label: &'static str,
652 /// The Containers instance type.
653 pub container: &'static str,
654 pub vcpu: f64,
655 pub memory_gib: f64,
656 pub disk_gb: f64,
657 /// What a second of it costs g1t as a multiple of the standard
658 /// machine's, with its vCPUs as busy (Cloudflare's list prices:
659 /// memory $0.0000025 a GiB-second, disk $0.00000007 a GB-second, vCPU
660 /// $0.00002 a second). Used to reserve before a job starts, and to
661 /// price a job that did not report its own CPU.
662 pub price_scale: f64,
663}
664
665/// The default: what `ubuntu-latest` and every other hosted label get.
666pub const STANDARD_INSTANCE: InstanceType =
667 InstanceType { label: "standard", container: "standard-1", vcpu: 0.5, memory_gib: 4.0, disk_gb: 8.0, price_scale: 1.0 };
668
669/// Every machine a workflow job can ask for, the default first.
670pub const INSTANCE_TYPES: [InstanceType; 3] = [
671 STANDARD_INSTANCE,
672 InstanceType { label: "g1t-2core", container: "standard-3", vcpu: 2.0, memory_gib: 8.0, disk_gb: 16.0, price_scale: 2.8 },
673 InstanceType { label: "g1t-4core", container: "standard-4", vcpu: 4.0, memory_gib: 12.0, disk_gb: 20.0, price_scale: 5.1 },
674];
675
676/// The machine a job's `runs-on` labels ask for: the largest named, or the
677/// standard one. Labels compare without regard to case.
678pub fn instance_for(labels: &[String]) -> InstanceType {
679 INSTANCE_TYPES
680 .iter()
681 .rev()
682 .find(|instance| instance.label != STANDARD_INSTANCE.label && labels.iter().any(|label| label.trim().eq_ignore_ascii_case(instance.label)))
683 .copied()
684 .unwrap_or(STANDARD_INSTANCE)
685}
686
687/// An instance type by its label, if it is one.
688pub fn instance_named(label: &str) -> Option<InstanceType> {
689 INSTANCE_TYPES.iter().find(|instance| instance.label.eq_ignore_ascii_case(label.trim())).copied()
690}
691
692// ── The cache (actions/cache) ─────────────────────────────────────────────
693//
694// Entries are kept in R2 by the API (the ACTIONS_CACHE bucket) and listed
695// here, by the actions service, which decides what is found, what fits and
696// what is evicted. A sandbox reaches these through the API with its job's
697// token: `/actions/jobs/{job}/cache` (see apps/api/src/blobs.rs).
698
699/// The largest one cache entry may be, compressed.
700pub const CACHE_MAX_ENTRY_BYTES: u64 = 2 * 1024 * 1024 * 1024;
701/// What one repository's entries may hold together. Saving past it evicts
702/// the entries restored longest ago.
703pub const CACHE_REPO_QUOTA_BYTES: u64 = 10 * 1024 * 1024 * 1024;
704/// An entry not restored for this long is deleted.
705pub const CACHE_UNUSED_DAYS: u64 = 7;
706/// An entry is deleted this long after it was saved, however often it is
707/// restored (the bucket's own lifecycle rule deletes objects at 30 days).
708pub const CACHE_MAX_AGE_DAYS: u64 = 28;
709/// An upload is sent in parts of this size (the last may be smaller).
710pub const CACHE_PART_BYTES: u64 = 32 * 1024 * 1024;
711/// What R2 charges g1t to store a GB for a month, in millionths of a
712/// dollar ($0.015): what the cache's storage is charged at, plus the margin.
713pub const CACHE_MICROS_PER_GB_MONTH: i64 = 15_000;
714
715/// `cache_lookup`: the entry a job restores: its key exactly, else the
716/// newest whose key starts with one of `restore`, in order.
717/// Returns `Outcome<Option<CacheHit>>`.
718#[derive(Debug, Serialize, Deserialize)]
719pub struct CacheLookupArgs {
720 pub job: String,
721 pub token: String,
722 pub key: String,
723 #[serde(default)]
724 pub restore: Vec<String>,
725 /// The hash of the entry's paths and compression, as the runner works it
726 /// out: an entry saved for other paths is not restored. Empty from
727 /// runners that do not send one.
728 #[serde(default)]
729 pub version: String,
730}
731
732#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
733pub struct CacheHit {
734 pub key: String,
735 pub object: String,
736 pub size: u64,
737}
738
739/// `cache_reserve`: a job about to save `size` bytes under `key`. Refused
740/// when the key is taken (`conflict`: keys are written once) or the entry
741/// is too large. Returns `Outcome<CacheReservation>`.
742#[derive(Debug, Serialize, Deserialize)]
743pub struct CacheReserveArgs {
744 pub job: String,
745 pub token: String,
746 pub key: String,
747 pub size: u64,
748 /// As in `CacheLookupArgs`.
749 #[serde(default)]
750 pub version: String,
751}
752
753#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
754pub struct CacheReservation {
755 pub id: String,
756 /// Where the API puts it in R2.
757 pub object: String,
758}
759
760/// `cache_commit`: the upload of `id` is complete, at `size` bytes. Returns
761/// `Outcome<CacheCommitted>`: the objects of entries it evicted, which the
762/// API deletes from R2.
763#[derive(Debug, Serialize, Deserialize)]
764pub struct CacheCommitArgs {
765 pub job: String,
766 pub token: String,
767 pub id: String,
768 pub size: u64,
769}
770
771#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
772pub struct CacheCommitted {
773 pub evicted: Vec<String>,
774}
775
776/// `cache_abort`: an upload that will not finish; its reservation goes.
777/// Returns `Outcome<bool>`.
778#[derive(Debug, Serialize, Deserialize)]
779pub struct CacheAbortArgs {
780 pub job: String,
781 pub token: String,
782 pub id: String,
783}
784
785#[cfg(test)]
786mod instance_tests {
787 use super::*;
788
789 fn labels(given: &[&str]) -> Vec<String> {
790 given.iter().map(|l| (*l).to_owned()).collect()
791 }
792
793 #[test]
794 fn runs_on_picks_the_machine() {
795 assert_eq!(instance_for(&labels(&["ubuntu-latest"])).container, "standard-1");
796 assert_eq!(instance_for(&labels(&[])).label, "standard");
797 assert_eq!(instance_for(&labels(&["g1t-4core"])).container, "standard-4");
798 assert_eq!(instance_for(&labels(&["ubuntu-latest", "G1T-2Core"])).container, "standard-3");
799 // Both named: the larger.
800 assert_eq!(instance_for(&labels(&["g1t-2core", "g1t-4core"])).label, "g1t-4core");
801 assert_eq!(instance_named("g1t-4core").map(|i| i.vcpu), Some(4.0));
802 assert_eq!(instance_named("standard"), Some(STANDARD_INSTANCE));
803 assert_eq!(instance_named("g1t-64core"), None);
804 }
805
806 #[test]
807 fn start_args_from_older_callers_read() {
808 let args: StartJobArgs = serde_json::from_value(serde_json::json!({
809 "job": "job_1", "token": "t", "repo": { "namespace": "acme", "name": "web" }, "timeoutMinutes": 30
810 }))
811 .unwrap();
812 assert!(args.workflow.is_none() && args.environment.is_none() && !args.trusted && args.instance.is_none());
813 }
814}
815
816#[cfg(test)]
817mod setting_args_tests {
818 use super::*;
819
820 #[test]
821 fn who_reads_a_row_is_read_as_the_site_and_api_send_it() {
822 let args: SetSettingArgs = serde_json::from_value(serde_json::json!({
823 "actor": { "id": "usr_1", "username": "a" },
824 "repo": { "namespace": "acme", "name": "web" },
825 "kind": "secret",
826 "name": "STRIPE_KEY",
827 "availableTo": ["deployments"],
828 "environments": ["production"],
829 }))
830 .unwrap();
831 assert_eq!(args.available_to, Some(vec!["deployments".to_owned()]));
832 }
833}