fad06fd
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use crate::dsl::{Concurrency, Job, MarkedPermissions, Step, WorkflowDefinition, WorkflowTriggers};
pub const ACTIONS_WORKFLOW_DIR: &str = ".github/workflows";
pub const SNAPSHOT_KEY: &str = "_gitdata_actions";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ActionsWorkflow {
pub file: String,
pub source: String,
pub document: Value,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
pub struct InputDefinition {
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub required: bool,
#[serde(default, rename = "type")]
pub kind: Option<String>,
#[serde(default)]
pub default: Option<Value>,
#[serde(default)]
pub options: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
pub struct NeedResult {
pub result: String,
#[serde(default)]
pub outputs: BTreeMap<String, String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
pub struct EngineInstance {
pub index: usize,
pub matrix: Value,
pub name: String,
pub runs_on: Vec<String>,
pub environment: String,
pub timeout_seconds: u64,
pub continue_on_error: bool,
pub fail_fast: bool,
pub max_parallel: usize,
pub concurrency: Value,
pub permissions: Value,
#[serde(rename = "with")]
pub inputs: Value,
pub uses: String,
pub group: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct EngineExpansion {
pub skipped: bool,
pub instances: Vec<EngineInstance>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ActionsExecution {
pub workflow: ActionsWorkflow,
pub job: String,
pub matrix: Value,
pub github: Value,
pub event: Value,
pub needs: BTreeMap<String, NeedResult>,
pub inputs: BTreeMap<String, Value>,
pub vars: BTreeMap<String, String>,
pub token: String,
pub runtime_url: String,
pub runtime_token: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, utoipa::ToSchema)]
pub struct EngineEvent {
pub event: String,
#[serde(default)]
pub data: Value,
#[serde(default)]
pub error: Option<String>,
#[serde(default)]
pub lease_token: Option<String>,
}
impl ActionsWorkflow {
pub fn name(&self) -> Option<String> {
self.document
.get("name")
.and_then(Value::as_str)
.map(str::to_owned)
}
pub fn triggers(&self) -> &Value {
self.document.get("on").unwrap_or(&Value::Null)
}
pub fn inputs(&self) -> BTreeMap<String, InputDefinition> {
self.triggers()
.get("workflow_dispatch")
.and_then(|v| v.get("inputs"))
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default()
}
pub fn projection(&self) -> WorkflowDefinition {
let on = self.triggers();
let triggers = WorkflowTriggers {
push: on.get("push").map(|value| crate::dsl::PushTrigger {
branches: strings(value.get("branches")),
tags: strings(value.get("tags")),
}),
pull_request: on
.get("pull_request")
.map(|value| crate::dsl::PullRequestTrigger {
branches: strings(value.get("branches")),
types: strings(value.get("types"))
.into_iter()
.filter_map(|s| serde_json::from_value(json!(s)).ok())
.collect(),
}),
workflow_dispatch: on
.get("workflow_dispatch")
.map(|_| crate::dsl::WorkflowDispatchTrigger::default()),
};
let jobs = self
.document
.get("jobs")
.and_then(Value::as_object)
.into_iter()
.flatten()
.map(|(key, value)| {
(
key.clone(),
Job {
runs_on: strings(value.get("runs-on")),
condition: None,
needs: strings(value.get("needs")),
environment: value
.get("environment")
.and_then(|v| {
v.as_str().or_else(|| v.get("name").and_then(Value::as_str))
})
.map(str::to_owned),
permissions: MarkedPermissions::default(),
container: None,
timeout_minutes: value.get("timeout-minutes").and_then(Value::as_u64),
env: env_map(value.get("env")),
artifacts: Vec::new(),
steps: value
.get("steps")
.and_then(Value::as_array)
.into_iter()
.flatten()
.map(|step| Step {
name: step.get("name").and_then(Value::as_str).map(str::to_owned),
run: step
.get("run")
.or_else(|| step.get("uses"))
.and_then(Value::as_str)
.unwrap_or_default()
.to_owned(),
shell: step.get("shell").and_then(Value::as_str).map(str::to_owned),
working_directory: step
.get("working-directory")
.and_then(Value::as_str)
.map(str::to_owned),
env: env_map(step.get("env")),
timeout_minutes: step
.get("timeout-minutes")
.and_then(Value::as_u64),
})
.collect(),
},
)
})
.collect();
WorkflowDefinition {
name: self.name(),
on: triggers,
env: env_map(self.document.get("env")),
concurrency: self
.document
.get("concurrency")
.and_then(|value| {
value
.as_str()
.or_else(|| value.get("group").and_then(Value::as_str))
})
.map(|group| Concurrency {
group: group.to_owned(),
cancel_in_progress: self.document["concurrency"]["cancel-in-progress"]
.as_bool()
.unwrap_or(false),
}),
jobs,
}
}
}
pub fn strings(value: Option<&Value>) -> Vec<String> {
match value {
Some(Value::String(value)) => vec![value.clone()],
Some(Value::Array(values)) => values
.iter()
.filter_map(Value::as_str)
.map(str::to_owned)
.collect(),
_ => Vec::new(),
}
}
pub fn env_map(value: Option<&Value>) -> BTreeMap<String, String> {
value
.and_then(Value::as_object)
.into_iter()
.flatten()
.map(|(name, value)| {
(
name.clone(),
value
.as_str()
.map(str::to_owned)
.unwrap_or_else(|| value.to_string()),
)
})
.collect()
}
pub fn from_snapshot(value: &Value) -> Option<ActionsWorkflow> {
serde_json::from_value(value.get(SNAPSHOT_KEY)?.clone()).ok()
}