Skip to content

feat: more informative error message#662

Closed
reandreev wants to merge 11 commits intozizmorcore:mainfrom
reandreev:main
Closed

feat: more informative error message#662
reandreev wants to merge 11 commits intozizmorcore:mainfrom
reandreev:main

Conversation

@reandreev
Copy link
Copy Markdown
Contributor

@reandreev reandreev commented Apr 13, 2025

Addresses #646

This PR adds a workflow validation step after a failed unmarshaling attempt in order to find what the concrete issues with the workflows are. Running zizmor on the following workflow:

name: Invalid

boom:

on:
  workflow_call:
    inputs:
      input:
        description: Input

permissions: {}

jobs:
  valid:
    runs-on: ubuntu-latest
    steps:
      - run: echo 'invalid'

will produce the following output:

failed to register input: .github/workflows/invalid.yaml

Caused by:
    0: invalid GitHub Actions workflow: file://.github/workflows/invalid.yaml
       
       Caused by:
           on.workflow_call.inputs.input: "type" is a required property
           Additional properties are not allowed ('boom' was unexpected)
           
    1: invalid GitHub Actions definition: file://.github/workflows/invalid.yaml
       
       Caused by:
           missing field `runs`
    2: failed to register input as workflow or action

An interesting thing I've noticed is that running zizmor on the following workflow:

name: Invalid

boom:

on:
  workflow_call:

permissions: {}

jobs:
  valid:
    runs-on: ubuntu-latest
    steps:
      - run: echo 'invalid'

will succeed. Even though it has that additional boom property at the top level, zizmor still managed to unmarshal all of the expected properties correctly since those were properly defined and simply ignored the invalid one. Not sure if this is an expected behavior, but thought I'd mention it.

Regarding the validator instantiation, another approach would be to keep the GitHub Workflow schema as a dependency of the tool and instead do something like:

static WORKFLOW_VALIDATOR: LazyLock<Option<Validator>> = LazyLock::new(||
    match serde_json::from_str(include_str!("../github-workflow.json")) {
        Ok(schema) => validator_for(&schema).ok(),
        Err(_) => None,
    }
); 

Considering how seldom the schema itself gets updated that could also be an approach to consider. Pros: the tool would be more self-contained, cons: the tool would need to be recompiled each time the schema gets updated.

Some other things to consider:

  1. In the example above the error messages could be sorted by their depth/nesting and/or the order of appearance within the file itself
  2. Line numbers could be added
  3. The schema could be cached somewhere instead of retrieving it on every run

@woodruffw
Copy link
Copy Markdown
Member

Hi @reandreev, thanks for looking into this! I'll do a full review in the coming days.

Even though it has that additional boom property at the top level, zizmor still managed to unmarshal all of the expected properties correctly since those were properly defined and simply ignored the invalid one. Not sure if this is an expected behavior, but thought I'd mention it.

Yep, this is semi-unintentional behavior -- see zizmorcore/github-actions-models#33 for some context 🙂

Regarding the validator instantiation, another approach would be to keep the GitHub Workflow schema as a dependency of the tool and instead do something like:

static WORKFLOW_VALIDATOR: LazyLock<Option<Validator>> = LazyLock::new(||
    match serde_json::from_str(include_str!("../github-workflow.json")) {
        Ok(schema) => validator_for(&schema).ok(),
        Err(_) => None,
    }
); 

Yes, let's do this! IMO a network request in an error path like this isn't ideal; it'd be a lot faster and more reliable to bake the schema into the binary (and I agree that it won't change frequently).

Comment thread src/utils.rs Outdated
Comment thread src/models.rs
Comment thread github-workflow.json
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No need to do it with this PR, but a thought: we'll probably want some kind of scheduled workflow that checks periodically for updates to this file.

(That concerns the CI though, so I'll do that in a followup.)

@woodruffw woodruffw added this to the 1.7.0 milestone Apr 17, 2025
@woodruffw woodruffw added the enhancement New feature or request label Apr 23, 2025
Comment thread src/utils.rs Outdated
pub(crate) fn validate_action(contents: String) -> Error {
match serde_yaml::from_str(&contents) {
Ok(workflow) => match ACTION_VALIDATOR.apply(&workflow).basic() {
Valid(_) => anyhow!("valid action but failed unmarshaling"),
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not really sure how to handle this case since in theory this should be unreachable (?), maybe you have some suggestion on what to do or what error message to print out here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

unreachable makes sense to me here 🙂

Suggested change
Valid(_) => anyhow!("valid action but failed unmarshaling"),
Valid(_) => unreachable!("valid action but invalid YAML"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think maybe the tests can remain as unit tests by attempting to capture the panic instead, what do you think? For example:
https://github.com/woodruffw/zizmor/blob/f642eaf19a95534c42bf68169156eaf995a19261/src/utils.rs#L387-L389
Either way I did have to update a snapshot there so thanks for reminding me 🙂
Also, regarding your suggestion "valid action but invalid YAML", this unreachable snippet is only reached when there is a discrepancy between the schema validation and the unmarshaling logic (which in theory shouldn't happen), I'm not sure if the YAML is guaranteed to be invalid in those cases? 🤔

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also, regarding your suggestion "valid action but invalid YAML", this unreachable snippet is only reached when there is a discrepancy between the schema validation and the unmarshaling logic (which in theory shouldn't happen), I'm not sure if the YAML is guaranteed to be invalid in those cases? 🤔

Oh yeah, I see what you mean (and I misunderstood the context for this error) -- yeah, this should probably instead indicate to the user that the bug is on zizmor's modeling side, rather than an invalid workflow/action definition per the schema. Sorry for the confusion!

(This also undoes the need for the panic handling, so sorry for the wild goose chase there.)

Comment thread src/utils.rs Outdated
@woodruffw
Copy link
Copy Markdown
Member

Thanks @reandreev! I went ahead and landed this with #719, and credited you in the release notes.

@woodruffw woodruffw closed this Apr 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants