Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
1,669 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 /// For a managed workflow (`managed/<name>.yml`): what installed it.
51 #[serde(default)]
52 pub managed_by: Option<ManagedBy>,
53}
54
55/// What installed a managed workflow in a repository.
56#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase")]
58pub struct ManagedBy {
59 /// `g1t` for one g1t's services installed (such as a project's deploy
60 /// workflow), or the workspace's name for one its owners added.
61 pub origin: String,
62 /// The workspace's: `required` or `default`. Empty for g1t's.
63 #[serde(default)]
64 pub mode: String,
65 /// The workspace's: the file it was copied from, as
66 /// `owner/repo/path@ref`.
67 #[serde(default)]
68 pub source: Option<String>,
69}
70
71#[derive(Clone, Debug, Serialize, Deserialize)]
72#[serde(rename_all = "camelCase")]
73pub struct WorkflowRun {
74 pub id: String,
75 pub workflow_id: String,
76 pub path: String,
77 /// The workflow's name.
78 pub name: String,
79 /// `run-name`, or what started it: a commit's subject, a pull request's title.
80 pub title: String,
81 /// Counts the workflow's runs: 1, 2, 3…
82 pub number: u64,
83 pub attempt: u64,
84 /// The GitHub event: `push`, `pull_request`, `schedule`…
85 pub event: String,
86 #[serde(rename = "ref")]
87 pub git_ref: String,
88 pub sha: String,
89 /// The pull request it ran for, if any.
90 pub pull: Option<u32>,
91 /// `queued`, `in_progress` or `completed`.
92 pub status: String,
93 /// When completed: `success`, `failure`, `cancelled` or `skipped`.
94 pub conclusion: Option<String>,
95 /// Why it could not start, such as a workflow file that does not read.
96 pub error: Option<String>,
97 /// Username of whoever caused it.
98 pub actor: Option<String>,
99 pub created_at: String,
100 pub started_at: Option<String>,
101 pub finished_at: Option<String>,
102}
103
104#[derive(Clone, Debug, Default, Serialize, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub struct StepState {
107 /// From 1.
108 pub number: u32,
109 pub name: String,
110 /// `queued`, `in_progress` or `completed`.
111 pub status: String,
112 /// `success`, `failure`, `cancelled` or `skipped`.
113 pub conclusion: Option<String>,
114 pub started_at: Option<String>,
115 pub finished_at: Option<String>,
116}
117
118/// A message a step left with `::error::`, `::warning::` or `::notice::`.
119#[derive(Clone, Debug, Default, Serialize, Deserialize)]
120#[serde(rename_all = "camelCase")]
121pub struct Annotation {
122 /// `error`, `warning` or `notice`.
123 pub level: String,
124 pub message: String,
125 pub title: Option<String>,
126 pub file: Option<String>,
127 pub line: Option<u32>,
128}
129
130#[derive(Clone, Debug, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct Job {
133 pub id: String,
134 pub run_id: String,
135 /// Its key under `jobs:`.
136 pub key: String,
137 /// With its matrix combination: `test (ubuntu-latest, 20)`.
138 pub name: String,
139 pub needs: Vec<String>,
140 /// `queued`, `waiting` (for the jobs it needs), `in_progress` or `completed`.
141 pub status: String,
142 pub conclusion: Option<String>,
143 pub steps: Vec<StepState>,
144 pub annotations: Vec<Annotation>,
145 /// Why it did not run, what stopped it, or what it waits for.
146 pub reason: Option<String>,
147 pub started_at: Option<String>,
148 pub finished_at: Option<String>,
149 /// The environment it names, once its needs are done (an expression
150 /// read by then). A job held by the environment's protection rules is
151 /// `pending` until they let it through.
152 #[serde(default)]
153 pub environment: Option<String>,
154 /// Its `runs-on` names self-hosted runners (see `runners`).
155 #[serde(default)]
156 pub self_hosted: bool,
157 /// The self-hosted runner that took it, by name.
158 #[serde(default)]
159 pub runner: Option<String>,
160 /// It was cancelled and is running its `if: always()` and `cancelled()`
161 /// steps and its post steps before it ends.
162 #[serde(default)]
163 pub cancelling: bool,
164 /// Where its deployment is (`environment.url`), for a job that deploys
165 /// and says; the current attempt's only.
166 #[serde(default, skip_serializing_if = "Option::is_none")]
167 pub environment_url: Option<String>,
168 /// For a job that calls a reusable workflow: that workflow's file. Its
169 /// jobs' keys start with this job's key and a `/`.
170 #[serde(default, skip_serializing_if = "Option::is_none")]
171 pub uses: Option<String>,
172}
173
174#[derive(Clone, Debug, Serialize, Deserialize)]
175#[serde(rename_all = "camelCase")]
176pub struct RunDetail {
177 pub run: WorkflowRun,
178 pub jobs: Vec<Job>,
179 /// The workflow's notes, as of the run's commit.
180 pub notes: Vec<WorkflowNote>,
181 /// For a run of a pull request from outside: whether it waits for, or
182 /// had, someone's approval (`status` is `action_required` while it waits).
183 #[serde(default)]
184 pub approval: Option<RunApproval>,
185 /// The environments whose protection rules hold its jobs, this attempt.
186 #[serde(default)]
187 pub pending_deployments: Vec<PendingDeployment>,
188 /// Every attempt of the run, oldest first, the one shown included.
189 /// `run.attempt` says which one `jobs` belong to.
190 #[serde(default)]
191 pub attempts: Vec<RunAttempt>,
192}
193
194/// One attempt of a run: the first, or a re-run.
195#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
196#[serde(rename_all = "camelCase")]
197pub struct RunAttempt {
198 /// From 1.
199 pub attempt: u64,
200 /// `queued`, `in_progress` or `completed`; earlier attempts are completed.
201 pub status: String,
202 pub conclusion: Option<String>,
203 /// Who started it: whoever caused the run for the first, whoever re-ran
204 /// it for the rest.
205 pub actor: Option<String>,
206 /// It ran with debug logging (`RUNNER_DEBUG=1`).
207 pub debug: bool,
208 pub started_at: Option<String>,
209 pub finished_at: Option<String>,
210}
211
212/// One job's summary: what its steps wrote to `$GITHUB_STEP_SUMMARY`, in
213/// Markdown, masked.
214#[derive(Clone, Debug, Serialize, Deserialize)]
215#[serde(rename_all = "camelCase")]
216pub struct JobSummary {
217 /// The job's id, as `RunDetail.jobs` gives it for the attempt.
218 pub job_id: String,
219 pub name: String,
220 pub steps: Vec<StepSummary>,
221}
222
223#[derive(Clone, Debug, Serialize, Deserialize)]
224#[serde(rename_all = "camelCase")]
225pub struct StepSummary {
226 /// The step, from 1 (post steps follow the job's own).
227 pub step: u32,
228 pub markdown: String,
229}
230
231/// A job's whole log, for downloading: its steps, to split the text by.
232#[derive(Clone, Debug, Serialize, Deserialize)]
233#[serde(rename_all = "camelCase")]
234pub struct JobLogText {
235 pub job_id: String,
236 pub name: String,
237 pub steps: Vec<StepState>,
238 pub chunks: Vec<LogChunk>,
239 /// Whether the job has finished.
240 pub done: bool,
241 /// Its log was left out: the run's logs reached `MAX_RUN_LOG_BYTES`.
242 #[serde(default)]
243 pub omitted: bool,
244}
245
246/// A run that needed approval before it started.
247#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
248#[serde(rename_all = "camelCase")]
249pub struct RunApproval {
250 /// `required` while it waits, then `approved`.
251 pub state: String,
252 /// Why it waits, in words.
253 pub reason: String,
254 /// Who approved it.
255 pub approved_by: Option<String>,
256}
257
258/// One person or team who may approve a job's deployment to an
259/// environment.
260#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
261pub struct EnvironmentReviewer {
262 /// `user` or `team`.
263 #[serde(rename = "type")]
264 pub kind: String,
265 /// A username, or a team's slug in the repository's workspace.
266 pub name: String,
267}
268
269/// A branch or tag pattern an environment lets deploy.
270#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
271pub struct BranchPattern {
272 /// fnmatch-style, as branch filters are: `main`, `release/*`, `v*`.
273 pub name: String,
274 /// `branch` or `tag`.
275 #[serde(rename = "type", default = "branch_kind")]
276 pub kind: String,
277}
278
279fn branch_kind() -> String {
280 "branch".to_owned()
281}
282
283/// The most reviewers an environment may have, as on GitHub.
284pub const MAX_ENVIRONMENT_REVIEWERS: usize = 6;
285/// The longest wait timer, in minutes: 30 days.
286pub const MAX_WAIT_MINUTES: u32 = 43_200;
287
288/// An environment and its protection rules. Jobs that name it with
289/// `environment:` wait until the rules let them through; only then does the
290/// job get the environment's secrets.
291#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
292#[serde(rename_all = "camelCase")]
293pub struct Environment {
294 /// Lowercase.
295 pub name: String,
296 /// Who may approve its jobs; none means no review is needed.
297 pub reviewers: Vec<EnvironmentReviewer>,
298 /// Whoever started a run may not approve its jobs, even as a reviewer.
299 pub prevent_self_review: bool,
300 /// Minutes each job waits before it may start.
301 pub wait_minutes: u32,
302 /// Which refs may deploy: `all`, `protected` (branches the rules
303 /// protect, the default branch included) or `selected` (`branch_patterns`).
304 pub branch_policy: String,
305 pub branch_patterns: Vec<BranchPattern>,
306 /// Admins may approve without being reviewers, which also skips the wait.
307 pub admins_bypass: bool,
308 /// Whether it has rules saved; false for one only named by a workflow,
309 /// a secret or a deployment.
310 pub protected: bool,
311 pub updated_at: Option<String>,
312 pub updated_by: Option<String>,
313}
314
315/// An environment holding a run's jobs, and where its rules stand.
316#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
317#[serde(rename_all = "camelCase")]
318pub struct PendingDeployment {
319 pub environment: String,
320 /// `waiting`, `approved` or `rejected`.
321 pub state: String,
322 /// Whether a reviewer must approve it before its jobs start.
323 pub needs_review: bool,
324 /// When its wait timer lets its jobs start, if it has one.
325 pub wait_until: Option<String>,
326 pub reviewers: Vec<EnvironmentReviewer>,
327 /// The jobs it holds, by name.
328 pub jobs: Vec<String>,
329 /// Whether the viewer may approve or reject it now.
330 #[serde(default)]
331 pub can_review: bool,
332 pub reviewed_by: Option<String>,
333 pub comment: Option<String>,
334 pub reviewed_at: Option<String>,
335}
336
337/// A repository's choices for its workflows.
338#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
339#[serde(rename_all = "camelCase")]
340pub struct ActionsSettings {
341 /// What a workflow without `permissions:` gets: `read` (contents and
342 /// packages read) or `write` (every permission). Unchosen, a repository
343 /// made before restricted tokens keeps `write`; a newer one takes its
344 /// workspace's default. Never more than the workspace's maximum.
345 pub default_permissions: String,
346 /// Whether the repository chose it, rather than taking it as above.
347 #[serde(default)]
348 pub default_chosen: bool,
349 /// The most the workspace lets a repository's default be.
350 #[serde(default = "write")]
351 pub max_permissions: String,
352 /// Which pull requests' runs wait for approval: `first_time_contributors`,
353 /// `outside_contributors` (the default) or `all_external_contributors`.
354 pub approval_policy: String,
355 /// Whether a job's token may open pull requests and approve them. Off
356 /// unless the repository turns it on, and only where the workspace
357 /// allows it.
358 #[serde(default)]
359 pub can_approve_pull_requests: bool,
360 /// Whether the workspace lets its repositories turn that on.
361 #[serde(default)]
362 pub workspace_allows_pull_requests: bool,
363 /// Who may use this repository's actions and reusable workflows from
364 /// their workflows, when it is private: `none` (only itself, the
365 /// default) or `organization` (private repositories of its workspace).
366 /// A public repository's are anyone's. See [`ACCESS_LEVELS`].
367 #[serde(default = "no_access")]
368 pub access_level: String,
369}
370
371fn no_access() -> String {
372 "none".to_owned()
373}
374
375/// The values of `access_level`. `user` is read as `organization`: a
376/// personal account's repositories are its own workspace's.
377pub const ACCESS_LEVELS: [&str; 2] = ["none", "organization"];
378
379/// `access_level` as given, as one of [`ACCESS_LEVELS`]; None when it is
380/// not one.
381pub fn access_level(given: &str) -> Option<&'static str> {
382 match given.trim().to_ascii_lowercase().as_str() {
383 "none" | "" => Some("none"),
384 "organization" | "user" | "workspace" => Some("organization"),
385 _ => None,
386 }
387}
388
389fn write() -> String {
390 "write".to_owned()
391}
392
393/// A workspace's policy for its repositories' tokens.
394#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
395#[serde(rename_all = "camelCase")]
396pub struct WorkspaceActionsSettings {
397 /// What a repository made from now on gets by default: `read` (the
398 /// default) or `write`.
399 pub default_permissions: String,
400 /// The most any repository's default may be: `write` (the default) or
401 /// `read`, which holds every repository to read-only.
402 pub max_permissions: String,
403 /// Whether its repositories may let jobs open and approve pull
404 /// requests. Off by default.
405 pub can_approve_pull_requests: bool,
406}
407
408/// `workspace_actions_settings`: members only. Returns
409/// `Outcome<WorkspaceActionsSettings>`.
410#[derive(Debug, Serialize, Deserialize)]
411pub struct WorkspaceActionsSettingsArgs {
412 pub viewer: Viewer,
413 pub workspace: String,
414}
415
416/// `set_workspace_actions_settings`: owners only. Fields left out stay as
417/// they are. Returns `Outcome<WorkspaceActionsSettings>`.
418#[derive(Debug, Serialize, Deserialize)]
419#[serde(rename_all = "camelCase")]
420pub struct SetWorkspaceActionsSettingsArgs {
421 pub actor: User,
422 pub workspace: String,
423 #[serde(default)]
424 pub default_permissions: Option<String>,
425 #[serde(default)]
426 pub max_permissions: Option<String>,
427 #[serde(default)]
428 pub can_approve_pull_requests: Option<bool>,
429}
430
431/// The approval policies, least strict first.
432pub const APPROVAL_POLICIES: [&str; 3] = ["first_time_contributors", "outside_contributors", "all_external_contributors"];
433
434/// `actions_settings`. Returns `Outcome<ActionsSettings>`; anyone who can
435/// read the repository may see them.
436#[derive(Debug, Serialize, Deserialize)]
437pub struct ActionsSettingsArgs {
438 pub viewer: Viewer,
439 pub repo: RepoPath,
440}
441
442/// `set_actions_settings`: Admins only. Fields left out stay as they are.
443/// Returns `Outcome<ActionsSettings>`.
444#[derive(Debug, Serialize, Deserialize)]
445#[serde(rename_all = "camelCase")]
446pub struct SetActionsSettingsArgs {
447 pub actor: User,
448 pub repo: RepoPath,
449 /// `read` or `write`; `inherit` goes back to the workspace's (or, for a
450 /// repository made before restricted tokens, `write`).
451 #[serde(default)]
452 pub default_permissions: Option<String>,
453 #[serde(default)]
454 pub approval_policy: Option<String>,
455 #[serde(default)]
456 pub can_approve_pull_requests: Option<bool>,
457 /// `none`, or `organization` (`user` reads the same). See
458 /// [`ActionsSettings::access_level`].
459 #[serde(default)]
460 pub access_level: Option<String>,
461}
462
463/// `environments`: every environment a repository's workflows, secrets,
464/// deployments or rules name, with its rules. `environment`: one, by
465/// `name`. Returns `Outcome<Vec<Environment>>` and `Outcome<Environment>`.
466#[derive(Debug, Serialize, Deserialize)]
467pub struct EnvironmentsArgs {
468 pub viewer: Viewer,
469 pub repo: RepoPath,
470 #[serde(default)]
471 pub name: Option<String>,
472}
473
474/// `set_environment`: create an environment's rules or change them. Fields
475/// left out stay as they are (none, for a new one). Admins only. Returns
476/// `Outcome<Environment>`.
477#[derive(Debug, Serialize, Deserialize)]
478#[serde(rename_all = "camelCase")]
479pub struct SetEnvironmentArgs {
480 pub actor: User,
481 pub repo: RepoPath,
482 pub name: String,
483 #[serde(default)]
484 pub reviewers: Option<Vec<EnvironmentReviewer>>,
485 #[serde(default)]
486 pub prevent_self_review: Option<bool>,
487 #[serde(default)]
488 pub wait_minutes: Option<u32>,
489 #[serde(default)]
490 pub branch_policy: Option<String>,
491 #[serde(default)]
492 pub branch_patterns: Option<Vec<BranchPattern>>,
493 #[serde(default)]
494 pub admins_bypass: Option<bool>,
495}
496
497/// `delete_environment`: its rules go; jobs naming it run without them.
498/// Its secrets' rows stay. Admins only. Returns `Outcome<bool>`.
499#[derive(Debug, Serialize, Deserialize)]
500pub struct DeleteEnvironmentArgs {
501 pub actor: User,
502 pub repo: RepoPath,
503 pub name: String,
504}
505
506/// `pending_deployments`: the environments holding a run's jobs. Returns
507/// `Outcome<Vec<PendingDeployment>>`.
508#[derive(Debug, Serialize, Deserialize)]
509pub struct PendingDeploymentsArgs {
510 pub viewer: Viewer,
511 pub repo: RepoPath,
512 pub id: String,
513}
514
515/// `review_deployments`: approve or reject a run's jobs for `environments`
516/// (every one waiting, if empty). Returns `Outcome<Vec<PendingDeployment>>`.
517#[derive(Debug, Serialize, Deserialize)]
518pub struct ReviewDeploymentsArgs {
519 pub actor: User,
520 pub repo: RepoPath,
521 pub id: String,
522 #[serde(default)]
523 pub environments: Vec<String>,
524 /// `approved` or `rejected`.
525 pub state: String,
526 #[serde(default)]
527 pub comment: Option<String>,
528}
529
530/// `repository_dispatch`: start the default branch's workflows that run
531/// `on: repository_dispatch` for `event_type`. Needs the Write role (a
532/// token's `code:write`). Returns `Outcome<u32>`: how many started.
533#[derive(Debug, Serialize, Deserialize)]
534#[serde(rename_all = "camelCase")]
535pub struct RepositoryDispatchArgs {
536 pub actor: User,
537 pub repo: RepoPath,
538 pub event_type: String,
539 #[serde(default)]
540 pub client_payload: Value,
541}
542
543#[derive(Clone, Debug, Serialize, Deserialize)]
544#[serde(rename_all = "camelCase")]
545pub struct LogChunk {
546 pub seq: u64,
547 /// The step it belongs to, from 1; 0 for the job's setup.
548 pub step: u32,
549 pub text: String,
550}
551
552#[derive(Clone, Debug, Serialize, Deserialize)]
553#[serde(rename_all = "camelCase")]
554pub struct JobLog {
555 pub chunks: Vec<LogChunk>,
556 /// Whether the job has finished, so no more will come.
557 pub done: bool,
558}
559
560/// Who may read a secret or variable: workflows (`secrets.*` and `vars.*`
561/// in GitHub Actions) and deployments (a deploy build's environment and the
562/// running app's bindings). Agents, checks and the merge queue read none.
563pub const CONSUMERS: [&str; 2] = ["workflows", "deployments"];
564
565/// One row of a repository's or workspace's secrets and variables, as
566/// Vercel lists environment variables: a key, its type, the environments
567/// it applies to and who reads it. A key may have one row per environment.
568/// Secrets' values are never returned.
569#[derive(Clone, Debug, Serialize, Deserialize)]
570#[serde(rename_all = "camelCase")]
571pub struct Setting {
572 #[serde(default)]
573 pub id: String,
574 pub name: String,
575 /// `secret`, or `variable` (shown as Config).
576 #[serde(default)]
577 pub kind: String,
578 /// A variable's value; secrets' are never returned.
579 pub value: Option<String>,
580 /// `project` (a repository's, which belong to its project) or
581 /// `workspace`.
582 pub scope: String,
583 pub updated_at: String,
584 /// `workflows` and/or `deployments`.
585 #[serde(default)]
586 pub available_to: Vec<String>,
587 /// The environments it applies to; empty is every environment.
588 #[serde(default)]
589 pub environments: Vec<String>,
590 /// A workspace's row: the projects it reaches, by slug; empty is every
591 /// project.
592 #[serde(default)]
593 pub projects: Vec<String>,
594 #[serde(default)]
595 pub note: Option<String>,
596 #[serde(default)]
597 pub updated_by: Option<String>,
598 /// A brokered secret's hosts: a job gets a stand-in for it, and g1t
599 /// puts the real value on the job's requests to these hosts as they
600 /// leave its sandbox. Empty for a secret that is not brokered, and for
601 /// config.
602 #[serde(default)]
603 pub brokered_hosts: Vec<String>,
604}
605
606/// How a brokered secret's stand-in starts. The rest is 48 random hex
607/// characters, so the whole is shaped like a token and tools take it.
608pub const STAND_IN_PREFIX: &str = "g1tsec_";
609
610/// A stand-in a running job was given for a brokered secret, as the runner
611/// Worker asks for them (`brokered_secrets`) to fill in the real value on
612/// the job's requests to `hosts`.
613#[derive(Clone, Debug, Serialize, Deserialize)]
614#[serde(rename_all = "camelCase")]
615pub struct BrokeredSecret {
616 pub stand_in: String,
617 /// The secret's key, for the job's log.
618 pub name: String,
619 pub value: String,
620 pub hosts: Vec<String>,
621}
622
623// --- Methods ---------------------------------------------------------------
624
625/// `workflows`. Returns `Outcome<Vec<Workflow>>`.
626#[derive(Debug, Serialize, Deserialize)]
627pub struct WorkflowsArgs {
628 pub repo: RepoPath,
629 pub viewer: Viewer,
630}
631
632/// `workflow`: one workflow, named by its id (`wfl_…`), its file name
633/// (`ci.yml`), its path or its name, with its file as of `ref` (the
634/// default branch when absent). Returns `Outcome<WorkflowDetail>`.
635#[derive(Debug, Serialize, Deserialize)]
636pub struct WorkflowArgs {
637 pub repo: RepoPath,
638 pub viewer: Viewer,
639 pub workflow: String,
640 #[serde(default, rename = "ref")]
641 pub git_ref: Option<String>,
642}
643
644/// A workflow with its file.
645#[derive(Clone, Debug, Serialize, Deserialize)]
646#[serde(rename_all = "camelCase")]
647pub struct WorkflowDetail {
648 #[serde(flatten)]
649 pub workflow: Workflow,
650 /// The branch or tag the file was read from.
651 #[serde(rename = "ref")]
652 pub git_ref: String,
653 /// The file, as written on `ref`.
654 pub source: String,
655}
656
657/// `runs`: newest first. Returns `Outcome<Vec<WorkflowRun>>`.
658#[derive(Debug, Serialize, Deserialize)]
659pub struct RunsArgs {
660 pub repo: RepoPath,
661 pub viewer: Viewer,
662 /// A workflow's id or file name.
663 #[serde(default)]
664 pub workflow: Option<String>,
665 #[serde(default)]
666 pub branch: Option<String>,
667 #[serde(default)]
668 pub event: Option<String>,
669 /// The pull request's number.
670 #[serde(default)]
671 pub pull: Option<u32>,
672 #[serde(default)]
673 pub sha: Option<String>,
674 #[serde(default)]
675 pub limit: Option<u32>,
676}
677
678/// `run`. Returns `Outcome<RunDetail>`.
679#[derive(Debug, Serialize, Deserialize)]
680pub struct RunArgs {
681 pub repo: RepoPath,
682 pub viewer: Viewer,
683 pub id: String,
684 /// An earlier attempt; the latest when absent.
685 #[serde(default)]
686 pub attempt: Option<u64>,
687}
688
689/// `summaries`: the job summaries of a run's attempt (the latest when
690/// `attempt` is absent), jobs in the run's order, those with none left
691/// out. Returns `Outcome<Vec<JobSummary>>`.
692#[derive(Debug, Serialize, Deserialize)]
693pub struct SummariesArgs {
694 pub repo: RepoPath,
695 pub viewer: Viewer,
696 pub id: String,
697 #[serde(default)]
698 pub attempt: Option<u64>,
699}
700
701/// `job_log_text`: one job's whole log, any attempt's (by the id the run
702/// gave the job). Returns `Outcome<JobLogText>`.
703#[derive(Debug, Serialize, Deserialize)]
704pub struct JobLogTextArgs {
705 pub repo: RepoPath,
706 pub viewer: Viewer,
707 pub job: String,
708}
709
710/// `run_logs`: every job's whole log for an attempt of a run (the latest
711/// when `attempt` is absent), until they add up to `MAX_RUN_LOG_BYTES`;
712/// jobs past it come `omitted`, with no chunks. Returns
713/// `Outcome<Vec<JobLogText>>`.
714#[derive(Debug, Serialize, Deserialize)]
715pub struct RunLogsArgs {
716 pub repo: RepoPath,
717 pub viewer: Viewer,
718 pub id: String,
719 #[serde(default)]
720 pub attempt: Option<u64>,
721}
722
723/// The most log `run_logs` returns at once, in bytes.
724pub const MAX_RUN_LOG_BYTES: usize = 24 * 1024 * 1024;
725
726/// `logs`: a job's log after `after`. Returns `Outcome<JobLog>`.
727#[derive(Debug, Serialize, Deserialize)]
728pub struct LogsArgs {
729 pub repo: RepoPath,
730 pub viewer: Viewer,
731 pub job: String,
732 #[serde(default)]
733 pub after: u64,
734}
735
736/// `dispatch`: run a workflow that has `workflow_dispatch`. Members only.
737/// Returns `Outcome<WorkflowRun>`.
738#[derive(Debug, Serialize, Deserialize)]
739pub struct DispatchArgs {
740 pub actor: User,
741 pub repo: RepoPath,
742 /// A workflow's id or file name.
743 pub workflow: String,
744 /// A branch or tag; the default branch when absent.
745 #[serde(default, rename = "ref")]
746 pub git_ref: Option<String>,
747 #[serde(default)]
748 pub inputs: serde_json::Map<String, Value>,
749}
750
751/// `cancel` and `rerun`: every job, with `failed_only` the ones that did
752/// not succeed, or with `job` that one job (by its id in the run's latest
753/// attempt); each with the jobs that need them. `debug` runs the new
754/// attempt with debug logging. Members only. Returns `Outcome<WorkflowRun>`.
755#[derive(Debug, Serialize, Deserialize)]
756pub struct RunActionArgs {
757 pub actor: User,
758 pub repo: RepoPath,
759 pub id: String,
760 #[serde(default)]
761 pub failed_only: bool,
762 #[serde(default)]
763 pub job: Option<String>,
764 #[serde(default)]
765 pub debug: bool,
766 /// `cancel`: stop running jobs outright, without their cleanup steps.
767 #[serde(default)]
768 pub force: bool,
769}
770
771/// `set_workflow_enabled`. Members only. Returns `Outcome<Workflow>`.
772#[derive(Debug, Serialize, Deserialize)]
773pub struct SetWorkflowEnabledArgs {
774 pub actor: User,
775 pub repo: RepoPath,
776 pub workflow: String,
777 pub enabled: bool,
778}
779
780/// Whose secrets or variables: a repository's, or with only `workspace`,
781/// a workspace's.
782#[derive(Clone, Debug, Serialize, Deserialize)]
783pub struct SettingsOwner {
784 #[serde(default)]
785 pub repo: Option<RepoPath>,
786 #[serde(default)]
787 pub workspace: Option<String>,
788}
789
790/// `settings`: the secrets (`kind: secret`) or variables (`kind: variable`)
791/// of a repository, with its workspace's, or of a workspace. Members only.
792/// Returns `Outcome<Vec<Setting>>`.
793#[derive(Debug, Serialize, Deserialize)]
794pub struct SettingsArgs {
795 pub actor: User,
796 #[serde(flatten)]
797 pub owner: SettingsOwner,
798 pub kind: String,
799}
800
801/// `set_setting`: add or replace one. A repository's need a member; a
802/// workspace's an owner. Returns `Outcome<Setting>`.
803#[derive(Debug, Serialize, Deserialize)]
804pub struct SetSettingArgs {
805 pub actor: User,
806 #[serde(flatten)]
807 pub owner: SettingsOwner,
808 /// `secret` or `variable`. Changing a variable's row to `secret` seals
809 /// it; a secret cannot become a variable.
810 pub kind: String,
811 pub name: String,
812 /// The row to change. Left out, the key's row for every environment, as
813 /// GitHub's API addresses a secret by name alone.
814 #[serde(default)]
815 pub id: Option<String>,
816 /// Needed for a new row; left out, an existing row keeps its value.
817 #[serde(default)]
818 pub value: Option<String>,
819 /// `workflows` and/or `deployments`; left out, unchanged (both, for a
820 /// new row).
821 // Named as callers send it: an `alias` is not honoured beside the
822 // flattened owner in the Worker's build.
823 #[serde(default, rename = "availableTo")]
824 pub available_to: Option<Vec<String>>,
825 /// The environments it applies to; empty is every one. Left out,
826 /// unchanged.
827 #[serde(default)]
828 pub environments: Option<Vec<String>>,
829 /// A workspace's row: project slugs; empty for every one.
830 #[serde(default)]
831 pub projects: Option<Vec<String>>,
832 #[serde(default)]
833 pub note: Option<String>,
834 /// A secret's brokered hosts (`Setting::brokered_hosts`); empty stops
835 /// brokering it. Left out, unchanged.
836 #[serde(default, rename = "brokeredHosts")]
837 pub brokered_hosts: Option<Vec<String>>,
838}
839
840/// `set_managed_workflow`: installs, or replaces, a managed workflow in a
841/// repository (`g1t_actions::managed`): one that runs as if it were in
842/// `.g1t/workflows/` without being committed there, under the path
843/// `managed/<name>.yml`. For g1t's own services; never reached from the API.
844/// Refused when `source` is not a workflow g1t can read. Returns `bool`.
845#[derive(Debug, Serialize, Deserialize)]
846#[serde(rename_all = "camelCase")]
847pub struct SetManagedWorkflowArgs {
848 pub repo_id: String,
849 /// Letters, digits and hyphens: the file name its path ends with.
850 pub name: String,
851 pub source: String,
852 /// What keeps it: `deployments`, or a workspace's id.
853 pub origin: String,
854}
855
856/// `set_managed_deploy`: installs, replaces or removes the deploy workflow
857/// a project's deployments run through (`g1t_actions::managed`), named
858/// `deploy-<project>`, from the project's settings. Nothing deploys (both
859/// `previews` and `production` off) removes it. For the deployments
860/// service only. Returns `bool`, whether one is installed now.
861#[derive(Debug, Serialize, Deserialize)]
862#[serde(rename_all = "camelCase")]
863pub struct SetManagedDeployArgs {
864 pub repo_id: String,
865 pub project: String,
866 pub production_branch: String,
867 #[serde(default)]
868 pub root_dir: String,
869 #[serde(default)]
870 pub build_command: String,
871 #[serde(default)]
872 pub output_dir: String,
873 pub previews: bool,
874 pub production: bool,
875}
876
877/// A workflow a workspace's owners added to its repositories
878/// (`g1t_actions::managed::Target`): it runs in each repository it targets
879/// as that repository's own, with its token and secrets, under the path
880/// `managed/<name>.yml`, from a copy of its source taken when it was saved.
881#[derive(Clone, Debug, Serialize, Deserialize)]
882#[serde(rename_all = "camelCase")]
883pub struct WorkspaceManagedWorkflow {
884 /// Lowercase letters, digits and hyphens: its path is
885 /// `managed/<name>.yml`.
886 pub name: String,
887 pub path: String,
888 /// Its `name:`, as repositories list it; a repository's workflow of
889 /// this name takes a default one's place.
890 pub workflow_name: String,
891 /// Where it was copied from: `owner/repo/path@ref`, a repository of the
892 /// same workspace.
893 pub source: String,
894 /// The commit it was copied at.
895 pub source_sha: Option<String>,
896 /// Whether the source repository is private, so the workflow runs in
897 /// private repositories only.
898 pub source_private: bool,
899 /// `required` or `default`.
900 pub mode: String,
901 /// Names and patterns of the repositories it runs in; empty is every one.
902 pub repositories: Vec<String>,
903 /// `all`, `public` or `private`.
904 pub visibility: String,
905 /// The copy that runs.
906 pub content: String,
907 pub created_at: String,
908 pub updated_at: String,
909 pub updated_by: String,
910}
911
912/// `workspace_managed_workflows`: owners only. Returns
913/// `Outcome<Vec<WorkspaceManagedWorkflow>>`.
914#[derive(Debug, Serialize, Deserialize)]
915pub struct WorkspaceManagedWorkflowsArgs {
916 pub viewer: Viewer,
917 pub workspace: String,
918}
919
920/// `set_workspace_managed_workflow`: owners only. Adds one (`create`, which
921/// needs `source` and `mode`) or changes one; fields left out stay as they
922/// are. The source is copied again when it is given or `refresh` is set.
923/// Refused when the source is not a workflow file in a repository of the
924/// workspace, or is private while the workflow could run in a public
925/// repository. Returns `Outcome<WorkspaceManagedWorkflow>`.
926#[derive(Debug, Serialize, Deserialize)]
927#[serde(rename_all = "camelCase")]
928pub struct SetWorkspaceManagedWorkflowArgs {
929 pub actor: User,
930 pub workspace: String,
931 pub name: String,
932 #[serde(default)]
933 pub create: bool,
934 /// `owner/repo/.g1t/workflows/file.yml@ref`.
935 #[serde(default)]
936 pub source: Option<String>,
937 #[serde(default)]
938 pub mode: Option<String>,
939 #[serde(default)]
940 pub repositories: Option<Vec<String>>,
941 #[serde(default)]
942 pub visibility: Option<String>,
943 #[serde(default)]
944 pub refresh: bool,
945}
946
947/// `remove_workspace_managed_workflow`: owners only. Its runs stay.
948/// Returns `Outcome<bool>`, whether there was one.
949#[derive(Debug, Serialize, Deserialize)]
950pub struct RemoveWorkspaceManagedWorkflowArgs {
951 pub actor: User,
952 pub workspace: String,
953 pub name: String,
954}
955
956/// `remove_managed_workflow`: takes a managed workflow out of a repository.
957/// Its runs stay. Returns `bool`, whether there was one.
958#[derive(Debug, Serialize, Deserialize)]
959#[serde(rename_all = "camelCase")]
960pub struct RemoveManagedWorkflowArgs {
961 pub repo_id: String,
962 pub name: String,
963}
964
965/// `resolve_settings`: the secrets and variables one reader gets, for the
966/// services that hand them out (the deployments service). Returns
967/// `ResolvedSettings`.
968#[derive(Debug, Serialize, Deserialize)]
969#[serde(rename_all = "camelCase")]
970pub struct ResolveSettingsArgs {
971 pub repo_id: String,
972 pub repo: RepoPath,
973 /// The project being read for; its repository's primary project if left
974 /// out.
975 #[serde(default)]
976 pub project_id: Option<String>,
977 #[serde(default)]
978 pub project_slug: Option<String>,
979 /// `workflows` or `deployments`.
980 pub consumer: String,
981 /// The environment being read for, such as `production` or `preview`.
982 #[serde(default)]
983 pub environment: Option<String>,
984 /// Whether the run is trusted; an untrusted one gets no secrets.
985 pub trusted: bool,
986}
987
988#[derive(Debug, Default, Serialize, Deserialize)]
989pub struct ResolvedSettings {
990 pub secrets: serde_json::Map<String, serde_json::Value>,
991 pub variables: serde_json::Map<String, serde_json::Value>,
992}
993
994/// `delete_setting`. Returns `Outcome<bool>`.
995#[derive(Debug, Serialize, Deserialize)]
996pub struct DeleteSettingArgs {
997 pub actor: User,
998 #[serde(flatten)]
999 pub owner: SettingsOwner,
1000 pub kind: String,
1001 pub name: String,
1002 /// One row; left out, every row of the key.
1003 #[serde(default)]
1004 pub id: Option<String>,
1005}
1006
1007/// `job_spec` and `job_report`: the sandbox running a job, with the job's
1008/// own token. `report` is one of:
1009/// `{"kind": "step", "number", "status", "conclusion"}`,
1010/// `{"kind": "log", "step", "text"}`,
1011/// `{"kind": "annotation", "level", "message", "title", "file", "line"}`,
1012/// `{"kind": "done", "conclusion", "outputs", "reason"}`.
1013#[derive(Debug, Serialize, Deserialize)]
1014pub struct JobCallArgs {
1015 pub job: String,
1016 pub token: String,
1017 #[serde(default)]
1018 pub report: Value,
1019 /// `job_spec` only: the harness takes a new token with the spec, and
1020 /// the one it asked with stops working, so a copy of it left on the
1021 /// runner's computer cannot fetch the job's secrets again. Harnesses
1022 /// that predate it never ask, and keep their token.
1023 #[serde(default)]
1024 pub rotate: bool,
1025 /// `job_spec` only: the harness on a self-hosted runner can pass the
1026 /// job's requests to a brokered secret's hosts through g1t
1027 /// (`POST /actions/jobs/{job}/broker`), so its spec lists each
1028 /// stand-in's hosts, never a value. Harnesses that predate it never
1029 /// ask, and their job's log says the stand-ins will not be filled in.
1030 #[serde(default)]
1031 pub broker: bool,
1032}
1033
1034/// What the runner needs to start a job's sandbox.
1035#[derive(Debug, Serialize, Deserialize)]
1036#[serde(rename_all = "camelCase")]
1037pub struct StartJobArgs {
1038 pub job: String,
1039 pub token: String,
1040 pub repo: RepoPath,
1041 /// Minutes before the job is stopped.
1042 pub timeout_minutes: u32,
1043 /// The workflow file the job is in (`.g1t/workflows/deploy.yml`), for
1044 /// the guardrails' workflow-only domains.
1045 #[serde(default)]
1046 pub workflow: Option<String>,
1047 /// The environment the job names with `environment:`, when it names
1048 /// one plainly (not with an expression).
1049 #[serde(default)]
1050 pub environment: Option<String>,
1051 /// Whether its run is trusted: not a pull request from a fork. Only a
1052 /// trusted run's jobs reach workflow-only domains.
1053 #[serde(default)]
1054 pub trusted: bool,
1055 /// The machine its `runs-on` asked for, by label (`instance_for`):
1056 /// `g1t-2core` or `g1t-4core`; absent, the standard one.
1057 #[serde(default)]
1058 pub instance: Option<String>,
1059 /// Whether the job may be given brokered secrets, so the runner sends
1060 /// its HTTP and HTTPS through the Worker even when its network is open.
1061 #[serde(default)]
1062 pub brokered: bool,
1063}
1064
1065/// A size of machine g1t runs workflow jobs on, asked for by a label in
1066/// `runs-on`. Each is a Cloudflare Containers instance type (named, or a
1067/// custom one: services/runner/wrangler.jsonc), every one with the 20 GB of
1068/// disk Containers allow at most; it costs what that instance costs g1t,
1069/// plus the margin, like any sandbox time.
1070#[derive(Clone, Copy, Debug, PartialEq)]
1071pub struct InstanceType {
1072 /// The `runs-on` label, or `standard` for the default.
1073 pub label: &'static str,
1074 /// The Containers instance type: a named one, or `custom` for one
1075 /// sized by its vCPU, memory and disk.
1076 pub container: &'static str,
1077 pub vcpu: f64,
1078 pub memory_gib: f64,
1079 pub disk_gb: f64,
1080 /// What a second of it costs g1t as a multiple of the standard
1081 /// machine's, with its vCPUs as busy (Cloudflare's list prices:
1082 /// memory $0.0000025 a GiB-second, disk $0.00000007 a GB-second, vCPU
1083 /// $0.00002 a second, and the Durable Object behind it at 128 MB,
1084 /// $0.0000125 a GB-second), to one decimal. Used to reserve before a
1085 /// job starts, and to price a job that did not report its own CPU.
1086 pub price_scale: f64,
1087}
1088
1089/// The default: what `ubuntu-latest` and every other hosted label get.
1090pub const STANDARD_INSTANCE: InstanceType =
1091 InstanceType { label: "standard", container: "custom", vcpu: 1.0, memory_gib: 4.0, disk_gb: 20.0, price_scale: 1.0 };
1092
1093/// Every machine a workflow job can ask for, the default first.
1094pub const INSTANCE_TYPES: [InstanceType; 3] = [
1095 STANDARD_INSTANCE,
1096 InstanceType { label: "g1t-2core", container: "custom", vcpu: 2.0, memory_gib: 8.0, disk_gb: 20.0, price_scale: 1.9 },
1097 InstanceType { label: "g1t-4core", container: "standard-4", vcpu: 4.0, memory_gib: 12.0, disk_gb: 20.0, price_scale: 3.4 },
1098];
1099
1100/// The machine a job's `runs-on` labels ask for: the largest named, or the
1101/// standard one. Labels compare without regard to case.
1102pub fn instance_for(labels: &[String]) -> InstanceType {
1103 INSTANCE_TYPES
1104 .iter()
1105 .rev()
1106 .find(|instance| instance.label != STANDARD_INSTANCE.label && labels.iter().any(|label| label.trim().eq_ignore_ascii_case(instance.label)))
1107 .copied()
1108 .unwrap_or(STANDARD_INSTANCE)
1109}
1110
1111/// An instance type by its label, if it is one.
1112pub fn instance_named(label: &str) -> Option<InstanceType> {
1113 INSTANCE_TYPES.iter().find(|instance| instance.label.eq_ignore_ascii_case(label.trim())).copied()
1114}
1115
1116// ── The cache (actions/cache) ─────────────────────────────────────────────
1117//
1118// Entries are kept in R2 by the API (the ACTIONS_CACHE bucket) and listed
1119// here, by the actions service, which decides what is found, what fits and
1120// what is evicted. A sandbox reaches these through the API with its job's
1121// token: `/actions/jobs/{job}/cache` (see apps/api/src/blobs.rs).
1122
1123/// The largest one cache entry may be, compressed.
1124pub const CACHE_MAX_ENTRY_BYTES: u64 = 2 * 1024 * 1024 * 1024;
1125/// What one repository's entries may hold together. Saving past it evicts
1126/// the entries restored longest ago.
1127pub const CACHE_REPO_QUOTA_BYTES: u64 = 10 * 1024 * 1024 * 1024;
1128/// An entry not restored for this long is deleted.
1129pub const CACHE_UNUSED_DAYS: u64 = 7;
1130/// An entry is deleted this long after it was saved, however often it is
1131/// restored (the bucket's own lifecycle rule deletes objects at 30 days).
1132pub const CACHE_MAX_AGE_DAYS: u64 = 28;
1133/// An upload is sent in parts of this size (the last may be smaller).
1134pub const CACHE_PART_BYTES: u64 = 32 * 1024 * 1024;
1135/// What R2 charges g1t to store a GB for a month, in millionths of a
1136/// dollar ($0.015): what the cache's storage is charged at, plus the margin.
1137pub const CACHE_MICROS_PER_GB_MONTH: i64 = 15_000;
1138
1139/// `cache_lookup`: the entry a job restores: its key exactly, else the
1140/// newest whose key starts with one of `restore`, in order.
1141/// Returns `Outcome<Option<CacheHit>>`.
1142#[derive(Debug, Serialize, Deserialize)]
1143pub struct CacheLookupArgs {
1144 pub job: String,
1145 pub token: String,
1146 pub key: String,
1147 #[serde(default)]
1148 pub restore: Vec<String>,
1149 /// The entry's version, a hash of its paths and compression, as the
1150 /// toolkit's client and g1t's runner both send it: only an entry of the
1151 /// same version is found. `None` from runners that send none, whose
1152 /// entries have none.
1153 #[serde(default)]
1154 pub version: Option<String>,
1155}
1156
1157#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1158pub struct CacheHit {
1159 pub key: String,
1160 pub object: String,
1161 pub size: u64,
1162 /// When it was saved, RFC 3339.
1163 #[serde(default)]
1164 pub created_at: String,
1165 /// A signed token for downloading it through the toolkit's blob
1166 /// endpoint, when the lookup came with a version.
1167 #[serde(default)]
1168 pub blob: Option<String>,
1169}
1170
1171/// `cache_reserve`: a job about to save `size` bytes under `key`. Refused
1172/// when the key is taken (`conflict`: keys are written once) or the entry
1173/// is too large. Returns `Outcome<CacheReservation>`.
1174#[derive(Debug, Serialize, Deserialize)]
1175pub struct CacheReserveArgs {
1176 pub job: String,
1177 pub token: String,
1178 pub key: String,
1179 /// Its size, when known before it is sent (the toolkit's newer client
1180 /// says only when it finishes: 0 then).
1181 pub size: u64,
1182 /// As in `CacheLookupArgs`.
1183 #[serde(default)]
1184 pub version: Option<String>,
1185}
1186
1187#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1188pub struct CacheReservation {
1189 pub id: String,
1190 /// Where the API puts it in R2.
1191 pub object: String,
1192 /// The entry's number, which the toolkit's older protocol names it by.
1193 #[serde(default)]
1194 pub number: u64,
1195 /// Its R2 upload, once one is started.
1196 #[serde(default)]
1197 pub upload: Option<String>,
1198 /// A signed token for sending its parts through the toolkit's blob
1199 /// endpoint, once its upload is started.
1200 #[serde(default)]
1201 pub blob: Option<String>,
1202}
1203
1204/// `cache_upload`: an entry a job is still uploading, by its number or by
1205/// key and version. Returns `Outcome<CacheReservation>`, with `upload` and
1206/// `blob` set once its upload has been started.
1207#[derive(Debug, Serialize, Deserialize)]
1208pub struct CacheUploadArgs {
1209 pub job: String,
1210 pub token: String,
1211 #[serde(default)]
1212 pub number: Option<u64>,
1213 #[serde(default)]
1214 pub key: Option<String>,
1215 #[serde(default)]
1216 pub version: Option<String>,
1217}
1218
1219/// `cache_commit`: the upload of `id` is complete, at `size` bytes. Returns
1220/// `Outcome<CacheCommitted>`: the objects of entries it evicted, which the
1221/// API deletes from R2.
1222#[derive(Debug, Serialize, Deserialize)]
1223pub struct CacheCommitArgs {
1224 pub job: String,
1225 pub token: String,
1226 pub id: String,
1227 pub size: u64,
1228}
1229
1230#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1231pub struct CacheCommitted {
1232 pub evicted: Vec<String>,
1233}
1234
1235/// `cache_abort`: an upload that will not finish; its reservation goes.
1236/// Returns `Outcome<bool>`.
1237#[derive(Debug, Serialize, Deserialize)]
1238pub struct CacheAbortArgs {
1239 pub job: String,
1240 pub token: String,
1241 pub id: String,
1242}
1243
1244/// A cache entry as people see it, in GitHub's shape: `id` is a number,
1245/// `git_ref` the ref whose run saved it (sent as `ref`; a pull request from
1246/// outside the repository saves under `untrusted:<ref>`), `version` the
1247/// hash of its paths and compression (empty for one g1t's own
1248/// `actions/cache` saved).
1249#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1250pub struct CacheEntry {
1251 pub id: u64,
1252 pub key: String,
1253 #[serde(rename = "ref")]
1254 pub git_ref: String,
1255 pub version: String,
1256 pub size_in_bytes: u64,
1257 pub created_at: String,
1258 pub last_accessed_at: String,
1259}
1260
1261/// A page of cache entries, in GitHub's shape.
1262#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1263pub struct CacheList {
1264 pub total_count: u64,
1265 pub actions_caches: Vec<CacheEntry>,
1266}
1267
1268/// How a list of cache entries is ordered: GitHub's `sort` names.
1269pub const CACHE_SORTS: [&str; 3] = ["last_accessed_at", "created_at", "size_in_bytes"];
1270
1271/// `caches`: a repository's saved cache entries, narrowed to keys starting
1272/// with `key` and to one `git_ref`, ordered by `sort` (one of
1273/// `CACHE_SORTS`, `last_accessed_at` by default) in `direction` (`asc` or
1274/// `desc`, the default), a page at a time (30 by default, at most 100).
1275/// Anyone who can see the repository. Returns `Outcome<CacheList>`.
1276#[derive(Debug, Serialize, Deserialize)]
1277pub struct CachesArgs {
1278 pub repo: RepoPath,
1279 pub viewer: Viewer,
1280 #[serde(default)]
1281 pub key: Option<String>,
1282 #[serde(default)]
1283 pub git_ref: Option<String>,
1284 #[serde(default)]
1285 pub sort: Option<String>,
1286 #[serde(default)]
1287 pub direction: Option<String>,
1288 #[serde(default)]
1289 pub page: Option<u32>,
1290 #[serde(default)]
1291 pub per_page: Option<u32>,
1292}
1293
1294/// `cache_usage`: what a repository's saved entries hold together, against
1295/// `CACHE_REPO_QUOTA_BYTES`. Anyone who can see the repository. Returns
1296/// `Outcome<CacheUsage>`.
1297#[derive(Debug, Serialize, Deserialize)]
1298pub struct CacheUsageArgs {
1299 pub repo: RepoPath,
1300 pub viewer: Viewer,
1301}
1302
1303/// GitHub's shape, with the most the repository may hold.
1304#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1305pub struct CacheUsage {
1306 pub active_caches_size_in_bytes: u64,
1307 pub active_caches_count: u64,
1308 pub quota_bytes: u64,
1309}
1310
1311/// `delete_caches`: deletes one entry by `id`, or every entry whose key is
1312/// exactly `key` (only `git_ref`'s, when given). Needs the Write role.
1313/// Returns the entries deleted: `Outcome<CacheList>`.
1314#[derive(Debug, Serialize, Deserialize)]
1315pub struct DeleteCachesArgs {
1316 pub actor: User,
1317 pub repo: RepoPath,
1318 #[serde(default)]
1319 pub id: Option<u64>,
1320 #[serde(default)]
1321 pub key: Option<String>,
1322 #[serde(default)]
1323 pub git_ref: Option<String>,
1324}
1325
1326// ── Artifacts (actions/upload-artifact) ───────────────────────────────────
1327//
1328// Kept in R2 by the API (the ACTIONS_CACHE bucket, under `a/`) and listed
1329// here, by the actions service, which decides names, sizes and how long
1330// each is kept. A sandbox reaches them with its job's token
1331// (`/actions/jobs/{job}/artifacts…`) or, through the toolkit's protocol,
1332// with its runtime token (`ACTIONS_RUNTIME_TOKEN`); people through the
1333// REST API and the run's page.
1334
1335/// The largest one artifact may be.
1336pub const ARTIFACT_MAX_BYTES: u64 = 5 * 1024 * 1024 * 1024;
1337/// What one run's artifacts may hold together.
1338pub const RUN_ARTIFACTS_MAX_BYTES: u64 = 10 * 1024 * 1024 * 1024;
1339/// How long artifacts are kept unless a repository says otherwise.
1340pub const ARTIFACT_RETENTION_DEFAULT_DAYS: u32 = 14;
1341/// The longest a repository may keep them.
1342pub const ARTIFACT_RETENTION_MAX_DAYS: u32 = 90;
1343/// A native upload is sent in parts of this size (the last may be smaller).
1344pub const ARTIFACT_PART_BYTES: u64 = 32 * 1024 * 1024;
1345
1346/// An artifact, as the API and the site show it.
1347#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1348pub struct Artifact {
1349 pub id: u64,
1350 pub name: String,
1351 pub size: u64,
1352 /// `sha256:<hex>`, when the uploader said.
1353 pub digest: Option<String>,
1354 /// `zip`, or `tgz` for one an older runner sent.
1355 pub format: String,
1356 pub run_id: String,
1357 pub job_id: String,
1358 pub repo_id: String,
1359 /// Whether it has expired or been deleted (its bytes are gone).
1360 pub expired: bool,
1361 pub created_at: String,
1362 pub updated_at: String,
1363 pub expires_at: String,
1364 /// The run's branch and commit, for the REST shape.
1365 #[serde(default)]
1366 pub head_branch: Option<String>,
1367 #[serde(default)]
1368 pub head_sha: Option<String>,
1369}
1370
1371/// An artifact with where its bytes are, and a signed token for them.
1372#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1373pub struct ArtifactBlob {
1374 pub artifact: Artifact,
1375 pub object: String,
1376 /// For the toolkit's blob endpoint (`/actions/toolkit/blobs/{blob}`).
1377 pub blob: String,
1378}
1379
1380/// A page of artifacts, in GitHub's shape.
1381#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1382pub struct ArtifactList {
1383 pub total_count: u64,
1384 pub artifacts: Vec<Artifact>,
1385}
1386
1387/// `artifact_reserve`: a job about to upload an artifact. Refused when its
1388/// run has one of that name and `overwrite` is not set (`conflict`), or it
1389/// is too large. Returns `Outcome<ArtifactReservation>`.
1390#[derive(Debug, Default, Serialize, Deserialize)]
1391pub struct ArtifactReserveArgs {
1392 pub job: String,
1393 /// The job's token, or its runtime token.
1394 pub token: String,
1395 pub name: String,
1396 /// Its size, when known before it is sent (0 otherwise).
1397 #[serde(default)]
1398 pub size: u64,
1399 /// Days to keep it: 0 for the repository's default; at most the
1400 /// repository's setting.
1401 #[serde(default)]
1402 pub retention_days: u32,
1403 /// When to expire it, RFC 3339, as the toolkit says it (in place of
1404 /// `retention_days`).
1405 #[serde(default)]
1406 pub expires_at: Option<String>,
1407 #[serde(default)]
1408 pub overwrite: bool,
1409 /// `zip` (the default) or `tgz`.
1410 #[serde(default)]
1411 pub format: Option<String>,
1412}
1413
1414#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1415pub struct ArtifactReservation {
1416 pub id: u64,
1417 /// Where the API puts it in R2.
1418 pub object: String,
1419 /// The days it will be kept, and until when.
1420 pub retention_days: u32,
1421 pub expires_at: String,
1422}
1423
1424/// `artifact_commit`: its upload is complete, at `size` bytes. The artifact
1425/// is named by `id`, or by `name` in the job's run (the toolkit's way).
1426/// Returns `Outcome<Artifact>`.
1427#[derive(Debug, Default, Serialize, Deserialize)]
1428pub struct ArtifactCommitArgs {
1429 pub job: String,
1430 pub token: String,
1431 #[serde(default)]
1432 pub id: Option<u64>,
1433 #[serde(default)]
1434 pub name: Option<String>,
1435 pub size: u64,
1436 #[serde(default)]
1437 pub digest: Option<String>,
1438}
1439
1440/// `job_artifacts`: a running job listing the artifacts of its own run, or
1441/// of another run of its repository (`run_id`), narrowed by `name` or
1442/// `id`: `Outcome<Vec<Artifact>>`. `job_artifact` gives the one named, with
1443/// a token to download it: `Outcome<ArtifactBlob>`. `job_delete_artifact`
1444/// deletes one of its own run's: `Outcome<Artifact>`. `artifact_abort`
1445/// gives up an upload by `id`: `Outcome<bool>`.
1446#[derive(Debug, Default, Serialize, Deserialize)]
1447pub struct JobArtifactsArgs {
1448 pub job: String,
1449 pub token: String,
1450 #[serde(default)]
1451 pub run_id: Option<String>,
1452 #[serde(default)]
1453 pub name: Option<String>,
1454 #[serde(default)]
1455 pub id: Option<u64>,
1456}
1457
1458/// `artifacts`: a repository's artifacts, newest first, or one run's.
1459/// Anyone who can see the repository. Returns `Outcome<ArtifactList>`.
1460#[derive(Debug, Serialize, Deserialize)]
1461pub struct ArtifactsArgs {
1462 pub repo: RepoPath,
1463 pub viewer: Viewer,
1464 #[serde(default)]
1465 pub run: Option<String>,
1466 #[serde(default)]
1467 pub name: Option<String>,
1468 #[serde(default)]
1469 pub page: Option<u32>,
1470 #[serde(default)]
1471 pub per_page: Option<u32>,
1472}
1473
1474/// `artifact` (`Outcome<Artifact>`) and `artifact_download`
1475/// (`Outcome<ArtifactBlob>`, with a token good for a few minutes): one
1476/// artifact by `id`, or by `name` within `run`. Anyone who can see the
1477/// repository.
1478#[derive(Debug, Serialize, Deserialize)]
1479pub struct ArtifactArgs {
1480 pub repo: RepoPath,
1481 pub viewer: Viewer,
1482 #[serde(default)]
1483 pub id: Option<u64>,
1484 #[serde(default)]
1485 pub run: Option<String>,
1486 #[serde(default)]
1487 pub name: Option<String>,
1488}
1489
1490/// `delete_artifact`: needs the Write role. Returns `Outcome<Artifact>`.
1491#[derive(Debug, Serialize, Deserialize)]
1492pub struct DeleteArtifactArgs {
1493 pub actor: User,
1494 pub repo: RepoPath,
1495 pub id: u64,
1496}
1497
1498/// `artifact_retention`: anyone who can see the repository. With `days`,
1499/// sets it, which needs the Maintain role. Returns
1500/// `Outcome<ArtifactRetention>`.
1501#[derive(Debug, Serialize, Deserialize)]
1502pub struct ArtifactRetentionArgs {
1503 pub repo: RepoPath,
1504 pub viewer: Viewer,
1505 #[serde(default)]
1506 pub days: Option<u32>,
1507}
1508
1509/// GitHub's shape: the days artifacts are kept by default, and the most a
1510/// repository may choose.
1511#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1512pub struct ArtifactRetention {
1513 pub days: u32,
1514 pub maximum_allowed_days: u32,
1515}
1516
1517// ── The toolkit's protocols ───────────────────────────────────────────────
1518//
1519// Actions built on GitHub's toolkit (`@actions/cache`, `@actions/artifact`,
1520// `@actions/core`'s `getIDToken`) reach g1t with the job's runtime token,
1521// `ACTIONS_RUNTIME_TOKEN`: a JSON Web Token whose `scp` names the run and
1522// job, signed with a key derived from the job's own token, so the actions
1523// service checks it without keeping another secret. Cache and artifact
1524// operations above take it in place of the job's token.
1525
1526/// `runtime_auth`: which job a runtime token is, while it runs:
1527/// `Outcome<RuntimeJob>`. `oidc_claims` takes the same and returns
1528/// `Outcome<Value>`: the claims of the job's OIDC token, less `iss`, `aud`,
1529/// `jti` and the times, or `forbidden` when the job's `permissions` do not
1530/// give it `id-token: write`.
1531#[derive(Debug, Serialize, Deserialize)]
1532pub struct RuntimeAuthArgs {
1533 pub job: String,
1534 pub token: String,
1535}
1536
1537#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1538pub struct RuntimeJob {
1539 pub job: String,
1540 pub run: String,
1541 pub repo_id: String,
1542 pub namespace: String,
1543 /// `owner/name`.
1544 pub repository: String,
1545}
1546
1547/// What a signed blob token lets its holder do.
1548#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1549pub struct BlobGrant {
1550 /// `cache` or `artifact`.
1551 pub kind: String,
1552 /// The entry's id: a cache entry's `cache_…`, an artifact's number.
1553 pub id: String,
1554 pub object: String,
1555 /// The R2 upload it sends parts to; `None` for a download.
1556 pub upload: Option<String>,
1557 /// For a download: what to call the file, and its type.
1558 #[serde(default)]
1559 pub filename: Option<String>,
1560 #[serde(default)]
1561 pub content_type: Option<String>,
1562}
1563
1564/// `blob_sign`: a token for uploading an entry the job reserved, to the R2
1565/// upload the API started for it. Returns `Outcome<String>`.
1566#[derive(Debug, Serialize, Deserialize)]
1567pub struct BlobSignArgs {
1568 pub job: String,
1569 pub token: String,
1570 /// `cache` or `artifact`.
1571 pub kind: String,
1572 pub id: String,
1573 pub upload: String,
1574}
1575
1576/// `blob_open`: what a signed token grants, while it is good and its entry
1577/// is there: `Outcome<BlobGrant>`. `blob_part` records a part sent with an
1578/// upload token (`part`, `etag`, `size`): `Outcome<bool>`. `blob_parts`
1579/// gives the parts recorded, in order: `Outcome<Vec<BlobPart>>`, and
1580/// `blob_done` forgets them: `Outcome<bool>`.
1581#[derive(Debug, Default, Serialize, Deserialize)]
1582pub struct BlobArgs {
1583 pub blob: String,
1584 #[serde(default)]
1585 pub part: u32,
1586 #[serde(default)]
1587 pub etag: String,
1588 #[serde(default)]
1589 pub size: u64,
1590}
1591
1592#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1593pub struct BlobPart {
1594 pub part: u32,
1595 pub etag: String,
1596 pub size: u64,
1597}
1598
1599#[cfg(test)]
1600mod instance_tests {
1601 use super::*;
1602
1603 fn labels(given: &[&str]) -> Vec<String> {
1604 given.iter().map(|l| (*l).to_owned()).collect()
1605 }
1606
1607 #[test]
1608 fn runs_on_picks_the_machine() {
1609 assert_eq!(instance_for(&labels(&["ubuntu-latest"])), STANDARD_INSTANCE);
1610 assert_eq!(instance_for(&labels(&[])).label, "standard");
1611 assert_eq!(instance_for(&labels(&["g1t-4core"])).container, "standard-4");
1612 assert_eq!(instance_for(&labels(&["ubuntu-latest", "G1T-2Core"])).label, "g1t-2core");
1613 // Both named: the larger.
1614 assert_eq!(instance_for(&labels(&["g1t-2core", "g1t-4core"])).label, "g1t-4core");
1615 assert_eq!(instance_named("g1t-4core").map(|i| i.vcpu), Some(4.0));
1616 assert_eq!(instance_named("standard"), Some(STANDARD_INSTANCE));
1617 assert_eq!(instance_named("g1t-64core"), None);
1618 }
1619
1620 #[test]
1621 fn every_machine_has_the_most_disk_containers_allow_and_fits_a_custom_type() {
1622 for instance in INSTANCE_TYPES {
1623 assert_eq!(instance.disk_gb, 20.0, "{}", instance.label);
1624 // Custom instance types: 1 to 4 vCPU, at most 12 GiB, at least
1625 // 3 GiB a vCPU.
1626 assert!((1.0..=4.0).contains(&instance.vcpu), "{}", instance.label);
1627 assert!(instance.memory_gib <= 12.0 && instance.memory_gib >= 3.0 * instance.vcpu, "{}", instance.label);
1628 }
1629 }
1630
1631 #[test]
1632 fn price_scales_follow_the_list_prices_with_every_vcpu_busy() {
1633 // Memory, disk and vCPU at Cloudflare's list prices, and the
1634 // Durable Object behind the container (128 MB), in micros a second.
1635 let busy = |i: &InstanceType| i.memory_gib * 2.5 + i.disk_gb * 0.07 + i.vcpu * 20.0 + 0.125 * 12.5;
1636 for instance in INSTANCE_TYPES {
1637 let scale = busy(&instance) / busy(&STANDARD_INSTANCE);
1638 assert!((instance.price_scale - (scale * 10.0).round() / 10.0).abs() < 1e-9, "{}: {scale}", instance.label);
1639 }
1640 }
1641
1642 #[test]
1643 fn start_args_from_older_callers_read() {
1644 let args: StartJobArgs = serde_json::from_value(serde_json::json!({
1645 "job": "job_1", "token": "t", "repo": { "namespace": "acme", "name": "web" }, "timeoutMinutes": 30
1646 }))
1647 .unwrap();
1648 assert!(args.workflow.is_none() && args.environment.is_none() && !args.trusted && args.instance.is_none());
1649 }
1650}
1651
1652#[cfg(test)]
1653mod setting_args_tests {
1654 use super::*;
1655
1656 #[test]
1657 fn who_reads_a_row_is_read_as_the_site_and_api_send_it() {
1658 let args: SetSettingArgs = serde_json::from_value(serde_json::json!({
1659 "actor": { "id": "usr_1", "username": "a" },
1660 "repo": { "namespace": "acme", "name": "web" },
1661 "kind": "secret",
1662 "name": "STRIPE_KEY",
1663 "availableTo": ["deployments"],
1664 "environments": ["production"],
1665 }))
1666 .unwrap();
1667 assert_eq!(args.available_to, Some(vec!["deployments".to_owned()]));
1668 }
1669}