hackety hack

This commit is contained in:
William Woodruff 2024-01-01 20:37:16 -05:00
parent d1f298a0c6
commit 0d39e51123
No known key found for this signature in database
10 changed files with 719 additions and 213 deletions

View file

@ -7,6 +7,6 @@ edition = "2021"
[dependencies]
serde = { version = "1.0.193", features = ["derive"] }
serde_yaml = "0.9.29"
[dev-dependencies]
serde_yaml = "0.9.29"

View file

@ -1,12 +1,14 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use crate::common::Env;
/// A GitHub Actions action definition.
///
/// See: <https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions>
/// and <https://json.schemastore.org/github-action.json>
#[derive(Deserialize, Serialize)]
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Action {
pub name: String,
@ -19,7 +21,7 @@ pub struct Action {
pub runs: Runs,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Input {
pub description: String,
@ -27,7 +29,7 @@ pub struct Input {
pub default: Option<String>,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Output {
pub description: String,
@ -35,7 +37,7 @@ pub struct Output {
pub value: Option<String>,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum Runs {
JavaScript(JavaScript),
@ -43,7 +45,7 @@ pub enum Runs {
Docker(Docker),
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct JavaScript {
// "node12" | "node16" | "node20"
@ -57,7 +59,7 @@ pub struct JavaScript {
pub post_if: Option<String>,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Composite {
// "composite"
@ -65,7 +67,7 @@ pub struct Composite {
pub steps: Vec<Step>,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum Step {
/// A step that runs a command in a shell.
@ -74,7 +76,7 @@ pub enum Step {
UseAction(UseAction),
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct RunShell {
pub run: String,
@ -83,34 +85,13 @@ pub struct RunShell {
pub id: Option<String>,
pub r#if: Option<String>,
#[serde(default)]
pub env: HashMap<String, EnvValue>,
pub env: Env,
#[serde(default)]
pub continue_on_error: bool,
pub working_directory: Option<String>,
}
/// Environment variable values are always strings, but GitHub Actions
/// allows users to configure them as various native YAML types before
/// internal stringification.
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum EnvValue {
String(String),
Number(f64),
Boolean(bool),
}
impl ToString for EnvValue {
fn to_string(&self) -> String {
match self {
Self::String(s) => s.clone(),
Self::Number(n) => n.to_string(),
Self::Boolean(b) => b.to_string(),
}
}
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct UseAction {
pub uses: String,
@ -119,14 +100,14 @@ pub struct UseAction {
pub r#if: Option<String>,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Docker {
// "docker"
pub using: String,
pub image: String,
#[serde(default)]
pub env: HashMap<String, EnvValue>,
pub env: Env,
pub entrypoint: Option<String>,
pub pre_entrypoint: Option<String>,
// Defaults to `always()`

68
src/common.rs Normal file
View file

@ -0,0 +1,68 @@
use std::collections::HashMap;
use serde::Deserialize;
pub type Env = HashMap<String, EnvValue>;
/// Environment variable values are always strings, but GitHub Actions
/// allows users to configure them as various native YAML types before
/// internal stringification.
#[derive(Deserialize)]
#[serde(untagged)]
pub enum EnvValue {
String(String),
Number(f64),
Boolean(bool),
}
impl ToString for EnvValue {
fn to_string(&self) -> String {
match self {
Self::String(s) => s.clone(),
Self::Number(n) => n.to_string(),
Self::Boolean(b) => b.to_string(),
}
}
}
/// A "literal or expr" type, for places in GitHub Actions where a
/// key can either have a literal value (array, object, etc.) or an
/// expression string.
#[derive(Deserialize)]
#[serde(untagged)]
pub enum LoE<T> {
Literal(T),
Expr(String),
}
impl<T> Default for LoE<T>
where
T: Default,
{
fn default() -> Self {
Self::Literal(T::default())
}
}
pub type BoE = LoE<bool>;
/// A "scalar or vector" type, for places in GitHub Actions where a
/// key can have either a scalar value or an array of values.
#[derive(Debug, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum SoV<T> {
One(T),
Many(Vec<T>),
}
impl<T> From<Vec<T>> for SoV<T> {
fn from(value: Vec<T>) -> Self {
Self::Many(value)
}
}
impl<T> From<T> for SoV<T> {
fn from(value: T) -> Self {
Self::One(value)
}
}

View file

@ -1,3 +1,4 @@
pub mod action;
pub mod common;
pub mod dependabot;
pub mod workflow;

View file

@ -1,177 +0,0 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
/// A single GitHub Actions workflow.
///
/// See: <https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions>
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct Workflow {
pub name: Option<String>,
pub run_name: Option<String>,
pub on: Trigger,
#[serde(default)]
pub permissions: Permissions,
}
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum Trigger {
// A single "bare" event, like `on: push`.
BareEvent(BareEvent),
// Multiple "bare" events, like `on: [push, pull_request]`
BareEvents(Vec<BareEvent>),
// `schedule:` events.
Schedule { schedule: Vec<Cron> },
WorkflowCall { workflow_call: Option<WorkflowCall> },
// "Rich" events, i.e. each event with its optional filters.
Events(HashMap<BareEvent, Option<RichEvent>>),
}
#[derive(Deserialize, Serialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum BareEvent {
BranchProtectionRule,
CheckRun,
CheckSuite,
Create,
Delete,
Deployment,
DeploymentStatus,
Discussion,
DiscussionComment,
Fork,
Gollum,
IssueComment,
Issues,
Label,
MergeGroup,
Milestone,
PageBuild,
Project,
ProjectCard,
ProjectColumn,
Public,
PullRequest,
PullRequestComment,
PullRequestReview,
PullRequestReviewComment,
PullRequestTarget,
Push,
RegistryPackage,
Release,
RepositoryDispatch,
// NOTE: `schedule` is omitted, since it's never bare.
Status,
Watch,
WorkflowCall,
WorkflowDispatch,
WorkflowRun,
}
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct Cron {
cron: String,
}
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct WorkflowCall {
inputs: HashMap<String, WorkflowCallInput>,
outputs: HashMap<String, WorkflowCallOutput>,
secrets: HashMap<String, WorkflowCallSecret>,
}
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct WorkflowCallInput {
description: Option<String>,
// TODO: model `default`?
#[serde(default)]
required: bool,
r#type: String,
}
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct WorkflowCallOutput {
description: Option<String>,
value: String,
}
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct WorkflowCallSecret {
description: Option<String>,
required: bool,
}
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct RichEvent {
#[serde(default)]
types: Vec<String>,
// `push | pull_request | pull_request_target` only.
#[serde(default)]
branches: Vec<String>,
// `push | pull_request | pull_request_target` only.
#[serde(default)]
branches_ignore: Vec<String>,
// `push` only.
#[serde(default)]
tags: Vec<String>,
// `push` only.
#[serde(default)]
tags_ignore: Vec<String>,
// `push | pull_request | pull_request_target` only.
#[serde(default)]
paths: Vec<String>,
// `push | pull_request | pull_request_target` only.
#[serde(default)]
paths_ignore: Vec<String>,
}
#[derive(Deserialize, Serialize, Default)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum Permissions {
/// Whatever default permissions come from the workflow's `GITHUB_TOKEN`.
#[default]
Token,
ReadAll,
WriteAll,
Explicit(ExplicitPermissions),
}
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct ExplicitPermissions {
pub actions: Permission,
pub checks: Permission,
pub contents: Permission,
pub deployments: Permission,
pub id_token: Permission,
pub issues: Permission,
pub discussions: Permission,
pub packages: Permission,
pub pages: Permission,
pub pull_requests: Permission,
pub repository_projects: Permission,
pub security_events: Permission,
pub statuses: Permission,
}
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum Permission {
Read,
Write,
None,
}

246
src/workflow/event.rs Normal file
View file

@ -0,0 +1,246 @@
use std::collections::HashMap;
use serde::Deserialize;
#[derive(Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum BareEvent {
BranchProtectionRule,
CheckRun,
CheckSuite,
Create,
Delete,
Deployment,
DeploymentStatus,
Discussion,
DiscussionComment,
Fork,
Gollum,
IssueComment,
Issues,
Label,
MergeGroup,
Milestone,
PageBuild,
Project,
ProjectCard,
ProjectColumn,
Public,
PullRequest,
PullRequestComment,
PullRequestReview,
PullRequestReviewComment,
PullRequestTarget,
Push,
RegistryPackage,
Release,
RepositoryDispatch,
// NOTE: `schedule` is omitted, since it's never bare.
Status,
Watch,
WorkflowCall,
WorkflowDispatch,
WorkflowRun,
}
#[derive(Default, Deserialize)]
#[serde(default, rename_all = "snake_case")]
pub struct Events {
pub branch_protection_rule: OptionalBody<GenericEvent>,
pub check_run: OptionalBody<GenericEvent>,
pub check_suite: OptionalBody<GenericEvent>,
// TODO: create + delete
// TODO: deployment + deployment_status
pub discussion: OptionalBody<GenericEvent>,
pub discussion_comment: OptionalBody<GenericEvent>,
// TODO: fork + gollum
pub issue_comment: OptionalBody<GenericEvent>,
pub issues: OptionalBody<GenericEvent>,
pub label: OptionalBody<GenericEvent>,
pub merge_group: OptionalBody<GenericEvent>,
pub milestone: OptionalBody<GenericEvent>,
// TODO: page_build
pub project: OptionalBody<GenericEvent>,
pub project_card: OptionalBody<GenericEvent>,
pub project_column: OptionalBody<GenericEvent>,
// TODO: public
pub pull_request: OptionalBody<PullRequest>,
pub pull_request_comment: OptionalBody<GenericEvent>,
pub pull_request_review: OptionalBody<GenericEvent>,
pub pull_request_review_comment: OptionalBody<GenericEvent>,
// NOTE: `pull_request_target` appears to have the same trigger filters as `pull_request`.
pub pull_request_target: OptionalBody<PullRequest>,
pub push: OptionalBody<Push>,
pub registry_package: OptionalBody<GenericEvent>,
pub release: OptionalBody<GenericEvent>,
pub repository_dispatch: OptionalBody<GenericEvent>,
pub schedule: OptionalBody<Vec<Cron>>,
// TODO: status
pub watch: OptionalBody<GenericEvent>,
pub workflow_call: OptionalBody<WorkflowCall>,
// TODO: Custom type.
pub workflow_dispatch: OptionalBody<WorkflowDispatch>,
pub workflow_run: OptionalBody<WorkflowRun>,
}
/// A generic container type for distinguishing between
/// a missing key, an explicitly null key, and an explicit value `T`.
///
/// This is needed for modeling `on:` triggers, since GitHub distinguishes
/// between the non-presence of an event (no trigger) and the presence
/// of an empty event body (e.g. `pull_request:`), which means "trigger
/// with the defaults for this event type."
pub enum OptionalBody<T> {
Default,
Missing,
Body(T),
}
impl<'de, T> Deserialize<'de> for OptionalBody<T>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Option::deserialize(deserializer).map(Into::into)
}
}
impl<T> From<Option<T>> for OptionalBody<T> {
fn from(value: Option<T>) -> Self {
match value {
Some(v) => OptionalBody::Body(v),
None => OptionalBody::Default,
}
}
}
impl<T> Default for OptionalBody<T> {
fn default() -> Self {
OptionalBody::Missing
}
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct GenericEvent {
#[serde(default)]
pub types: Vec<String>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct PullRequest {
#[serde(default)]
pub types: Vec<String>,
#[serde(flatten)]
pub branch_filters: Option<BranchFilters>,
#[serde(flatten)]
pub path_filters: Option<PathFilters>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Push {
#[serde(flatten)]
pub branch_filters: Option<BranchFilters>,
#[serde(flatten)]
pub path_filters: Option<PathFilters>,
#[serde(flatten)]
pub tag_filters: Option<TagFilters>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Cron {
pub cron: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct WorkflowCall {
pub inputs: HashMap<String, WorkflowCallInput>,
pub outputs: HashMap<String, WorkflowCallOutput>,
pub secrets: HashMap<String, WorkflowCallSecret>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct WorkflowCallInput {
pub description: Option<String>,
// TODO: model `default`?
#[serde(default)]
pub required: bool,
pub r#type: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct WorkflowCallOutput {
pub description: Option<String>,
pub value: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct WorkflowCallSecret {
pub description: Option<String>,
pub required: bool,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct WorkflowDispatch {
#[serde(default)]
pub inputs: HashMap<String, WorkflowDispatchInput>, // TODO: WorkflowDispatchInput
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct WorkflowDispatchInput {
pub description: Option<String>,
// TODO: model `default`?
#[serde(default)]
pub required: bool,
pub r#type: String,
// Only present when `type` is `choice`.
#[serde(default)]
pub options: Vec<String>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct WorkflowRun {
pub workflows: Vec<String>,
#[serde(default)]
pub types: Vec<String>,
#[serde(flatten)]
pub branch_filters: Option<BranchFilters>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum BranchFilters {
Branches(Vec<String>),
BranchesIgnore(Vec<String>),
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TagFilters {
Tags(Vec<String>),
TagsIgnore(Vec<String>),
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum PathFilters {
Paths(Vec<String>),
PathsIgnore(Vec<String>),
}

144
src/workflow/job.rs Normal file
View file

@ -0,0 +1,144 @@
use std::collections::HashMap;
use serde::Deserialize;
use serde_yaml::Value;
use crate::common::{BoE, Env, LoE, SoV};
use super::{Concurrency, Defaults, Permissions};
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct NormalJob {
pub name: Option<String>,
#[serde(default)]
pub permissions: Permissions,
#[serde(default)]
pub needs: Vec<String>,
pub r#if: Option<String>,
pub runs_on: RunsOn,
pub environment: Option<DeploymentEnvironment>,
pub concurrency: Option<Concurrency>,
#[serde(default)]
pub outputs: HashMap<String, String>,
#[serde(default)]
pub env: Env,
pub defaults: Option<Defaults>,
pub steps: Vec<Step>,
pub timeout_minutes: Option<u64>,
pub strategy: Option<Strategy>,
#[serde(default)]
pub continue_on_error: BoE,
pub container: Option<Container>,
#[serde(default)]
pub services: HashMap<String, Container>,
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum RunsOn {
Target(SoV<String>),
Group { group: String },
Label { label: SoV<String> },
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum DeploymentEnvironment {
Name(String),
NameURL { name: String, url: Option<String> },
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Step {
pub id: Option<String>,
pub r#if: Option<String>,
pub name: Option<String>,
pub timeout_minutes: Option<u64>,
#[serde(default)]
pub continue_on_error: BoE,
#[serde(flatten)]
pub body: StepBody,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum StepBody {
Uses {
uses: String,
working_directory: Option<String>,
shell: Option<String>,
#[serde(default)]
env: Env,
},
Run {
run: String,
#[serde(default)]
with: Env,
},
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Strategy {
pub matrix: Matrix,
pub fail_fast: Option<BoE>,
pub max_parallel: Option<u64>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Matrix {
#[serde(default)]
pub include: LoE<Vec<HashMap<String, Value>>>,
#[serde(default)]
pub exclude: LoE<Vec<HashMap<String, Value>>>,
#[serde(flatten)]
pub dimensions: LoE<HashMap<String, Vec<Value>>>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum Container {
Name(String),
Container {
image: String,
credentials: Option<DockerCredentials>,
#[serde(default)]
env: Env,
// TODO: model `ports`?
#[serde(default)]
volumes: Vec<String>,
options: Option<String>,
},
}
#[derive(Deserialize)]
pub struct DockerCredentials {
pub username: Option<String>,
pub password: Option<String>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ReusableWorkflowCallJob {
pub name: Option<String>,
#[serde(default)]
pub permissions: Permissions,
#[serde(default)]
pub needs: Vec<String>,
pub r#if: Option<String>,
pub uses: String,
#[serde(default)]
pub with: Env,
pub secrets: Option<Secrets>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Secrets {
Inherit,
#[serde(untagged)]
Env(#[serde(default)] Env),
}

169
src/workflow/mod.rs Normal file
View file

@ -0,0 +1,169 @@
use std::collections::HashMap;
use serde::Deserialize;
use crate::common::{BoE, Env};
pub mod event;
pub mod job;
/// A single GitHub Actions workflow.
///
/// See: <https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions>
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Workflow {
pub name: Option<String>,
pub run_name: Option<String>,
pub on: Trigger,
#[serde(default)]
pub permissions: Permissions,
#[serde(default)]
pub env: Env,
pub defaults: Option<Defaults>,
pub concurrency: Option<Concurrency>,
pub jobs: HashMap<String, Job>,
}
/// The triggering condition or conditions for a workflow.
///
/// Workflow triggers take three forms:
///
/// 1. A single webhook event name:
///
/// ```yaml
/// on: push
/// ```
/// 2. A list of webhook event names:
///
/// ```yaml
/// on: [push, fork]
/// ```
///
/// 3. A mapping of event names with (optional) configurations:
///
/// ```yaml
/// on:
/// push:
/// branches: [main]
/// pull_request:
/// ```
#[derive(Deserialize)]
#[serde(rename_all = "snake_case", untagged)]
pub enum Trigger {
BareEvent(event::BareEvent),
BareEvents(Vec<event::BareEvent>),
Events(event::Events),
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum Permissions {
Base(BasePermission),
Explicit(ExplicitPermissions),
}
impl Default for Permissions {
fn default() -> Self {
Self::Base(BasePermission::Default)
}
}
#[derive(Deserialize, Default, Debug, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum BasePermission {
/// Whatever default permissions come from the workflow's `GITHUB_TOKEN`.
#[default]
Default,
ReadAll,
WriteAll,
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub struct ExplicitPermissions {
#[serde(default)]
pub actions: Permission,
#[serde(default)]
pub checks: Permission,
#[serde(default)]
pub contents: Permission,
#[serde(default)]
pub deployments: Permission,
#[serde(default)]
pub id_token: Permission,
#[serde(default)]
pub issues: Permission,
#[serde(default)]
pub discussions: Permission,
#[serde(default)]
pub packages: Permission,
#[serde(default)]
pub pages: Permission,
#[serde(default)]
pub pull_requests: Permission,
#[serde(default)]
pub repository_projects: Permission,
#[serde(default)]
pub security_events: Permission,
#[serde(default)]
pub statuses: Permission,
}
#[derive(Deserialize, Default, Debug, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum Permission {
Read,
Write,
#[default]
None,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Defaults {
pub run: Option<RunDefaults>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct RunDefaults {
pub shell: Option<String>,
pub working_directory: Option<String>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Concurrency {
pub group: String,
#[serde(default)]
pub cancel_in_progress: BoE,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum Job {
NormalJob(job::NormalJob),
ReusableWorkflowCallJob(job::ReusableWorkflowCallJob),
}
#[cfg(test)]
mod tests {
use crate::workflow::ExplicitPermissions;
use super::Permissions;
#[test]
fn permissions_deserializes() {
assert_eq!(
serde_yaml::from_str::<Permissions>("read-all").unwrap(),
Permissions::Base(crate::workflow::BasePermission::ReadAll)
);
let perm = "security-events: write";
assert!(matches!(
serde_yaml::from_str::<ExplicitPermissions>(perm),
Ok(_)
));
}
}

View file

@ -0,0 +1,54 @@
# https://github.com/pypa/pip-audit/blob/1fd67af0653a8e66b9470adab2e408a435632f19/.github/workflows/scorecards.yml
name: Scorecards supply-chain security
on:
# Only the default branch is supported.
branch_protection_rule:
schedule:
- cron: "19 4 * * 0"
push:
branches: ["main"]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecards analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Used to receive a badge. (Upcoming feature)
id-token: write
steps:
- name: "Checkout code"
uses: actions/checkout@v4.1.1 # tag=v3.0.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # tag=v2.3.1
with:
results_file: results.sarif
results_format: sarif
# Publish the results for public repositories to enable scorecard badges. For more details, see
# https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories, `publish_results` will automatically be set to `false`, regardless
# of the value entered here.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # tag=v3.1.3
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@cdcdbb579706841c47f7063dda365e292e5cad7a # tag=v2.13.4
with:
sarif_file: results.sarif

View file

@ -1,6 +1,6 @@
use std::{env, path::Path};
use glomar_models::workflow::Workflow;
use glomar_models::workflow::{event::OptionalBody, job::RunsOn, Job, Trigger, Workflow};
fn load_workflow(name: &str) -> Workflow {
let workflow_path = Path::new(env!("CARGO_MANIFEST_DIR"))
@ -20,3 +20,23 @@ fn test_load_all() {
serde_yaml::from_str::<Workflow>(&workflow_contents).unwrap();
}
}
#[test]
fn test_pip_audit_ci() {
let workflow = load_workflow("pip-audit-ci.yml");
assert!(
matches!(workflow.on, Trigger::Events(events) if matches!(events.pull_request, OptionalBody::Default))
);
let test_job = &workflow.jobs["test"];
if let Job::NormalJob(test_job) = test_job {
assert_eq!(test_job.name, None);
assert_eq!(
test_job.runs_on,
RunsOn::Target(String::from("ubuntu-latest").into())
);
} else {
panic!("oops");
}
}