flagon-io/g1t

public

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/crates/actions/src/events.rs

222 lines9,007 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

GitHub Actions on g1t, part one: reading workflows1//! g1t's events as GitHub's: which event and activity type each one is,
2//! and the `github` context and `GITHUB_*` variables a run sees.
3
4use serde::{Deserialize, Serialize};
5use serde_json::{Map, Value, json};
6
7/// The GitHub event and activity type for a g1t event, if it has one.
8/// A g1t event can be more than one GitHub event: a pull request opening
9/// is `pull_request` and `pull_request_target`.
10pub fn github_events(kind: &str) -> Vec<(&'static str, Option<&'static str>)> {
11 let pull = |action| vec![("pull_request", Some(action)), ("pull_request_target", Some(action))];
12 match kind {
13 "git.push" => vec![("push", None)],
14 "pull.opened" => pull("opened"),
15 "pull.updated" => pull("synchronize"),
16 "pull.ready" => pull("ready_for_review"),
17 "pull.closed" | "pull.merged" => pull("closed"),
18 "issue.opened" => vec![("issues", Some("opened"))],
19 "issue.updated" => vec![("issues", Some("edited"))],
20 "issue.closed" => vec![("issues", Some("closed"))],
21 "issue.reopened" => vec![("issues", Some("reopened"))],
22 "issue.assigned" => vec![("issues", Some("assigned"))],
23 "comment.created" => vec![("issue_comment", Some("created"))],
24 "review.completed" => vec![("pull_request_review", Some("submitted"))],
Actions: workflow_run, workflow.completed, artifacts on the run page, Node 2425 "workflow.completed" => vec![("workflow_run", Some("completed"))],
GitHub Actions on g1t, part one: reading workflows26 _ => Vec::new(),
27 }
28}
29
30/// What a run is about: enough to fill the `github` context.
31#[derive(Clone, Debug, Default, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct RunInfo {
34 /// `acme/web`.
35 pub repository: String,
36 pub repository_id: String,
37 pub default_branch: String,
38 pub event_name: String,
39 /// The webhook-shaped payload, `github.event`.
40 pub event: Value,
41 /// `refs/heads/main`, `refs/tags/v1`, `refs/pull/3/merge`.
42 pub git_ref: String,
43 pub sha: String,
44 /// For pull requests: the head and base branches.
45 pub head_ref: Option<String>,
46 pub base_ref: Option<String>,
47 pub actor: String,
48 pub actor_id: String,
49 pub triggering_actor: String,
50 pub run_id: String,
51 pub run_number: u64,
52 pub run_attempt: u64,
53 /// The workflow's name.
54 pub workflow: String,
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs55 /// `.g1t/workflows/ci.yml`.
GitHub Actions on g1t, part one: reading workflows56 pub workflow_path: String,
57 pub server_url: String,
58 pub api_url: String,
59}
60
61impl RunInfo {
62 pub fn ref_name(&self) -> String {
63 self.git_ref
64 .strip_prefix("refs/heads/")
65 .or_else(|| self.git_ref.strip_prefix("refs/tags/"))
66 .or_else(|| self.git_ref.strip_prefix("refs/"))
67 .unwrap_or(&self.git_ref)
68 .to_owned()
69 }
70
71 pub fn ref_type(&self) -> &'static str {
72 if self.git_ref.starts_with("refs/tags/") { "tag" } else { "branch" }
73 }
74
75 fn owner(&self) -> &str {
76 self.repository.split('/').next().unwrap_or_default()
77 }
78
79 /// The `github` context for a job. `token` is `github.token` (and
80 /// `secrets.GITHUB_TOKEN`); `job` is the job's id.
81 pub fn context(&self, job: &str, token: &str, action: Option<&str>) -> Value {
82 json!({
83 "action": action.unwrap_or_default(),
84 "action_path": "",
85 "action_ref": "",
86 "action_repository": "",
87 "actor": self.actor,
88 "actor_id": self.actor_id,
89 "api_url": self.api_url,
90 "base_ref": self.base_ref.clone().unwrap_or_default(),
91 "env": "",
92 "event": self.event,
93 "event_name": self.event_name,
94 "event_path": "/home/runner/_temp/event.json",
95 "graphql_url": "",
96 "head_ref": self.head_ref.clone().unwrap_or_default(),
97 "job": job,
98 "path": "",
99 "ref": self.git_ref,
100 "ref_name": self.ref_name(),
101 "ref_protected": self.ref_name() == self.default_branch,
102 "ref_type": self.ref_type(),
103 "repository": self.repository,
104 "repository_id": self.repository_id,
105 "repository_owner": self.owner(),
106 "repository_owner_id": "",
107 "repositoryUrl": format!("{}/{}.git", self.server_url, self.repository),
108 "retention_days": 14,
109 "run_attempt": self.run_attempt.to_string(),
110 "run_id": self.run_id,
111 "run_number": self.run_number.to_string(),
112 "secret_source": "Actions",
113 "server_url": self.server_url,
114 "sha": self.sha,
115 "token": token,
116 "triggering_actor": self.triggering_actor,
117 "workflow": self.workflow,
118 "workflow_ref": format!("{}/{}@{}", self.repository, self.workflow_path, self.git_ref),
119 "workflow_sha": self.sha,
120 "workspace": WORKSPACE,
121 })
122 }
123
124 /// The `GITHUB_*` and `RUNNER_*` variables every step gets.
125 pub fn variables(&self, job: &str) -> Map<String, Value> {
126 let mut vars = Map::new();
127 let mut set = |key: &str, value: String| {
128 vars.insert(key.to_owned(), Value::String(value));
129 };
130 set("CI", "true".into());
131 set("GITHUB_ACTIONS", "true".into());
132 set("G1T", "true".into());
133 set("GITHUB_ACTOR", self.actor.clone());
134 set("GITHUB_ACTOR_ID", self.actor_id.clone());
135 set("GITHUB_API_URL", self.api_url.clone());
136 set("GITHUB_BASE_REF", self.base_ref.clone().unwrap_or_default());
137 set("GITHUB_EVENT_NAME", self.event_name.clone());
138 set("GITHUB_EVENT_PATH", "/home/runner/_temp/event.json".into());
139 set("GITHUB_GRAPHQL_URL", String::new());
140 set("GITHUB_HEAD_REF", self.head_ref.clone().unwrap_or_default());
141 set("GITHUB_JOB", job.to_owned());
142 set("GITHUB_REF", self.git_ref.clone());
143 set("GITHUB_REF_NAME", self.ref_name());
144 set("GITHUB_REF_PROTECTED", (self.ref_name() == self.default_branch).to_string());
145 set("GITHUB_REF_TYPE", self.ref_type().into());
146 set("GITHUB_REPOSITORY", self.repository.clone());
147 set("GITHUB_REPOSITORY_ID", self.repository_id.clone());
148 set("GITHUB_REPOSITORY_OWNER", self.owner().to_owned());
149 set("GITHUB_RETENTION_DAYS", "14".into());
150 set("GITHUB_RUN_ATTEMPT", self.run_attempt.to_string());
151 set("GITHUB_RUN_ID", self.run_id.clone());
152 set("GITHUB_RUN_NUMBER", self.run_number.to_string());
153 set("GITHUB_SERVER_URL", self.server_url.clone());
154 set("GITHUB_SHA", self.sha.clone());
155 set("GITHUB_TRIGGERING_ACTOR", self.triggering_actor.clone());
156 set("GITHUB_WORKFLOW", self.workflow.clone());
157 set("GITHUB_WORKFLOW_REF", format!("{}/{}@{}", self.repository, self.workflow_path, self.git_ref));
158 set("GITHUB_WORKFLOW_SHA", self.sha.clone());
159 set("GITHUB_WORKSPACE", WORKSPACE.into());
160 set("RUNNER_ARCH", "X64".into());
161 set("RUNNER_NAME", "g1t".into());
162 set("RUNNER_OS", "Linux".into());
163 set("RUNNER_TEMP", "/home/runner/_temp".into());
164 set("RUNNER_TOOL_CACHE", "/home/runner/_tool".into());
165 set("RUNNER_ENVIRONMENT", "github-hosted".into());
166 vars
167 }
168}
169
170/// Where a job's repository is checked out, as on GitHub's runners.
171pub const WORKSPACE: &str = "/home/runner/work/repo";
172
173/// The `runner` context.
174pub fn runner_context() -> Value {
175 json!({
176 "name": "g1t",
177 "os": "Linux",
178 "arch": "X64",
179 "temp": "/home/runner/_temp",
180 "tool_cache": "/home/runner/_tool",
181 "environment": "github-hosted",
182 "debug": "",
183 })
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn g1t_events_are_github_events() {
192 assert_eq!(github_events("git.push"), [("push", None)]);
193 assert_eq!(github_events("pull.updated")[0], ("pull_request", Some("synchronize")));
194 assert_eq!(github_events("pull.merged")[1], ("pull_request_target", Some("closed")));
195 assert_eq!(github_events("comment.created"), [("issue_comment", Some("created"))]);
196 assert!(github_events("session.appended").is_empty());
197 }
198
199 #[test]
200 fn contexts_and_variables_agree() {
201 let info = RunInfo {
202 repository: "acme/web".into(),
203 default_branch: "main".into(),
204 event_name: "push".into(),
205 git_ref: "refs/tags/v1.2.0".into(),
206 sha: "abc".into(),
207 run_number: 7,
208 run_attempt: 1,
209 server_url: "https://g1t.sh".into(),
210 ..RunInfo::default()
211 };
212 let github = info.context("build", "tok", None);
213 assert_eq!(github["ref_name"], "v1.2.0");
214 assert_eq!(github["ref_type"], "tag");
215 assert_eq!(github["repository_owner"], "acme");
216 assert_eq!(github["run_number"], "7");
217 let vars = info.variables("build");
218 assert_eq!(vars["GITHUB_REF_NAME"], "v1.2.0");
219 assert_eq!(vars["GITHUB_JOB"], "build");
220 assert_eq!(vars["RUNNER_OS"], "Linux");
221 }
222}