Skip to content

38 validate action version exists#39

Open
award28 wants to merge 1 commit intompalmer:mainfrom
award28:38-validate-action-version-exists
Open

38 validate action version exists#39
award28 wants to merge 1 commit intompalmer:mainfrom
award28:38-validate-action-version-exists

Conversation

@award28
Copy link
Copy Markdown

@award28 award28 commented Mar 8, 2023

TODO

  • Update the README.md to include a section on the remote-checks feature flag.

Background

This PR resolves #38, adding a feature flag to enable remote checks. When the flag is enabled, action-validator performs remote checks to validate that a provided action exists, as well as the specified commit/tag/version. There are a few other additions as well, covered in the release notes.

Uses Validation

Non-Remote uses Validation

The uses validation from the schema store does not perform any validation on the value which is provided. However, this value can't be any string; it must be one of a few values.

There are a few variations for each, but the core difference is that a valid uses is either a GitHub Action, a Docker image, or a path to an action. We can perform non-remote checks for each of these by making sure they match the expected uses type. Further, for a path value, we can validate that the path exists.

remote-checks Feature

The real benefit of this PR is the remote-checks feature flag. With this enabled, the user is giving action-validator permission to perform validation that can only be confirmed through network calls. For the uses statement, we perform http requests to verify a given action exists or a docker image exists. There are some limitations to this, listed below.

Current Limitations

  • 401 responses from a Docker Registry are assumed as available
  • Private github actions can't be found

Future Improvements

  • Validate with attributes on uses statements
  • Perform docker auth to confirm images when we receive a 401
  • Use async tokio main to enable async requests
  • Validate action in supplied path is actually an Action

Other Changes

Documentation

  • Updated the CONTRIBUTING.md file with sections covering environment setup, running the validator locally during development, and writing tests.

Testing

  • Updated the test directory name from test to tests so rust can find it automatically.
  • Recreated the tests/run shell script in the tests/snapshot_tests.rs to take advantage of rusts testing capabilities
    • Parameterized the snapshot tests
    • Added the save-snapshots feature flag to easily update outdated snapshots

@award28 award28 marked this pull request as ready for review March 17, 2023 04:37
Copy link
Copy Markdown
Contributor

@bcheidemann bcheidemann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great :) I think moving the snapshot test suite to Rust makes a lot of sense since it will be easier to maintain and makes it easier to test @action-validator/cli with the same runner. If @mpalmer approves, once this is merged I plan to integrate the tests from tests/run.mjs.

In addition to the comments I left, I have the following questions:

  1. Is tests/run now redundant?
  2. If so, can we remove it and update .github/workflows/qa.yml to run cargo test instead
  3. I think some minor updates are needed to tests/run.mjs (updating test to tests) and also the validation_state.snap.json files need to be added for the new test cases.

Regarding point number 3, I am happy to do that myself or to give further instruction on what needs to change.

@mpalmer what do you think? Would be great to get your input on this PR when you have time since I think it probably needs to go in ahead of further Node API related re-factoring & re-factoring of the test suite.

Comment thread tests/snapshot_tests.rs Outdated
Comment on lines +120 to +129
#[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")]
#[cfg(not(feature = "remote-checks"))]
Copy link
Copy Markdown
Contributor

@bcheidemann bcheidemann Mar 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't these tests run regardless of whether the remote-checks feature is enabled? (it would be nice for cargo test to run all tests by default)

Suggested change
#[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")]
#[cfg(not(feature = "remote-checks"))]
#[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")]

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this as it is, because I don't like it when a test suite makes any more assumptions about the local environment than it absolutely needs to (and "I have a real, working, fully-open Internet connection" is still, in some cases, an incorrect assumption). I sometimes do dev without a working Internet connection, and a test suite that starts barfing red in all directions gives me the absolute pip.

In any event, I never trust developers (including myself!) to run all the tests before pushing (one cannot mandate that a pre-commit hook is in place in a remote repo, after all), so the most important thing is to make sure that CI is comprehensive -- which means, in this case, running the tests with remote-checks enabled.

It would be a nice "quality of life" thing if the test suite could print something at the end of a cargo test run saying "some tests weren't run because the remote-checks feature wasn't enabled", to give people a hint that re-running with extra features would be a good idea, but a lack of it wouldn't be a blocker for merge for me.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mpalmer when you put it like that I completely agree :D

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see @mpalmer's point, however I made this decision for a different reason :) Interestingly, enabling remote-checks and running those first 9 tests resulted in different output for those tests! Hence why they were disabled for remote checks. We could alter those tests so they are idempotent to the issues they're testing, but I wanted to reduce the changes as much as I could.

Copy link
Copy Markdown
Contributor

@bcheidemann bcheidemann Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be a nice "quality of life" thing if the test suite could print something at the end of a cargo test run saying "some tests weren't run because the remote-checks feature wasn't enabled", to give people a hint that re-running with extra features would be a good idea, but a lack of it wouldn't be a blocker for merge for me.

If we want to use a Cargo feature to enable/disable this functionality at build time, we should be able to do this as follows:

#[fixtures(["tests/fixtures/*"])]
#[cfg_attr(
    feature = "remote-checks",
    fixtures::ignore(
        paths = "tests/fixtures/{001_basic_workflow,002_basic_action,...}",
        reason = "because the remote-checks feature was enabled. This causes different output for this test."
    )
)]
#[cfg_attr(
    not(feature = "remote-checks"),
    fixtures::ignore(
        paths = "tests/fixtures/{010_remote_checks_ok,011_remote_checks_failure}",
        reason = "because the remote-checks feature was disabled."
    )
)]
#[test]
fn snapshot(dir: &Path) {
    SnapshotTest::new(dir).execute();
}

This should do it.

test snapshot::_001_basic_workflow ... ignored because the remote-checks feature was enabled. This causes different output for this test.
test snapshot::_010_remote_checks_ok ... ignored because the remote-checks feature was disabled.


steps:
- name: Checkout
uses: actions/checkouts@v2
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nitpick but it would maybe be clearer to rename this to something obviously wrong like actions/does-not-exist@v2. I had to re-read this a few times because I didn't spot the trailing "s" (possibly this is only an issue for me because I'm dyslexic!).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update this in #48.

Comment thread CONTRIBUTING.md Outdated
Comment thread src/validators/models.rs Outdated
Comment on lines +123 to +128
// 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(());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, we are logging a warning to the console in the NPM version when we skip validating a glob. This was a bit of a hack since I didn't anticipate any other use cases for warnings. However, I'm wondering if it would make sense to add record warnings in the validation state, e.g.

#[derive(Serialize, Debug)]
pub struct ValidationState {
    // --snip--
    pub warnings: Vec<ValidationWarning>,
}

Then we could record and report a warning in this case.

Alternatively, we could also report this as an error but add a severity to errors e.g.

pub enum ErrorSeverity {
  Error,
  Warning,
}

I guess if we went with the latter approach it would make more sense to rename errors to diagnostics but probably best to avoid breaking API changes.

What do you think?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm OK with breaking API changes when we're early in the lifecycle, and I don't have strong opinions on either of these approaches, but it does seem like a good idea to be recording problems for later consumption, rather than logging anything immediately.

Comment thread src/validators/models.rs Outdated
Comment on lines +130 to +135
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(),
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to add a new variant to the ValidationError enum for missign actions. Maybe something like ValidationError::UnresolvedAction.

Comment thread src/validators/models.rs Outdated
Comment on lines +194 to +199
Err(ValidationError::InvalidGlob {
code: "invalid_uses".into(),
detail: Some(format!("The `uses` {uses} is invalid.")),
path: self.origin.to_owned(),
title: "Invalid Uses".into(),
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could use ValidationError::UnresolvedAction as suggested above, or if this is worth covering with it's own error code then perhaps something like ValidationError::InvalidActionFormat.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like having very granular error codes, so I'm in team "InvalidActionFormat".

@mpalmer
Copy link
Copy Markdown
Owner

mpalmer commented Apr 1, 2023

Sorry, @award28, I missed that you'd marked this ready for review.

I'm in agreement with most of @bcheidemann's comments (I'll respond to those on which I've got a differing opinion shortly). If you're up for it, I'd really appreciate it if you could pull out the dev instruction improvments and test suite run changes into their own PRs, because (a) I'd like to get those in ASAP, and (b) I find it quite difficult to wrap my head around reviewing changes with multiple different purposes (I get some sort of mental whiplash flicking back and forth).

@award28
Copy link
Copy Markdown
Author

award28 commented Apr 6, 2023

@mpalmer no worries, been very busy with work recently. This feedback sounds good, I'm happy to split this apart into two different PRs!

@award28
Copy link
Copy Markdown
Author

award28 commented Apr 6, 2023

This looks great :) I think moving the snapshot test suite to Rust makes a lot of sense since it will be easier to maintain and makes it easier to test @action-validator/cli with the same runner. If @mpalmer approves, once this is merged I plan to integrate the tests from tests/run.mjs.

In addition to the comments I left, I have the following questions:

  1. Is tests/run now redundant?

  2. If so, can we remove it and update .github/workflows/qa.yml to run cargo test instead

  3. I think some minor updates are needed to tests/run.mjs (updating test to tests) and also the validation_state.snap.json files need to be added for the new test cases.

Regarding point number 3, I am happy to do that myself or to give further instruction on what needs to change.

@mpalmer what do you think? Would be great to get your input on this PR when you have time since I think it probably needs to go in ahead of further Node API related re-factoring & re-factoring of the test suite.

@bcheidemann This all sounds good to me! I didn't remove the shell script for the run since I wasn't sure if we would want to go with this approach but I'm good to remove it based on @mpalmer's feedback.

@bcheidemann
Copy link
Copy Markdown
Contributor

@award28 let me know if there's any way I can help with this :) I have some time off work this week so would be happy to help any way I can

@award28
Copy link
Copy Markdown
Author

award28 commented May 3, 2023

@bcheidemann thanks for the hand, Ben! I've definitely been distracted with other stuff and haven't had the time to address this feedback. I'm going to work on extracting the testing changes/contributing.md into their own PR tonight. Once that's done, it'd be great to get some feedback on that.

@award28
Copy link
Copy Markdown
Author

award28 commented May 3, 2023

Once #48 is merged, I'll rebase this branch against main and update the PR based on the feedback.

@mpalmer
Copy link
Copy Markdown
Owner

mpalmer commented Oct 7, 2025

Now that #48's spiritual successor has landed, are you willing/able to rebase this to get it landed, @award28?

@award28
Copy link
Copy Markdown
Author

award28 commented Oct 8, 2025

Hey @mpalmer ! Apologies about dropping off, I became quite busy and lost the time.

Let's do it 🏄 I'll take a peak at it this week and make some updates here.

Comment thread Cargo.toml Outdated

[features]
js = ["console_error_panic_hook", "valico/js"]
remote-checks = ["reqwest"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why this should be cargo feature, or could we make this a CLI flag instead? e.g. --allow-remote-checks. This might be a little more flexible than opting in at build time.

@mpalmer mpalmer force-pushed the main branch 4 times, most recently from 6d5741f to c994f42 Compare April 8, 2026 23:55
award28 added a commit to award28/action-validator that referenced this pull request Apr 18, 2026
Validates every `uses:` reference in a workflow or composite action:

- Always: parses the reference shape (`owner/repo[/path]@ref`,
  `docker://image`, or `./local/path`) and records `InvalidActionFormat`
  for anything that doesn't match.
- Always: checks that local `./` paths exist on disk relative to the
  configured rootdir, recording `UnresolvedAction` if not.
- Opt-in via `--allow-remote-checks`: issues a HEAD request to
  `api.github.com/repos/{owner}/{repo}/commits/{ref}` to confirm the
  referenced GitHub action resolves. 404 becomes `UnresolvedAction`;
  401/403 are treated as "assume exists"; network failures become
  warnings rather than errors. Docker image checks are deferred and
  recorded as a skipped-check warning.

`ValidationState` gains a `warnings` field so skipped-remote checks are
surfaced to programmatic consumers without polluting CLI stderr (they
print only under `--verbose`). Empty `warnings` is elided from Debug
output to avoid churning existing snapshots.

Tests: three new snapshot fixtures (012 well-formed, 013 invalid
format, 014 missing local path) plus unit tests for the reference
parser and httpmock-driven 404/200 coverage of the remote-check path,
which keeps `cargo test` green offline.

Refs mpalmer#38. Supersedes the earlier remote-checks cargo feature in favour
of a runtime CLI flag, per bcheidemann's review feedback on mpalmer#39.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@award28 award28 force-pushed the 38-validate-action-version-exists branch from 53ffc60 to 33619a7 Compare April 18, 2026 18:37
award28 added a commit to award28/action-validator that referenced this pull request Apr 18, 2026
Validates every `uses:` reference in a workflow or composite action:

- Always: parses the reference shape (`owner/repo[/path]@ref`,
  `docker://image`, or `./local/path`) and records `InvalidActionFormat`
  for anything that doesn't match.
- Always: checks that local `./` paths exist on disk relative to the
  configured rootdir, recording `UnresolvedAction` if not.
- Opt-in via `--allow-remote-checks`: issues a HEAD request to
  `api.github.com/repos/{owner}/{repo}/commits/{ref}` to confirm the
  referenced GitHub action resolves. 404 becomes `UnresolvedAction`;
  401/403 are treated as "assume exists"; network failures become
  warnings rather than errors. Docker image checks are deferred and
  recorded as a skipped-check warning.

`ValidationState` gains a `warnings` field so skipped-remote checks are
surfaced to programmatic consumers without polluting CLI stderr (they
print only under `--verbose`). Empty `warnings` is elided from Debug
output to avoid churning existing snapshots.

Tests: three new snapshot fixtures (012 well-formed, 013 invalid
format, 014 missing local path) plus unit tests for the reference
parser and httpmock-driven 404/200 coverage of the remote-check path,
which keeps `cargo test` green offline.

Refs mpalmer#38. Supersedes the earlier remote-checks cargo feature in favour
of a runtime CLI flag, per bcheidemann's review feedback on mpalmer#39.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@award28 award28 force-pushed the 38-validate-action-version-exists branch from 33619a7 to e1ca2d4 Compare April 18, 2026 18:43
award28 added a commit to award28/action-validator that referenced this pull request Apr 18, 2026
Validates every `uses:` reference in a workflow or composite action:

- Always: parses the reference shape (`owner/repo[/path]@ref`,
  `docker://image`, or `./local/path`) and records `InvalidActionFormat`
  for anything that doesn't match.
- Always: checks that local `./` paths exist on disk relative to the
  configured rootdir, recording `UnresolvedAction` if not.
- Opt-in via `--allow-remote-checks`: issues a HEAD request to
  `api.github.com/repos/{owner}/{repo}/commits/{ref}` to confirm the
  referenced GitHub action resolves. 404 becomes `UnresolvedAction`;
  401/403 are treated as "assume exists"; network failures become
  warnings rather than errors. Docker image checks are deferred and
  recorded as a skipped-check warning.

`ValidationState` gains a `warnings` field so skipped-remote checks are
surfaced to programmatic consumers without polluting CLI stderr (they
print only under `--verbose`). Empty `warnings` is elided from Debug
output to avoid churning existing snapshots.

Tests: three new snapshot fixtures (012 well-formed, 013 invalid
format, 014 missing local path) plus unit tests for the reference
parser and httpmock-driven 404/200 coverage of the remote-check path,
which keeps `cargo test` green offline.

Refs mpalmer#38. Supersedes the earlier remote-checks cargo feature in favour
of a runtime CLI flag, per bcheidemann's review feedback on mpalmer#39.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@award28 award28 force-pushed the 38-validate-action-version-exists branch from e1ca2d4 to 391e431 Compare April 18, 2026 18:46
Validates every `uses:` reference in a workflow or composite action:

- Always: parses the reference shape (`owner/repo[/path]@ref`,
  `docker://image`, or `./local/path`) and records `InvalidActionFormat`
  for anything that doesn't match.
- Always: checks that local `./` paths exist on disk relative to the
  configured rootdir, recording `UnresolvedAction` if not.
- Opt-in via `--allow-remote-checks`: issues a HEAD request to
  `api.github.com/repos/{owner}/{repo}/commits/{ref}` to confirm the
  referenced GitHub action resolves. 404 becomes `UnresolvedAction`;
  401/403 are treated as "assume exists"; network failures become
  warnings rather than errors. Docker image checks are deferred and
  recorded as a skipped-check warning.

`ValidationState` gains a `warnings` field so skipped-remote checks are
surfaced to programmatic consumers without polluting CLI stderr (they
print only under `--verbose`). Empty `warnings` is elided from Debug
output to avoid churning existing snapshots.

Tests: three new snapshot fixtures (012 well-formed, 013 invalid
format, 014 missing local path) plus unit tests for the reference
parser and httpmock-driven 404/200 coverage of the remote-check path,
which keeps `cargo test` green offline.

Refs mpalmer#38. Supersedes the earlier remote-checks cargo feature in favour
of a runtime CLI flag, per bcheidemann's review feedback on mpalmer#39.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@award28 award28 force-pushed the 38-validate-action-version-exists branch from 391e431 to 50395aa Compare April 18, 2026 18:54
@award28
Copy link
Copy Markdown
Author

award28 commented Apr 18, 2026

@mpalmer let me know if there's anything I missed!

@award28 award28 requested review from bcheidemann and mpalmer April 18, 2026 19:45
Comment thread src/lib.rs
Comment on lines +438 to +439
let mut refs = Vec::new();
collect_uses(doc, state, &mut refs);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not return the refs from collect_uses? e.g.

let refs = collect_uses(doc, state);

Comment thread src/lib.rs
) {
use crate::config::ActionType;

match state.action_type {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels a little inelegant to check the action type here, since it's already checked further up the call stack and the two paths don't share any code:

collect_uses -- checks action type
validate_uses
run -- already knows action type

The additional check could be avoided by breaking up collect_uses into two functions (collect_uses_for_workflow and collect_uses_for_action):

stateDiagram-v2
    [*] --> run

    state type <<choice>>

    run --> type

    type --> validate_uses_for_workflow
    validate_uses_refs --> [*]

    state validate_uses_for_workflow {
        collect_uses_refs_for_workflow
    }

    validate_uses_for_workflow --> validate_uses_refs

    type --> validate_uses_for_action

    state validate_uses_for_action {
        collect_uses_refs_for_action
    }

    validate_uses_for_action --> validate_uses_refs

Loading

@bcheidemann
Copy link
Copy Markdown
Contributor

@award28 I didn't have time to do a full review but a I did a quick scan and to me it all looks good. I think JS side should be easy enough to implement as well, but I'd be happy to pick that up once this is merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validate the action and action-version exists in workflows

3 participants