From a11e7342e5200f8e19de13c848456bbe876013d2 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Tue, 7 Mar 2023 08:57:37 -0500 Subject: [PATCH 01/33] Add sections to contributing guide --- CONTRIBUTING.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c2b407..e14fc87 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,3 +8,9 @@ request](https://github.com/mpalmer/action-validator/pulls). * At all times, abide by the Code of Conduct (CODE_OF_CONDUCT.md). + +# Environment Setup + +# Running the Validator Locally + +# Writing Tests From fcf615aa198ae5948677de57d85bc4e73486b08f Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Tue, 7 Mar 2023 09:04:16 -0500 Subject: [PATCH 02/33] Add rust environment setup. --- CONTRIBUTING.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e14fc87..59b3744 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,6 +11,11 @@ # Environment Setup +## Install Rust +Firstly, you'll need make any changes to the core functionality of this project. We recommend use `rustup`, on the recommendation of the rust team. You can find the installation instructions [here](https://www.rust-lang.org/tools/install). + +To confirm that rust is installed, run the `cargo` command. If you don't receive the help docs output, you may need to add rust to your shell rc file. + # Running the Validator Locally # Writing Tests From 90861f297b675a8f8a5a3b3199d3d63e73d94cb9 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Tue, 7 Mar 2023 12:30:34 -0500 Subject: [PATCH 03/33] Update CONTRIBUTING.md --- CONTRIBUTING.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 59b3744..24b77a6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,5 @@ +# Overview + * If you have found a discrepancy in documented and observed behaviour, that is a bug. Feel free to [report it as an issue](https://github.com/mpalmer/action-validator/issues), providing @@ -9,6 +11,8 @@ * At all times, abide by the Code of Conduct (CODE_OF_CONDUCT.md). +--- + # Environment Setup ## Install Rust @@ -16,6 +20,57 @@ Firstly, you'll need make any changes to the core functionality of this project. To confirm that rust is installed, run the `cargo` command. If you don't receive the help docs output, you may need to add rust to your shell rc file. +## Git Submodule Setup +This repository uses [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules). Specifically for the use of [schemastore](https://github.com/SchemaStore/schemastore). + +To setup the git submodule after cloning this repo to your local, you'll want to run the following commands: +1. `git submodule init` +2. `git submodule update` + +It should look similar to the output below. + +```bash +❯ git submodule init +Submodule 'src/schemastore' (https://github.com/SchemaStore/schemastore) registered for path 'src +/schemastore' +❯ git submodule update +Cloning into '/Users/someuser/action-validator/src/schemastore'... +Submodule path 'src/schemastore': checked out 'd3e6ab7727380b214acbab05570fb09a3e5d2dfc' +``` + +At this point, you should be all set to `cargo run`! If you run into any issues here, please [create an issue](https://github.com/mpalmer/action-validator/issues/new/choose). + # Running the Validator Locally +## `cargo run [FILE] -- [OPTIONS]` +`cargo run` will create a _debug_ executable and run the project. If this is your first time running the command, cargo will compile the development binary with `cargo build`. This will install all of the dependencies and create the debug binary `action-validator` in the `/target/debug/` directory. `cargo run` will then invoke this binary after creation. + +One caveat if you're running with `cargo run`: if you want to supply the program with options, you need to use the `--` operator between `cargo run` and your provided options. This let's cargo know which flags are meant for cargo, and which are meant for the executable. + +## `cargo build` && `./target/debug/action-validator [OPTIONS]` +As discussed in the prior section, `cargo build` install dependencies (if they're not cached) and build the development binary. This binary can then be invoked directly by running `./target/debug/action-validator`. This does **not** require the use of the `--` operator in between the binary and any provided options. + +## Try It Yourself! + +Run the command `cargo run -- --help`. You should see an output similar to the following. +```bash +❯ cargo run -- --help + Finished dev [unoptimized + debuginfo] target(s) in 0.05s + Running `target/debug/action-validator --help` +A validator for GitHub Action and Workflow YAML files + +Usage: action-validator [OPTIONS] [path_to_action_yaml]... + +Arguments: + [path_to_action_yaml]... Input file + +Options: + -v, --verbose Be more verbose + -h, --help Print help information + -V, --version Print version information +``` + # Writing Tests +``` +TODO: This section could use some help. +``` From 629d854e718a21379697f90417f6e8757ff26939 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Tue, 7 Mar 2023 12:40:24 -0500 Subject: [PATCH 04/33] Add remote-checks flag --- src/config.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config.rs b/src/config.rs index 241daae..efc752a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,6 +13,10 @@ pub struct CliConfig { #[arg(short, long)] pub verbose: bool, + /// Perform remote calls to validate actions exist + #[arg(short, long)] + pub remote_checks: bool, + /// Input file #[arg(name = "path_to_action_yaml")] pub src: Vec, From 8d7248e06042233f60352511c5b6c2dc3888d892 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Tue, 7 Mar 2023 12:46:07 -0500 Subject: [PATCH 05/33] Capture `remote_checks` flag in all configs --- src/config.rs | 3 +++ src/lib.rs | 1 + 2 files changed, 4 insertions(+) diff --git a/src/config.rs b/src/config.rs index efc752a..236fda3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -34,6 +34,7 @@ pub struct JsConfig<'a> { pub action_type: ActionType, pub src: &'a str, pub verbose: bool, + pub remote_checks: bool, } pub struct RunConfig<'a> { @@ -42,6 +43,7 @@ pub struct RunConfig<'a> { pub action_type: ActionType, pub src: &'a str, pub verbose: bool, + pub remote_checks: bool, } impl<'a> From<&JsConfig<'a>> for RunConfig<'a> { @@ -52,6 +54,7 @@ impl<'a> From<&JsConfig<'a>> for RunConfig<'a> { action_type: config.action_type, src: config.src, verbose: config.verbose, + remote_checks: config.remote_checks, } } } diff --git a/src/lib.rs b/src/lib.rs index 12996ba..ac64b73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,6 +107,7 @@ pub mod cli { }, src, verbose: config.verbose, + remote_checks: config.remote_checks, }; let state = crate::run(&config); From 7b10b14cf6937ef1ed355506e92a7f384d29d7b1 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Tue, 7 Mar 2023 12:58:00 -0500 Subject: [PATCH 06/33] Create skeleton for `validate_remote_checks` --- src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ac64b73..8756445 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -155,6 +155,9 @@ fn run(config: &RunConfig) -> ValidationState { validate_paths(&doc, &mut state); validate_job_needs(&doc, &mut state); + if config.remote_checks { + validate_remote_checks(&doc, &mut state); + } state } @@ -264,3 +267,7 @@ fn validate_job_needs(doc: &serde_json::Value, state: &mut ValidationState) { } } } + +fn validate_remote_checks(doc: &serde_json::Value, state: &mut ValidationState) { + println!("{}, {:#?}", doc.to_string(), state); +} From d3aa4b583da64234feb1f79e64550f1cac5784d9 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Wed, 8 Mar 2023 00:44:23 -0500 Subject: [PATCH 07/33] First working prototype! Needs some cleanup, but getting closer --- .github/workflows/audit.yml | 13 +- Cargo.lock | 603 +++++++++++++++++++++++++++++++++++- Cargo.toml | 6 + src/lib.rs | 108 ++++++- 4 files changed, 721 insertions(+), 9 deletions(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index b61e4c9..b5c3ff1 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -14,11 +14,20 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout with: submodules: true - name: Audit - uses: actions-rs/audit-check@v1 + uses: actions-rs/audit-check/testing@v with: token: ${{ secrets.GITHUB_TOKEN }} + + - name: Path + uses: ./.github/actions/action1 + + - name: DockerHub + uses: docker://alpine:3.8 + + - name: DockerRegistry + uses: docker://gcr.io/cloud-builders/gradle diff --git a/Cargo.lock b/Cargo.lock index dae02ae..fca9c24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,10 @@ version = "0.0.0-git" dependencies = [ "clap", "console_error_panic_hook", + "futures", "glob", + "regex", + "reqwest", "serde", "serde-wasm-bindgen", "serde_json", @@ -86,6 +89,12 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + [[package]] name = "cc" version = "1.0.78" @@ -173,6 +182,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -229,6 +248,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "erased-serde" version = "0.3.24" @@ -269,6 +297,36 @@ dependencies = [ "regex", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fnv" +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.0.1" @@ -279,6 +337,95 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" + +[[package]] +name = "futures-executor" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" + +[[package]] +name = "futures-macro" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" + +[[package]] +name = "futures-task" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" + +[[package]] +name = "futures-util" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "getrandom" version = "0.2.3" @@ -288,7 +435,7 @@ dependencies = [ "cfg-if 1.0.0", "js-sys", "libc", - "wasi", + "wasi 0.10.2+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -298,6 +445,25 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "h2" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -319,6 +485,77 @@ dependencies = [ "libc", ] +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.5", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.5", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -364,6 +601,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "io-lifetimes" version = "1.0.3" @@ -374,6 +620,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "ipnet" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" + [[package]] name = "is-terminal" version = "0.4.2" @@ -426,6 +678,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.139" @@ -474,6 +732,42 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -493,12 +787,67 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "once_cell" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +[[package]] +name = "openssl" +version = "0.10.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "os_str_bytes" version = "6.4.1" @@ -549,6 +898,24 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -621,11 +988,20 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" -version = "1.5.4" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -634,9 +1010,46 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "reqwest" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] [[package]] name = "rustix" @@ -658,6 +1071,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -670,6 +1092,29 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.152" @@ -712,6 +1157,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.5", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.17" @@ -731,6 +1188,25 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "strsim" version = "0.10.0" @@ -748,6 +1224,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -772,6 +1261,79 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "tokio" +version = "1.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "pin-project-lite", + "socket2", + "winapi", +] + +[[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-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if 1.0.0", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -860,18 +1422,40 @@ dependencies = [ "uuid", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.84" @@ -1071,3 +1655,12 @@ name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] diff --git a/Cargo.toml b/Cargo.toml index 8ad7c11..13a44f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,12 @@ console_error_panic_hook = { version = "0.1.6", optional = true } # allocator, however. wee_alloc = { version = "0.4.5", optional = true } serde-wasm-bindgen = "0.4.5" +regex = "1.7.1" +futures = "0.3.26" + +[dependencies.reqwest] +version = "0.11.14" # latest version at time of writing +features = ["blocking"] [dev-dependencies] wasm-bindgen-test = "0.3.13" diff --git a/src/lib.rs b/src/lib.rs index 8756445..c4ef850 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,10 @@ mod utils; mod validation_error; mod validation_state; + use config::{ActionType, RunConfig}; +use regex::{Regex}; +use reqwest::blocking::Client; use validation_error::ValidationError; use validation_state::ValidationState; @@ -268,6 +271,107 @@ fn validate_job_needs(doc: &serde_json::Value, state: &mut ValidationState) { } } -fn validate_remote_checks(doc: &serde_json::Value, state: &mut ValidationState) { - println!("{}, {:#?}", doc.to_string(), state); +// #[derive(Debug)] +// enum Uses<'a> { +// Action(&'a str), +// Path(&'a str), +// Docker(&'a str), +// Invalid(&'a str), +// } + +trait Uses<'a>: std::fmt::Debug { + fn validate(&self) -> Option<()>; +} + +#[derive(Debug)] +struct Action<'a> { + owner: &'a str, + repo: &'a str, + _path: Option<&'a str>, + reference: &'a str, +} + + +impl Uses<'_> for Action<'_> { + fn validate(&self) -> Option<()> { + println!("{:#?}", self); + let request = Client::new() + .get(format!( + "https://github.com/{0}/{1}/tree/{2}", + self.owner, + self.repo, + self.reference, + )); + let response = request.send(); + if response.ok()?.status() == 200 { + return Some(()); + } + None + } +} + + +fn validate_remote_checks(doc: &serde_json::Value, state: &mut ValidationState) -> Option<()> { + // If this regex doesn't compile, that should be considered a compile-time + // error. As such, we should unwrap to purposefully panic in the event of + // a malformed regex. + let r = Regex::new(r"(?x)^ + (?P.{0}(?P[^/]*)/(?P[^/]*)(/(?P.*))?@(?P.*))| + (?P.{0}\./([^/]+))| + (?P.{0}docker://(?P.*)(:(?P.*))?)| + $").unwrap(); + + for (job_name, job) in doc["jobs"].as_object()?.iter() { + let _ = job_name; + for step in job["steps"].as_array()?.iter() { + let uses_step = step["uses"].as_str()?; + let matched = r.captures(uses_step)?; + let uses_op = vec![ + "Action", "Path", "Docker", + ] + .into_iter() + .find_map(|name| { + matched.name(name)?; + match name { + "Action" => Some(Box::new(Action { + owner: matched.name("owner")?.as_str(), + repo: matched.name("repo")?.as_str(), + _path: matched.name("path").map(|m| m.as_str()), + reference: matched.name("ref")?.as_str(), + })), + // "Path" => Some(Uses::Path(value)), + // "Docker" => Some(Uses::Docker(value)), + _ => None, + } + }); + println!("{:#?}", uses_op); + if let Some(uses) = uses_op { + if uses.validate().is_none() { + state.errors.push(ValidationError::UnresolvedJob { + code: "uses_not_found".into(), + path: format!("/jobs/{job_name}/steps"), + title: "Invalid uses".into(), + detail: Some(format!("Could not find the action `{uses_step}`.")), + }); + } + } + // if uses.is_none() { + // println!("Error: {}", uses_step); + // continue; + // // TODO: Push error + // } + // if let Some(Uses::Invalid(value)) = uses { + // println!("Error: {}", uses_step); + // state.errors.push(ValidationError::UnresolvedJob { + // code: "invalid_uses".into(), + // path: format!("/jobs/{job_name}/uses/{uses_step}"), + // title: "Invalid uses".into(), + // detail: Some(format!("Invalid uses {value}")), + // }); + // continue; + // } + // println!("Success: {:#?}", uses?); + } + } + Some(()) } From dac8ef205031a6b75d75604e83610eaa763df5c2 Mon Sep 17 00:00:00 2001 From: award28 Date: Wed, 8 Mar 2023 10:51:57 -0500 Subject: [PATCH 08/33] Working dyn trait --- Cargo.lock | 289 ++++++++++++++++++++++++++------------------------ Cargo.toml | 6 +- src/config.rs | 2 - src/lib.rs | 84 +++++++++++---- src/main.rs | 3 + 5 files changed, 215 insertions(+), 169 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fca9c24..ba8ee05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,9 +34,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -52,9 +52,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" @@ -97,9 +97,9 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -127,9 +127,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.32" +version = "4.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" +checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5" dependencies = [ "bitflags", "clap_derive", @@ -142,9 +142,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.0.21" +version = "4.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0" dependencies = [ "heck", "proc-macro-error", @@ -155,9 +155,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" dependencies = [ "os_str_bytes", ] @@ -200,9 +200,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cxx" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc831ee6a32dd495436e317595e639a587aa9907bef96fe6e6abc290ab6204e9" +checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72" dependencies = [ "cc", "cxxbridge-flags", @@ -212,9 +212,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94331d54f1b1a8895cd81049f7eaaaef9d05a7dcb4d1fd08bf3ff0806246789d" +checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613" dependencies = [ "cc", "codespan-reporting", @@ -227,15 +227,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dcd35ba14ca9b40d6e4b4b39961f23d835dbb8eed74565ded361d93e1feb8a" +checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97" [[package]] name = "cxxbridge-macro" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bbeb29798b407ccd82a3324ade1a7286e0d29851475990b612670f6f5124d2" +checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56" dependencies = [ "proc-macro2", "quote", @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "erased-serde" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ca605381c017ec7a5fef5e548f1cfaa419ed0f6df6367339300db74c92aa7d" +checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569" dependencies = [ "serde", ] @@ -329,11 +329,10 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] @@ -428,22 +427,22 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", "js-sys", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "glob" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" @@ -472,9 +471,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -485,6 +484,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "http" version = "0.2.9" @@ -493,7 +498,7 @@ checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", - "itoa 1.0.5", + "itoa", ] [[package]] @@ -534,7 +539,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.5", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -582,11 +587,10 @@ dependencies = [ [[package]] name = "idna" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] @@ -612,12 +616,12 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.3" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -628,27 +632,21 @@ checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] name = "is-terminal" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" @@ -714,17 +712,11 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memory_units" @@ -740,14 +732,14 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "mio" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "wasi", + "windows-sys 0.45.0", ] [[package]] @@ -770,9 +762,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -780,9 +772,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] @@ -793,15 +785,15 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "once_cell" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl" @@ -942,9 +934,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] @@ -1053,23 +1045,23 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.6" +version = "0.36.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "schannel" @@ -1077,7 +1069,7 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1088,9 +1080,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scratch" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "security-framework" @@ -1117,9 +1109,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.152" +version = "1.0.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "3a382c72b4ba118526e187430bb4963cd6d55051ebf13d9b25574d379cc98d20" dependencies = [ "serde_derive", ] @@ -1137,9 +1129,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "1ef476a5790f0f6decbc66726b6e5d63680ed518283e64c7df415989d880954f" dependencies = [ "proc-macro2", "quote", @@ -1148,11 +1140,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.68" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ - "itoa 0.4.8", + "itoa", "ryu", "serde", ] @@ -1164,19 +1156,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.5", + "itoa", "ryu", "serde", ] [[package]] name = "serde_yaml" -version = "0.9.17" +version = "0.9.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567" +checksum = "f82e6c8c047aa50a7328632d067bcae6ef38772a79e28daf32f735e0e4f3dd10" dependencies = [ "indexmap", - "itoa 1.0.5", + "itoa", "ryu", "serde", "unsafe-libyaml", @@ -1184,9 +1176,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.7" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "slab" @@ -1215,9 +1207,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -1234,48 +1226,48 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "tinyvec" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.19.2" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" dependencies = [ + "autocfg", "bytes", "libc", "memchr", "mio", "num_cpus", - "once_cell", "pin-project-lite", "socket2", - "winapi", + "windows-sys 0.45.0", ] [[package]] @@ -1290,9 +1282,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.3" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", @@ -1336,21 +1328,21 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] @@ -1363,9 +1355,9 @@ checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unsafe-libyaml" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" +checksum = "ad2024452afd3874bf539695e04af6732ba06517424dbf958fdb16a01f3bef6c" [[package]] name = "uritemplate-next" @@ -1378,13 +1370,12 @@ dependencies = [ [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -1444,12 +1435,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1614,47 +1599,71 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "winreg" diff --git a/Cargo.toml b/Cargo.toml index 13a44f3..fa09889 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ crate-type = ["cdylib", "rlib"] [features] js = ["console_error_panic_hook", "wee_alloc", "valico/js"] +remote-checks = ["reqwest"] [dependencies] clap = { version = "4.0", features = ["derive"] } @@ -46,10 +47,7 @@ wee_alloc = { version = "0.4.5", optional = true } serde-wasm-bindgen = "0.4.5" regex = "1.7.1" futures = "0.3.26" - -[dependencies.reqwest] -version = "0.11.14" # latest version at time of writing -features = ["blocking"] +reqwest = { version = "0.11.14", features = ["blocking"], optional = true } [dev-dependencies] wasm-bindgen-test = "0.3.13" diff --git a/src/config.rs b/src/config.rs index 236fda3..b3a9328 100644 --- a/src/config.rs +++ b/src/config.rs @@ -43,7 +43,6 @@ pub struct RunConfig<'a> { pub action_type: ActionType, pub src: &'a str, pub verbose: bool, - pub remote_checks: bool, } impl<'a> From<&JsConfig<'a>> for RunConfig<'a> { @@ -54,7 +53,6 @@ impl<'a> From<&JsConfig<'a>> for RunConfig<'a> { action_type: config.action_type, src: config.src, verbose: config.verbose, - remote_checks: config.remote_checks, } } } diff --git a/src/lib.rs b/src/lib.rs index c4ef850..ba5959e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ mod validation_state; use config::{ActionType, RunConfig}; use regex::{Regex}; +#[cfg(feature="remote-checks")] use reqwest::blocking::Client; use validation_error::ValidationError; use validation_state::ValidationState; @@ -110,7 +111,6 @@ pub mod cli { }, src, verbose: config.verbose, - remote_checks: config.remote_checks, }; let state = crate::run(&config); @@ -158,9 +158,7 @@ fn run(config: &RunConfig) -> ValidationState { validate_paths(&doc, &mut state); validate_job_needs(&doc, &mut state); - if config.remote_checks { - validate_remote_checks(&doc, &mut state); - } + validate_job_uses(&doc, &mut state); state } @@ -283,18 +281,38 @@ trait Uses<'a>: std::fmt::Debug { fn validate(&self) -> Option<()>; } +#[derive(Debug)] +struct Invalid; + +impl Uses<'_> for Invalid { + fn validate(&self) -> Option<()> { println!("Invalid"); Some(()) } +} + #[derive(Debug)] struct Action<'a> { owner: &'a str, repo: &'a str, - _path: Option<&'a str>, + path: Option<&'a str>, reference: &'a str, } impl Uses<'_> for Action<'_> { + #[cfg(not(feature="remote-checks"))] + fn validate(&self) -> Option<()> { + println!( + "{}, {}, {}, {}", + self.owner, + self.repo, + self.path.unwrap_or("..."), + self.reference, + ); + Some(()) + } + + #[cfg(feature="remote-checks")] fn validate(&self) -> Option<()> { - println!("{:#?}", self); + println!("{}", self.path.unwrap_or("...")); let request = Client::new() .get(format!( "https://github.com/{0}/{1}/tree/{2}", @@ -310,8 +328,7 @@ impl Uses<'_> for Action<'_> { } } - -fn validate_remote_checks(doc: &serde_json::Value, state: &mut ValidationState) -> Option<()> { +fn validate_job_uses(doc: &serde_json::Value, state: &mut ValidationState) -> Option<()> { // If this regex doesn't compile, that should be considered a compile-time // error. As such, we should unwrap to purposefully panic in the event of // a malformed regex. @@ -326,35 +343,45 @@ fn validate_remote_checks(doc: &serde_json::Value, state: &mut ValidationState) for step in job["steps"].as_array()?.iter() { let uses_step = step["uses"].as_str()?; let matched = r.captures(uses_step)?; - let uses_op = vec![ + let uses_op: Option> = vec![ "Action", "Path", "Docker", ] .into_iter() - .find_map(|name| { + .find_map::, _>(|name| { matched.name(name)?; match name { + // "Path" => Some(Uses::Path(value)), + // "Docker" => Some(Uses::Docker(value)), "Action" => Some(Box::new(Action { owner: matched.name("owner")?.as_str(), repo: matched.name("repo")?.as_str(), - _path: matched.name("path").map(|m| m.as_str()), + path: matched.name("path").map(|m| m.as_str()), reference: matched.name("ref")?.as_str(), })), - // "Path" => Some(Uses::Path(value)), - // "Docker" => Some(Uses::Docker(value)), - _ => None, + _ => Some(Box::new(Invalid{})), } }); println!("{:#?}", uses_op); - if let Some(uses) = uses_op { - if uses.validate().is_none() { - state.errors.push(ValidationError::UnresolvedJob { - code: "uses_not_found".into(), - path: format!("/jobs/{job_name}/steps"), - title: "Invalid uses".into(), - detail: Some(format!("Could not find the action `{uses_step}`.")), - }); - } + let uses: Box = Box::new(Action { + owner: "owner", + repo: "repo", + path: Some("path"), + reference: "ref", + }); + if cfg!(feature="remote-checks") { + validate_remote_checks(uses, state); } + // println!("{:#?}", uses_op); + // if let Some(uses) = uses_op { + // if uses.validate().is_none() { + // state.errors.push(ValidationError::UnresolvedJob { + // code: "uses_not_found".into(), + // path: format!("/jobs/{job_name}/steps"), + // title: "Invalid uses".into(), + // detail: Some(format!("Could not find the action `{uses_step}`.")), + // }); + // } + // } // if uses.is_none() { // println!("Error: {}", uses_step); // continue; @@ -375,3 +402,14 @@ fn validate_remote_checks(doc: &serde_json::Value, state: &mut ValidationState) } Some(()) } + +#[cfg(not(feature="remote-checks"))] +fn validate_remote_checks(_: Box, _: &mut ValidationState) -> Option<()> { + Some(()) +} + +#[cfg(feature="remote-checks")] +fn validate_remote_checks(uses: Box, _: &mut ValidationState) -> Option<()> { + uses.validate(); + Some(()) +} diff --git a/src/main.rs b/src/main.rs index 5b50cbd..75581c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,3 +16,6 @@ fn main() { process::exit(1); } } + + + From 1443dc1934114fcac8d94754f2d81ef0a9cb2d02 Mon Sep 17 00:00:00 2001 From: award28 Date: Wed, 8 Mar 2023 15:55:08 -0500 Subject: [PATCH 09/33] Action validation is working! Time to get docker and path done --- .github/workflows/audit.yml | 4 +- src/lib.rs | 154 +----------------------------------- src/validators/job_uses.rs | 96 ++++++++++++++++++++++ src/validators/mod.rs | 2 + src/validators/models.rs | 76 ++++++++++++++++++ 5 files changed, 179 insertions(+), 153 deletions(-) create mode 100644 src/validators/job_uses.rs create mode 100644 src/validators/mod.rs create mode 100644 src/validators/models.rs diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index b5c3ff1..67549d2 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -14,12 +14,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout + uses: actions/checkout@v1 with: submodules: true - name: Audit - uses: actions-rs/audit-check/testing@v + uses: actions-rs/audit-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/src/lib.rs b/src/lib.rs index ba5959e..d5dfd23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,18 +2,16 @@ mod config; mod log; mod schemas; mod utils; +mod validators; mod validation_error; mod validation_state; -use config::{ActionType, RunConfig}; -use regex::{Regex}; -#[cfg(feature="remote-checks")] -use reqwest::blocking::Client; use validation_error::ValidationError; use validation_state::ValidationState; pub use crate::config::CliConfig; +use config::{ActionType, RunConfig}; use crate::schemas::{validate_as_action, validate_as_workflow}; #[cfg(not(feature = "js"))] use glob::glob; @@ -158,8 +156,7 @@ fn run(config: &RunConfig) -> ValidationState { validate_paths(&doc, &mut state); validate_job_needs(&doc, &mut state); - validate_job_uses(&doc, &mut state); - + validators::job_uses::validate(&doc, &mut state); state } }, @@ -268,148 +265,3 @@ fn validate_job_needs(doc: &serde_json::Value, state: &mut ValidationState) { } } } - -// #[derive(Debug)] -// enum Uses<'a> { -// Action(&'a str), -// Path(&'a str), -// Docker(&'a str), -// Invalid(&'a str), -// } - -trait Uses<'a>: std::fmt::Debug { - fn validate(&self) -> Option<()>; -} - -#[derive(Debug)] -struct Invalid; - -impl Uses<'_> for Invalid { - fn validate(&self) -> Option<()> { println!("Invalid"); Some(()) } -} - -#[derive(Debug)] -struct Action<'a> { - owner: &'a str, - repo: &'a str, - path: Option<&'a str>, - reference: &'a str, -} - - -impl Uses<'_> for Action<'_> { - #[cfg(not(feature="remote-checks"))] - fn validate(&self) -> Option<()> { - println!( - "{}, {}, {}, {}", - self.owner, - self.repo, - self.path.unwrap_or("..."), - self.reference, - ); - Some(()) - } - - #[cfg(feature="remote-checks")] - fn validate(&self) -> Option<()> { - println!("{}", self.path.unwrap_or("...")); - let request = Client::new() - .get(format!( - "https://github.com/{0}/{1}/tree/{2}", - self.owner, - self.repo, - self.reference, - )); - let response = request.send(); - if response.ok()?.status() == 200 { - return Some(()); - } - None - } -} - -fn validate_job_uses(doc: &serde_json::Value, state: &mut ValidationState) -> Option<()> { - // If this regex doesn't compile, that should be considered a compile-time - // error. As such, we should unwrap to purposefully panic in the event of - // a malformed regex. - let r = Regex::new(r"(?x)^ - (?P.{0}(?P[^/]*)/(?P[^/]*)(/(?P.*))?@(?P.*))| - (?P.{0}\./([^/]+))| - (?P.{0}docker://(?P.*)(:(?P.*))?)| - $").unwrap(); - - for (job_name, job) in doc["jobs"].as_object()?.iter() { - let _ = job_name; - for step in job["steps"].as_array()?.iter() { - let uses_step = step["uses"].as_str()?; - let matched = r.captures(uses_step)?; - let uses_op: Option> = vec![ - "Action", "Path", "Docker", - ] - .into_iter() - .find_map::, _>(|name| { - matched.name(name)?; - match name { - // "Path" => Some(Uses::Path(value)), - // "Docker" => Some(Uses::Docker(value)), - "Action" => Some(Box::new(Action { - owner: matched.name("owner")?.as_str(), - repo: matched.name("repo")?.as_str(), - path: matched.name("path").map(|m| m.as_str()), - reference: matched.name("ref")?.as_str(), - })), - _ => Some(Box::new(Invalid{})), - } - }); - println!("{:#?}", uses_op); - let uses: Box = Box::new(Action { - owner: "owner", - repo: "repo", - path: Some("path"), - reference: "ref", - }); - if cfg!(feature="remote-checks") { - validate_remote_checks(uses, state); - } - // println!("{:#?}", uses_op); - // if let Some(uses) = uses_op { - // if uses.validate().is_none() { - // state.errors.push(ValidationError::UnresolvedJob { - // code: "uses_not_found".into(), - // path: format!("/jobs/{job_name}/steps"), - // title: "Invalid uses".into(), - // detail: Some(format!("Could not find the action `{uses_step}`.")), - // }); - // } - // } - // if uses.is_none() { - // println!("Error: {}", uses_step); - // continue; - // // TODO: Push error - // } - // if let Some(Uses::Invalid(value)) = uses { - // println!("Error: {}", uses_step); - // state.errors.push(ValidationError::UnresolvedJob { - // code: "invalid_uses".into(), - // path: format!("/jobs/{job_name}/uses/{uses_step}"), - // title: "Invalid uses".into(), - // detail: Some(format!("Invalid uses {value}")), - // }); - // continue; - // } - // println!("Success: {:#?}", uses?); - } - } - Some(()) -} - -#[cfg(not(feature="remote-checks"))] -fn validate_remote_checks(_: Box, _: &mut ValidationState) -> Option<()> { - Some(()) -} - -#[cfg(feature="remote-checks")] -fn validate_remote_checks(uses: Box, _: &mut ValidationState) -> Option<()> { - uses.validate(); - Some(()) -} diff --git a/src/validators/job_uses.rs b/src/validators/job_uses.rs new file mode 100644 index 0000000..4f5e369 --- /dev/null +++ b/src/validators/job_uses.rs @@ -0,0 +1,96 @@ +use regex::Regex; + +use crate::validation_error::ValidationError; +use crate::validation_state::ValidationState; + +use crate::validators::models::{Action, Uses, Invalid}; + + +pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option<()> { + // If this regex doesn't compile, that should be considered a compile-time + // error. As such, we should unwrap to purposefully panic in the event of + // a malformed regex. + let r = Regex::new(r"(?x)^ + (?P.{0}(?P[^/]*)/(?P[^/]*)(/(?P.*))?@(?P.*))| + (?P.{0}\./([^/]+))| + (?P.{0}docker://(?P.*)(:(?P.*))?)| + $").unwrap(); + + let all_uses = doc["jobs"] + .as_object()? + .iter() + .map(|(job_name, job)| { + job["steps"].as_array().map(|steps| { + steps + .iter() + .map(|step| { + Some((job_name, step["uses"].as_str()?)) + }) + .flatten() + .collect::>() + }) + .unwrap_or(vec![]) + }) + .flatten() + .collect::>(); + for (job_name, uses) in all_uses { + let matched = r.captures(uses)?; + let uses_op: Option> = vec![ + "Action", "Path", "Docker", + ] + .into_iter() + .find_map::, _>(|name| { + // If the regex didn't match any of them, + // then it's an error. + matched.name(name)?; + + let origin = format!("jobs/{job_name}/steps/uses"); + let uses = String::from(&matched[0]); + match name { + "Path" => { + Some(Box::new(Invalid{uses, origin})) + }, + "Docker" => { + Some(Box::new(Invalid{uses, origin})) + }, + "Action" => Some(Box::new(Action { + owner: String::from(matched.name("owner")?.as_str()), + repo: String::from(matched.name("repo")?.as_str()), + path: matched.name("path").map(|m| String::from(m.as_str())), + reference: String::from(matched.name("ref")?.as_str()), + origin, + })), + _ => { + Some(Box::new(Invalid{uses, origin})) + }, + } + }); + + if let Some(uses) = uses_op { + if cfg!(feature="remote-checks") { + validate_remote_checks(uses, state); + } + } else { + state.errors.push(ValidationError::InvalidGlob { + code: "invalid_uses".into(), + detail: Some(format!("The `uses` {uses} is invalid.")), + path: "".into(), + title: "".into(), + }); + } + } + let _ = state; + let _ = validate_remote_checks; + + + Some(()) +} + +fn validate_remote_checks(uses: Box, state: &mut ValidationState) -> () { + if !cfg!(feature="remote-checks") { + return (); + } + if let Err(v) = uses.validate() { + state.errors.push(v); + } +} diff --git a/src/validators/mod.rs b/src/validators/mod.rs new file mode 100644 index 0000000..6377967 --- /dev/null +++ b/src/validators/mod.rs @@ -0,0 +1,2 @@ +pub mod models; +pub mod job_uses; diff --git a/src/validators/models.rs b/src/validators/models.rs new file mode 100644 index 0000000..049643e --- /dev/null +++ b/src/validators/models.rs @@ -0,0 +1,76 @@ +use core::fmt; + +use crate::validation_error::ValidationError; + +#[cfg(feature="remote-checks")] +use reqwest::blocking::Client; + +pub trait Uses<'a>: std::fmt::Debug { + fn validate(&self) -> Result<(), ValidationError>; +} + +#[derive(Debug)] +pub struct Invalid { + pub uses: String, + pub origin: String, +} + +impl Uses<'_> for Invalid { + fn validate(&self) -> Result<(), ValidationError> { + println!("{:#?}", self); + Ok(()) + } +} + +#[derive(Debug)] +pub struct Action { + pub owner: String, + pub repo: String, + pub path: Option, + pub reference: String, + pub origin: String, +} + +impl fmt::Display for Action { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut p = vec![self.owner.as_ref(), self.repo.as_ref()]; + if let Some(path) = self.path.as_ref() { + p.push(path.as_str()); + } + write!(f, "{}@{}", p.join("/"), self.reference.as_str()) + } +} + + +impl Uses<'_> for Action { + #[cfg(not(feature="remote-checks"))] + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } + + #[cfg(feature="remote-checks")] + fn validate(&self) -> Result<(), ValidationError> { + let request = Client::new() + .get(format!( + "https://github.com/{0}/{1}/tree/{2}/{3}", + self.owner, + self.repo, + self.reference, + self.path.as_ref().unwrap_or(&String::new()), + )); + let response = request.send(); + if let Some(r) = response.ok() { + if r.status() == 200 { + return Ok(()); + } + return Err(ValidationError::Unknown { + code: "action_not_found".into(), + detail: Some(format!("Could not find action: {}", self)), + path: self.origin.to_owned(), + title: "Action Not Found".into(), + }); + } + + Ok(()) + } +} From be57be90467dccc74d5413ccca6e564f4236cfa8 Mon Sep 17 00:00:00 2001 From: award28 Date: Wed, 8 Mar 2023 21:01:19 -0500 Subject: [PATCH 10/33] Path working - Docker in progress --- .github/workflows/audit.yml | 2 +- src/validators/job_uses.rs | 48 +++++++++++++------- src/validators/models.rs | 87 ++++++++++++++++++++++++++++++------- 3 files changed, 105 insertions(+), 32 deletions(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 67549d2..4fcb822 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -24,7 +24,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - name: Path - uses: ./.github/actions/action1 + uses: ./.github - name: DockerHub uses: docker://alpine:3.8 diff --git a/src/validators/job_uses.rs b/src/validators/job_uses.rs index 4f5e369..91bec95 100644 --- a/src/validators/job_uses.rs +++ b/src/validators/job_uses.rs @@ -3,7 +3,7 @@ use regex::Regex; use crate::validation_error::ValidationError; use crate::validation_state::ValidationState; -use crate::validators::models::{Action, Uses, Invalid}; +use crate::validators::models; pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option<()> { @@ -12,8 +12,8 @@ pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option< // a malformed regex. let r = Regex::new(r"(?x)^ (?P.{0}(?P[^/]*)/(?P[^/]*)(/(?P.*))?@(?P.*))| - (?P.{0}\./([^/]+))| - (?P.{0}docker://(?P.*)(:(?P.*))?)| + (?P.{0}\./([^/]+/?)+)| + (?P.{0}(?:docker://)(?P([^:]+)/)?(?P[^/:]+)(?::(?P.+))?)| $").unwrap(); let all_uses = doc["jobs"] @@ -35,11 +35,11 @@ pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option< .collect::>(); for (job_name, uses) in all_uses { let matched = r.captures(uses)?; - let uses_op: Option> = vec![ + let uses_op: Option> = vec![ "Action", "Path", "Docker", ] .into_iter() - .find_map::, _>(|name| { + .find_map::, _>(|name| { // If the regex didn't match any of them, // then it's an error. matched.name(name)?; @@ -48,20 +48,36 @@ pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option< let uses = String::from(&matched[0]); match name { "Path" => { - Some(Box::new(Invalid{uses, origin})) + Some(Box::new(models::Path{uses, origin})) }, "Docker" => { - Some(Box::new(Invalid{uses, origin})) + let image = String::from(matched.name("image").unwrap().as_str()); + let url = matched.name("url").map(|v| String::from(v.as_str())); + let tag = matched.name("tag").map(|v| String::from(v.as_str())); + Some(Box::new(models::Docker { + uses, + origin, + image, + url, + tag, + })) + }, + "Action" => { + let owner = String::from(matched.name("owner").unwrap().as_str()); + let repo = String::from(matched.name("repo").unwrap().as_str()); + let path = matched.name("path").map(|v| String::from(v.as_str())); + let reference = String::from(matched.name("ref").unwrap().as_str()); + Some(Box::new(models::Action { + uses, + origin, + owner, + repo, + path, + reference, + })) }, - "Action" => Some(Box::new(Action { - owner: String::from(matched.name("owner")?.as_str()), - repo: String::from(matched.name("repo")?.as_str()), - path: matched.name("path").map(|m| String::from(m.as_str())), - reference: String::from(matched.name("ref")?.as_str()), - origin, - })), _ => { - Some(Box::new(Invalid{uses, origin})) + Some(Box::new(models::Invalid{uses, origin})) }, } }); @@ -86,7 +102,7 @@ pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option< Some(()) } -fn validate_remote_checks(uses: Box, state: &mut ValidationState) -> () { +fn validate_remote_checks(uses: Box, state: &mut ValidationState) -> () { if !cfg!(feature="remote-checks") { return (); } diff --git a/src/validators/models.rs b/src/validators/models.rs index 049643e..665b8de 100644 --- a/src/validators/models.rs +++ b/src/validators/models.rs @@ -1,5 +1,3 @@ -use core::fmt; - use crate::validation_error::ValidationError; #[cfg(feature="remote-checks")] @@ -9,6 +7,9 @@ pub trait Uses<'a>: std::fmt::Debug { fn validate(&self) -> Result<(), ValidationError>; } +pub trait Other { +} + #[derive(Debug)] pub struct Invalid { pub uses: String, @@ -24,24 +25,14 @@ impl Uses<'_> for Invalid { #[derive(Debug)] pub struct Action { + pub uses: String, + pub origin: String, pub owner: String, pub repo: String, pub path: Option, pub reference: String, - pub origin: String, -} - -impl fmt::Display for Action { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut p = vec![self.owner.as_ref(), self.repo.as_ref()]; - if let Some(path) = self.path.as_ref() { - p.push(path.as_str()); - } - write!(f, "{}@{}", p.join("/"), self.reference.as_str()) - } } - impl Uses<'_> for Action { #[cfg(not(feature="remote-checks"))] fn validate(&self) -> Result<(), ValidationError> { @@ -65,7 +56,7 @@ impl Uses<'_> for Action { } return Err(ValidationError::Unknown { code: "action_not_found".into(), - detail: Some(format!("Could not find action: {}", self)), + detail: Some(format!("Could not find action: {}", self.uses)), path: self.origin.to_owned(), title: "Action Not Found".into(), }); @@ -74,3 +65,69 @@ impl Uses<'_> for Action { Ok(()) } } + + +#[derive(Debug)] +pub struct Docker { + pub uses: String, + pub origin: String, + pub image: String, + pub url: Option, + pub tag: Option, +} + +impl Uses<'_> for Docker { + // #[cfg(not(feature="remote-checks"))] + fn validate(&self) -> Result<(), ValidationError> { + println!("{:#?}", self); + Ok(()) + } + + // #[cfg(feature="remote-checks")] + // fn validate(&self) -> Result<(), ValidationError> { + // let request = Client::new() + // .get(format!( + // "https://github.com/{0}/{1}/tree/{2}/{3}", + // self.owner, + // self.repo, + // self.reference, + // self.path.as_ref().unwrap_or(&String::new()), + // )); + // let response = request.send(); + // if let Some(r) = response.ok() { + // if r.status() == 200 { + // return Ok(()); + // } + // return Err(ValidationError::Unknown { + // code: "action_not_found".into(), + // detail: Some(format!("Could not find action: {}", self.uses)), + // path: self.origin.to_owned(), + // title: "Action Not Found".into(), + // }); + // } + + // Ok(()) + // } + +} + +#[derive(Debug)] +pub struct Path { + pub uses: String, + pub origin: String, +} + +impl Uses<'_> for Path { + fn validate(&self) -> Result<(), ValidationError> { + if std::path::Path::new(self.uses.as_str()).exists() { + return Ok(()); + } + Err(ValidationError::Unknown { + code: "action_not_found".into(), + detail: Some(format!("The action path does not exist: {}", self.uses)), + path: self.origin.to_owned(), + title: "Action Not Found".into(), + }) + } +} + From d2c4328bf81d9ffa3b95f56c719b7f07194ea5e4 Mon Sep 17 00:00:00 2001 From: award28 Date: Thu, 9 Mar 2023 12:03:40 -0500 Subject: [PATCH 11/33] Docker images working :D --- src/validators/job_uses.rs | 2 +- src/validators/models.rs | 101 +++++++++++++++++++++++++------------ 2 files changed, 71 insertions(+), 32 deletions(-) diff --git a/src/validators/job_uses.rs b/src/validators/job_uses.rs index 91bec95..fc20ffd 100644 --- a/src/validators/job_uses.rs +++ b/src/validators/job_uses.rs @@ -13,7 +13,7 @@ pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option< let r = Regex::new(r"(?x)^ (?P.{0}(?P[^/]*)/(?P[^/]*)(/(?P.*))?@(?P.*))| (?P.{0}\./([^/]+/?)+)| - (?P.{0}(?:docker://)(?P([^:]+)/)?(?P[^/:]+)(?::(?P.+))?)| + (?P.{0}(?:docker://)((?P([^/:]+))/)?(?P[^:]+)(?::(?P.+))?)| $").unwrap(); let all_uses = doc["jobs"] diff --git a/src/validators/models.rs b/src/validators/models.rs index 665b8de..adfb6bc 100644 --- a/src/validators/models.rs +++ b/src/validators/models.rs @@ -1,7 +1,7 @@ use crate::validation_error::ValidationError; #[cfg(feature="remote-checks")] -use reqwest::blocking::Client; +use reqwest::{StatusCode, blocking::Client}; pub trait Uses<'a>: std::fmt::Debug { fn validate(&self) -> Result<(), ValidationError>; @@ -33,6 +33,11 @@ pub struct Action { pub reference: String, } +#[cfg(feature="remote-checks")] +fn _get_request(url: String) -> Result { + Client::new().get(url).send() +} + impl Uses<'_> for Action { #[cfg(not(feature="remote-checks"))] fn validate(&self) -> Result<(), ValidationError> { @@ -41,15 +46,13 @@ impl Uses<'_> for Action { #[cfg(feature="remote-checks")] fn validate(&self) -> Result<(), ValidationError> { - let request = Client::new() - .get(format!( + let response = _get_request(format!( "https://github.com/{0}/{1}/tree/{2}/{3}", self.owner, self.repo, self.reference, self.path.as_ref().unwrap_or(&String::new()), )); - let response = request.send(); if let Some(r) = response.ok() { if r.status() == 200 { return Ok(()); @@ -77,38 +80,74 @@ pub struct Docker { } impl Uses<'_> for Docker { - // #[cfg(not(feature="remote-checks"))] + #[cfg(not(feature="remote-checks"))] fn validate(&self) -> Result<(), ValidationError> { - println!("{:#?}", self); Ok(()) } - // #[cfg(feature="remote-checks")] - // fn validate(&self) -> Result<(), ValidationError> { - // let request = Client::new() - // .get(format!( - // "https://github.com/{0}/{1}/tree/{2}/{3}", - // self.owner, - // self.repo, - // self.reference, - // self.path.as_ref().unwrap_or(&String::new()), - // )); - // let response = request.send(); - // if let Some(r) = response.ok() { - // if r.status() == 200 { - // return Ok(()); - // } - // return Err(ValidationError::Unknown { - // code: "action_not_found".into(), - // detail: Some(format!("Could not find action: {}", self.uses)), - // path: self.origin.to_owned(), - // title: "Action Not Found".into(), - // }); - // } - - // Ok(()) - // } + #[cfg(feature="remote-checks")] + fn validate(&self) -> Result<(), ValidationError> { + let mut image = self.image.to_owned(); + + // If there's no prefix and the image is in docker hub, then it means the image is a + // library image. + if self.url.is_none() && !image.contains("/") { + image = format!("library/{image}"); + } + let url = match (self.url.as_ref(), self.tag.as_ref()) { + // Lookup V2 protocol tag + (Some(url), Some(tag)) => format!("https://{url}/v2/{image}/manifest/{tag}"), + // Lookup V2 protocol image + (Some(url), None) => format!("https://{url}/v2/{image}/tags/list"), + // Lookup docker hub tag + (None, Some(tag)) => { + format!("https://registry.hub.docker.com/v2/repositories/{image}/tags/{tag}") + }, + // Lookup docker hub image + (None, None) => { + format!("https://registry.hub.docker.com/v2/repositories/{image}") + }, + }; + + println!("{}", url); + let response = _get_request(url); + + // How do we handle failed requests? If the user has the `remote-checks` feature enable, + // they likely want to know about these failures. We can mark this as a validation error. + if let Some(r) = response.ok() { + let status = r.status(); + if status == StatusCode::OK { + return Ok(()); + } else if status == StatusCode::UNAUTHORIZED { + // In this case, pull access requires authorized. For now, we should assume + // this the tag is valid. We could perform authentication, but that would + // require user creds and add a whole lot of scope to this feature. For now, an + // unauthenticated requests means the image exists, and that is sufficient. + return Ok(()); + } else if status == StatusCode::NOT_FOUND { + return Err(ValidationError::Unknown { + code: "docker_action_not_found".into(), + detail: Some(format!("Could not find the docker action: {}", self.uses)), + path: self.origin.to_owned(), + title: "Docker Action Not Found".into(), + }); + } else { + return Err(ValidationError::Unknown { + code: "unexpected_server_response".into(), + detail: Some(format!("Unexpected status code: {}", status)), + path: self.origin.to_owned(), + title: "Docker Action Not Found".into(), + }); + } + } + Err(ValidationError::Unknown { + code: "unexpected_server_response".into(), + detail: Some(format!("Could not find docker action: {}", self.uses)), + path: self.origin.to_owned(), + title: "Docker Action Not Found".into(), + }) + } } #[derive(Debug)] From 2139d218dc77f644bc1496da9306b39cbdc38023 Mon Sep 17 00:00:00 2001 From: award28 Date: Thu, 9 Mar 2023 12:40:17 -0500 Subject: [PATCH 12/33] Working for all v2 docker protocols --- .github/workflows/audit.yml | 3 +++ src/validators/job_uses.rs | 4 ++-- src/validators/models.rs | 15 ++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 4fcb822..06755f8 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -31,3 +31,6 @@ jobs: - name: DockerRegistry uses: docker://gcr.io/cloud-builders/gradle + + - name: GHCR + uses: docker://ghcr.io/aquasecurity/trivy:latest diff --git a/src/validators/job_uses.rs b/src/validators/job_uses.rs index fc20ffd..ecde7e3 100644 --- a/src/validators/job_uses.rs +++ b/src/validators/job_uses.rs @@ -13,7 +13,7 @@ pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option< let r = Regex::new(r"(?x)^ (?P.{0}(?P[^/]*)/(?P[^/]*)(/(?P.*))?@(?P.*))| (?P.{0}\./([^/]+/?)+)| - (?P.{0}(?:docker://)((?P([^/:]+))/)?(?P[^:]+)(?::(?P.+))?)| + (?P.{0}(?:docker://)(?P([^/:]+)\.([^/:]+)/)?(?P[^:]+)(?::(?P.+))?)| $").unwrap(); let all_uses = doc["jobs"] @@ -44,8 +44,8 @@ pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option< // then it's an error. matched.name(name)?; - let origin = format!("jobs/{job_name}/steps/uses"); let uses = String::from(&matched[0]); + let origin = format!("jobs/{job_name}/steps/uses/{uses}"); match name { "Path" => { Some(Box::new(models::Path{uses, origin})) diff --git a/src/validators/models.rs b/src/validators/models.rs index adfb6bc..49dd706 100644 --- a/src/validators/models.rs +++ b/src/validators/models.rs @@ -18,7 +18,6 @@ pub struct Invalid { impl Uses<'_> for Invalid { fn validate(&self) -> Result<(), ValidationError> { - println!("{:#?}", self); Ok(()) } } @@ -97,7 +96,7 @@ impl Uses<'_> for Docker { let url = match (self.url.as_ref(), self.tag.as_ref()) { // Lookup V2 protocol tag - (Some(url), Some(tag)) => format!("https://{url}/v2/{image}/manifest/{tag}"), + (Some(url), Some(tag)) => format!("https://{url}/v2/{image}/manifests/{tag}"), // Lookup V2 protocol image (Some(url), None) => format!("https://{url}/v2/{image}/tags/list"), // Lookup docker hub tag @@ -110,12 +109,7 @@ impl Uses<'_> for Docker { }, }; - println!("{}", url); - let response = _get_request(url); - - // How do we handle failed requests? If the user has the `remote-checks` feature enable, - // they likely want to know about these failures. We can mark this as a validation error. - if let Some(r) = response.ok() { + if let Some(r) = _get_request(url).ok() { let status = r.status(); if status == StatusCode::OK { return Ok(()); @@ -135,12 +129,15 @@ impl Uses<'_> for Docker { } else { return Err(ValidationError::Unknown { code: "unexpected_server_response".into(), - detail: Some(format!("Unexpected status code: {}", status)), + detail: Some(format!("Unexpected server response: {}", status)), path: self.origin.to_owned(), title: "Docker Action Not Found".into(), }); } } + + // How do we handle failed requests? If the user has the `remote-checks` feature enable, + // they likely want to know about these failures. We can mark this as a validation error. Err(ValidationError::Unknown { code: "unexpected_server_response".into(), detail: Some(format!("Could not find docker action: {}", self.uses)), From 03505b11d7a9ac6a3cb7740f6fc29fe8c56e0a2c Mon Sep 17 00:00:00 2001 From: award28 Date: Thu, 9 Mar 2023 12:41:32 -0500 Subject: [PATCH 13/33] Reset audit workflow --- .github/workflows/audit.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 06755f8..b61e4c9 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -22,15 +22,3 @@ jobs: uses: actions-rs/audit-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - - - name: Path - uses: ./.github - - - name: DockerHub - uses: docker://alpine:3.8 - - - name: DockerRegistry - uses: docker://gcr.io/cloud-builders/gradle - - - name: GHCR - uses: docker://ghcr.io/aquasecurity/trivy:latest From 41fcda5a4575c7353a25768a89295bfb68f08199 Mon Sep 17 00:00:00 2001 From: award28 Date: Thu, 9 Mar 2023 12:44:02 -0500 Subject: [PATCH 14/33] Remove unused remote checks flag --- src/config.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/config.rs b/src/config.rs index b3a9328..241daae 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,10 +13,6 @@ pub struct CliConfig { #[arg(short, long)] pub verbose: bool, - /// Perform remote calls to validate actions exist - #[arg(short, long)] - pub remote_checks: bool, - /// Input file #[arg(name = "path_to_action_yaml")] pub src: Vec, @@ -34,7 +30,6 @@ pub struct JsConfig<'a> { pub action_type: ActionType, pub src: &'a str, pub verbose: bool, - pub remote_checks: bool, } pub struct RunConfig<'a> { From fe6e3bde06bfc83469fafb553d4492b4ac325a88 Mon Sep 17 00:00:00 2001 From: award28 Date: Thu, 9 Mar 2023 12:44:30 -0500 Subject: [PATCH 15/33] Remove spacing --- src/main.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 75581c6..5b50cbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,3 @@ fn main() { process::exit(1); } } - - - From 3d5cbfdd457c51011525ed6a05a721720ce17930 Mon Sep 17 00:00:00 2001 From: award28 Date: Thu, 9 Mar 2023 21:51:26 -0500 Subject: [PATCH 16/33] Clean up code --- src/validators/job_uses.rs | 148 +++++++++++++++++-------------------- src/validators/models.rs | 53 ++++++++----- 2 files changed, 101 insertions(+), 100 deletions(-) diff --git a/src/validators/job_uses.rs b/src/validators/job_uses.rs index ecde7e3..683009b 100644 --- a/src/validators/job_uses.rs +++ b/src/validators/job_uses.rs @@ -1,112 +1,100 @@ -use regex::Regex; +use regex::{Regex, Captures}; -use crate::validation_error::ValidationError; use crate::validation_state::ValidationState; use crate::validators::models; +use super::models::Invalid; + pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option<()> { // If this regex doesn't compile, that should be considered a compile-time // error. As such, we should unwrap to purposefully panic in the event of // a malformed regex. - let r = Regex::new(r"(?x)^ - (?P.{0}(?P[^/]*)/(?P[^/]*)(/(?P.*))?@(?P.*))| - (?P.{0}\./([^/]+/?)+)| - (?P.{0}(?:docker://)(?P([^/:]+)\.([^/:]+)/)?(?P[^:]+)(?::(?P.+))?)| - $").unwrap(); + let pattern = vec![ + models::Action::PATTERN, + models::Docker::PATTERN, + models::Path::PATTERN, + ].join("|"); + let r = Regex::new(format!(r"(?x)^{pattern}$").as_str()).unwrap(); - let all_uses = doc["jobs"] + let jobs_step_uses = doc["jobs"] .as_object()? .iter() .map(|(job_name, job)| { job["steps"].as_array().map(|steps| { steps .iter() - .map(|step| { - Some((job_name, step["uses"].as_str()?)) - }) + .map(|step| Some((job_name, step["uses"].as_str()?))) .flatten() .collect::>() }) .unwrap_or(vec![]) - }) - .flatten() - .collect::>(); - for (job_name, uses) in all_uses { - let matched = r.captures(uses)?; - let uses_op: Option> = vec![ - "Action", "Path", "Docker", - ] - .into_iter() - .find_map::, _>(|name| { - // If the regex didn't match any of them, - // then it's an error. - matched.name(name)?; + }) + .flatten() + .collect::>(); - let uses = String::from(&matched[0]); - let origin = format!("jobs/{job_name}/steps/uses/{uses}"); - match name { - "Path" => { - Some(Box::new(models::Path{uses, origin})) - }, - "Docker" => { - let image = String::from(matched.name("image").unwrap().as_str()); - let url = matched.name("url").map(|v| String::from(v.as_str())); - let tag = matched.name("tag").map(|v| String::from(v.as_str())); - Some(Box::new(models::Docker { - uses, - origin, - image, - url, - tag, - })) - }, - "Action" => { - let owner = String::from(matched.name("owner").unwrap().as_str()); - let repo = String::from(matched.name("repo").unwrap().as_str()); - let path = matched.name("path").map(|v| String::from(v.as_str())); - let reference = String::from(matched.name("ref").unwrap().as_str()); - Some(Box::new(models::Action { - uses, - origin, - owner, - repo, - path, - reference, + for (job_name, uses) in jobs_step_uses { + let origin = format!("jobs/{job_name}/steps/uses/{uses}"); + let captures_op = &r.captures(uses); + + let uses_type = vec![ActionType::Action, ActionType::Docker, ActionType::Path] + .into_iter() + .find_map(|action_type| { + if let Some(captures) = captures_op { + Some(_action_type(action_type, &origin, &captures)) + } else { + Some(Box::new(Invalid{ + uses: String::from(uses), + origin: origin.to_owned(), })) - }, - _ => { - Some(Box::new(models::Invalid{uses, origin})) - }, - } - }); + } + }) + .unwrap_or(Box::new(Invalid{ + uses: String::from(uses), + origin: origin.to_owned(), + })); - if let Some(uses) = uses_op { - if cfg!(feature="remote-checks") { - validate_remote_checks(uses, state); - } - } else { - state.errors.push(ValidationError::InvalidGlob { - code: "invalid_uses".into(), - detail: Some(format!("The `uses` {uses} is invalid.")), - path: "".into(), - title: "".into(), - }); + if let Err(v) = uses_type.validate() { + state.errors.push(v); } } - let _ = state; - let _ = validate_remote_checks; - Some(()) } -fn validate_remote_checks(uses: Box, state: &mut ValidationState) -> () { - if !cfg!(feature="remote-checks") { - return (); - } - if let Err(v) = uses.validate() { - state.errors.push(v); +enum ActionType { + Action, + Docker, + Path, +} + +fn _action_type<'a>( + action_type: ActionType, + origin: &String, + captures: &Captures<'a>, +) -> Box> { + let origin = origin.to_owned(); + let uses = String::from(&captures[0]); + match action_type { + ActionType::Path => Box::new(models::Path{uses, origin}), + ActionType::Docker => Box::new(models::Docker { + uses, + origin, + // The `image` capture group is guranteed to exist when `Docker` does. + image: String::from(captures.name("image").unwrap().as_str()), + url: captures.name("url").map(|v| String::from(v.as_str())), + tag: captures.name("tag").map(|v| String::from(v.as_str())), + }), + ActionType::Action => Box::new(models::Action { + uses, + origin, + // The `owner`, `repo`, and `reference` capture groups are guranteed + // to exist when `Action` does. + owner: String::from(captures.name("owner").unwrap().as_str()), + repo: String::from(captures.name("repo").unwrap().as_str()), + reference: String::from(captures.name("ref").unwrap().as_str()), + path: captures.name("path").map(|v| String::from(v.as_str())), + }), } } diff --git a/src/validators/models.rs b/src/validators/models.rs index 49dd706..2a6953b 100644 --- a/src/validators/models.rs +++ b/src/validators/models.rs @@ -1,25 +1,15 @@ use crate::validation_error::ValidationError; #[cfg(feature="remote-checks")] -use reqwest::{StatusCode, blocking::Client}; +use reqwest::{blocking::Response, StatusCode, blocking::Client}; -pub trait Uses<'a>: std::fmt::Debug { - fn validate(&self) -> Result<(), ValidationError>; -} - -pub trait Other { -} - -#[derive(Debug)] -pub struct Invalid { - pub uses: String, - pub origin: String, +#[cfg(feature="remote-checks")] +fn _get_request(url: String) -> Result { + Client::new().get(url).send() } -impl Uses<'_> for Invalid { - fn validate(&self) -> Result<(), ValidationError> { - Ok(()) - } +pub trait Uses<'a>: std::fmt::Debug { + fn validate(&self) -> Result<(), ValidationError>; } #[derive(Debug)] @@ -32,9 +22,8 @@ pub struct Action { pub reference: String, } -#[cfg(feature="remote-checks")] -fn _get_request(url: String) -> Result { - Client::new().get(url).send() +impl Action { + pub const PATTERN: &str = r"(?P.{0}(?P[^/]*)/(?P[^/]*)(/(?P.*))?@(?P.*))"; } impl Uses<'_> for Action { @@ -68,7 +57,6 @@ impl Uses<'_> for Action { } } - #[derive(Debug)] pub struct Docker { pub uses: String, @@ -78,6 +66,10 @@ pub struct Docker { pub tag: Option, } +impl Docker { + pub const PATTERN: &str = r"(?P.{0}(?:docker://)(?P([^/:]+)\.([^/:]+)/)?(?P[^:]+)(?::(?P.+))?)"; +} + impl Uses<'_> for Docker { #[cfg(not(feature="remote-checks"))] fn validate(&self) -> Result<(), ValidationError> { @@ -153,6 +145,10 @@ pub struct Path { pub origin: String, } +impl Path { + pub const PATTERN: &str = r"(?P.{0}\./([^/]+/?)+)"; +} + impl Uses<'_> for Path { fn validate(&self) -> Result<(), ValidationError> { if std::path::Path::new(self.uses.as_str()).exists() { @@ -167,3 +163,20 @@ impl Uses<'_> for Path { } } +#[derive(Debug)] +pub struct Invalid { + pub uses: String, + pub origin: String, +} + +impl Uses<'_> for Invalid { + fn validate(&self) -> Result<(), ValidationError> { + let uses = self.uses.to_owned(); + Err(ValidationError::InvalidGlob { + code: "invalid_uses".into(), + detail: Some(format!("The `uses` {uses} is invalid.")), + path: self.origin.to_owned(), + title: "Invalid Uses".into(), + }) + } +} From baa6910d89376fc4ec872706d3e9d5954e018028 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Fri, 10 Mar 2023 08:29:34 -0500 Subject: [PATCH 17/33] Cleanup iter --- src/validators/job_uses.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/validators/job_uses.rs b/src/validators/job_uses.rs index 683009b..ff6c732 100644 --- a/src/validators/job_uses.rs +++ b/src/validators/job_uses.rs @@ -21,17 +21,15 @@ pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option< let jobs_step_uses = doc["jobs"] .as_object()? .iter() - .map(|(job_name, job)| { - job["steps"].as_array().map(|steps| { - steps - .iter() - .map(|step| Some((job_name, step["uses"].as_str()?))) - .flatten() - .collect::>() + .flat_map(|(job_name, job)| { + Some((job_name, job["steps"].as_array()?.iter())) + }) + .flat_map(|(job_name, steps)| { + steps.map(|step| { + Some((job_name.to_owned(), step["uses"].as_str()?)) }) - .unwrap_or(vec![]) }) - .flatten() + .filter_map(|o| o) .collect::>(); for (job_name, uses) in jobs_step_uses { From 5898f7cd7f131db47f4a6dc251a441f4d1db9455 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Fri, 10 Mar 2023 09:16:18 -0500 Subject: [PATCH 18/33] Remote option return value --- src/validators/job_uses.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/validators/job_uses.rs b/src/validators/job_uses.rs index ff6c732..d0aef52 100644 --- a/src/validators/job_uses.rs +++ b/src/validators/job_uses.rs @@ -7,7 +7,7 @@ use crate::validators::models; use super::models::Invalid; -pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option<()> { +pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) { // If this regex doesn't compile, that should be considered a compile-time // error. As such, we should unwrap to purposefully panic in the event of // a malformed regex. @@ -18,8 +18,10 @@ pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option< ].join("|"); let r = Regex::new(format!(r"(?x)^{pattern}$").as_str()).unwrap(); + let default_map = &serde_json::Map::::new(); let jobs_step_uses = doc["jobs"] - .as_object()? + .as_object() + .unwrap_or(default_map) .iter() .flat_map(|(job_name, job)| { Some((job_name, job["steps"].as_array()?.iter())) @@ -36,17 +38,12 @@ pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option< let origin = format!("jobs/{job_name}/steps/uses/{uses}"); let captures_op = &r.captures(uses); - let uses_type = vec![ActionType::Action, ActionType::Docker, ActionType::Path] + let uses_type = vec![ + ActionType::Action, ActionType::Docker, ActionType::Path, + ] .into_iter() .find_map(|action_type| { - if let Some(captures) = captures_op { - Some(_action_type(action_type, &origin, &captures)) - } else { - Some(Box::new(Invalid{ - uses: String::from(uses), - origin: origin.to_owned(), - })) - } + Some(_action_type(action_type, &origin, captures_op.as_ref()?)) }) .unwrap_or(Box::new(Invalid{ uses: String::from(uses), @@ -57,8 +54,6 @@ pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) -> Option< state.errors.push(v); } } - - Some(()) } enum ActionType { From f3e0b82515fb83aed7f0e5975b79f369246fc71b Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Fri, 10 Mar 2023 09:19:01 -0500 Subject: [PATCH 19/33] Remove futures dep --- Cargo.lock | 58 +++++------------------------------------------------- Cargo.toml | 1 - 2 files changed, 5 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61ef9ad..080a8aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,6 @@ version = "0.0.0-git" dependencies = [ "clap", "console_error_panic_hook", - "futures", "glob", "regex", "reqwest", @@ -247,7 +246,7 @@ version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -329,21 +328,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.26" @@ -351,7 +335,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -360,34 +343,12 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" -[[package]] -name = "futures-executor" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - [[package]] name = "futures-io" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" -[[package]] -name = "futures-macro" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "futures-sink" version = "0.3.26" @@ -406,11 +367,8 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ - "futures-channel", "futures-core", "futures-io", - "futures-macro", - "futures-sink", "futures-task", "memchr", "pin-project-lite", @@ -604,7 +562,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -711,12 +669,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "memory_units" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" - [[package]] name = "mime" version = "0.3.16" @@ -795,7 +747,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" dependencies = [ "bitflags", - "cfg-if 1.0.0", + "cfg-if", "foreign-types", "libc", "once_cell", @@ -1215,7 +1167,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fastrand", "redox_syscall", "rustix", @@ -1299,7 +1251,7 @@ version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "pin-project-lite", "tracing-core", ] diff --git a/Cargo.toml b/Cargo.toml index 7b437e4..82b1705 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,6 @@ console_error_panic_hook = { version = "0.1.6", optional = true } serde-wasm-bindgen = "0.4.5" regex = "1.7.1" -futures = "0.3.26" reqwest = { version = "0.11.14", features = ["blocking"], optional = true } [dev-dependencies] From fb50f1a22aa3862a327b253967428f82d158b6ca Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Fri, 10 Mar 2023 09:20:31 -0500 Subject: [PATCH 20/33] Remove super import --- src/validators/job_uses.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/validators/job_uses.rs b/src/validators/job_uses.rs index d0aef52..bd77480 100644 --- a/src/validators/job_uses.rs +++ b/src/validators/job_uses.rs @@ -1,11 +1,13 @@ use regex::{Regex, Captures}; use crate::validation_state::ValidationState; - use crate::validators::models; -use super::models::Invalid; - +enum ActionType { + Action, + Docker, + Path, +} pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) { // If this regex doesn't compile, that should be considered a compile-time @@ -45,7 +47,7 @@ pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) { .find_map(|action_type| { Some(_action_type(action_type, &origin, captures_op.as_ref()?)) }) - .unwrap_or(Box::new(Invalid{ + .unwrap_or(Box::new(models::Invalid{ uses: String::from(uses), origin: origin.to_owned(), })); @@ -56,12 +58,6 @@ pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) { } } -enum ActionType { - Action, - Docker, - Path, -} - fn _action_type<'a>( action_type: ActionType, origin: &String, From aba30d4ffbed9578621a5b23f54d9ac22e5325e9 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Fri, 10 Mar 2023 10:06:34 -0500 Subject: [PATCH 21/33] Add docs for validator --- src/validators/job_uses.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/validators/job_uses.rs b/src/validators/job_uses.rs index bd77480..33e1534 100644 --- a/src/validators/job_uses.rs +++ b/src/validators/job_uses.rs @@ -3,12 +3,25 @@ use regex::{Regex, Captures}; use crate::validation_state::ValidationState; use crate::validators::models; +/// A simple enum providing exhaustive matching to [`_match_action`]. enum ActionType { Action, Docker, Path, } +/// Validates all `jobs..steps[*].uses` values in the provided workflow file(s). This +/// validator has remote checks which will only run if the `remote-checks` feature flag is enabled. +/// If the feature flag is disabled, then this validate confirms the shape of the uses statement +/// matches GitHub's expected format +/// ([more here](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsuses)). +/// +/// If the feature flag is enabled, the above checks will be validated in addition to the remote +/// checks. The [`models::Action`] and [`models::Docker`] structs both implement remote checks. +/// +/// # Arguments +/// * doc - The parsed workflow document, to be validated. +/// * state - The [`ValidationState`] to which will be used to provide validation errors. pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) { // If this regex doesn't compile, that should be considered a compile-time // error. As such, we should unwrap to purposefully panic in the event of @@ -58,6 +71,13 @@ pub fn validate(doc: &serde_json::Value, state: &mut ValidationState) { } } +/// Matches on the provided `action_type` and extracts the `captures` named capture groups for that +/// implementation of the `Uses` trait. +/// +/// # Arguments +/// * `action_type` - An enum indicating if the action type is `Action`, `Docker`, or `Path`. +/// * `origin` - The origin path of the `uses` string being validated from the workflow. +/// * `captures` - The capture group which matched the validation regex. fn _action_type<'a>( action_type: ActionType, origin: &String, From 1546226ac94e64caebc7d489be8ff32b0d9e44c7 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Fri, 10 Mar 2023 10:30:06 -0500 Subject: [PATCH 22/33] Add model notes --- src/validators/models.rs | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/validators/models.rs b/src/validators/models.rs index 2a6953b..97a3249 100644 --- a/src/validators/models.rs +++ b/src/validators/models.rs @@ -3,11 +3,18 @@ use crate::validation_error::ValidationError; #[cfg(feature="remote-checks")] use reqwest::{blocking::Response, StatusCode, blocking::Client}; +/// A method to perform a synchronous get request and return it's result. +/// +/// # Arguments +/// * url - The FQDN where the GET request will be sent. #[cfg(feature="remote-checks")] fn _get_request(url: String) -> Result { Client::new().get(url).send() } +/// This trait provides a common method for all action type implementations to validate said action +/// type. An implementation of `validate` should return a [`ValidationError`] if one were to occur, +/// the validation error should be pushed onto the validation error stack. pub trait Uses<'a>: std::fmt::Debug { fn validate(&self) -> Result<(), ValidationError>; } @@ -32,6 +39,9 @@ impl Uses<'_> for Action { Ok(()) } + /// Remote-check validation logic for GitHub Actions. If the destructured action is able to be + /// retrieved from the repositories tree, then the action exists. Otherwise, the action is + /// formatted properly but does not exist. #[cfg(feature="remote-checks")] fn validate(&self) -> Result<(), ValidationError> { let response = _get_request(format!( @@ -76,6 +86,10 @@ impl Uses<'_> for Docker { Ok(()) } + /// Remote-check validation logic for Docker images. If the destructured image is able to be + /// retrieved from Docker Hub or using the registries v2 endpoints, then the image exists. If + /// the image results in a 401 (UNAUTHORIZED), the image _could_ exist. In this case, + /// action-validator will assume the image exists. #[cfg(feature="remote-checks")] fn validate(&self) -> Result<(), ValidationError> { let mut image = self.image.to_owned(); @@ -106,10 +120,11 @@ impl Uses<'_> for Docker { if status == StatusCode::OK { return Ok(()); } else if status == StatusCode::UNAUTHORIZED { - // In this case, pull access requires authorized. For now, we should assume - // this the tag is valid. We could perform authentication, but that would - // require user creds and add a whole lot of scope to this feature. For now, an - // unauthenticated requests means the image exists, and that is sufficient. + // In this case, pull access requires authorization. There are simple cases that + // only require the bearer token retrieval followed by manifest access, but there + // are also others that require user credentials. For now, we should assume that + // the tag tag is valid. We could perform authentication, but that would requrie + // user creds and adds a whole lot of scope to this feature. return Ok(()); } else if status == StatusCode::NOT_FOUND { return Err(ValidationError::Unknown { @@ -128,8 +143,8 @@ impl Uses<'_> for Docker { } } - // How do we handle failed requests? If the user has the `remote-checks` feature enable, - // they likely want to know about these failures. We can mark this as a validation error. + // The remote-check request failed. If the user has the `remote-checks` feature enable, + // they likely want to know about these failures. We can mark this as an error. Err(ValidationError::Unknown { code: "unexpected_server_response".into(), detail: Some(format!("Could not find docker action: {}", self.uses)), @@ -150,6 +165,7 @@ impl Path { } impl Uses<'_> for Path { + /// Validates that the supplied path exists. fn validate(&self) -> Result<(), ValidationError> { if std::path::Path::new(self.uses.as_str()).exists() { return Ok(()); @@ -170,6 +186,9 @@ pub struct Invalid { } impl Uses<'_> for Invalid { + /// If the provided action is not a [`Docker`], [`Action`], or [`Path`], the action is invalid. + /// When this [`Uses`] implementation is created, the validation will always return a + /// validation error. fn validate(&self) -> Result<(), ValidationError> { let uses = self.uses.to_owned(); Err(ValidationError::InvalidGlob { From c1ffa2bcead33754af8b12781ea4c4b99186021b Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Fri, 10 Mar 2023 10:34:17 -0500 Subject: [PATCH 23/33] Typo --- src/validators/job_uses.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validators/job_uses.rs b/src/validators/job_uses.rs index 33e1534..99fb2e1 100644 --- a/src/validators/job_uses.rs +++ b/src/validators/job_uses.rs @@ -3,7 +3,7 @@ use regex::{Regex, Captures}; use crate::validation_state::ValidationState; use crate::validators::models; -/// A simple enum providing exhaustive matching to [`_match_action`]. +/// A simple enum providing exhaustive matching to [`_action_type`]. enum ActionType { Action, Docker, From 3bdea4f7a06c2ef9d5632b141e748ee28e9f3a39 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Fri, 10 Mar 2023 10:35:54 -0500 Subject: [PATCH 24/33] spelling --- src/validators/job_uses.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validators/job_uses.rs b/src/validators/job_uses.rs index 99fb2e1..54b79c0 100644 --- a/src/validators/job_uses.rs +++ b/src/validators/job_uses.rs @@ -12,7 +12,7 @@ enum ActionType { /// Validates all `jobs..steps[*].uses` values in the provided workflow file(s). This /// validator has remote checks which will only run if the `remote-checks` feature flag is enabled. -/// If the feature flag is disabled, then this validate confirms the shape of the uses statement +/// If the feature flag is disabled, then this validation confirms the shape of the uses statement /// matches GitHub's expected format /// ([more here](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsuses)). /// From 08d4af7ae0bf3071a466b26c3ec245561394ec55 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Wed, 15 Mar 2023 09:39:24 -0400 Subject: [PATCH 25/33] Running tests through rust testing --- Cargo.lock | 215 +++++++++++++++++- Cargo.toml | 2 + test/004_failing_globs/stdout | 1 - test/007_funky_syntax/stdout | 1 - test/008_job_dependencies/stdout | 1 - test/009_multi_file/stdout | 1 - {test => tests}/001_basic_workflow/test.yml | 0 .../validation_state.snap.json | 0 {test => tests}/002_basic_action/action.yml | 0 .../validation_state.snap.json | 0 {test => tests}/003_successful_globs/glob.yml | 2 +- .../validation_state.snap.json | 0 {test => tests}/004_failing_globs/exitcode | 0 {test => tests}/004_failing_globs/glob.yml | 2 +- {test => tests}/004_failing_globs/stderr | 4 +- tests/004_failing_globs/stdout | 1 + .../validation_state.snap.json | 0 .../005_conditional_step_in_action/action.yml | 0 .../validation_state.snap.json | 0 .../test.yml | 0 .../validation_state.snap.json | 0 {test => tests}/007_funky_syntax/exitcode | 0 .../007_funky_syntax/rust-check.yml | 0 {test => tests}/007_funky_syntax/stderr | 2 +- tests/007_funky_syntax/stdout | 1 + .../validation_state.snap.json | 0 {test => tests}/008_job_dependencies/exitcode | 0 {test => tests}/008_job_dependencies/stderr | 2 +- tests/008_job_dependencies/stdout | 1 + {test => tests}/008_job_dependencies/test.yml | 0 .../validation_state.snap.json | 0 {test => tests}/009_multi_file/exitcode | 0 {test => tests}/009_multi_file/stderr | 2 +- tests/009_multi_file/stdout | 1 + {test => tests}/009_multi_file/valid.yml | 0 .../009_multi_file/validation_state.snap.json | 0 {test => tests}/009_multi_file/xinvalid.yml | 0 {test => tests}/run | 0 {test => tests}/run.mjs | 0 tests/snapshot_tests.rs | 85 +++++++ 40 files changed, 301 insertions(+), 23 deletions(-) delete mode 100644 test/004_failing_globs/stdout delete mode 100644 test/007_funky_syntax/stdout delete mode 100644 test/008_job_dependencies/stdout delete mode 100644 test/009_multi_file/stdout rename {test => tests}/001_basic_workflow/test.yml (100%) rename {test => tests}/001_basic_workflow/validation_state.snap.json (100%) rename {test => tests}/002_basic_action/action.yml (100%) rename {test => tests}/002_basic_action/validation_state.snap.json (100%) rename {test => tests}/003_successful_globs/glob.yml (88%) rename {test => tests}/003_successful_globs/validation_state.snap.json (100%) rename {test => tests}/004_failing_globs/exitcode (100%) rename {test => tests}/004_failing_globs/glob.yml (89%) rename {test => tests}/004_failing_globs/stderr (70%) create mode 100644 tests/004_failing_globs/stdout rename {test => tests}/004_failing_globs/validation_state.snap.json (100%) rename {test => tests}/005_conditional_step_in_action/action.yml (100%) rename {test => tests}/005_conditional_step_in_action/validation_state.snap.json (100%) rename {test => tests}/006_workflow_dispatch_inputs_options/test.yml (100%) rename {test => tests}/006_workflow_dispatch_inputs_options/validation_state.snap.json (100%) rename {test => tests}/007_funky_syntax/exitcode (100%) rename {test => tests}/007_funky_syntax/rust-check.yml (100%) rename {test => tests}/007_funky_syntax/stderr (91%) create mode 100644 tests/007_funky_syntax/stdout rename {test => tests}/007_funky_syntax/validation_state.snap.json (100%) rename {test => tests}/008_job_dependencies/exitcode (100%) rename {test => tests}/008_job_dependencies/stderr (88%) create mode 100644 tests/008_job_dependencies/stdout rename {test => tests}/008_job_dependencies/test.yml (100%) rename {test => tests}/008_job_dependencies/validation_state.snap.json (100%) rename {test => tests}/009_multi_file/exitcode (100%) rename {test => tests}/009_multi_file/stderr (92%) create mode 100644 tests/009_multi_file/stdout rename {test => tests}/009_multi_file/valid.yml (100%) rename {test => tests}/009_multi_file/validation_state.snap.json (100%) rename {test => tests}/009_multi_file/xinvalid.yml (100%) rename {test => tests}/run (100%) rename {test => tests}/run.mjs (100%) create mode 100644 tests/snapshot_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 080a8aa..87ed8cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,11 +6,13 @@ version = 3 name = "action-validator" version = "0.0.0-git" dependencies = [ + "assert_cmd", "clap", "console_error_panic_hook", "glob", "regex", "reqwest", + "rstest", "serde", "serde-wasm-bindgen", "serde_json", @@ -48,6 +50,20 @@ dependencies = [ "libc", ] +[[package]] +name = "assert_cmd" +version = "2.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9834fcc22e0874394a010230586367d4a3e9f11b560f469262678547e1d2575e" +dependencies = [ + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -81,6 +97,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bstr" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" +dependencies = [ + "memchr", + "once_cell", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.12.0" @@ -234,12 +262,30 @@ dependencies = [ "syn", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "downcast-rs" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "encoding_rs" version = "0.8.32" @@ -328,47 +374,94 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" + +[[package]] +name = "futures-executor" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] [[package]] name = "futures-io" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" +checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" + +[[package]] +name = "futures-macro" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "futures-sink" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" +checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" [[package]] name = "futures-task" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", + "futures-sink", "futures-task", "memchr", "pin-project-lite", @@ -593,6 +686,15 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.6" @@ -853,6 +955,33 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "predicates" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" +dependencies = [ + "difflib", + "itertools", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2" + +[[package]] +name = "predicates-tree" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -945,6 +1074,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "regex-syntax" version = "0.6.28" @@ -988,6 +1123,41 @@ dependencies = [ "winreg", ] +[[package]] +name = "rstest" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07f2d176c472198ec1e6551dc7da28f1c089652f66a7b722676c2238ebc0edf" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7229b505ae0706e64f37ffc54a9c163e11022a6636d58fe1f3f52018257ff9f7" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-ident", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.36.9" @@ -1052,6 +1222,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "serde" version = "1.0.153" @@ -1183,6 +1359,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" + [[package]] name = "tinyvec" version = "1.6.0" @@ -1370,6 +1552,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "want" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 82b1705..b140e93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,8 @@ reqwest = { version = "0.11.14", features = ["blocking"], optional = true } [dev-dependencies] wasm-bindgen-test = "0.3.13" +rstest = "0.16.0" +assert_cmd = "2.0.8" [profile.release] # Tell `rustc` to optimize for small code size. diff --git a/test/004_failing_globs/stdout b/test/004_failing_globs/stdout deleted file mode 100644 index 4179a2f..0000000 --- a/test/004_failing_globs/stdout +++ /dev/null @@ -1 +0,0 @@ -Fatal error validating 004_failing_globs/glob.yml diff --git a/test/007_funky_syntax/stdout b/test/007_funky_syntax/stdout deleted file mode 100644 index f57da30..0000000 --- a/test/007_funky_syntax/stdout +++ /dev/null @@ -1 +0,0 @@ -Fatal error validating 007_funky_syntax/rust-check.yml diff --git a/test/008_job_dependencies/stdout b/test/008_job_dependencies/stdout deleted file mode 100644 index 73dc3ee..0000000 --- a/test/008_job_dependencies/stdout +++ /dev/null @@ -1 +0,0 @@ -Fatal error validating 008_job_dependencies/test.yml diff --git a/test/009_multi_file/stdout b/test/009_multi_file/stdout deleted file mode 100644 index ec1a3fd..0000000 --- a/test/009_multi_file/stdout +++ /dev/null @@ -1 +0,0 @@ -Fatal error validating 009_multi_file/xinvalid.yml diff --git a/test/001_basic_workflow/test.yml b/tests/001_basic_workflow/test.yml similarity index 100% rename from test/001_basic_workflow/test.yml rename to tests/001_basic_workflow/test.yml diff --git a/test/001_basic_workflow/validation_state.snap.json b/tests/001_basic_workflow/validation_state.snap.json similarity index 100% rename from test/001_basic_workflow/validation_state.snap.json rename to tests/001_basic_workflow/validation_state.snap.json diff --git a/test/002_basic_action/action.yml b/tests/002_basic_action/action.yml similarity index 100% rename from test/002_basic_action/action.yml rename to tests/002_basic_action/action.yml diff --git a/test/002_basic_action/validation_state.snap.json b/tests/002_basic_action/validation_state.snap.json similarity index 100% rename from test/002_basic_action/validation_state.snap.json rename to tests/002_basic_action/validation_state.snap.json diff --git a/test/003_successful_globs/glob.yml b/tests/003_successful_globs/glob.yml similarity index 88% rename from test/003_successful_globs/glob.yml rename to tests/003_successful_globs/glob.yml index 6608e8b..2981f5b 100644 --- a/test/003_successful_globs/glob.yml +++ b/tests/003_successful_globs/glob.yml @@ -3,7 +3,7 @@ name: Test on: push: paths: - - 003_successful_globs/* + - ./tests/003_successful_globs/* defaults: run: diff --git a/test/003_successful_globs/validation_state.snap.json b/tests/003_successful_globs/validation_state.snap.json similarity index 100% rename from test/003_successful_globs/validation_state.snap.json rename to tests/003_successful_globs/validation_state.snap.json diff --git a/test/004_failing_globs/exitcode b/tests/004_failing_globs/exitcode similarity index 100% rename from test/004_failing_globs/exitcode rename to tests/004_failing_globs/exitcode diff --git a/test/004_failing_globs/glob.yml b/tests/004_failing_globs/glob.yml similarity index 89% rename from test/004_failing_globs/glob.yml rename to tests/004_failing_globs/glob.yml index 7c4d27e..fa2a7c8 100644 --- a/test/004_failing_globs/glob.yml +++ b/tests/004_failing_globs/glob.yml @@ -3,7 +3,7 @@ name: Bad globs, no biscuit on: push: paths: - - 004_bad_globs/*.txt + - ./tests/004_bad_globs/*.txt defaults: run: diff --git a/test/004_failing_globs/stderr b/tests/004_failing_globs/stderr similarity index 70% rename from test/004_failing_globs/stderr rename to tests/004_failing_globs/stderr index 2171874..f7e987a 100644 --- a/test/004_failing_globs/stderr +++ b/tests/004_failing_globs/stderr @@ -3,13 +3,13 @@ Validation failed: ValidationState { Workflow, ), file_path: Some( - "004_failing_globs/glob.yml", + "./tests/004_failing_globs/glob.yml", ), errors: [ NoFilesMatchingGlob { code: "glob_not_matched", detail: Some( - "Glob \"004_bad_globs/*.txt\" in /on/push/paths does not match any files", + "Glob \"./tests/004_bad_globs/*.txt\" in /on/push/paths does not match any files", ), path: "/on/push/paths", title: "Glob does not match any files", diff --git a/tests/004_failing_globs/stdout b/tests/004_failing_globs/stdout new file mode 100644 index 0000000..2455d96 --- /dev/null +++ b/tests/004_failing_globs/stdout @@ -0,0 +1 @@ +Fatal error validating ./tests/004_failing_globs/glob.yml diff --git a/test/004_failing_globs/validation_state.snap.json b/tests/004_failing_globs/validation_state.snap.json similarity index 100% rename from test/004_failing_globs/validation_state.snap.json rename to tests/004_failing_globs/validation_state.snap.json diff --git a/test/005_conditional_step_in_action/action.yml b/tests/005_conditional_step_in_action/action.yml similarity index 100% rename from test/005_conditional_step_in_action/action.yml rename to tests/005_conditional_step_in_action/action.yml diff --git a/test/005_conditional_step_in_action/validation_state.snap.json b/tests/005_conditional_step_in_action/validation_state.snap.json similarity index 100% rename from test/005_conditional_step_in_action/validation_state.snap.json rename to tests/005_conditional_step_in_action/validation_state.snap.json diff --git a/test/006_workflow_dispatch_inputs_options/test.yml b/tests/006_workflow_dispatch_inputs_options/test.yml similarity index 100% rename from test/006_workflow_dispatch_inputs_options/test.yml rename to tests/006_workflow_dispatch_inputs_options/test.yml diff --git a/test/006_workflow_dispatch_inputs_options/validation_state.snap.json b/tests/006_workflow_dispatch_inputs_options/validation_state.snap.json similarity index 100% rename from test/006_workflow_dispatch_inputs_options/validation_state.snap.json rename to tests/006_workflow_dispatch_inputs_options/validation_state.snap.json diff --git a/test/007_funky_syntax/exitcode b/tests/007_funky_syntax/exitcode similarity index 100% rename from test/007_funky_syntax/exitcode rename to tests/007_funky_syntax/exitcode diff --git a/test/007_funky_syntax/rust-check.yml b/tests/007_funky_syntax/rust-check.yml similarity index 100% rename from test/007_funky_syntax/rust-check.yml rename to tests/007_funky_syntax/rust-check.yml diff --git a/test/007_funky_syntax/stderr b/tests/007_funky_syntax/stderr similarity index 91% rename from test/007_funky_syntax/stderr rename to tests/007_funky_syntax/stderr index 17c1d62..fbafbf5 100644 --- a/test/007_funky_syntax/stderr +++ b/tests/007_funky_syntax/stderr @@ -3,7 +3,7 @@ Validation failed: ValidationState { Workflow, ), file_path: Some( - "007_funky_syntax/rust-check.yml", + "./tests/007_funky_syntax/rust-check.yml", ), errors: [ Parse { diff --git a/tests/007_funky_syntax/stdout b/tests/007_funky_syntax/stdout new file mode 100644 index 0000000..a8b577f --- /dev/null +++ b/tests/007_funky_syntax/stdout @@ -0,0 +1 @@ +Fatal error validating ./tests/007_funky_syntax/rust-check.yml diff --git a/test/007_funky_syntax/validation_state.snap.json b/tests/007_funky_syntax/validation_state.snap.json similarity index 100% rename from test/007_funky_syntax/validation_state.snap.json rename to tests/007_funky_syntax/validation_state.snap.json diff --git a/test/008_job_dependencies/exitcode b/tests/008_job_dependencies/exitcode similarity index 100% rename from test/008_job_dependencies/exitcode rename to tests/008_job_dependencies/exitcode diff --git a/test/008_job_dependencies/stderr b/tests/008_job_dependencies/stderr similarity index 88% rename from test/008_job_dependencies/stderr rename to tests/008_job_dependencies/stderr index a8d64bf..d859143 100644 --- a/test/008_job_dependencies/stderr +++ b/tests/008_job_dependencies/stderr @@ -3,7 +3,7 @@ Validation failed: ValidationState { Workflow, ), file_path: Some( - "008_job_dependencies/test.yml", + "./tests/008_job_dependencies/test.yml", ), errors: [ UnresolvedJob { diff --git a/tests/008_job_dependencies/stdout b/tests/008_job_dependencies/stdout new file mode 100644 index 0000000..3576abf --- /dev/null +++ b/tests/008_job_dependencies/stdout @@ -0,0 +1 @@ +Fatal error validating ./tests/008_job_dependencies/test.yml diff --git a/test/008_job_dependencies/test.yml b/tests/008_job_dependencies/test.yml similarity index 100% rename from test/008_job_dependencies/test.yml rename to tests/008_job_dependencies/test.yml diff --git a/test/008_job_dependencies/validation_state.snap.json b/tests/008_job_dependencies/validation_state.snap.json similarity index 100% rename from test/008_job_dependencies/validation_state.snap.json rename to tests/008_job_dependencies/validation_state.snap.json diff --git a/test/009_multi_file/exitcode b/tests/009_multi_file/exitcode similarity index 100% rename from test/009_multi_file/exitcode rename to tests/009_multi_file/exitcode diff --git a/test/009_multi_file/stderr b/tests/009_multi_file/stderr similarity index 92% rename from test/009_multi_file/stderr rename to tests/009_multi_file/stderr index b4fe130..6e52470 100644 --- a/test/009_multi_file/stderr +++ b/tests/009_multi_file/stderr @@ -3,7 +3,7 @@ Validation failed: ValidationState { Workflow, ), file_path: Some( - "009_multi_file/xinvalid.yml", + "./tests/009_multi_file/xinvalid.yml", ), errors: [ Parse { diff --git a/tests/009_multi_file/stdout b/tests/009_multi_file/stdout new file mode 100644 index 0000000..8e003c7 --- /dev/null +++ b/tests/009_multi_file/stdout @@ -0,0 +1 @@ +Fatal error validating ./tests/009_multi_file/xinvalid.yml diff --git a/test/009_multi_file/valid.yml b/tests/009_multi_file/valid.yml similarity index 100% rename from test/009_multi_file/valid.yml rename to tests/009_multi_file/valid.yml diff --git a/test/009_multi_file/validation_state.snap.json b/tests/009_multi_file/validation_state.snap.json similarity index 100% rename from test/009_multi_file/validation_state.snap.json rename to tests/009_multi_file/validation_state.snap.json diff --git a/test/009_multi_file/xinvalid.yml b/tests/009_multi_file/xinvalid.yml similarity index 100% rename from test/009_multi_file/xinvalid.yml rename to tests/009_multi_file/xinvalid.yml diff --git a/test/run b/tests/run similarity index 100% rename from test/run rename to tests/run diff --git a/test/run.mjs b/tests/run.mjs similarity index 100% rename from test/run.mjs rename to tests/run.mjs diff --git a/tests/snapshot_tests.rs b/tests/snapshot_tests.rs new file mode 100644 index 0000000..b9e515e --- /dev/null +++ b/tests/snapshot_tests.rs @@ -0,0 +1,85 @@ +use rstest::rstest; +use std::{fs, ffi::{OsString, OsStr}}; + + +#[derive(Debug)] +struct SnapshotTest { + exitcode: i32, + stderr: String, + stdout: String, + test_files: Vec, +} + +impl SnapshotTest { + fn new(test_name: String) -> Self { + let stderr = fs::read_to_string( + format!("./tests/{test_name}/stderr"), + ).unwrap_or(String::from("")); + + let stdout = fs::read_to_string( + format!("./tests/{test_name}/stdout"), + ).unwrap_or(String::from("")); + + let exitcode: i32 = fs::read_to_string( + format!("./tests/{test_name}/exitcode"), + ).map(|s| { + s + .strip_suffix("\n") + .unwrap_or("") + .parse::() + .unwrap_or(0) + }).unwrap_or(0); + + let test_files = Self::_get_files(test_name); + + SnapshotTest { + exitcode, + stderr, + stdout, + test_files, + } + } + + fn _get_files(test_name: String) -> Vec { + let yml = Some(OsStr::new("yml")); + fs::read_dir( + format!("./tests/{test_name}"), + ) + .unwrap() + .filter_map(Result::ok) + .filter(|f| f.path().extension() == yml) + .map(|f| f.path().into_os_string()) + .collect::>() + } +} + +use assert_cmd::Command; + +#[rstest] +#[case("001_basic_workflow")] +#[case("002_basic_action")] +#[case("003_successful_globs")] +#[case("004_failing_globs")] +#[case("005_conditional_step_in_action")] +#[case("006_workflow_dispatch_inputs_options")] +#[case("007_funky_syntax")] +#[case("008_job_dependencies")] +#[case("009_multi_file")] +fn snapshot(#[case] dir_name: String) { + + let test = SnapshotTest::new(dir_name); + let cmd = Command::cargo_bin( + env!("CARGO_PKG_NAME"), + ) + .expect("binary to execute") + .args(test.test_files) + .assert() + .stdout(test.stdout) + .stderr(test.stderr) + .code(test.exitcode); + + // let result = cmd.output().expect("ls command failed to start"); + + // println!("stdout: {:?}", output.stdout); + // println!("stderr: {:?}", output.stderr); +} From c95321aa81f46f1b9ca72f61e070a14ea9a77f1b Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Wed, 15 Mar 2023 09:43:39 -0400 Subject: [PATCH 26/33] Cleanup --- tests/snapshot_tests.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/snapshot_tests.rs b/tests/snapshot_tests.rs index b9e515e..9d0acf6 100644 --- a/tests/snapshot_tests.rs +++ b/tests/snapshot_tests.rs @@ -68,7 +68,7 @@ use assert_cmd::Command; fn snapshot(#[case] dir_name: String) { let test = SnapshotTest::new(dir_name); - let cmd = Command::cargo_bin( + Command::cargo_bin( env!("CARGO_PKG_NAME"), ) .expect("binary to execute") @@ -77,9 +77,4 @@ fn snapshot(#[case] dir_name: String) { .stdout(test.stdout) .stderr(test.stderr) .code(test.exitcode); - - // let result = cmd.output().expect("ls command failed to start"); - - // println!("stdout: {:?}", output.stdout); - // println!("stderr: {:?}", output.stderr); } From 4c16bfbb04b726ed1f8faebd784f524ea339bd3d Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Wed, 15 Mar 2023 10:11:47 -0400 Subject: [PATCH 27/33] Add remote-checks tests --- tests/010_remote_checks_ok/test.yml | 78 +++++++++++++++++++ .../validation_state.snap.json | 4 + tests/011_remote_checks_failure/exitcode | 1 + tests/011_remote_checks_failure/stderr | 18 +++++ tests/011_remote_checks_failure/stdout | 1 + tests/011_remote_checks_failure/test.yml | 78 +++++++++++++++++++ .../validation_state.snap.json | 4 + tests/snapshot_tests.rs | 19 +++++ 8 files changed, 203 insertions(+) create mode 100644 tests/010_remote_checks_ok/test.yml create mode 100644 tests/010_remote_checks_ok/validation_state.snap.json create mode 100644 tests/011_remote_checks_failure/exitcode create mode 100644 tests/011_remote_checks_failure/stderr create mode 100644 tests/011_remote_checks_failure/stdout create mode 100644 tests/011_remote_checks_failure/test.yml create mode 100644 tests/011_remote_checks_failure/validation_state.snap.json diff --git a/tests/010_remote_checks_ok/test.yml b/tests/010_remote_checks_ok/test.yml new file mode 100644 index 0000000..88adf73 --- /dev/null +++ b/tests/010_remote_checks_ok/test.yml @@ -0,0 +1,78 @@ +name: Test + +on: + push: + pull_request: + branches: + - main + +defaults: + run: + shell: bash + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + default: true + + - name: Check Formatting + uses: actions-rs/cargo@v1 + with: + command: fmt + args: -- --check + + - name: Check with Clippy + uses: actions-rs/clippy-check@v1 + with: + args: -- -Dwarnings + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Shellcheck + uses: ludeeus/action-shellcheck@master + + - name: Install shfmt + uses: mfinelli/setup-shfmt@master + + - name: Run shfmt + run: shfmt -d bin/* + + + build: + strategy: + matrix: + rust-toolchain: + - stable + - nightly + os: + - ubuntu-latest + - macos-latest + - windows-latest + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust-toolchain }} + override: true + default: true + + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --release --all-features diff --git a/tests/010_remote_checks_ok/validation_state.snap.json b/tests/010_remote_checks_ok/validation_state.snap.json new file mode 100644 index 0000000..c313801 --- /dev/null +++ b/tests/010_remote_checks_ok/validation_state.snap.json @@ -0,0 +1,4 @@ +{ + "actionType": "workflow", + "errors": [] +} diff --git a/tests/011_remote_checks_failure/exitcode b/tests/011_remote_checks_failure/exitcode new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/tests/011_remote_checks_failure/exitcode @@ -0,0 +1 @@ +1 diff --git a/tests/011_remote_checks_failure/stderr b/tests/011_remote_checks_failure/stderr new file mode 100644 index 0000000..c9bc9dc --- /dev/null +++ b/tests/011_remote_checks_failure/stderr @@ -0,0 +1,18 @@ +Validation failed: ValidationState { + action_type: Some( + Workflow, + ), + file_path: Some( + "./tests/011_remote_checks_failure/test.yml", + ), + errors: [ + Unknown { + code: "action_not_found", + detail: Some( + "Could not find action: actions/checkouts@v2", + ), + path: "jobs/build/steps/uses/actions/checkouts@v2", + title: "Action Not Found", + }, + ], +} diff --git a/tests/011_remote_checks_failure/stdout b/tests/011_remote_checks_failure/stdout new file mode 100644 index 0000000..79ef8c0 --- /dev/null +++ b/tests/011_remote_checks_failure/stdout @@ -0,0 +1 @@ +Fatal error validating ./tests/011_remote_checks_failure/test.yml diff --git a/tests/011_remote_checks_failure/test.yml b/tests/011_remote_checks_failure/test.yml new file mode 100644 index 0000000..6a5105e --- /dev/null +++ b/tests/011_remote_checks_failure/test.yml @@ -0,0 +1,78 @@ +name: Test + +on: + push: + pull_request: + branches: + - main + +defaults: + run: + shell: bash + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + default: true + + - name: Check Formatting + uses: actions-rs/cargo@v1 + with: + command: fmt + args: -- --check + + - name: Check with Clippy + uses: actions-rs/clippy-check@v1 + with: + args: -- -Dwarnings + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Shellcheck + uses: ludeeus/action-shellcheck@master + + - name: Install shfmt + uses: mfinelli/setup-shfmt@master + + - name: Run shfmt + run: shfmt -d bin/* + + + build: + strategy: + matrix: + rust-toolchain: + - stable + - nightly + os: + - ubuntu-latest + - macos-latest + - windows-latest + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout + uses: actions/checkouts@v2 + + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust-toolchain }} + override: true + default: true + + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --release --all-features diff --git a/tests/011_remote_checks_failure/validation_state.snap.json b/tests/011_remote_checks_failure/validation_state.snap.json new file mode 100644 index 0000000..c313801 --- /dev/null +++ b/tests/011_remote_checks_failure/validation_state.snap.json @@ -0,0 +1,4 @@ +{ + "actionType": "workflow", + "errors": [] +} diff --git a/tests/snapshot_tests.rs b/tests/snapshot_tests.rs index 9d0acf6..3765811 100644 --- a/tests/snapshot_tests.rs +++ b/tests/snapshot_tests.rs @@ -55,6 +55,7 @@ impl SnapshotTest { use assert_cmd::Command; +#[cfg(not(feature = "remote-checks"))] #[rstest] #[case("001_basic_workflow")] #[case("002_basic_action")] @@ -78,3 +79,21 @@ fn snapshot(#[case] dir_name: String) { .stderr(test.stderr) .code(test.exitcode); } + +#[cfg(feature = "remote-checks")] +#[rstest] +#[case("010_remote_checks_ok")] +#[case("011_remote_checks_failure")] +fn snapshot_remote_checks(#[case] dir_name: String) { + + let test = SnapshotTest::new(dir_name); + Command::cargo_bin( + env!("CARGO_PKG_NAME"), + ) + .expect("binary to execute") + .args(test.test_files) + .assert() + .stdout(test.stdout) + .stderr(test.stderr) + .code(test.exitcode); +} From fcd0fb158be94c6694081fd9a3b9868d1137744d Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Wed, 15 Mar 2023 11:18:26 -0400 Subject: [PATCH 28/33] Create snap-snapshots feature --- Cargo.toml | 1 + tests/004_failing_globs/exitcode | 2 +- tests/007_funky_syntax/exitcode | 2 +- tests/008_job_dependencies/exitcode | 2 +- tests/009_multi_file/exitcode | 2 +- .../validation_state.snap.json | 4 - tests/011_remote_checks_failure/exitcode | 2 +- .../validation_state.snap.json | 4 - tests/snapshot_tests.rs | 105 ++++++++++++------ 9 files changed, 79 insertions(+), 45 deletions(-) delete mode 100644 tests/010_remote_checks_ok/validation_state.snap.json delete mode 100644 tests/011_remote_checks_failure/validation_state.snap.json diff --git a/Cargo.toml b/Cargo.toml index b140e93..6b877f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ crate-type = ["cdylib", "rlib"] [features] js = ["console_error_panic_hook", "valico/js"] remote-checks = ["reqwest"] +save-snapshots = [] [dependencies] clap = { version = "4.0", features = ["derive"] } diff --git a/tests/004_failing_globs/exitcode b/tests/004_failing_globs/exitcode index d00491f..56a6051 100644 --- a/tests/004_failing_globs/exitcode +++ b/tests/004_failing_globs/exitcode @@ -1 +1 @@ -1 +1 \ No newline at end of file diff --git a/tests/007_funky_syntax/exitcode b/tests/007_funky_syntax/exitcode index d00491f..56a6051 100644 --- a/tests/007_funky_syntax/exitcode +++ b/tests/007_funky_syntax/exitcode @@ -1 +1 @@ -1 +1 \ No newline at end of file diff --git a/tests/008_job_dependencies/exitcode b/tests/008_job_dependencies/exitcode index d00491f..56a6051 100644 --- a/tests/008_job_dependencies/exitcode +++ b/tests/008_job_dependencies/exitcode @@ -1 +1 @@ -1 +1 \ No newline at end of file diff --git a/tests/009_multi_file/exitcode b/tests/009_multi_file/exitcode index d00491f..56a6051 100644 --- a/tests/009_multi_file/exitcode +++ b/tests/009_multi_file/exitcode @@ -1 +1 @@ -1 +1 \ No newline at end of file diff --git a/tests/010_remote_checks_ok/validation_state.snap.json b/tests/010_remote_checks_ok/validation_state.snap.json deleted file mode 100644 index c313801..0000000 --- a/tests/010_remote_checks_ok/validation_state.snap.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "actionType": "workflow", - "errors": [] -} diff --git a/tests/011_remote_checks_failure/exitcode b/tests/011_remote_checks_failure/exitcode index d00491f..56a6051 100644 --- a/tests/011_remote_checks_failure/exitcode +++ b/tests/011_remote_checks_failure/exitcode @@ -1 +1 @@ -1 +1 \ No newline at end of file diff --git a/tests/011_remote_checks_failure/validation_state.snap.json b/tests/011_remote_checks_failure/validation_state.snap.json deleted file mode 100644 index c313801..0000000 --- a/tests/011_remote_checks_failure/validation_state.snap.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "actionType": "workflow", - "errors": [] -} diff --git a/tests/snapshot_tests.rs b/tests/snapshot_tests.rs index 3765811..0f7eaf9 100644 --- a/tests/snapshot_tests.rs +++ b/tests/snapshot_tests.rs @@ -1,9 +1,13 @@ +use std::{fs::{self, File}, ffi::{OsString, OsStr}}; +use std::io::prelude::*; + use rstest::rstest; -use std::{fs, ffi::{OsString, OsStr}}; +use assert_cmd::Command; #[derive(Debug)] struct SnapshotTest { + test_dir: String, exitcode: i32, stderr: String, stdout: String, @@ -11,28 +15,29 @@ struct SnapshotTest { } impl SnapshotTest { - fn new(test_name: String) -> Self { + fn new(test_dir: String) -> Self { let stderr = fs::read_to_string( - format!("./tests/{test_name}/stderr"), + format!("./tests/{test_dir}/stderr"), ).unwrap_or(String::from("")); let stdout = fs::read_to_string( - format!("./tests/{test_name}/stdout"), + format!("./tests/{test_dir}/stdout"), ).unwrap_or(String::from("")); let exitcode: i32 = fs::read_to_string( - format!("./tests/{test_name}/exitcode"), + format!("./tests/{test_dir}/exitcode"), ).map(|s| { s .strip_suffix("\n") - .unwrap_or("") + .unwrap_or(s.as_str()) .parse::() .unwrap_or(0) }).unwrap_or(0); - let test_files = Self::_get_files(test_name); + let test_files = Self::_get_files(&test_dir); SnapshotTest { + test_dir, exitcode, stderr, stdout, @@ -40,10 +45,67 @@ impl SnapshotTest { } } - fn _get_files(test_name: String) -> Vec { + #[cfg(not(feature = "save-snapshots"))] + fn execute(self) { + Command::cargo_bin( + env!("CARGO_PKG_NAME"), + ) + .expect("binary to execute") + .args(self.test_files) + .assert() + .stdout(self.stdout) + .stderr(self.stderr) + .code(self.exitcode); + } + + #[cfg(feature = "save-snapshots")] + fn execute(&self) { + let test_dir = self.test_dir.to_owned(); + let test_files = self.test_files.to_owned(); + let result = Command::cargo_bin( + env!("CARGO_PKG_NAME"), + ) + .expect("binary to execute") + .args(test_files).ok().unwrap_or_else(|e| e.as_output().unwrap().to_owned()); + + if !result.stdout.is_empty() { + self._save_contents( + format!("./tests/{test_dir}/stdout"), + result.stdout, + ); + } + if !result.stderr.is_empty() { + self._save_contents( + format!("./tests/{test_dir}/stderr"), + result.stderr, + ); + } + if let Some(exitcode) = result.status.code() { + if exitcode > 0 { + self._save_contents( + format!("./tests/{test_dir}/exitcode"), + format!("{exitcode}").into(), + ); + } + } + } + + fn _save_contents( + &self, + file_name: String, + contents: Vec, + ) { + println!("Saving {}", file_name); + let res = File::create(file_name).unwrap().write_all( + &contents, + ); + assert!(res.is_ok(), "{:?}", res); + } + + fn _get_files(test_dir: &String) -> Vec { let yml = Some(OsStr::new("yml")); fs::read_dir( - format!("./tests/{test_name}"), + format!("./tests/{test_dir}"), ) .unwrap() .filter_map(Result::ok) @@ -53,7 +115,6 @@ impl SnapshotTest { } } -use assert_cmd::Command; #[cfg(not(feature = "remote-checks"))] #[rstest] @@ -67,17 +128,7 @@ use assert_cmd::Command; #[case("008_job_dependencies")] #[case("009_multi_file")] fn snapshot(#[case] dir_name: String) { - - let test = SnapshotTest::new(dir_name); - Command::cargo_bin( - env!("CARGO_PKG_NAME"), - ) - .expect("binary to execute") - .args(test.test_files) - .assert() - .stdout(test.stdout) - .stderr(test.stderr) - .code(test.exitcode); + SnapshotTest::new(dir_name).execute(); } #[cfg(feature = "remote-checks")] @@ -85,15 +136,5 @@ fn snapshot(#[case] dir_name: String) { #[case("010_remote_checks_ok")] #[case("011_remote_checks_failure")] fn snapshot_remote_checks(#[case] dir_name: String) { - - let test = SnapshotTest::new(dir_name); - Command::cargo_bin( - env!("CARGO_PKG_NAME"), - ) - .expect("binary to execute") - .args(test.test_files) - .assert() - .stdout(test.stdout) - .stderr(test.stderr) - .code(test.exitcode); + SnapshotTest::new(dir_name).execute(); } From 476df9b3e7539447f0cbbd763a73c96f4463ac29 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Wed, 15 Mar 2023 11:24:08 -0400 Subject: [PATCH 29/33] Move cfg --- tests/snapshot_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/snapshot_tests.rs b/tests/snapshot_tests.rs index 0f7eaf9..5dd304c 100644 --- a/tests/snapshot_tests.rs +++ b/tests/snapshot_tests.rs @@ -116,7 +116,6 @@ impl SnapshotTest { } -#[cfg(not(feature = "remote-checks"))] #[rstest] #[case("001_basic_workflow")] #[case("002_basic_action")] @@ -127,14 +126,15 @@ impl SnapshotTest { #[case("007_funky_syntax")] #[case("008_job_dependencies")] #[case("009_multi_file")] +#[cfg(not(feature = "remote-checks"))] fn snapshot(#[case] dir_name: String) { SnapshotTest::new(dir_name).execute(); } -#[cfg(feature = "remote-checks")] #[rstest] #[case("010_remote_checks_ok")] #[case("011_remote_checks_failure")] +#[cfg(feature = "remote-checks")] fn snapshot_remote_checks(#[case] dir_name: String) { SnapshotTest::new(dir_name).execute(); } From ded9825f9ff9820f6bdaed361e0ce94012ab513a Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Wed, 15 Mar 2023 11:28:10 -0400 Subject: [PATCH 30/33] Update --- tests/004_failing_globs/exitcode | 2 +- tests/007_funky_syntax/exitcode | 2 +- tests/008_job_dependencies/exitcode | 2 +- tests/009_multi_file/exitcode | 2 +- tests/011_remote_checks_failure/exitcode | 2 +- tests/snapshot_tests.rs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/004_failing_globs/exitcode b/tests/004_failing_globs/exitcode index 56a6051..d00491f 100644 --- a/tests/004_failing_globs/exitcode +++ b/tests/004_failing_globs/exitcode @@ -1 +1 @@ -1 \ No newline at end of file +1 diff --git a/tests/007_funky_syntax/exitcode b/tests/007_funky_syntax/exitcode index 56a6051..d00491f 100644 --- a/tests/007_funky_syntax/exitcode +++ b/tests/007_funky_syntax/exitcode @@ -1 +1 @@ -1 \ No newline at end of file +1 diff --git a/tests/008_job_dependencies/exitcode b/tests/008_job_dependencies/exitcode index 56a6051..d00491f 100644 --- a/tests/008_job_dependencies/exitcode +++ b/tests/008_job_dependencies/exitcode @@ -1 +1 @@ -1 \ No newline at end of file +1 diff --git a/tests/009_multi_file/exitcode b/tests/009_multi_file/exitcode index 56a6051..d00491f 100644 --- a/tests/009_multi_file/exitcode +++ b/tests/009_multi_file/exitcode @@ -1 +1 @@ -1 \ No newline at end of file +1 diff --git a/tests/011_remote_checks_failure/exitcode b/tests/011_remote_checks_failure/exitcode index 56a6051..d00491f 100644 --- a/tests/011_remote_checks_failure/exitcode +++ b/tests/011_remote_checks_failure/exitcode @@ -1 +1 @@ -1 \ No newline at end of file +1 diff --git a/tests/snapshot_tests.rs b/tests/snapshot_tests.rs index 5dd304c..9f56ff0 100644 --- a/tests/snapshot_tests.rs +++ b/tests/snapshot_tests.rs @@ -84,7 +84,7 @@ impl SnapshotTest { if exitcode > 0 { self._save_contents( format!("./tests/{test_dir}/exitcode"), - format!("{exitcode}").into(), + format!("{exitcode}\n").into(), ); } } From 8e9d9abd302215faeadc185899847c032b055933 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Thu, 16 Mar 2023 21:59:55 -0400 Subject: [PATCH 31/33] Update contributing.md --- CONTRIBUTING.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 24b77a6..ef8fcc9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,6 +71,23 @@ Options: ``` # Writing Tests -``` -TODO: This section could use some help. -``` +All tests live in the `tests` directory. Currently, this project implements snapshot testing, +but that's not to say you couldn't write unit or integration tests with the current structure. +To run the tests, simply run `cargo test` from the root directory. If you want to test a specific +feature, you can add the `-F {feature}` flag (e.g. `cargo test -F remote-checks`). + +## Unit/Integration Tests +As of this writing, there are no unit or integration tests. If you are looking to write some, please +follow the directions in [this guide](https://doc.rust-lang.org/book/ch11-01-writing-tests.html). + +## Snapshot Tests +A snapshot test is performed when we execute the cli and capture `stdout`, `stderr`, and/or an exit code. +When the tests is run, the results of the test must exactly match those of the previous run. For this project, +the snapshot tests are named in the format `{next_id}_{whats_being_tested}` (e.g. `011_remote_checks_failure`). + +If you have made changes which will change the output of the program and cause snapshots to fail, you can run +`cargo test -F save-snapshots`. This feature causes the executed command to save the `stdout`, `stderr`, and/or +exit code to the specified testing directory. + +If you are writing a net new test, you will need to create the test directory with your workflow or action file. +Once you're done, you can save the results to that directy by running `cargo test -F save-snapshots`. From 6070859b16c68bc5b3bbb53773c93e0784ce1682 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Fri, 17 Mar 2023 00:13:37 -0400 Subject: [PATCH 32/33] Update authors --- Cargo.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6b877f3..11220c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,11 @@ include = [ "/src/schemastore/src/schemas/json/github-action.json" ] version = "0.0.0-git" -authors = ["Matt Palmer "] +authors = [ + "Matt Palmer ", + "Ben Heidemann ", + "Austin Ward " +] edition = "2021" # If this is changed, .github/workflows/qa.yml build matrix needs updating as well rust-version = "1.60.0" From 53ffc6043a903263a4740c7794852d3540639749 Mon Sep 17 00:00:00 2001 From: Austin Ward Date: Tue, 2 May 2023 21:17:33 -0400 Subject: [PATCH 33/33] Update CONTRIBUTING.md Co-authored-by: Ben Heidemann <56122437+bcheidemann@users.noreply.github.com> --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef8fcc9..1593235 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ To confirm that rust is installed, run the `cargo` command. If you don't receive ## Git Submodule Setup This repository uses [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules). Specifically for the use of [schemastore](https://github.com/SchemaStore/schemastore). -To setup the git submodule after cloning this repo to your local, you'll want to run the following commands: +To setup the git submodule after cloning this repo to your local, you'll want to run the following commands: 1. `git submodule init` 2. `git submodule update`