pr_01m47d15m3e54sn21z27rpy5n9/crates/contracts/src/actions.rs

421 lines13,725 bytesCodeBlame
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}
131
132#[derive(Clone, Debug, Serialize, Deserialize)]
133#[serde(rename_all = "camelCase")]
134pub struct RunDetail {
135 pub run: WorkflowRun,
136 pub jobs: Vec<Job>,
137 /// The workflow's notes, as of the run's commit.
138 pub notes: Vec<WorkflowNote>,
139}
140
141#[derive(Clone, Debug, Serialize, Deserialize)]
142#[serde(rename_all = "camelCase")]
143pub struct LogChunk {
144 pub seq: u64,
145 /// The step it belongs to, from 1; 0 for the job's setup.
146 pub step: u32,
147 pub text: String,
148}
149
150#[derive(Clone, Debug, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct JobLog {
153 pub chunks: Vec<LogChunk>,
154 /// Whether the job has finished, so no more will come.
155 pub done: bool,
156}
157
158/// Who may read a secret or variable: workflows (`secrets.*` and `vars.*`
159/// in GitHub Actions) and deployments (a deploy build's environment and the
160/// running app's bindings). Agents, checks and the merge queue read none.
161pub const CONSUMERS: [&str; 2] = ["workflows", "deployments"];
162
163/// One row of a repository's or workspace's secrets and variables, as
164/// Vercel lists environment variables: a key, its type, the environments
165/// it applies to and who reads it. A key may have one row per environment.
166/// Secrets' values are never returned.
167#[derive(Clone, Debug, Serialize, Deserialize)]
168#[serde(rename_all = "camelCase")]
169pub struct Setting {
170 #[serde(default)]
171 pub id: String,
172 pub name: String,
173 /// `secret`, or `variable` (shown as Config).
174 #[serde(default)]
175 pub kind: String,
176 /// A variable's value; secrets' are never returned.
177 pub value: Option<String>,
178 /// `project` (a repository's, which belong to its project) or
179 /// `workspace`.
180 pub scope: String,
181 pub updated_at: String,
182 /// `workflows` and/or `deployments`.
183 #[serde(default)]
184 pub available_to: Vec<String>,
185 /// The environments it applies to; empty is every environment.
186 #[serde(default)]
187 pub environments: Vec<String>,
188 /// A workspace's row: the projects it reaches, by slug; empty is every
189 /// project.
190 #[serde(default)]
191 pub projects: Vec<String>,
192 #[serde(default)]
193 pub note: Option<String>,
194 #[serde(default)]
195 pub updated_by: Option<String>,
196}
197
198// --- Methods ---------------------------------------------------------------
199
200/// `workflows`. Returns `Outcome<Vec<Workflow>>`.
201#[derive(Debug, Serialize, Deserialize)]
202pub struct WorkflowsArgs {
203 pub repo: RepoPath,
204 pub viewer: Viewer,
205}
206
207/// `runs`: newest first. Returns `Outcome<Vec<WorkflowRun>>`.
208#[derive(Debug, Serialize, Deserialize)]
209pub struct RunsArgs {
210 pub repo: RepoPath,
211 pub viewer: Viewer,
212 /// A workflow's id or file name.
213 #[serde(default)]
214 pub workflow: Option<String>,
215 #[serde(default)]
216 pub branch: Option<String>,
217 #[serde(default)]
218 pub event: Option<String>,
219 /// The pull request's number.
220 #[serde(default)]
221 pub pull: Option<u32>,
222 #[serde(default)]
223 pub sha: Option<String>,
224 #[serde(default)]
225 pub limit: Option<u32>,
226}
227
228/// `run`. Returns `Outcome<RunDetail>`.
229#[derive(Debug, Serialize, Deserialize)]
230pub struct RunArgs {
231 pub repo: RepoPath,
232 pub viewer: Viewer,
233 pub id: String,
234}
235
236/// `logs`: a job's log after `after`. Returns `Outcome<JobLog>`.
237#[derive(Debug, Serialize, Deserialize)]
238pub struct LogsArgs {
239 pub repo: RepoPath,
240 pub viewer: Viewer,
241 pub job: String,
242 #[serde(default)]
243 pub after: u64,
244}
245
246/// `dispatch`: run a workflow that has `workflow_dispatch`. Members only.
247/// Returns `Outcome<WorkflowRun>`.
248#[derive(Debug, Serialize, Deserialize)]
249pub struct DispatchArgs {
250 pub actor: User,
251 pub repo: RepoPath,
252 /// A workflow's id or file name.
253 pub workflow: String,
254 /// A branch or tag; the default branch when absent.
255 #[serde(default, rename = "ref")]
256 pub git_ref: Option<String>,
257 #[serde(default)]
258 pub inputs: serde_json::Map<String, Value>,
259}
260
261/// `cancel` and `rerun` (all jobs, or with `failed_only` the ones that did
262/// not succeed). Members only. Returns `Outcome<WorkflowRun>`.
263#[derive(Debug, Serialize, Deserialize)]
264pub struct RunActionArgs {
265 pub actor: User,
266 pub repo: RepoPath,
267 pub id: String,
268 #[serde(default)]
269 pub failed_only: bool,
270}
271
272/// `set_workflow_enabled`. Members only. Returns `Outcome<Workflow>`.
273#[derive(Debug, Serialize, Deserialize)]
274pub struct SetWorkflowEnabledArgs {
275 pub actor: User,
276 pub repo: RepoPath,
277 pub workflow: String,
278 pub enabled: bool,
279}
280
281/// Whose secrets or variables: a repository's, or with only `workspace`,
282/// a workspace's.
283#[derive(Clone, Debug, Serialize, Deserialize)]
284pub struct SettingsOwner {
285 #[serde(default)]
286 pub repo: Option<RepoPath>,
287 #[serde(default)]
288 pub workspace: Option<String>,
289}
290
291/// `settings`: the secrets (`kind: secret`) or variables (`kind: variable`)
292/// of a repository, with its workspace's, or of a workspace. Members only.
293/// Returns `Outcome<Vec<Setting>>`.
294#[derive(Debug, Serialize, Deserialize)]
295pub struct SettingsArgs {
296 pub actor: User,
297 #[serde(flatten)]
298 pub owner: SettingsOwner,
299 pub kind: String,
300}
301
302/// `set_setting`: add or replace one. A repository's need a member; a
303/// workspace's an owner. Returns `Outcome<Setting>`.
304#[derive(Debug, Serialize, Deserialize)]
305pub struct SetSettingArgs {
306 pub actor: User,
307 #[serde(flatten)]
308 pub owner: SettingsOwner,
309 /// `secret` or `variable`. Changing a variable's row to `secret` seals
310 /// it; a secret cannot become a variable.
311 pub kind: String,
312 pub name: String,
313 /// The row to change. Left out, the key's row for every environment, as
314 /// GitHub's API addresses a secret by name alone.
315 #[serde(default)]
316 pub id: Option<String>,
317 /// Needed for a new row; left out, an existing row keeps its value.
318 #[serde(default)]
319 pub value: Option<String>,
320 /// `workflows` and/or `deployments`; left out, unchanged (both, for a
321 /// new row).
322 // Named as callers send it: an `alias` is not honoured beside the
323 // flattened owner in the Worker's build.
324 #[serde(default, rename = "availableTo")]
325 pub available_to: Option<Vec<String>>,
326 /// The environments it applies to; empty is every one. Left out,
327 /// unchanged.
328 #[serde(default)]
329 pub environments: Option<Vec<String>>,
330 /// A workspace's row: project slugs; empty for every one.
331 #[serde(default)]
332 pub projects: Option<Vec<String>>,
333 #[serde(default)]
334 pub note: Option<String>,
335}
336
337/// `resolve_settings`: the secrets and variables one reader gets, for the
338/// services that hand them out (the deployments service). Returns
339/// `ResolvedSettings`.
340#[derive(Debug, Serialize, Deserialize)]
341#[serde(rename_all = "camelCase")]
342pub struct ResolveSettingsArgs {
343 pub repo_id: String,
344 pub repo: RepoPath,
345 /// The project being read for; its repository's primary project if left
346 /// out.
347 #[serde(default)]
348 pub project_id: Option<String>,
349 #[serde(default)]
350 pub project_slug: Option<String>,
351 /// `workflows` or `deployments`.
352 pub consumer: String,
353 /// The environment being read for, such as `production` or `preview`.
354 #[serde(default)]
355 pub environment: Option<String>,
356 /// Whether the run is trusted; an untrusted one gets no secrets.
357 pub trusted: bool,
358}
359
360#[derive(Debug, Default, Serialize, Deserialize)]
361pub struct ResolvedSettings {
362 pub secrets: serde_json::Map<String, serde_json::Value>,
363 pub variables: serde_json::Map<String, serde_json::Value>,
364}
365
366/// `delete_setting`. Returns `Outcome<bool>`.
367#[derive(Debug, Serialize, Deserialize)]
368pub struct DeleteSettingArgs {
369 pub actor: User,
370 #[serde(flatten)]
371 pub owner: SettingsOwner,
372 pub kind: String,
373 pub name: String,
374 /// One row; left out, every row of the key.
375 #[serde(default)]
376 pub id: Option<String>,
377}
378
379/// `job_spec` and `job_report`: the sandbox running a job, with the job's
380/// own token. `report` is one of:
381/// `{"kind": "step", "number", "status", "conclusion"}`,
382/// `{"kind": "log", "step", "text"}`,
383/// `{"kind": "annotation", "level", "message", "title", "file", "line"}`,
384/// `{"kind": "done", "conclusion", "outputs", "reason"}`.
385#[derive(Debug, Serialize, Deserialize)]
386pub struct JobCallArgs {
387 pub job: String,
388 pub token: String,
389 #[serde(default)]
390 pub report: Value,
391}
392
393/// What the runner needs to start a job's sandbox.
394#[derive(Debug, Serialize, Deserialize)]
395#[serde(rename_all = "camelCase")]
396pub struct StartJobArgs {
397 pub job: String,
398 pub token: String,
399 pub repo: RepoPath,
400 /// Minutes before the job is stopped.
401 pub timeout_minutes: u32,
402}
403
404#[cfg(test)]
405mod setting_args_tests {
406 use super::*;
407
408 #[test]
409 fn who_reads_a_row_is_read_as_the_site_and_api_send_it() {
410 let args: SetSettingArgs = serde_json::from_value(serde_json::json!({
411 "actor": { "id": "usr_1", "username": "a" },
412 "repo": { "namespace": "acme", "name": "web" },
413 "kind": "secret",
414 "name": "STRIPE_KEY",
415 "availableTo": ["deployments"],
416 "environments": ["production"],
417 }))
418 .unwrap();
419 assert_eq!(args.available_to, Some(vec!["deployments".to_owned()]));
420 }
421}