///|
pub struct RunDefaults {
shell : String?
working_directory : String?
}
///|
pub struct PermissionsSpec {
values : Map[String, String]
}
///|
pub struct ConcurrencySpec {
group : String
cancel_in_progress : String?
}
///|
pub struct WorkflowCallInputSpec {
description : String
required : Bool
default_value : String?
input_type : String
}
///|
pub struct WorkflowCallOutputSpec {
description : String
value : String
}
///|
pub struct WorkflowCallSecretSpec {
required : Bool
}
///|
pub struct JobContainerCredentialsSpec {
username : String
password : String
}
///|
pub struct JobContainerSpec {
image : String
credentials : JobContainerCredentialsSpec?
env : Map[String, String]
ports : Array[String]
volumes : Array[String]
options : String?
}
///|
pub struct WorkflowCallSpec {
inputs : Map[String, WorkflowCallInputSpec]
outputs : Map[String, WorkflowCallOutputSpec]
secrets : Map[String, WorkflowCallSecretSpec]
}
///|
pub fn new_permissions_spec(
values? : Map[String, String] = {},
) -> PermissionsSpec {
{ values, }
}
///|
pub fn new_concurrency_spec(
group : String,
cancel_in_progress? : String? = None,
) -> ConcurrencySpec {
{ group, cancel_in_progress }
}
///|
pub fn new_workflow_call_input_spec(
description? : String = "",
required? : Bool = false,
default_value? : String? = None,
input_type? : String = "",
) -> WorkflowCallInputSpec {
{ description, required, default_value, input_type }
}
///|
pub fn new_workflow_call_output_spec(
description? : String = "",
value? : String = "",
) -> WorkflowCallOutputSpec {
{ description, value }
}
///|
pub fn new_workflow_call_secret_spec(
required? : Bool = false,
) -> WorkflowCallSecretSpec {
{ required, }
}
///|
pub fn new_job_container_credentials_spec(
username : String,
password : String,
) -> JobContainerCredentialsSpec {
{ username, password }
}
///|
pub fn new_job_container_spec(
image : String,
credentials? : JobContainerCredentialsSpec? = None,
env? : Map[String, String] = {},
ports? : Array[String] = [],
volumes? : Array[String] = [],
options? : String? = None,
) -> JobContainerSpec {
{ image, credentials, env, ports, volumes, options }
}
///|
pub fn new_workflow_call_spec(
inputs? : Map[String, WorkflowCallInputSpec] = {},
outputs? : Map[String, WorkflowCallOutputSpec] = {},
secrets? : Map[String, WorkflowCallSecretSpec] = {},
) -> WorkflowCallSpec {
{ inputs, outputs, secrets }
}
///|
pub fn new_run_defaults(
shell? : String? = None,
working_directory? : String? = None,
) -> RunDefaults {
{ shell, working_directory }
}
///|
pub struct PushTrigger {
branches : Array[String]
branches_ignore : Array[String]
paths : Array[String]
paths_ignore : Array[String]
tags : Array[String]
tags_ignore : Array[String]
}
///|
pub fn new_push_trigger(
branches? : Array[String] = [],
branches_ignore? : Array[String] = [],
paths? : Array[String] = [],
paths_ignore? : Array[String] = [],
tags? : Array[String] = [],
tags_ignore? : Array[String] = [],
) -> PushTrigger {
{ branches, branches_ignore, paths, paths_ignore, tags, tags_ignore }
}
///|
pub struct PushEvent {
ref_name : String
before_sha : String
after_sha : String
changed_paths : Array[String]
repository : String
actor : String
}
///|
pub fn new_push_event(
ref_name : String,
changed_paths : Array[String],
before_sha? : String = "",
after_sha? : String = "",
repository? : String = "",
actor? : String = "",
) -> PushEvent {
{ ref_name, before_sha, after_sha, changed_paths, repository, actor }
}
///|
pub struct StepSpec {
id : String
name : String
run : String?
uses : String?
shell : String?
working_directory : String?
env : Map[String, String]
with_values : Map[String, String]
if_condition : String
continue_on_error : String
timeout_minutes : Int
}
///|
pub fn new_run_step(
id : String,
run : String,
name? : String = "",
shell? : String? = None,
working_directory? : String? = None,
env? : Map[String, String] = {},
if_condition? : String = "success()",
with_values? : Map[String, String] = {},
continue_on_error? : String = "false",
timeout_minutes? : Int = 0,
) -> StepSpec {
{
id,
name,
run: Some(run),
uses: None,
shell,
working_directory,
env,
with_values,
if_condition,
continue_on_error,
timeout_minutes,
}
}
///|
pub fn new_uses_step(
id : String,
uses : String,
name? : String = "",
env? : Map[String, String] = {},
if_condition? : String = "success()",
with_values? : Map[String, String] = {},
continue_on_error? : String = "false",
timeout_minutes? : Int = 0,
) -> StepSpec {
{
id,
name,
run: None,
uses: Some(uses),
shell: None,
working_directory: None,
env,
with_values,
if_condition,
continue_on_error,
timeout_minutes,
}
}
///|
pub struct JobSpec {
id : String
name : String
if_condition : String
needs : Array[String]
outputs : Map[String, String]
permissions : PermissionsSpec?
concurrency : ConcurrencySpec?
runs_on : Array[String]
env : Map[String, String]
defaults : RunDefaults
steps : Array[StepSpec]
matrix : JobMatrixSpec?
reusable_workflow : String?
reusable_workflow_with : Map[String, String]
reusable_workflow_secrets : Map[String, String]
reusable_workflow_inherit_secrets : Bool
services : Map[String, JobContainerSpec]
container : JobContainerSpec?
container_image : String?
timeout_minutes : Int
environment : String
}
///|
pub struct JobMatrixSpec {
rows : Array[Map[String, String]]
fail_fast : Bool
max_parallel : Int?
}
///|
pub fn new_job_matrix(
rows : Array[Map[String, String]],
fail_fast? : Bool = true,
max_parallel? : Int? = None,
) -> JobMatrixSpec {
{ rows, fail_fast, max_parallel }
}
///|
pub fn new_job(
id : String,
steps : Array[StepSpec],
name? : String = "",
if_condition? : String = "success()",
needs? : Array[String] = [],
outputs? : Map[String, String] = {},
permissions? : PermissionsSpec? = None,
concurrency? : ConcurrencySpec? = None,
runs_on? : Array[String] = ["ubuntu-latest"],
env? : Map[String, String] = {},
defaults? : RunDefaults = new_run_defaults(),
matrix? : JobMatrixSpec? = None,
reusable_workflow? : String? = None,
reusable_workflow_with? : Map[String, String] = {},
reusable_workflow_secrets? : Map[String, String] = {},
reusable_workflow_inherit_secrets? : Bool = false,
container? : JobContainerSpec? = None,
container_image? : String? = None,
services? : Map[String, JobContainerSpec] = {},
timeout_minutes? : Int = 0,
environment? : String = "",
) -> JobSpec {
let actual_container = match container {
Some(spec) => Some(spec)
None =>
match container_image {
Some(image) => Some(new_job_container_spec(image))
None => None
}
}
let actual_container_image = match actual_container {
Some(spec) => Some(spec.image)
None => None
}
{
id,
name,
if_condition,
needs,
outputs,
permissions,
concurrency,
runs_on,
env,
defaults,
steps,
matrix,
reusable_workflow,
reusable_workflow_with,
reusable_workflow_secrets,
reusable_workflow_inherit_secrets,
services,
container: actual_container,
container_image: actual_container_image,
timeout_minutes,
environment,
}
}
///|
pub struct WorkflowSpec {
name : String
run_name : String
trigger : PushTrigger
pull_request_trigger : PushTrigger?
workflow_call : Bool
workflow_call_spec : WorkflowCallSpec?
permissions : PermissionsSpec?
concurrency : ConcurrencySpec?
env : Map[String, String]
defaults : RunDefaults
jobs : Array[JobSpec]
}
///|
pub fn strip_unsupported_workflow_fields(
workflow : WorkflowSpec,
) -> WorkflowSpec {
let stripped_jobs : Array[JobSpec] = []
for job in workflow.jobs {
stripped_jobs.push({ ..job, permissions: None, concurrency: None })
}
{ ..workflow, permissions: None, concurrency: None, jobs: stripped_jobs }
}
///|
pub fn step_matches_skip_pattern(
step : StepSpec,
patterns : Array[String],
) -> Bool {
match step.uses {
Some(uses) => {
let mut matched = false
for pattern in patterns {
if uses.contains(pattern) {
matched = true
break
}
}
matched
}
None => false
}
}
///|
pub fn filter_workflow_steps(
workflow : WorkflowSpec,
skip_patterns : Array[String],
) -> WorkflowSpec {
if skip_patterns.length() == 0 {
return workflow
}
let filtered_jobs : Array[JobSpec] = []
for job in workflow.jobs {
let filtered_steps : Array[StepSpec] = []
for step in job.steps {
if !step_matches_skip_pattern(step, skip_patterns) {
filtered_steps.push(step)
}
}
filtered_jobs.push({ ..job, steps: filtered_steps })
}
{ ..workflow, jobs: filtered_jobs }
}
///|
pub fn override_workflow_steps(
workflow : WorkflowSpec,
overrides : Map[String, String],
) -> WorkflowSpec {
if overrides.length() == 0 {
return workflow
}
let result_jobs : Array[JobSpec] = []
for job in workflow.jobs {
let result_steps : Array[StepSpec] = []
for step in job.steps {
match step.uses {
Some(uses) => {
let mut matched_script : String? = None
for pattern, script in overrides {
if uses.contains(pattern) {
matched_script = Some(script)
break
}
}
match matched_script {
Some(script) =>
result_steps.push({
..step,
uses: None,
run: Some(script),
shell: Some("bash"),
})
None => result_steps.push(step)
}
}
None => result_steps.push(step)
}
}
result_jobs.push({ ..job, steps: result_steps })
}
{ ..workflow, jobs: result_jobs }
}
///|
pub fn filter_workflow_jobs(
workflow : WorkflowSpec,
skip_job_ids : Array[String],
) -> WorkflowSpec {
if skip_job_ids.length() == 0 {
return workflow
}
let filtered_jobs : Array[JobSpec] = []
for job in workflow.jobs {
let mut should_skip = false
for skip_id in skip_job_ids {
if job.id == skip_id {
should_skip = true
break
}
}
if !should_skip {
filtered_jobs.push(job)
}
}
{ ..workflow, jobs: filtered_jobs }
}
///|
pub fn select_workflow_job(
workflow : WorkflowSpec,
job_id : String,
) -> WorkflowSpec {
if job_id.length() == 0 {
return workflow
}
let filtered_jobs : Array[JobSpec] = []
for job in workflow.jobs {
if job.id == job_id {
filtered_jobs.push(job)
}
}
{ ..workflow, jobs: filtered_jobs }
}
///|
pub fn select_workflow_step(
workflow : WorkflowSpec,
job_id : String,
step_id : String,
) -> WorkflowSpec {
if step_id.length() == 0 {
return workflow
}
let filtered_jobs : Array[JobSpec] = []
for job in workflow.jobs {
if job_id.length() > 0 && job.id != job_id {
continue
}
let filtered_steps : Array[StepSpec] = []
for step in job.steps {
if step.id == step_id || step.name == step_id {
filtered_steps.push(step)
}
}
if filtered_steps.length() > 0 {
filtered_jobs.push({ ..job, steps: filtered_steps })
}
}
{ ..workflow, jobs: filtered_jobs }
}
///|
pub fn new_workflow(
name : String,
jobs : Array[JobSpec],
run_name? : String = "",
trigger? : PushTrigger = new_push_trigger(),
pull_request_trigger? : PushTrigger? = None,
workflow_call? : Bool = false,
workflow_call_spec? : WorkflowCallSpec? = None,
permissions? : PermissionsSpec? = None,
concurrency? : ConcurrencySpec? = None,
env? : Map[String, String] = {},
defaults? : RunDefaults = new_run_defaults(),
) -> WorkflowSpec {
{
name,
run_name,
trigger,
pull_request_trigger,
workflow_call,
workflow_call_spec,
permissions,
concurrency,
env,
defaults,
jobs,
}
}
///|
pub struct TaskPlan {
id : String
kind : String
job_id : String
step_id : String
name : String
script : String
shell : String
working_directory : String
if_condition : String
runs_on : Array[String]
env : Map[String, String]
with_values : Map[String, String]
action : ResolvedAction?
action_scope : String?
requires_action_started : Bool
continue_on_error : String
// Parsed from step or job timeout-minutes. 0 means no timeout.
// Currently parsed but not enforced at execution time.
timeout_minutes : Int
}
///|
pub fn new_task_plan(
id : String,
kind : String,
job_id : String,
step_id : String,
name : String,
script : String,
shell : String,
working_directory : String,
if_condition : String,
runs_on : Array[String],
env : Map[String, String],
with_values? : Map[String, String] = {},
action? : ResolvedAction? = None,
action_scope? : String? = None,
requires_action_started? : Bool = false,
continue_on_error? : String = "false",
timeout_minutes? : Int = 0,
) -> TaskPlan {
{
id,
kind,
job_id,
step_id,
name,
script,
shell,
working_directory,
if_condition,
runs_on,
env,
with_values,
action,
action_scope,
requires_action_started,
continue_on_error,
timeout_minutes,
}
}
///|
pub struct ExecutionPlan {
tasks : Array[TaskPlan]
job_outputs : Map[String, Map[String, String]]
job_if_conditions : Map[String, String]
job_needs : Map[String, Array[String]]
job_need_targets : Map[String, Map[String, Array[String]]]
job_virtual_targets : Map[String, Array[String]]
job_virtual_output_targets : Map[String, Map[String, Array[String]]]
job_matrix_groups : Map[String, String]
job_matrix_fail_fast : Map[String, Bool]
job_containers : Map[String, JobContainerSpec]
job_services : Map[String, Map[String, JobContainerSpec]]
composite_output_mappings : Map[String, Map[String, String]]
}
///|
pub fn find_task_plan(plan : ExecutionPlan, id : String) -> TaskPlan? {
for task in plan.tasks {
if task.id == id {
return Some(task)
}
}
None
}
///|
pub struct LoweringResult {
ir : @wf.FlowIr
plan : ExecutionPlan
errors : Array[String]
}
///|
pub struct WorkflowParseResult {
workflow : WorkflowSpec?
errors : Array[String]
}
///|
pub struct LocalActionSpec {
name : String
inputs : Map[String, String]
outputs : Map[String, String]
steps : Array[StepSpec]
}
///|
pub fn new_local_action(
steps : Array[StepSpec],
name? : String = "",
inputs? : Map[String, String] = {},
outputs? : Map[String, String] = {},
) -> LocalActionSpec {
{ name, inputs, outputs, steps }
}
///|
pub struct LocalActionParseResult {
action : LocalActionSpec?
errors : Array[String]
}
///|
pub struct BitWorkspace {
repo_root : String
git_dir : String
workspace_root : String
commit_sha : String
refname : String
remote_url : String
}
///|
pub struct BitWorkspaceResult {
workspace : BitWorkspace?
errors : Array[String]
}
///|
pub struct BitChangedPathsResult {
paths : Array[String]
base_sha : String
head_sha : String
errors : Array[String]
}
///|
pub struct PushEventJsonParseResult {
event : PushEvent?
errors : Array[String]
}
///|
pub struct GitHubActionPrefetchResult {
fetched : Array[String]
errors : Array[String]
}
///|
pub struct TaskRunReport {
id : String
kind : String
status : String
code : Int
duration_ms : UInt64
shell : String
script : String
cwd : String
stdout : String
stderr : String
summary : String
}
///|
pub struct WorkflowStepReport {
id : String
status : String
required : Bool
duration_ms : UInt64
message : String
}
///|
pub struct WorkflowRunReport {
ok : Bool
state : String
order : Array[String]
steps : Array[WorkflowStepReport]
issues : Array[String]
task_reports : Array[TaskRunReport]
}
///|
fn strip_add_mask_lines(text : String) -> String {
let lines : Array[String] = []
for line_view in text.split("\n") {
let trimmed = line_view.trim(chars=" \t\r")
if trimmed.has_prefix("::add-mask::") {
continue
}
if trimmed.has_prefix("::group::") {
continue
}
if trimmed.has_prefix("::endgroup::") {
continue
}
if trimmed.has_prefix("::debug::") {
continue
}
lines.push(line_view.to_owned())
}
lines.join("\n")
}
///|
pub fn extract_add_mask_values(report : WorkflowRunReport) -> Array[String] {
let values : Array[String] = []
let prefix = "::add-mask::"
for task in report.task_reports {
for line_view in task.stdout.split("\n") {
let line = line_view.trim(chars=" \t\r").to_owned()
if line.has_prefix(prefix) && line.length() > prefix.length() {
let value = String::unsafe_substring(
line,
start=prefix.length(),
end=line.length(),
)
if value.length() > 0 {
values.push(value)
}
}
}
for line_view in task.stderr.split("\n") {
let line = line_view.trim(chars=" \t\r").to_owned()
if line.has_prefix(prefix) && line.length() > prefix.length() {
let value = String::unsafe_substring(
line,
start=prefix.length(),
end=line.length(),
)
if value.length() > 0 {
values.push(value)
}
}
}
}
values
}
///|
pub fn mask_report_secrets(
report : WorkflowRunReport,
secret_values : Array[String],
) -> WorkflowRunReport {
// Also collect ::add-mask:: values from stdout/stderr
let add_mask_values = extract_add_mask_values(report)
let all_secrets : Array[String] = []
for value in secret_values {
all_secrets.push(value)
}
for value in add_mask_values {
all_secrets.push(value)
}
if all_secrets.length() == 0 {
// Still strip workflow command lines even without secrets
let stripped_tasks : Array[TaskRunReport] = []
for task in report.task_reports {
stripped_tasks.push({
..task,
stdout: strip_add_mask_lines(task.stdout),
stderr: strip_add_mask_lines(task.stderr),
})
}
return { ..report, task_reports: stripped_tasks }
}
let masked_tasks : Array[TaskRunReport] = []
for task in report.task_reports {
masked_tasks.push({
..task,
script: mask_secrets(task.script, all_secrets),
stdout: strip_add_mask_lines(mask_secrets(task.stdout, all_secrets)),
stderr: strip_add_mask_lines(mask_secrets(task.stderr, all_secrets)),
summary: mask_secrets(task.summary, all_secrets),
})
}
let masked_steps : Array[WorkflowStepReport] = []
for step in report.steps {
masked_steps.push({
..step,
message: mask_secrets(step.message, all_secrets),
})
}
{ ..report, task_reports: masked_tasks, steps: masked_steps }
}
///|
pub fn find_workflow_step_report(
report : WorkflowRunReport,
id : String,
) -> WorkflowStepReport? {
for item in report.steps {
if item.id == id {
return Some(item)
}
}
None
}
///|
pub fn find_task_run_report(
report : WorkflowRunReport,
id : String,
) -> TaskRunReport? {
for item in report.task_reports {
if item.id == id {
return Some(item)
}
}
None
}