Skip to content
920 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, or what stopped it.
127 pub reason: Option<String>,
128 pub started_at: Option<String>,
129 pub finished_at: Option<String>,
130 /// Its `runs-on` names self-hosted runners (see `runners`).
131 #[serde(default)]
132 pub self_hosted: bool,
133 /// The self-hosted runner that took it, by name.
134 #[serde(default)]
135 pub runner: Option<String>,
136}
137
138#[derive(Clone, Debug, Serialize, Deserialize)]
139#[serde(rename_all = "camelCase")]
140pub struct RunDetail {
141 pub run: WorkflowRun,
142 pub jobs: Vec<Job>,
143 /// The workflow's notes, as of the run's commit.
144 pub notes: Vec<WorkflowNote>,
145}
146
147#[derive(Clone, Debug, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct LogChunk {
150 pub seq: u64,
151 /// The step it belongs to, from 1; 0 for the job's setup.
152 pub step: u32,
153 pub text: String,
154}
155
156#[derive(Clone, Debug, Serialize, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct JobLog {
159 pub chunks: Vec<LogChunk>,
160 /// Whether the job has finished, so no more will come.
161 pub done: bool,
162}
163
164/// Who may read a secret or variable: workflows (`secrets.*` and `vars.*`
165/// in GitHub Actions) and deployments (a deploy build's environment and the
166/// running app's bindings). Agents, checks and the merge queue read none.
167pub const CONSUMERS: [&str; 2] = ["workflows", "deployments"];
168
169/// One row of a repository's or workspace's secrets and variables, as
170/// Vercel lists environment variables: a key, its type, the environments
171/// it applies to and who reads it. A key may have one row per environment.
172/// Secrets' values are never returned.
173#[derive(Clone, Debug, Serialize, Deserialize)]
174#[serde(rename_all = "camelCase")]
175pub struct Setting {
176 #[serde(default)]
177 pub id: String,
178 pub name: String,
179 /// `secret`, or `variable` (shown as Config).
180 #[serde(default)]
181 pub kind: String,
182 /// A variable's value; secrets' are never returned.
183 pub value: Option<String>,
184 /// `project` (a repository's, which belong to its project) or
185 /// `workspace`.
186 pub scope: String,
187 pub updated_at: String,
188 /// `workflows` and/or `deployments`.
189 #[serde(default)]
190 pub available_to: Vec<String>,
191 /// The environments it applies to; empty is every environment.
192 #[serde(default)]
193 pub environments: Vec<String>,
194 /// A workspace's row: the projects it reaches, by slug; empty is every
195 /// project.
196 #[serde(default)]
197 pub projects: Vec<String>,
198 #[serde(default)]
199 pub note: Option<String>,
200 #[serde(default)]
201 pub updated_by: Option<String>,
202}
203
204// --- Methods ---------------------------------------------------------------
205
206/// `workflows`. Returns `Outcome<Vec<Workflow>>`.
207#[derive(Debug, Serialize, Deserialize)]
208pub struct WorkflowsArgs {
209 pub repo: RepoPath,
210 pub viewer: Viewer,
211}
212
213/// `runs`: newest first. Returns `Outcome<Vec<WorkflowRun>>`.
214#[derive(Debug, Serialize, Deserialize)]
215pub struct RunsArgs {
216 pub repo: RepoPath,
217 pub viewer: Viewer,
218 /// A workflow's id or file name.
219 #[serde(default)]
220 pub workflow: Option<String>,
221 #[serde(default)]
222 pub branch: Option<String>,
223 #[serde(default)]
224 pub event: Option<String>,
225 /// The pull request's number.
226 #[serde(default)]
227 pub pull: Option<u32>,
228 #[serde(default)]
229 pub sha: Option<String>,
230 #[serde(default)]
231 pub limit: Option<u32>,
232}
233
234/// `run`. Returns `Outcome<RunDetail>`.
235#[derive(Debug, Serialize, Deserialize)]
236pub struct RunArgs {
237 pub repo: RepoPath,
238 pub viewer: Viewer,
239 pub id: String,
240}
241
242/// `logs`: a job's log after `after`. Returns `Outcome<JobLog>`.
243#[derive(Debug, Serialize, Deserialize)]
244pub struct LogsArgs {
245 pub repo: RepoPath,
246 pub viewer: Viewer,
247 pub job: String,
248 #[serde(default)]
249 pub after: u64,
250}
251
252/// `dispatch`: run a workflow that has `workflow_dispatch`. Members only.
253/// Returns `Outcome<WorkflowRun>`.
254#[derive(Debug, Serialize, Deserialize)]
255pub struct DispatchArgs {
256 pub actor: User,
257 pub repo: RepoPath,
258 /// A workflow's id or file name.
259 pub workflow: String,
260 /// A branch or tag; the default branch when absent.
261 #[serde(default, rename = "ref")]
262 pub git_ref: Option<String>,
263 #[serde(default)]
264 pub inputs: serde_json::Map<String, Value>,
265}
266
267/// `cancel` and `rerun` (all jobs, or with `failed_only` the ones that did
268/// not succeed). Members only. Returns `Outcome<WorkflowRun>`.
269#[derive(Debug, Serialize, Deserialize)]
270pub struct RunActionArgs {
271 pub actor: User,
272 pub repo: RepoPath,
273 pub id: String,
274 #[serde(default)]
275 pub failed_only: bool,
276}
277
278/// `set_workflow_enabled`. Members only. Returns `Outcome<Workflow>`.
279#[derive(Debug, Serialize, Deserialize)]
280pub struct SetWorkflowEnabledArgs {
281 pub actor: User,
282 pub repo: RepoPath,
283 pub workflow: String,
284 pub enabled: bool,
285}
286
287/// Whose secrets or variables: a repository's, or with only `workspace`,
288/// a workspace's.
289#[derive(Clone, Debug, Serialize, Deserialize)]
290pub struct SettingsOwner {
291 #[serde(default)]
292 pub repo: Option<RepoPath>,
293 #[serde(default)]
294 pub workspace: Option<String>,
295}
296
297/// `settings`: the secrets (`kind: secret`) or variables (`kind: variable`)
298/// of a repository, with its workspace's, or of a workspace. Members only.
299/// Returns `Outcome<Vec<Setting>>`.
300#[derive(Debug, Serialize, Deserialize)]
301pub struct SettingsArgs {
302 pub actor: User,
303 #[serde(flatten)]
304 pub owner: SettingsOwner,
305 pub kind: String,
306}
307
308/// `set_setting`: add or replace one. A repository's need a member; a
309/// workspace's an owner. Returns `Outcome<Setting>`.
310#[derive(Debug, Serialize, Deserialize)]
311pub struct SetSettingArgs {
312 pub actor: User,
313 #[serde(flatten)]
314 pub owner: SettingsOwner,
315 /// `secret` or `variable`. Changing a variable's row to `secret` seals
316 /// it; a secret cannot become a variable.
317 pub kind: String,
318 pub name: String,
319 /// The row to change. Left out, the key's row for every environment, as
320 /// GitHub's API addresses a secret by name alone.
321 #[serde(default)]
322 pub id: Option<String>,
323 /// Needed for a new row; left out, an existing row keeps its value.
324 #[serde(default)]
325 pub value: Option<String>,
326 /// `workflows` and/or `deployments`; left out, unchanged (both, for a
327 /// new row).
328 // Named as callers send it: an `alias` is not honoured beside the
329 // flattened owner in the Worker's build.
330 #[serde(default, rename = "availableTo")]
331 pub available_to: Option<Vec<String>>,
332 /// The environments it applies to; empty is every one. Left out,
333 /// unchanged.
334 #[serde(default)]
335 pub environments: Option<Vec<String>>,
336 /// A workspace's row: project slugs; empty for every one.
337 #[serde(default)]
338 pub projects: Option<Vec<String>>,
339 #[serde(default)]
340 pub note: Option<String>,
341}
342
343/// `resolve_settings`: the secrets and variables one reader gets, for the
344/// services that hand them out (the deployments service). Returns
345/// `ResolvedSettings`.
346#[derive(Debug, Serialize, Deserialize)]
347#[serde(rename_all = "camelCase")]
348pub struct ResolveSettingsArgs {
349 pub repo_id: String,
350 pub repo: RepoPath,
351 /// The project being read for; its repository's primary project if left
352 /// out.
353 #[serde(default)]
354 pub project_id: Option<String>,
355 #[serde(default)]
356 pub project_slug: Option<String>,
357 /// `workflows` or `deployments`.
358 pub consumer: String,
359 /// The environment being read for, such as `production` or `preview`.
360 #[serde(default)]
361 pub environment: Option<String>,
362 /// Whether the run is trusted; an untrusted one gets no secrets.
363 pub trusted: bool,
364}
365
366#[derive(Debug, Default, Serialize, Deserialize)]
367pub struct ResolvedSettings {
368 pub secrets: serde_json::Map<String, serde_json::Value>,
369 pub variables: serde_json::Map<String, serde_json::Value>,
370}
371
372/// `delete_setting`. Returns `Outcome<bool>`.
373#[derive(Debug, Serialize, Deserialize)]
374pub struct DeleteSettingArgs {
375 pub actor: User,
376 #[serde(flatten)]
377 pub owner: SettingsOwner,
378 pub kind: String,
379 pub name: String,
380 /// One row; left out, every row of the key.
381 #[serde(default)]
382 pub id: Option<String>,
383}
384
385/// `job_spec` and `job_report`: the sandbox running a job, with the job's
386/// own token. `report` is one of:
387/// `{"kind": "step", "number", "status", "conclusion"}`,
388/// `{"kind": "log", "step", "text"}`,
389/// `{"kind": "annotation", "level", "message", "title", "file", "line"}`,
390/// `{"kind": "done", "conclusion", "outputs", "reason"}`.
391#[derive(Debug, Serialize, Deserialize)]
392pub struct JobCallArgs {
393 pub job: String,
394 pub token: String,
395 #[serde(default)]
396 pub report: Value,
397}
398
399/// What the runner needs to start a job's sandbox.
400#[derive(Debug, Serialize, Deserialize)]
401#[serde(rename_all = "camelCase")]
402pub struct StartJobArgs {
403 pub job: String,
404 pub token: String,
405 pub repo: RepoPath,
406 /// Minutes before the job is stopped.
407 pub timeout_minutes: u32,
408 /// The workflow file the job is in (`.g1t/workflows/deploy.yml`), for
409 /// the guardrails' workflow-only domains.
410 #[serde(default)]
411 pub workflow: Option<String>,
412 /// The environment the job names with `environment:`, when it names
413 /// one plainly (not with an expression).
414 #[serde(default)]
415 pub environment: Option<String>,
416 /// Whether its run is trusted: not a pull request from a fork. Only a
417 /// trusted run's jobs reach workflow-only domains.
418 #[serde(default)]
419 pub trusted: bool,
420 /// The machine its `runs-on` asked for, by label (`instance_for`):
421 /// `g1t-2core` or `g1t-4core`; absent, the standard one.
422 #[serde(default)]
423 pub instance: Option<String>,
424}
425
426/// A size of machine g1t runs workflow jobs on, asked for by a label in
427/// `runs-on`. Each is a Cloudflare Containers instance type; it costs what
428/// that instance costs g1t, plus the margin, like any sandbox time.
429#[derive(Clone, Copy, Debug, PartialEq)]
430pub struct InstanceType {
431 /// The `runs-on` label, or `standard` for the default.
432 pub label: &'static str,
433 /// The Containers instance type.
434 pub container: &'static str,
435 pub vcpu: f64,
436 pub memory_gib: f64,
437 pub disk_gb: f64,
438 /// What a second of it costs g1t as a multiple of the standard
439 /// machine's, with its vCPUs as busy (Cloudflare's list prices:
440 /// memory $0.0000025 a GiB-second, disk $0.00000007 a GB-second, vCPU
441 /// $0.00002 a second). Used to reserve before a job starts, and to
442 /// price a job that did not report its own CPU.
443 pub price_scale: f64,
444}
445
446/// The default: what `ubuntu-latest` and every other hosted label get.
447pub const STANDARD_INSTANCE: InstanceType =
448 InstanceType { label: "standard", container: "standard-1", vcpu: 0.5, memory_gib: 4.0, disk_gb: 8.0, price_scale: 1.0 };
449
450/// Every machine a workflow job can ask for, the default first.
451pub const INSTANCE_TYPES: [InstanceType; 3] = [
452 STANDARD_INSTANCE,
453 InstanceType { label: "g1t-2core", container: "standard-3", vcpu: 2.0, memory_gib: 8.0, disk_gb: 16.0, price_scale: 2.8 },
454 InstanceType { label: "g1t-4core", container: "standard-4", vcpu: 4.0, memory_gib: 12.0, disk_gb: 20.0, price_scale: 5.1 },
455];
456
457/// The machine a job's `runs-on` labels ask for: the largest named, or the
458/// standard one. Labels compare without regard to case.
459pub fn instance_for(labels: &[String]) -> InstanceType {
460 INSTANCE_TYPES
461 .iter()
462 .rev()
463 .find(|instance| instance.label != STANDARD_INSTANCE.label && labels.iter().any(|label| label.trim().eq_ignore_ascii_case(instance.label)))
464 .copied()
465 .unwrap_or(STANDARD_INSTANCE)
466}
467
468/// An instance type by its label, if it is one.
469pub fn instance_named(label: &str) -> Option<InstanceType> {
470 INSTANCE_TYPES.iter().find(|instance| instance.label.eq_ignore_ascii_case(label.trim())).copied()
471}
472
473// ── The cache (actions/cache) ─────────────────────────────────────────────
474//
475// Entries are kept in R2 by the API (the ACTIONS_CACHE bucket) and listed
476// here, by the actions service, which decides what is found, what fits and
477// what is evicted. A sandbox reaches these through the API with its job's
478// token: `/actions/jobs/{job}/cache` (see apps/api/src/blobs.rs).
479
480/// The largest one cache entry may be, compressed.
481pub const CACHE_MAX_ENTRY_BYTES: u64 = 2 * 1024 * 1024 * 1024;
482/// What one repository's entries may hold together. Saving past it evicts
483/// the entries restored longest ago.
484pub const CACHE_REPO_QUOTA_BYTES: u64 = 10 * 1024 * 1024 * 1024;
485/// An entry not restored for this long is deleted.
486pub const CACHE_UNUSED_DAYS: u64 = 7;
487/// An entry is deleted this long after it was saved, however often it is
488/// restored (the bucket's own lifecycle rule deletes objects at 30 days).
489pub const CACHE_MAX_AGE_DAYS: u64 = 28;
490/// An upload is sent in parts of this size (the last may be smaller).
491pub const CACHE_PART_BYTES: u64 = 32 * 1024 * 1024;
492/// What R2 charges g1t to store a GB for a month, in millionths of a
493/// dollar ($0.015): what the cache's storage is charged at, plus the margin.
494pub const CACHE_MICROS_PER_GB_MONTH: i64 = 15_000;
495
496/// `cache_lookup`: the entry a job restores: its key exactly, else the
497/// newest whose key starts with one of `restore`, in order.
498/// Returns `Outcome<Option<CacheHit>>`.
499#[derive(Debug, Serialize, Deserialize)]
500pub struct CacheLookupArgs {
501 pub job: String,
502 pub token: String,
503 pub key: String,
504 #[serde(default)]
505 pub restore: Vec<String>,
506 /// The toolkit's version of the entry (a hash of its paths and
507 /// compression): only an entry of the same version is found. `None`
508 /// for g1t's own `actions/cache`, whose entries have none.
509 #[serde(default)]
510 pub version: Option<String>,
511}
512
513#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
514pub struct CacheHit {
515 pub key: String,
516 pub object: String,
517 pub size: u64,
518 /// When it was saved, RFC 3339.
519 #[serde(default)]
520 pub created_at: String,
521 /// A signed token for downloading it through the toolkit's blob
522 /// endpoint, when the lookup came with a version.
523 #[serde(default)]
524 pub blob: Option<String>,
525}
526
527/// `cache_reserve`: a job about to save `size` bytes under `key`. Refused
528/// when the key is taken (`conflict`: keys are written once) or the entry
529/// is too large. Returns `Outcome<CacheReservation>`.
530#[derive(Debug, Serialize, Deserialize)]
531pub struct CacheReserveArgs {
532 pub job: String,
533 pub token: String,
534 pub key: String,
535 /// Its size, when known before it is sent (the toolkit's newer client
536 /// says only when it finishes: 0 then).
537 pub size: u64,
538 #[serde(default)]
539 pub version: Option<String>,
540}
541
542#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
543pub struct CacheReservation {
544 pub id: String,
545 /// Where the API puts it in R2.
546 pub object: String,
547 /// The entry's number, which the toolkit's older protocol names it by.
548 #[serde(default)]
549 pub number: u64,
550 /// Its R2 upload, once one is started.
551 #[serde(default)]
552 pub upload: Option<String>,
553 /// A signed token for sending its parts through the toolkit's blob
554 /// endpoint, once its upload is started.
555 #[serde(default)]
556 pub blob: Option<String>,
557}
558
559/// `cache_upload`: an entry a job is still uploading, by its number or by
560/// key and version. Returns `Outcome<CacheReservation>`, with `upload` and
561/// `blob` set once its upload has been started.
562#[derive(Debug, Serialize, Deserialize)]
563pub struct CacheUploadArgs {
564 pub job: String,
565 pub token: String,
566 #[serde(default)]
567 pub number: Option<u64>,
568 #[serde(default)]
569 pub key: Option<String>,
570 #[serde(default)]
571 pub version: Option<String>,
572}
573
574/// `cache_commit`: the upload of `id` is complete, at `size` bytes. Returns
575/// `Outcome<CacheCommitted>`: the objects of entries it evicted, which the
576/// API deletes from R2.
577#[derive(Debug, Serialize, Deserialize)]
578pub struct CacheCommitArgs {
579 pub job: String,
580 pub token: String,
581 pub id: String,
582 pub size: u64,
583}
584
585#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
586pub struct CacheCommitted {
587 pub evicted: Vec<String>,
588}
589
590/// `cache_abort`: an upload that will not finish; its reservation goes.
591/// Returns `Outcome<bool>`.
592#[derive(Debug, Serialize, Deserialize)]
593pub struct CacheAbortArgs {
594 pub job: String,
595 pub token: String,
596 pub id: String,
597}
598
599// ── Artifacts (actions/upload-artifact) ───────────────────────────────────
600//
601// Kept in R2 by the API (the ACTIONS_CACHE bucket, under `a/`) and listed
602// here, by the actions service, which decides names, sizes and how long
603// each is kept. A sandbox reaches them with its job's token
604// (`/actions/jobs/{job}/artifacts…`) or, through the toolkit's protocol,
605// with its runtime token (`ACTIONS_RUNTIME_TOKEN`); people through the
606// REST API and the run's page.
607
608/// The largest one artifact may be.
609pub const ARTIFACT_MAX_BYTES: u64 = 5 * 1024 * 1024 * 1024;
610/// What one run's artifacts may hold together.
611pub const RUN_ARTIFACTS_MAX_BYTES: u64 = 10 * 1024 * 1024 * 1024;
612/// How long artifacts are kept unless a repository says otherwise.
613pub const ARTIFACT_RETENTION_DEFAULT_DAYS: u32 = 14;
614/// The longest a repository may keep them.
615pub const ARTIFACT_RETENTION_MAX_DAYS: u32 = 90;
616/// A native upload is sent in parts of this size (the last may be smaller).
617pub const ARTIFACT_PART_BYTES: u64 = 32 * 1024 * 1024;
618
619/// An artifact, as the API and the site show it.
620#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
621pub struct Artifact {
622 pub id: u64,
623 pub name: String,
624 pub size: u64,
625 /// `sha256:<hex>`, when the uploader said.
626 pub digest: Option<String>,
627 /// `zip`, or `tgz` for one an older runner sent.
628 pub format: String,
629 pub run_id: String,
630 pub job_id: String,
631 pub repo_id: String,
632 /// Whether it has expired or been deleted (its bytes are gone).
633 pub expired: bool,
634 pub created_at: String,
635 pub updated_at: String,
636 pub expires_at: String,
637 /// The run's branch and commit, for the REST shape.
638 #[serde(default)]
639 pub head_branch: Option<String>,
640 #[serde(default)]
641 pub head_sha: Option<String>,
642}
643
644/// An artifact with where its bytes are, and a signed token for them.
645#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
646pub struct ArtifactBlob {
647 pub artifact: Artifact,
648 pub object: String,
649 /// For the toolkit's blob endpoint (`/actions/toolkit/blobs/{blob}`).
650 pub blob: String,
651}
652
653/// A page of artifacts, in GitHub's shape.
654#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
655pub struct ArtifactList {
656 pub total_count: u64,
657 pub artifacts: Vec<Artifact>,
658}
659
660/// `artifact_reserve`: a job about to upload an artifact. Refused when its
661/// run has one of that name and `overwrite` is not set (`conflict`), or it
662/// is too large. Returns `Outcome<ArtifactReservation>`.
663#[derive(Debug, Default, Serialize, Deserialize)]
664pub struct ArtifactReserveArgs {
665 pub job: String,
666 /// The job's token, or its runtime token.
667 pub token: String,
668 pub name: String,
669 /// Its size, when known before it is sent (0 otherwise).
670 #[serde(default)]
671 pub size: u64,
672 /// Days to keep it: 0 for the repository's default; at most the
673 /// repository's setting.
674 #[serde(default)]
675 pub retention_days: u32,
676 /// When to expire it, RFC 3339, as the toolkit says it (in place of
677 /// `retention_days`).
678 #[serde(default)]
679 pub expires_at: Option<String>,
680 #[serde(default)]
681 pub overwrite: bool,
682 /// `zip` (the default) or `tgz`.
683 #[serde(default)]
684 pub format: Option<String>,
685}
686
687#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
688pub struct ArtifactReservation {
689 pub id: u64,
690 /// Where the API puts it in R2.
691 pub object: String,
692 /// The days it will be kept, and until when.
693 pub retention_days: u32,
694 pub expires_at: String,
695}
696
697/// `artifact_commit`: its upload is complete, at `size` bytes. The artifact
698/// is named by `id`, or by `name` in the job's run (the toolkit's way).
699/// Returns `Outcome<Artifact>`.
700#[derive(Debug, Default, Serialize, Deserialize)]
701pub struct ArtifactCommitArgs {
702 pub job: String,
703 pub token: String,
704 #[serde(default)]
705 pub id: Option<u64>,
706 #[serde(default)]
707 pub name: Option<String>,
708 pub size: u64,
709 #[serde(default)]
710 pub digest: Option<String>,
711}
712
713/// `job_artifacts`: a running job listing the artifacts of its own run, or
714/// of another run of its repository (`run_id`), narrowed by `name` or
715/// `id`: `Outcome<Vec<Artifact>>`. `job_artifact` gives the one named, with
716/// a token to download it: `Outcome<ArtifactBlob>`. `job_delete_artifact`
717/// deletes one of its own run's: `Outcome<Artifact>`. `artifact_abort`
718/// gives up an upload by `id`: `Outcome<bool>`.
719#[derive(Debug, Default, Serialize, Deserialize)]
720pub struct JobArtifactsArgs {
721 pub job: String,
722 pub token: String,
723 #[serde(default)]
724 pub run_id: Option<String>,
725 #[serde(default)]
726 pub name: Option<String>,
727 #[serde(default)]
728 pub id: Option<u64>,
729}
730
731/// `artifacts`: a repository's artifacts, newest first, or one run's.
732/// Anyone who can see the repository. Returns `Outcome<ArtifactList>`.
733#[derive(Debug, Serialize, Deserialize)]
734pub struct ArtifactsArgs {
735 pub repo: RepoPath,
736 pub viewer: Viewer,
737 #[serde(default)]
738 pub run: Option<String>,
739 #[serde(default)]
740 pub name: Option<String>,
741 #[serde(default)]
742 pub page: Option<u32>,
743 #[serde(default)]
744 pub per_page: Option<u32>,
745}
746
747/// `artifact` (`Outcome<Artifact>`) and `artifact_download`
748/// (`Outcome<ArtifactBlob>`, with a token good for a few minutes): one
749/// artifact by `id`, or by `name` within `run`. Anyone who can see the
750/// repository.
751#[derive(Debug, Serialize, Deserialize)]
752pub struct ArtifactArgs {
753 pub repo: RepoPath,
754 pub viewer: Viewer,
755 #[serde(default)]
756 pub id: Option<u64>,
757 #[serde(default)]
758 pub run: Option<String>,
759 #[serde(default)]
760 pub name: Option<String>,
761}
762
763/// `delete_artifact`: needs the Write role. Returns `Outcome<Artifact>`.
764#[derive(Debug, Serialize, Deserialize)]
765pub struct DeleteArtifactArgs {
766 pub actor: User,
767 pub repo: RepoPath,
768 pub id: u64,
769}
770
771/// `artifact_retention`: anyone who can see the repository. With `days`,
772/// sets it, which needs the Maintain role. Returns
773/// `Outcome<ArtifactRetention>`.
774#[derive(Debug, Serialize, Deserialize)]
775pub struct ArtifactRetentionArgs {
776 pub repo: RepoPath,
777 pub viewer: Viewer,
778 #[serde(default)]
779 pub days: Option<u32>,
780}
781
782/// GitHub's shape: the days artifacts are kept by default, and the most a
783/// repository may choose.
784#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
785pub struct ArtifactRetention {
786 pub days: u32,
787 pub maximum_allowed_days: u32,
788}
789
790// ── The toolkit's protocols ───────────────────────────────────────────────
791//
792// Actions built on GitHub's toolkit (`@actions/cache`, `@actions/artifact`,
793// `@actions/core`'s `getIDToken`) reach g1t with the job's runtime token,
794// `ACTIONS_RUNTIME_TOKEN`: a JSON Web Token whose `scp` names the run and
795// job, signed with a key derived from the job's own token, so the actions
796// service checks it without keeping another secret. Cache and artifact
797// operations above take it in place of the job's token.
798
799/// `runtime_auth`: which job a runtime token is, while it runs:
800/// `Outcome<RuntimeJob>`. `oidc_claims` takes the same and returns
801/// `Outcome<Value>`: the claims of the job's OIDC token, less `iss`, `aud`,
802/// `jti` and the times, or `forbidden` when the job's `permissions` do not
803/// give it `id-token: write`.
804#[derive(Debug, Serialize, Deserialize)]
805pub struct RuntimeAuthArgs {
806 pub job: String,
807 pub token: String,
808}
809
810#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
811pub struct RuntimeJob {
812 pub job: String,
813 pub run: String,
814 pub repo_id: String,
815 pub namespace: String,
816 /// `owner/name`.
817 pub repository: String,
818}
819
820/// What a signed blob token lets its holder do.
821#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
822pub struct BlobGrant {
823 /// `cache` or `artifact`.
824 pub kind: String,
825 /// The entry's id: a cache entry's `cache_…`, an artifact's number.
826 pub id: String,
827 pub object: String,
828 /// The R2 upload it sends parts to; `None` for a download.
829 pub upload: Option<String>,
830 /// For a download: what to call the file, and its type.
831 #[serde(default)]
832 pub filename: Option<String>,
833 #[serde(default)]
834 pub content_type: Option<String>,
835}
836
837/// `blob_sign`: a token for uploading an entry the job reserved, to the R2
838/// upload the API started for it. Returns `Outcome<String>`.
839#[derive(Debug, Serialize, Deserialize)]
840pub struct BlobSignArgs {
841 pub job: String,
842 pub token: String,
843 /// `cache` or `artifact`.
844 pub kind: String,
845 pub id: String,
846 pub upload: String,
847}
848
849/// `blob_open`: what a signed token grants, while it is good and its entry
850/// is there: `Outcome<BlobGrant>`. `blob_part` records a part sent with an
851/// upload token (`part`, `etag`, `size`): `Outcome<bool>`. `blob_parts`
852/// gives the parts recorded, in order: `Outcome<Vec<BlobPart>>`, and
853/// `blob_done` forgets them: `Outcome<bool>`.
854#[derive(Debug, Default, Serialize, Deserialize)]
855pub struct BlobArgs {
856 pub blob: String,
857 #[serde(default)]
858 pub part: u32,
859 #[serde(default)]
860 pub etag: String,
861 #[serde(default)]
862 pub size: u64,
863}
864
865#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
866pub struct BlobPart {
867 pub part: u32,
868 pub etag: String,
869 pub size: u64,
870}
871
872#[cfg(test)]
873mod instance_tests {
874 use super::*;
875
876 fn labels(given: &[&str]) -> Vec<String> {
877 given.iter().map(|l| (*l).to_owned()).collect()
878 }
879
880 #[test]
881 fn runs_on_picks_the_machine() {
882 assert_eq!(instance_for(&labels(&["ubuntu-latest"])).container, "standard-1");
883 assert_eq!(instance_for(&labels(&[])).label, "standard");
884 assert_eq!(instance_for(&labels(&["g1t-4core"])).container, "standard-4");
885 assert_eq!(instance_for(&labels(&["ubuntu-latest", "G1T-2Core"])).container, "standard-3");
886 // Both named: the larger.
887 assert_eq!(instance_for(&labels(&["g1t-2core", "g1t-4core"])).label, "g1t-4core");
888 assert_eq!(instance_named("g1t-4core").map(|i| i.vcpu), Some(4.0));
889 assert_eq!(instance_named("standard"), Some(STANDARD_INSTANCE));
890 assert_eq!(instance_named("g1t-64core"), None);
891 }
892
893 #[test]
894 fn start_args_from_older_callers_read() {
895 let args: StartJobArgs = serde_json::from_value(serde_json::json!({
896 "job": "job_1", "token": "t", "repo": { "namespace": "acme", "name": "web" }, "timeoutMinutes": 30
897 }))
898 .unwrap();
899 assert!(args.workflow.is_none() && args.environment.is_none() && !args.trusted && args.instance.is_none());
900 }
901}
902
903#[cfg(test)]
904mod setting_args_tests {
905 use super::*;
906
907 #[test]
908 fn who_reads_a_row_is_read_as_the_site_and_api_send_it() {
909 let args: SetSettingArgs = serde_json::from_value(serde_json::json!({
910 "actor": { "id": "usr_1", "username": "a" },
911 "repo": { "namespace": "acme", "name": "web" },
912 "kind": "secret",
913 "name": "STRIPE_KEY",
914 "availableTo": ["deployments"],
915 "environments": ["production"],
916 }))
917 .unwrap();
918 assert_eq!(args.available_to, Some(vec!["deployments".to_owned()]));
919 }
920}