diff --git a/Cargo.lock b/Cargo.lock index fbd715122..cd01b90d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,7 +469,6 @@ dependencies = [ "serde", "serde_json", "serde_with", - "serde_yml", "serial_test", "strum", "strum_macros", @@ -504,7 +503,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f46ba11692cd1e4b09cd123877e02b74e180acae237caf905ef20b42e14e206" dependencies = [ "anyhow", - "core-foundation 0.10.1", + "core-foundation", "filetime", "hex", "ignore", @@ -731,16 +730,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -1025,15 +1014,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -1118,21 +1098,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1531,22 +1496,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" version = "0.1.16" @@ -1566,11 +1515,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.0", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] @@ -1955,16 +1902,6 @@ dependencies = [ "redox_syscall 0.5.17", ] -[[package]] -name = "libyml" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3302702afa434ffa30847a83305f0a69d6abd74293b6554c18ec85c7ef30c980" -dependencies = [ - "anyhow", - "version_check", -] - [[package]] name = "libz-rs-sys" version = "0.5.2" @@ -2106,23 +2043,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework 2.11.1", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nu-ansi-term" version = "0.50.1" @@ -2354,50 +2274,12 @@ dependencies = [ "uuid", ] -[[package]] -name = "openssl" -version = "0.10.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" -[[package]] -name = "openssl-sys" -version = "0.9.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "opentelemetry" version = "0.30.0" @@ -2997,7 +2879,6 @@ checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64 0.22.1", "bytes", - "encoding_rs", "futures-channel", "futures-core", "futures-util", @@ -3007,12 +2888,9 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", - "mime", - "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -3023,7 +2901,6 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", "tokio-rustls", "tokio-util", "tower", @@ -3137,7 +3014,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.3.0", + "security-framework", ] [[package]] @@ -3254,19 +3131,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.9.4", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - [[package]] name = "security-framework" version = "3.3.0" @@ -3274,7 +3138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" dependencies = [ "bitflags 2.9.4", - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -3476,21 +3340,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "serde_yml" -version = "0.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" -dependencies = [ - "indexmap 2.11.1", - "itoa", - "libyml", - "memchr", - "ryu", - "serde", - "version_check", -] - [[package]] name = "serial_test" version = "3.2.0" @@ -3749,27 +3598,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.9.4", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tempfile" version = "3.22.0" @@ -3963,16 +3791,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.2" @@ -4673,17 +4491,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" -[[package]] -name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link 0.1.3", - "windows-result", - "windows-strings", -] - [[package]] name = "windows-result" version = "0.3.4" diff --git a/Cargo.toml b/Cargo.toml index ed15c38ef..139f65e6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,6 @@ port_check = "0.3.0" rand = "0.9.2" rust-toolchain-file = "0.1.1" serde_json = "1.0.145" -serde_yml = "0.0.12" strum_macros = "0.27.2" tracing = { version = "0.1.41", features = ["std"] } tracing-subscriber = { version = "0.3.20", features = [ @@ -88,6 +87,7 @@ cargo-util = "0.2.22" diffy = "0.4.2" strum = "0.27.2" mime_guess = "2.0.5" +reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls" ] } [dependencies.anyhow] features = [] @@ -156,7 +156,7 @@ version = "1.47.1" assert_fs = "1.1.3" dotenvy = "0.15" indoc = "2.0.6" -reqwest = "0.12" +reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls" ] } serial_test = "3.2.0" testcontainers = "0.25.0" wiremock = "0.6.5" diff --git a/src/commands/check_workspace/mod.rs b/src/commands/check_workspace/mod.rs index 3bd059706..23023d2a0 100644 --- a/src/commands/check_workspace/mod.rs +++ b/src/commands/check_workspace/mod.rs @@ -20,8 +20,8 @@ use rust_toolchain_file::toml::Parser as ToolchainParser; use serde::ser::{Serialize as SerSerialize, SerializeStruct, Serializer as SerSerializer}; use serde::{Deserialize, Serialize, Serializer}; use serde_json::from_value; -use serde_yml::Value; use strum_macros::EnumString; +use toml::Value; use crate::commands::check_workspace::binary::BinaryStore; use crate::commands::check_workspace::docker::Docker; diff --git a/src/commands/generate_workflow/mod.rs b/src/commands/generate_workflow/mod.rs deleted file mode 100644 index 815caaef5..000000000 --- a/src/commands/generate_workflow/mod.rs +++ /dev/null @@ -1,782 +0,0 @@ -use std::default::Default; -use std::fmt::{Display, Formatter}; -use std::fs::File; -use std::hash::Hash; -use std::io::{BufReader, BufWriter}; -use std::path::PathBuf; -use std::str::FromStr; - -use anyhow::Context; -use clap::Parser; -use indexmap::IndexMap; -use serde::ser::{SerializeMap, SerializeSeq, SerializeStruct}; -use serde::{Deserialize, Serialize, Serializer}; -use serde_with::{OneOrMany, formats::PreferOne, serde_as}; -use serde_yml::Value; -use void::Void; - -use cargo_metadata::PackageId; - -use itertools::Itertools; - -use crate::commands::check_workspace::{Options as CheckWorkspaceOptions, check_workspace}; -use crate::utils::{FromMap, deserialize_opt_string_or_map, deserialize_opt_string_or_struct}; -use crate::{PackageRelatedOptions, PrettyPrintable}; - -use self::workflows::Workflow; -use self::workflows::publish_docker::PublishDockerWorkflow; -use self::workflows::publish_npm_napi::PublishNpmNapiWorkflow; -use self::workflows::publish_rust_binary::PublishRustBinaryWorkflow; -use self::workflows::publish_rust_installer::PublishRustInstallerWorkflow; -use self::workflows::publish_rust_registry::PublishRustRegistryWorkflow; - -use super::check_workspace::Results as CheckResults; - -mod workflows; - -const EMPTY_WORKFLOW: &str = r#" -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: -"#; - -const CHECK_SCRIPT: &str = r#"if [ -z "${HEAD_REF}" ]; then - CHECK_CHANGED=() -else - CHECK_CHANGED=('--check-changed' '--changed-base-ref' "origin/${BASE_REF}" '--changed-head-ref' "${HEAD_REF}") - git fetch origin ${BASE_REF} --depth 1 -fi -workspace=$(fslabscli check-workspace --json --check-publish "${CHECK_CHANGED[@]}" --binary-store-storage-account ${{ vars.BINARY_STORE_STORAGE_ACCOUNT }} --binary-store-container-name ${{ vars.BINARY_STORE_CONTAINER_NAME }} --binary-store-access-key ${{ secrets.BINARY_STORE_ACCESS_KEY }} --cargo-default-publish --cargo-registry foresight-mining-software-corporation --cargo-registry-url https://shipyard.rs/api/v1/shipyard/krates/by-name/ --cargo-registry-user-agent "shipyard ${{ secrets.CARGO_PRIVATE_REGISTRY_TOKEN }}") -if [ $? -ne 0 ]; then - echo "Could not check workspace" - exit 1 -fi -echo workspace=${workspace} >> $GITHUB_OUTPUT"#; - -#[derive(Debug, Parser)] -#[command(about = "Check directory for crates that need to be published.")] -pub struct Options { - #[arg(long)] - template: Option, - #[arg(long, default_value_t = false)] - no_depends_on_template_jobs: bool, - #[arg(long, default_value = "main")] - build_workflow_version: String, - #[arg(long, default_value = "2.7.0")] - fslabscli_version: String, - #[arg(long, default_value_t = false)] - cargo_default_publish: bool, - #[arg(long, default_value = "0 19 * * *")] - nightly_cron_schedule: String, - /// Default branch to consider for publishing and schedule release - #[arg(long, default_value = "main")] - default_branch: String, -} - -#[derive(Serialize)] -pub struct GenerateResult {} - -impl Display for GenerateResult { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "") - } -} - -impl PrettyPrintable for GenerateResult { - fn pretty_print(&self) -> String { - format!("{self}") - } -} - -#[derive(Serialize, Debug, Deserialize, PartialEq, Clone)] -pub struct GithubWorkflowInput { - pub description: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub default: Option, - #[serde(default)] - pub required: bool, - #[serde(rename = "type")] - pub input_type: String, -} - -#[derive(Serialize, Debug, Deserialize, PartialEq, Clone)] -pub struct GithubWorkflowSecret { - pub description: String, - #[serde(default)] - pub required: bool, -} - -#[derive(Serialize, Debug, Deserialize, PartialEq, Clone)] -pub struct GithubWorkflowCron { - pub cron: String, -} - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct GithubWorkflowTriggerPayload { - #[serde(skip_serializing_if = "Option::is_none")] - pub branches: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub tags: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub paths: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub inputs: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub secrets: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub crons: Option>, -} - -impl Serialize for GithubWorkflowTriggerPayload { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - if let Some(crons) = self.crons.clone() { - let mut seq = serializer.serialize_seq(Some(crons.len()))?; - for e in crons { - seq.serialize_element(&e)?; - } - seq.end() - } else { - let mut state = serializer.serialize_struct("GithubWorkflowTriggerPayload", 5)?; - if let Some(branches) = &self.branches { - state.serialize_field("branches", branches)?; - } - if let Some(tags) = &self.tags { - state.serialize_field("tags", tags)?; - } - if let Some(paths) = &self.paths { - state.serialize_field("paths", paths)?; - } - if let Some(inputs) = &self.inputs { - state.serialize_field("inputs", inputs)?; - } - if let Some(secrets) = &self.secrets { - state.serialize_field("secrets", secrets)?; - } - state.end() - } - } -} - -#[derive(Serialize, Debug, Deserialize, Eq, PartialEq, Hash, Ord, PartialOrd, Clone)] -#[serde(rename_all = "snake_case")] -pub enum GithubWorkflowTrigger { - PullRequest, - Push, - WorkflowCall, - WorkflowDispatch, - Schedule, -} - -#[derive(Debug, Default, Deserialize, Eq, PartialEq, Clone)] -pub struct GithubWorkflowJobSecret { - pub inherit: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub secrets: Option>, -} - -impl Serialize for GithubWorkflowJobSecret { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - if self.inherit { - serializer.serialize_str("inherit") - } else { - match self.secrets.clone() { - Some(secrets) => { - let mut map = serializer.serialize_map(Some(secrets.len()))?; - for (k, v) in secrets { - map.serialize_entry(&k, &v)?; - } - map.end() - } - None => serializer.serialize_none(), - } - } - } -} - -#[derive(Serialize, Debug, Default, Deserialize, Eq, PartialEq, Clone)] -pub struct GithubWorkflowJobEnvironment { - pub name: String, - pub url: Option, -} - -#[derive(Serialize, Debug, Default, Deserialize, Eq, PartialEq, Clone)] -#[serde(rename_all = "snake_case")] -pub struct GithubWorkflowJobStrategy { - pub matrix: IndexMap, - pub fail_false: Option, -} - -#[derive(Serialize, Debug, Default, Deserialize, Eq, PartialEq, Clone)] -#[serde(rename_all = "kebab-case")] -pub struct GithubWorkflowJobSteps { - #[serde(skip_serializing_if = "Option::is_none")] - id: Option, - #[serde(rename = "if", skip_serializing_if = "Option::is_none")] - pub step_if: Option, - #[serde(skip_serializing_if = "Option::is_none")] - name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - shell: Option, - #[serde(skip_serializing_if = "Option::is_none")] - run: Option, - #[serde(skip_serializing_if = "Option::is_none")] - uses: Option, - #[serde(skip_serializing_if = "Option::is_none")] - working_directory: Option, - #[serde(skip_serializing_if = "Option::is_none")] - with: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - env: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - continue_on_error: Option, - #[serde(skip_serializing_if = "Option::is_none")] - timeout_minutes: Option, -} - -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -struct GithubWorkflowJobContainer { - pub image: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub env: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub credentials: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub ports: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub volumes: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, -} - -#[serde_as] -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -#[serde(rename_all = "kebab-case")] -struct GithubWorkflowJob { - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub permissions: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub uses: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub needs: Option>, - #[serde(rename = "if", skip_serializing_if = "Option::is_none")] - pub job_if: Option, - #[serde_as(deserialize_as = "Option>")] - #[serde(skip_serializing_if = "Option::is_none")] - pub runs_on: Option>, - #[serde( - default, - deserialize_with = "deserialize_opt_string_or_struct", - skip_serializing_if = "Option::is_none" - )] - pub environment: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub concurrency: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub defaults: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub with: Option>, - #[serde( - default, - deserialize_with = "deserialize_opt_string_or_map", - skip_serializing_if = "Option::is_none" - )] - pub secrets: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub env: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub outputs: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub steps: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub timeout_minutes: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub strategy: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub continue_on_error: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub container: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub services: Option>, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "kebab-case")] -struct GithubWorkflowDefaultsRun { - pub shell: Option, - pub working_directory: Option, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -struct GithubWorkflowDefaults { - pub run: GithubWorkflowDefaultsRun, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -struct GithubWorkflow { - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub run_name: Option, - #[serde(rename = "on", skip_serializing_if = "Option::is_none")] - pub triggers: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub defaults: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub env: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub concurrency: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub permissions: Option>, - pub jobs: IndexMap, -} - -impl FromStr for GithubWorkflowJobSecret { - type Err = Void; - - fn from_str(_s: &str) -> Result { - Ok(Self { - inherit: true, - secrets: None, - }) - } -} - -impl FromStr for GithubWorkflowJobEnvironment { - type Err = Void; - - fn from_str(s: &str) -> Result { - Ok(Self { - name: s.to_string(), - url: None, - }) - } -} - -impl FromMap for GithubWorkflowJobSecret { - fn from_map(map: IndexMap) -> Result - where - Self: Sized, - { - Ok(Self { - inherit: false, - secrets: Some(map), - }) - } -} - -#[derive(Clone, Default, Debug)] -pub struct StringBool(bool); - -impl From for Value { - fn from(val: StringBool) -> Value { - Value::String(match val.0 { - true => "true".to_string(), - false => "false".to_string(), - }) - } -} - -impl From for StringBool { - fn from(value: Value) -> Self { - Self(value.as_bool().unwrap_or(false)) - } -} - -impl Serialize for StringBool { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self.0 { - true => serializer.serialize_str("true"), - false => serializer.serialize_str("false"), - } - } -} - -fn get_base_workflow(template: &Option, name: String) -> anyhow::Result { - let mut workflow: GithubWorkflow = (match template { - Some(template) => { - let file = File::open(template)?; - let reader = BufReader::new(file); - serde_yml::from_reader(reader) - } - None => serde_yml::from_str(EMPTY_WORKFLOW), - }) - .with_context(|| "Could not parse workflow template")?; - workflow.name = Some(name); - Ok(workflow) -} - -fn get_publish_triggers( - default_branch: String, -) -> IndexMap { - IndexMap::from([ - ( - GithubWorkflowTrigger::Push, - GithubWorkflowTriggerPayload { - branches: Some(vec![default_branch]), - tags: Some(vec![ - "*-alpha-*.*.*".to_string(), - "*-beta-*.*.*".to_string(), - "*-prod-*.*.*".to_string(), - ]), - ..Default::default() - }, - ), - ( - GithubWorkflowTrigger::WorkflowDispatch, - GithubWorkflowTriggerPayload { - inputs: Some(IndexMap::from([( - "publish".to_string(), - GithubWorkflowInput { - description: "Trigger with publish".to_string(), - default: None, - required: false, - input_type: "boolean".to_string(), - }, - )])), - ..Default::default() - }, - ), - ]) -} - -fn get_check_workspace_job( - results: &CheckResults, - required_jobs: Vec, - fslabscli_version: &str, -) -> GithubWorkflowJob { - // For each package published to docker, we need to login to the registry in order to check if the package needs publishing - let docker_steps: Vec = results - .members - .iter() - .filter(|(_, v)| v.publish_detail.docker.publish) - .unique_by(|(_, v)| v.publish_detail.docker.repository.clone()) - .filter_map(|(_, v)| { - if let Some(repo) = v.publish_detail.docker.repository.clone() { - let github_secret_key = repo.clone().replace('.', "_").to_ascii_uppercase(); - return Some(GithubWorkflowJobSteps { - name: Some(format!("Docker Login to {repo}")), - uses: Some("docker/login-action@v3".to_string()), - with: Some(IndexMap::from([ - ("registry".to_string(), repo.clone()), - ( - "username".to_string(), - format!("${{{{ secrets.DOCKER_{github_secret_key}_USERNAME }}}}"), - ), - ( - "password".to_string(), - format!("${{{{ secrets.DOCKER_{github_secret_key}_PASSWORD }}}}"), - ), - ])), - ..Default::default() - }); - } - None - }) - .collect(); - // For each packaeg published to npm, we need to login to the npm registry - let npm_steps: Vec = results - .members - .iter() - .filter(|(_, v)| v.publish_detail.npm_napi.publish) - .unique_by(|(_, v)| v.publish_detail.npm_napi.scope.clone()) - .filter_map(|(_, v)| { - if let Some(scope) = v.publish_detail.npm_napi.scope.clone() { - let github_secret_key = scope.clone().replace('.', "_").to_ascii_uppercase(); - let run = format!( - r#" -echo "@{scope}:registry=https://npm.pkg.github.com/" >> ~/.npmrc -echo "//npm.pkg.github.com/:_authToken=${{{{ secrets.NPM_{github_secret_key}_TOKEN }}}}" >> ~/.npmrc - "# - ); - return Some(GithubWorkflowJobSteps { - name: Some(format!("NPM Login to {scope}")), - shell: Some("bash".to_string()), - run: Some(run.to_string()), - ..Default::default() - }); - } - None - }) - .collect(); - let mut install_fslabscli_args = IndexMap::from([( - "token".to_string(), - "${{ secrets.GITHUB_TOKEN }}".to_string(), - )]); - install_fslabscli_args.insert("version".to_string(), fslabscli_version.to_string()); - let steps = vec![ - GithubWorkflowJobSteps { - name: Some("Install FSLABScli".to_string()), - uses: Some("ForesightMiningSoftwareCorporation/fslabscli-action@v1".to_string()), - with: Some(install_fslabscli_args), - ..Default::default() - }, - GithubWorkflowJobSteps { - name: Some("Checkout repo".to_string()), - uses: Some("actions/checkout@v4".to_string()), - with: Some(IndexMap::from([( - "ref".to_string(), - "${{ github.head_ref }}".to_string(), - )])), - ..Default::default() - }, - GithubWorkflowJobSteps { - name: Some("Check workspace".to_string()), - working_directory: Some(".".to_string()), - id: Some("check_workspace".to_string()), - shell: Some("bash".to_string()), - env: Some(IndexMap::from([ - ("BASE_REF".to_string(), "${{ github.base_ref }}".to_string()), - ("HEAD_REF".to_string(), "${{ github.head_ref }}".to_string()), - ])), - run: Some(CHECK_SCRIPT.to_string()), - ..Default::default() - }, - ]; - GithubWorkflowJob { - name: Some("Check which workspace member changed and / or needs publishing".to_string()), - runs_on: Some(vec!["ubuntu-latest".to_string()]), - needs: Some(required_jobs), - outputs: Some(IndexMap::from([( - "workspace".to_string(), - "${{ steps.check_workspace.outputs.workspace }}".to_string(), - )])), - steps: Some([docker_steps, npm_steps, steps].concat()), - ..Default::default() - } -} - -pub async fn generate_workflow( - options: Box, - working_directory: PathBuf, -) -> anyhow::Result { - // Get Directory information - let package_related_options = PackageRelatedOptions::default(); - let check_workspace_options = CheckWorkspaceOptions::new(); - let results = check_workspace( - &package_related_options, - &check_workspace_options, - working_directory.clone(), - ) - .await - .with_context(|| "Could not get directory information")?; - // Get workflows, useful in case where additional tools need to be run before - let mut publish_workflow = - get_base_workflow(&options.template, "CI - CD: Publishing".to_string())?; - - // Triggers - let mut publish_triggers = get_publish_triggers(options.default_branch); - // If we have binaries to publish, nightly Publish should be done every night at 3AM - if results - .members - .values() - .any(|r| r.publish_detail.binary.publish || r.publish_detail.binary.installer.publish) - { - publish_triggers.insert( - GithubWorkflowTrigger::Schedule, - GithubWorkflowTriggerPayload { - crons: Some(vec![GithubWorkflowCron { - cron: options.nightly_cron_schedule.clone(), - }]), - ..Default::default() - }, - ); - } - publish_workflow.triggers = Some(publish_triggers); - - // Create check_workspace job, and add it to both workflows - let check_job_key = "check_changed_and_publish".to_string(); - let check_workspace_job = get_check_workspace_job( - &results, - publish_workflow.jobs.keys().cloned().collect(), - &options.fslabscli_version, - ); - publish_workflow - .jobs - .insert(check_job_key.clone(), check_workspace_job); - - let mut member_keys: Vec = results.members.keys().cloned().collect(); - member_keys.sort(); - let base_if = "!cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled')".to_string(); - - let mut actual_publishings: Vec = vec![]; - for member_key in member_keys { - let Some(member) = results.members.get(&member_key) else { - continue; - }; - let working_directory = member.path.to_string_lossy().to_string(); - let dynamic_value_base = - format!("(fromJSON(needs.{check_job_key}.outputs.workspace).{member_key}"); - - let mut testing_requirements: Vec = vec![check_job_key.clone()]; - let mut publishing_requirements: Vec = vec![check_job_key.clone()]; - - for dependency in &member.dependencies { - // Each testing job needs to depends on its'previous testing job - if let Some(package_name) = dependency.package_id.clone() { - testing_requirements.push(format!("test_{package_name}")); - if dependency.publishable - && let Some(dependency_package) = results.members.get(&package_name) - { - if dependency_package.publish_detail.binary.publish { - publishing_requirements - .push(format!("publish_rust_binary_{package_name}",)); - } - if dependency_package.publish_detail.binary.installer.publish { - publishing_requirements - .push(format!("publish_rust_installer_{package_name}",)); - } - if dependency_package.publish_detail.cargo.publish { - publishing_requirements - .push(format!("publish_rust_registry_{package_name}",)); - } - if dependency_package.publish_detail.docker.publish { - publishing_requirements.push(format!("publish_docker_{package_name}",)); - } - if dependency_package.publish_detail.npm_napi.publish { - publishing_requirements.push(format!("publish_npm_napi_{package_name}",)); - } - } - } - } - let mut member_workflows: Vec> = vec![]; - if member.publish { - if member.publish_detail.binary.publish { - let windows_working_directory = match working_directory == "." { - //true => "".to_string(), - true => working_directory.clone(), - false => working_directory.clone(), - }; - member_workflows.push(Box::new(PublishRustBinaryWorkflow::new( - member.package.clone(), - member.publish_detail.binary.targets.clone(), - member.publish_detail.additional_args.clone(), - member.publish_detail.binary.sign, - windows_working_directory.clone(), - &dynamic_value_base, - ))); - if member.publish_detail.binary.installer.publish { - member_workflows.push(Box::new(PublishRustInstallerWorkflow::new( - member.package.clone(), - windows_working_directory.clone(), - member.publish_detail.binary.sign, - &dynamic_value_base, - ))); - } - } - if member.publish_detail.docker.publish { - member_workflows.push(Box::new(PublishDockerWorkflow::new( - member.package.clone(), - member.package.clone(), - working_directory.clone(), - member.publish_detail.docker.context.clone(), - member.publish_detail.docker.dockerfile.clone(), - member.publish_detail.docker.repository.clone(), - &dynamic_value_base, - ))); - } - if member.publish_detail.npm_napi.publish { - member_workflows.push(Box::new(PublishNpmNapiWorkflow::new( - member.package.clone(), - working_directory.clone(), - &dynamic_value_base, - ))); - } - if member.publish_detail.cargo.publish { - member_workflows.push(Box::new(PublishRustRegistryWorkflow::new( - member.package.clone(), - working_directory.clone(), - &dynamic_value_base, - ))); - } - } - - let publishing_if = format!( - "{base_if} && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish))" - ); - - for publishing_job in member_workflows.iter() { - let mut needs = publishing_requirements.clone(); - if let Some(additional_keys) = publishing_job.get_additional_dependencies() { - needs.extend(additional_keys); - } - let job = GithubWorkflowJob { - name: Some(format!( - "{} {}: {}", - publishing_job.job_label(), - member.workspace, - member.package - )), - needs: Some(needs), - uses: Some( - format!( - "ForesightMiningSoftwareCorporation/github/.github/workflows/{}.yml@{}", - publishing_job.workflow_name(), - options.build_workflow_version - ) - .to_string(), - ), - job_if: Some(format!( - "${{{{ {} && {}.publish_detail.{}.publish) }}}}", - publishing_if, - dynamic_value_base, - publishing_job.publish_info_key() - )), - with: Some(publishing_job.get_inputs()), - secrets: Some(GithubWorkflowJobSecret { - inherit: true, - secrets: None, - }), - ..Default::default() - }; - let key = format!("{}_{}", publishing_job.job_prefix_key(), member.package); - actual_publishings.push(key.clone()); - publish_workflow.jobs.insert(key, job); - } - } - // Add Reporting - // Publishing - let mut publishing_reporting_needs = vec![check_job_key.clone()]; - publishing_reporting_needs.extend(actual_publishings); - publish_workflow.jobs.insert("publishing_results".to_string(), GithubWorkflowJob { - name: Some("Publishing Results".to_string()), - job_if: Some("always() && !contains(needs.*.result, 'cancelled')".to_string()), - uses: Some( - format!( - "ForesightMiningSoftwareCorporation/github/.github/workflows/check_summaries.yml@{}", - options.build_workflow_version - ) - ), - with: Some( - IndexMap::from([ - ("run_type".to_string(), "publishing".into()), - ("check_changed_outcome".to_string(), format!("${{{{ needs.{check_job_key}.result }}}}").into()), - ("fslabscli_version".to_string(), options.fslabscli_version.clone().into()) - ]) - ), - secrets: Some(GithubWorkflowJobSecret { - inherit: true, - secrets: None, - }), - needs: Some(publishing_reporting_needs), - ..Default::default() - }); - // If we are splitted then we actually need to create two files - let release_file_path = working_directory.join(".github/workflows/release_publish.yml"); - let release_file = File::create(release_file_path)?; - let mut release_writer = BufWriter::new(release_file); - serde_yml::to_writer(&mut release_writer, &publish_workflow)?; - Ok(GenerateResult {}) -} diff --git a/src/commands/generate_workflow/workflows/mod.rs b/src/commands/generate_workflow/workflows/mod.rs deleted file mode 100644 index 6f0ed3bd1..000000000 --- a/src/commands/generate_workflow/workflows/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -use indexmap::IndexMap; -use serde_yml::Value; -pub mod publish_docker; -pub mod publish_npm_napi; -pub mod publish_rust_binary; -pub mod publish_rust_installer; -pub mod publish_rust_registry; - -pub trait Workflow { - fn job_prefix_key(&self) -> String; - fn job_label(&self) -> String; - fn workflow_name(&self) -> String; - fn publish_info_key(&self) -> String; - fn get_inputs(&self) -> IndexMap; - fn get_additional_dependencies(&self) -> Option>; -} diff --git a/src/commands/generate_workflow/workflows/publish_docker.rs b/src/commands/generate_workflow/workflows/publish_docker.rs deleted file mode 100644 index 1b2258ad5..000000000 --- a/src/commands/generate_workflow/workflows/publish_docker.rs +++ /dev/null @@ -1,107 +0,0 @@ -use indexmap::IndexMap; -use serde_yml::Value; - -use super::Workflow; - -#[derive(Default, Clone)] -pub struct PublishDockerWorkflowOutputs { - /// Was the binary released - pub _released: bool, -} - -#[derive(Default, Clone)] -pub struct PublishDockerWorkflowInputs { - /// Package name - pub package: String, - /// Package version - pub version: String, - /// Docker image - pub image: String, - /// Docker Context image - pub context: Option, - /// Dockerfile path - pub dockerfile: Option, - /// Docker registry - pub registry: Option, - /// Which toolchain to use - pub toolchain: String, - /// Working directory to run the cargo command - pub working_directory: String, -} - -impl From<&PublishDockerWorkflowInputs> for IndexMap { - fn from(val: &PublishDockerWorkflowInputs) -> Self { - let mut map: IndexMap = IndexMap::new(); - map.insert("package".to_string(), val.package.clone().into()); - map.insert("version".to_string(), val.version.clone().into()); - map.insert("image".to_string(), val.image.clone().into()); - map.insert("toolchain".to_string(), val.toolchain.clone().into()); - if let Some(context) = &val.context { - map.insert("context".to_string(), context.clone().into()); - } - if let Some(dockerfile) = &val.dockerfile { - map.insert("dockerfile".to_string(), dockerfile.clone().into()); - } - if let Some(registry) = &val.registry { - map.insert("registry".to_string(), registry.clone().into()); - } - map.insert( - "working_directory".to_string(), - val.working_directory.clone().into(), - ); - map - } -} - -pub struct PublishDockerWorkflow { - pub inputs: PublishDockerWorkflowInputs, - pub _outputs: Option, -} - -impl PublishDockerWorkflow { - pub fn new( - package: String, - image: String, - working_directory: String, - context: Option, - dockerfile: Option, - registry: Option, - dynamic_value_base: &str, - ) -> Self { - Self { - inputs: PublishDockerWorkflowInputs { - package, - image, - working_directory, - context, - dockerfile, - registry, - version: format!("${{{{ {}.{}) }}}}", dynamic_value_base, "version"), - toolchain: format!("${{{{ {}.{}) }}}}", dynamic_value_base, "toolchain"), - }, - _outputs: None, - } - } -} - -impl Workflow for PublishDockerWorkflow { - fn job_prefix_key(&self) -> String { - "publish_docker".to_string() - } - - fn job_label(&self) -> String { - "Publish Docker".to_string() - } - fn workflow_name(&self) -> String { - "docker_publish".to_string() - } - fn publish_info_key(&self) -> String { - "docker".to_string() - } - fn get_inputs(&self) -> IndexMap { - (&self.inputs).into() - } - fn get_additional_dependencies(&self) -> Option> { - None - } -} diff --git a/src/commands/generate_workflow/workflows/publish_npm_napi.rs b/src/commands/generate_workflow/workflows/publish_npm_napi.rs deleted file mode 100644 index daf271782..000000000 --- a/src/commands/generate_workflow/workflows/publish_npm_napi.rs +++ /dev/null @@ -1,73 +0,0 @@ -use indexmap::IndexMap; -use serde_yml::Value; - -use super::Workflow; - -#[derive(Default, Clone)] -pub struct PublishNpmNapiWorkflowOutputs { - /// Was the binary released - pub _released: bool, -} - -#[derive(Default, Clone)] -pub struct PublishNpmNapiWorkflowInputs { - /// Package name - pub package: String, - /// Package version - pub version: String, - /// Working directory to run the cargo command - pub working_directory: String, -} - -impl From<&PublishNpmNapiWorkflowInputs> for IndexMap { - fn from(val: &PublishNpmNapiWorkflowInputs) -> Self { - let mut map: IndexMap = IndexMap::new(); - map.insert("package".to_string(), val.package.clone().into()); - map.insert("version".to_string(), val.version.clone().into()); - map.insert( - "working_directory".to_string(), - val.working_directory.clone().into(), - ); - map - } -} - -pub struct PublishNpmNapiWorkflow { - pub inputs: PublishNpmNapiWorkflowInputs, - pub _outputs: Option, -} - -impl PublishNpmNapiWorkflow { - pub fn new(package: String, working_directory: String, dynamic_value_base: &str) -> Self { - Self { - inputs: PublishNpmNapiWorkflowInputs { - package, - working_directory, - version: format!("${{{{ {}.{}) }}}}", dynamic_value_base, "version"), - }, - _outputs: None, - } - } -} - -impl Workflow for PublishNpmNapiWorkflow { - fn job_prefix_key(&self) -> String { - "publish_npm_napi".to_string() - } - - fn job_label(&self) -> String { - "Publish Npm Napi".to_string() - } - fn workflow_name(&self) -> String { - "npm_napi_publish".to_string() - } - fn publish_info_key(&self) -> String { - "npm_napi".to_string() - } - fn get_inputs(&self) -> IndexMap { - (&self.inputs).into() - } - fn get_additional_dependencies(&self) -> Option> { - None - } -} diff --git a/src/commands/generate_workflow/workflows/publish_rust_binary.rs b/src/commands/generate_workflow/workflows/publish_rust_binary.rs deleted file mode 100644 index 1c1dff7d7..000000000 --- a/src/commands/generate_workflow/workflows/publish_rust_binary.rs +++ /dev/null @@ -1,145 +0,0 @@ -use indexmap::IndexMap; -use serde_yml::Value; - -use super::Workflow; - -#[derive(Default, Clone)] -pub struct PublishRustBinaryWorkflowOutputs { - /// Was the binary released - pub _released: bool, -} - -#[derive(Default, Clone)] -pub struct PublishRustBinaryWorkflowInputs { - /// Package name - pub package: String, - /// Package version - pub version: String, - /// Which toolchain to use - pub toolchain: String, - pub launcher_app_name: String, - pub launcher_fallback_app_name: String, - /// Which release_channel - pub release_channel: String, - /// Binaries targets - pub targets: Option>, - /// Additional args to pass to the cargo command - pub additional_args: Option, - /// Working directory to run the cargo command - pub working_directory: String, // '' - /// Should the binary bin be signed - pub sign_build: Option, - /// Used to configure the target runner and extension - pub targets_config: Option, -} - -impl From<&PublishRustBinaryWorkflowInputs> for IndexMap { - fn from(val: &PublishRustBinaryWorkflowInputs) -> Self { - let mut map: IndexMap = IndexMap::new(); - map.insert("package".to_string(), val.package.clone().into()); - map.insert("version".to_string(), val.version.clone().into()); - map.insert("toolchain".to_string(), val.toolchain.clone().into()); - map.insert( - "release_channel".to_string(), - val.release_channel.clone().into(), - ); - map.insert( - "working_directory".to_string(), - val.working_directory.clone().into(), - ); - map.insert( - "launcher_app_name".to_string(), - val.launcher_app_name.clone().into(), - ); - map.insert( - "launcher_fallback_app_name".to_string(), - val.launcher_fallback_app_name.clone().into(), - ); - - if let Some(targets) = &val.targets { - map.insert( - "targets".to_string(), - format!("[\"{}\"]", targets.clone().join("\",\"")).into(), - ); - } - if let Some(additional_args) = &val.additional_args { - map.insert( - "additional_args".to_string(), - additional_args.clone().into(), - ); - } - if let Some(sign_build) = &val.sign_build { - map.insert("sign_build".to_string(), (*sign_build).into()); - } - if let Some(targets_config) = &val.targets_config { - map.insert("targets_config".to_string(), targets_config.clone().into()); - } - map - } -} - -pub struct PublishRustBinaryWorkflow { - pub inputs: PublishRustBinaryWorkflowInputs, - pub _outputs: Option, -} -impl PublishRustBinaryWorkflow { - pub fn new( - package: String, - targets: Vec, - additional_args: Option, - sign_build: bool, - working_directory: String, - dynamic_value_base: &str, - ) -> Self { - Self { - inputs: PublishRustBinaryWorkflowInputs { - package, - version: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.rc_version" - ), - toolchain: format!("${{{{ {}.{}) }}}}", dynamic_value_base, "toolchain"), - release_channel: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.release_channel" - ), - launcher_app_name: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.name" - ), - launcher_fallback_app_name: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.fallback_name" - ), - targets: Some(targets), - additional_args, - working_directory, - sign_build: Some(sign_build), - targets_config: None, - }, - _outputs: None, - } - } -} - -impl Workflow for PublishRustBinaryWorkflow { - fn job_prefix_key(&self) -> String { - "publish_rust_binary".to_string() - } - - fn job_label(&self) -> String { - "Publish Rust binary".to_string() - } - fn workflow_name(&self) -> String { - "rust_binary_publish".to_string() - } - fn publish_info_key(&self) -> String { - "binary".to_string() - } - fn get_inputs(&self) -> IndexMap { - (&self.inputs).into() - } - fn get_additional_dependencies(&self) -> Option> { - None - } -} diff --git a/src/commands/generate_workflow/workflows/publish_rust_installer.rs b/src/commands/generate_workflow/workflows/publish_rust_installer.rs deleted file mode 100644 index 0e41a9208..000000000 --- a/src/commands/generate_workflow/workflows/publish_rust_installer.rs +++ /dev/null @@ -1,220 +0,0 @@ -use indexmap::IndexMap; -use serde_yml::Value; - -use super::Workflow; - -#[derive(Default, Clone)] -pub struct PublishRustInstallerWorkflowOutputs { - /// Was the binary released - pub _released: bool, -} - -#[derive(Default, Clone)] -pub struct PublishRustInstallerWorkflowInputs { - /// Package name - pub package: String, - /// Package version - pub version: String, - /// Which toolchain to use - pub toolchain: String, - pub application_name: String, - pub application_fallback_name: String, - /// Name of the blob to download to get access to the launcher - pub launcher_blob_dir: String, - /// Name of the blob to download to get access to the launcher - pub launcher_name: String, - /// Name of the blob to download to get access to the application - pub package_blob_dir: String, - /// Name of the blob to download to get access to the application - pub package_name: String, - /// Name of the blob to upload the installer to - pub installer_blob_dir: String, - /// Name of the installer to - pub installer_name: String, - /// Name of the signed installer to - pub installer_signed_name: String, - /// Which release_channel - pub release_channel: String, - /// Working directory to run the cargo command - pub working_directory: String, // '' - /// Should the binary bin be signed - pub sign_build: Option, - pub upgrade_code: String, - pub guid_prefix: String, - pub sas_expiry: String, - pub sub_apps_download_script: String, -} - -impl From<&PublishRustInstallerWorkflowInputs> for IndexMap { - fn from(val: &PublishRustInstallerWorkflowInputs) -> Self { - let mut map: IndexMap = IndexMap::new(); - map.insert("package".to_string(), val.package.clone().into()); - map.insert("version".to_string(), val.version.clone().into()); - map.insert("toolchain".to_string(), val.toolchain.clone().into()); - map.insert( - "release_channel".to_string(), - val.release_channel.clone().into(), - ); - map.insert( - "working_directory".to_string(), - val.working_directory.clone().into(), - ); - map.insert( - "application_name".to_string(), - val.application_name.clone().into(), - ); - map.insert( - "application_fallback_name".to_string(), - val.application_fallback_name.clone().into(), - ); - map.insert( - "launcher_blob_dir".to_string(), - val.launcher_blob_dir.clone().into(), - ); - map.insert( - "launcher_name".to_string(), - val.launcher_name.clone().into(), - ); - map.insert( - "package_blob_dir".to_string(), - val.package_blob_dir.clone().into(), - ); - - map.insert("package_name".to_string(), val.package_name.clone().into()); - map.insert( - "installer_blob_dir".to_string(), - val.installer_blob_dir.clone().into(), - ); - map.insert( - "installer_name".to_string(), - val.installer_name.clone().into(), - ); - map.insert( - "installer_signed_name".to_string(), - val.installer_signed_name.clone().into(), - ); - map.insert("upgrade_code".to_string(), val.upgrade_code.clone().into()); - - map.insert("guid_prefix".to_string(), val.guid_prefix.clone().into()); - map.insert("sas_expiry".to_string(), val.sas_expiry.clone().into()); - map.insert( - "sub_apps_download_script".to_string(), - val.sub_apps_download_script.clone().into(), - ); - - if let Some(sign_build) = &val.sign_build { - map.insert("sign_build".to_string(), (*sign_build).into()); - } - map - } -} - -pub struct PublishRustInstallerWorkflow { - pub inputs: PublishRustInstallerWorkflowInputs, - pub _outputs: Option, -} - -impl PublishRustInstallerWorkflow { - pub fn new( - package: String, - working_directory: String, - sign_build: bool, - dynamic_value_base: &str, - ) -> Self { - Self { - inputs: PublishRustInstallerWorkflowInputs { - package, - sign_build: Some(sign_build), - working_directory, - version: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.rc_version" - ), - toolchain: format!("${{{{ {}.{}) }}}}", dynamic_value_base, "toolchain"), - release_channel: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.release_channel" - ), - application_name: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.name" - ), - application_fallback_name: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.fallback_name" - ), - launcher_blob_dir: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.installer.launcher_blob_dir" - ), - launcher_name: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.installer.launcher_blob_name" - ), - package_blob_dir: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.blob_dir" - ), - package_name: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.blob_name" - ), - installer_blob_dir: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.installer.installer_blob_dir" - ), - installer_name: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.installer.installer_blob_name" - ), - installer_signed_name: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, - "publish_detail.binary.installer.installer_blob_signed_name" - ), - upgrade_code: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.installer.upgrade_code" - ), - guid_prefix: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.installer.guid_prefix" - ), - sas_expiry: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.installer.sas_expiry" - ), - sub_apps_download_script: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.binary.installer.sub_apps_download_script" - ), - }, - _outputs: None, - } - } -} - -impl Workflow for PublishRustInstallerWorkflow { - fn job_prefix_key(&self) -> String { - "publish_rust_installer".to_string() - } - - fn job_label(&self) -> String { - "Publish Rust installer".to_string() - } - fn workflow_name(&self) -> String { - "rust_installer_publish".to_string() - } - fn publish_info_key(&self) -> String { - "binary.installer".to_string() - } - fn get_inputs(&self) -> IndexMap { - (&self.inputs).into() - } - fn get_additional_dependencies(&self) -> Option> { - Some(vec![ - format!("publish_rust_binary_{}", self.inputs.package), - format!("publish_rust_binary_{}_launcher", self.inputs.package), - ]) - } -} diff --git a/src/commands/generate_workflow/workflows/publish_rust_registry.rs b/src/commands/generate_workflow/workflows/publish_rust_registry.rs deleted file mode 100644 index 7fc97a5d7..000000000 --- a/src/commands/generate_workflow/workflows/publish_rust_registry.rs +++ /dev/null @@ -1,123 +0,0 @@ -use indexmap::IndexMap; -use serde_yml::Value; - -use super::Workflow; - -#[derive(Default, Clone)] -pub struct PublishRustRegistryWorkflowOutputs { - /// Was the binary released - pub _released: bool, -} - -#[derive(Default, Clone)] -pub struct PublishRustRegistryWorkflowInputs { - /// Package name - pub package: String, - /// Package version - pub version: String, - /// Working directory to run the cargo command - pub working_directory: String, - /// Which toolchain to use - pub toolchain: String, - /// Additional args to pass to the cargo command - pub additional_args: String, - /// Additional script to pass to the cargo command - pub additional_script: String, - /// Additional args to pass to the cargo command - pub custom_cargo_commands: String, - /// Public release - pub public_release: String, - pub ci_runner: String, -} - -impl From<&PublishRustRegistryWorkflowInputs> for IndexMap { - fn from(val: &PublishRustRegistryWorkflowInputs) -> Self { - let mut map: IndexMap = IndexMap::new(); - map.insert("package".to_string(), val.package.clone().into()); - map.insert("version".to_string(), val.version.clone().into()); - map.insert( - "working_directory".to_string(), - val.working_directory.clone().into(), - ); - map.insert("toolchain".to_string(), val.toolchain.clone().into()); - map.insert( - "public_release".to_string(), - val.public_release.clone().into(), - ); - map.insert( - "additional_args".to_string(), - val.additional_args.clone().into(), - ); - map.insert( - "additional_script".to_string(), - val.additional_script.clone().into(), - ); - map.insert( - "custom_cargo_commands".to_string(), - val.custom_cargo_commands.clone().into(), - ); - map.insert("ci_runner".to_string(), val.ci_runner.clone().into()); - map - } -} - -pub struct PublishRustRegistryWorkflow { - pub inputs: PublishRustRegistryWorkflowInputs, - pub _outputs: Option, -} - -impl PublishRustRegistryWorkflow { - pub fn new(package: String, working_directory: String, dynamic_value_base: &str) -> Self { - Self { - inputs: PublishRustRegistryWorkflowInputs { - package, - working_directory, - version: format!("${{{{ {}.{}) }}}}", dynamic_value_base, "version"), - toolchain: format!("${{{{ {}.{}) }}}}", dynamic_value_base, "toolchain"), - additional_args: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish.args.additional_args" - ), - additional_script: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish.args.additional_script" - ), - custom_cargo_commands: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish.args.custom_cargo_commands" - ), - public_release: format!( - "${{{{ {dynamic_value_base}).cargo.allow_public && 'true' || 'false' }}}}" - ), - ci_runner: format!( - "${{{{ {}.{}) }}}}", - dynamic_value_base, "publish_detail.ci_runner" - ), - }, - - _outputs: None, - } - } -} - -impl Workflow for PublishRustRegistryWorkflow { - fn job_prefix_key(&self) -> String { - "publish_rust_registry".to_string() - } - - fn job_label(&self) -> String { - "Publish rust registry".to_string() - } - fn workflow_name(&self) -> String { - "rust_registry_publish".to_string() - } - fn publish_info_key(&self) -> String { - "cargo".to_string() - } - fn get_inputs(&self) -> IndexMap { - (&self.inputs).into() - } - fn get_additional_dependencies(&self) -> Option> { - None - } -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index fdda42117..6dae127cc 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -3,7 +3,6 @@ pub mod docker_build_push; pub mod download_artifacts; pub mod fix_lock_files; pub mod generate_wix; -pub mod generate_workflow; pub mod github_app_token; pub mod publish; pub mod summaries; diff --git a/src/commands/tests/mod.rs b/src/commands/tests/mod.rs index b299cb39e..261e56706 100644 --- a/src/commands/tests/mod.rs +++ b/src/commands/tests/mod.rs @@ -10,19 +10,20 @@ use opentelemetry::{ use port_check::free_local_port; use rand::distr::{Alphanumeric, SampleString}; use serde::Serialize; -use serde_yml::Value; use std::{ collections::{HashMap, HashSet}, env, fmt::{Display, Formatter}, fs::{File, create_dir_all, remove_dir_all}, - io::Write, - path::PathBuf, + io::{Cursor, Write}, + path::{Path, PathBuf}, sync::Arc, thread::sleep, time::Duration, }; +use tempfile::tempdir; use tokio::sync::Semaphore; +use toml::Value; use crate::{ PackageRelatedOptions, PrettyPrintable, @@ -157,6 +158,26 @@ async fn has_cargo_nextest() -> bool { } } +/// Checks if a cargo deny config file exists; if not, downloads default to a temporary location +/// Returns the path to the config +async fn get_or_download_deny_config( + repo_root: &Path, + default_location: &str, + temp_dir: &Path, +) -> anyhow::Result { + let file_path = repo_root.join("deny.toml"); + if file_path.exists() { + Ok(file_path.to_path_buf()) + } else { + let temp_file_path = temp_dir.join("deny.toml"); + let mut file = File::create(&temp_file_path)?; + let response = reqwest::get(default_location).await?; + let mut content = Cursor::new(response.bytes().await?); + std::io::copy(&mut content, &mut file)?; + Ok(temp_file_path) + } +} + /// Count the number of test cases using `cargo nextest list` async fn count_nextest_tests(package_path: &PathBuf) -> usize { use serde::Deserialize; @@ -343,6 +364,10 @@ pub async fn tests( let mut global_junit_report = ReportBuilder::new().build(); + let temp_dir = tempdir()?; + let deny_config_location = + get_or_download_deny_config(&repo_root, &options.default_deny_location, temp_dir.path()) + .await?; // Global fail tracker let mut global_failed = false; @@ -368,6 +393,7 @@ pub async fn tests( member, metrics.clone(), semaphore.clone(), + deny_config_location.clone(), )); handles.push(task_handle); } @@ -440,6 +466,7 @@ async fn do_test_on_package( member: super::check_workspace::Result, metrics: Metrics, semaphore: Arc, + deny_location: PathBuf, ) -> (bool, Report) { let permit = semaphore.acquire().await; @@ -703,6 +730,30 @@ async fn do_test_on_package( envs: HashMap::from([("RUSTDOCFLAGS".to_string(), "-D warnings".to_string())]), ..Default::default() }, + FslabsTest { + id: "cargo_deny_licenses".to_string(), + command: format!( + "cargo deny check -c {} licenses", deny_location.to_string_lossy()), + ..Default::default() + }, + FslabsTest { + id: "cargo_deny_bans".to_string(), + command: format!( + "cargo deny check -c {} bans --allow unmatched-skip --allow unnecessary-skip", deny_location.to_string_lossy()), + ..Default::default() + }, + FslabsTest { + id: "cargo_deny_advisories".to_string(), + command: format!( + "cargo deny check -c {} advisories", deny_location.to_string_lossy()), + ..Default::default() + }, + FslabsTest { + id: "cargo_deny_sources".to_string(), + command: format!( + "cargo deny check -c {} sources", deny_location.to_string_lossy()), + ..Default::default() + }, FslabsTest { id: "cargo_test".to_string(), command: if use_nextest { diff --git a/src/main.rs b/src/main.rs index b2c9bf7fe..1a83c86c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ use crate::commands::download_artifacts::{ }; use crate::commands::fix_lock_files::{Options as FixLockFilesOptions, fix_lock_files}; use crate::commands::generate_wix::{Options as GenerateWixOptions, generate_wix}; -use crate::commands::generate_workflow::{Options as GenerateWorkflowOptions, generate_workflow}; use crate::commands::github_app_token::{Options as GithubAppTokenOptions, github_app_token}; use crate::commands::publish::{Options as PublishOptions, publish}; use crate::commands::summaries::{Options as SummariesOptions, summaries}; @@ -114,7 +113,6 @@ enum CargoSubcommand { #[derive(Debug, Subcommand)] enum Commands { - GenerateReleaseWorkflow(Box), GenerateWix(Box), /// Summarize a github action run Summaries(Box), @@ -378,9 +376,6 @@ async fn run() { let working_directory = dunce::canonicalize(cli.working_directory) .expect("Could not get full path from working_directory"); let result = match cli.command { - Commands::GenerateReleaseWorkflow(options) => generate_workflow(options, working_directory) - .await - .map(|r| display_results(cli.json, cli.pretty_print, r)), Commands::GenerateWix(options) => generate_wix(options, working_directory) .await .map(|r| display_results(cli.json, cli.pretty_print, r)), diff --git a/src/utils/mod.rs b/src/utils/mod.rs index efe59a921..67b2afe35 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,201 +1,19 @@ use std::env::VarError; -use std::fmt; -use std::marker::PhantomData; -use std::str::FromStr; -use indexmap::IndexMap; -use serde::de::{Error as SerdeError, MapAccess, Visitor}; -use serde::{Deserialize, Deserializer, de}; use std::{ collections::{HashMap, HashSet}, - fmt::Display, path::PathBuf, process::Stdio, }; use tokio::io::AsyncBufReadExt; use tracing::{debug, error, info, trace, warn}; -use void::Void; - pub mod auto_update; pub mod cargo; pub mod github; #[cfg(test)] pub mod test; -pub trait FromMap { - fn from_map(map: IndexMap) -> Result - where - Self: Sized; -} - -fn deserialize_string_or_map<'de, T, D>(deserializer: D) -> Result -where - T: Deserialize<'de> + FromStr + FromMap, - D: Deserializer<'de>, -{ - // This is a Visitor that forwards string types to T's `FromStr` impl and - // forwards map types to T's `Deserialize` impl. The `PhantomData` is to - // keep the compiler from complaining about T being an unused generic type - // parameter. We need T in order to know the Value type for the Visitor - // impl. - struct StringOrMap(PhantomData T>); - - impl<'de, T> Visitor<'de> for StringOrMap - where - T: Deserialize<'de> + FromStr + FromMap, - { - type Value = T; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("string or map") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - Ok(FromStr::from_str(value).unwrap()) - } - - fn visit_map(self, map: M) -> Result - where - M: MapAccess<'de>, - { - // `MapAccessDeserializer` is a wrapper that turns a `MapAccess` - // into a `Deserializer`, allowing it to be used as the input to T's - // `Deserialize` implementation. T then deserializes itself using - // the entries from the map visitor. - match FromMap::from_map(Deserialize::deserialize( - de::value::MapAccessDeserializer::new(map), - )?) { - Ok(s) => Ok(s), - Err(_) => Err(SerdeError::custom("Should never happens")), - } - } - } - - deserializer.deserialize_any(StringOrMap(PhantomData)) -} - -pub fn deserialize_opt_string_or_map<'de, T, D>(d: D) -> Result, D::Error> -where - T: Deserialize<'de> + FromStr + FromMap, - ::Err: Display, - D: Deserializer<'de>, -{ - /// Declare an internal visitor type to handle our input. - struct OptStringOrMap(PhantomData); - - impl<'de, T> de::Visitor<'de> for OptStringOrMap - where - T: Deserialize<'de> + FromStr + FromMap, - ::Err: Display, - { - type Value = Option; - - fn visit_none(self) -> Result - where - E: de::Error, - { - Ok(None) - } - - fn visit_some(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserialize_string_or_map(deserializer).map(Some) - } - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "a null, a string or a map") - } - } - - d.deserialize_option(OptStringOrMap(PhantomData)) -} - -pub fn deserialize_opt_string_or_struct<'de, T, D>(d: D) -> Result, D::Error> -where - T: Deserialize<'de> + FromStr, - D: Deserializer<'de>, -{ - /// Declare an internal visitor type to handle our input. - struct OptStringOrStruct(PhantomData); - - impl<'de, T> de::Visitor<'de> for OptStringOrStruct - where - T: Deserialize<'de> + FromStr, - { - type Value = Option; - - fn visit_none(self) -> Result - where - E: de::Error, - { - Ok(None) - } - - fn visit_some(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserialize_string_or_struct(deserializer).map(Some) - } - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "a null, a string or a map") - } - } - - d.deserialize_option(OptStringOrStruct(PhantomData)) -} - -fn deserialize_string_or_struct<'de, T, D>(deserializer: D) -> Result -where - T: Deserialize<'de> + FromStr, - D: Deserializer<'de>, -{ - // This is a Visitor that forwards string types to T's `FromStr` impl and - // forwards map types to T's `Deserialize` impl. The `PhantomData` is to - // keep the compiler from complaining about T being an unused generic type - // parameter. We need T in order to know the Value type for the Visitor - // impl. - struct StringOrStruct(PhantomData T>); - - impl<'de, T> Visitor<'de> for StringOrStruct - where - T: Deserialize<'de> + FromStr, - { - type Value = T; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("string or map") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - Ok(FromStr::from_str(value).unwrap()) - } - - fn visit_map(self, map: M) -> Result - where - M: MapAccess<'de>, - { - // `MapAccessDeserializer` is a wrapper that turns a `MapAccess` - // into a `Deserializer`, allowing it to be used as the input to T's - // `Deserialize` implementation. T then deserializes itself using - // the entries from the map visitor. - Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)) - } - } - - deserializer.deserialize_any(StringOrStruct(PhantomData)) -} - /// [`execute_command`] with intermediate logging disabled. pub async fn execute_command_without_logging( command: &str,