Source code for validate_actions.rules.action_input
"""Validates input specifications in workflow action 'uses:' fields."""
from typing import Generator, List
from validate_actions.domain_model.ast import ExecAction
from validate_actions.globals.problems import Problem, ProblemLevel
from validate_actions.rules.rule import Rule
[docs]
class ActionInput(Rule):
"""Validates input specifications in workflow action 'uses:' fields.
This rule checks GitHub Actions workflow steps that reference external actions
via the 'uses:' field. It validates input specifications to ensure proper
action configuration.
Key validations:
- Validates required inputs are provided
- Checks that only defined inputs are used
"""
NAME = "action-input"
# ====================
# MAIN VALIDATION METHODS
# ====================
[docs]
def check(self) -> Generator[Problem, None, None]:
"""Validates all actions in the workflow for input issues.
Iterates through all workflow jobs and their steps, collecting
ExecAction instances (steps that use the 'uses:' field) and
validates them for input requirements.
Yields:
Problem: Problems found during validation including missing inputs
and usage of undefined inputs.
"""
actions = []
for job in self.workflow.jobs_.values():
steps = job.steps_
for step in steps:
if isinstance(step.exec, ExecAction):
actions.append(step.exec)
return self._check_single_action(actions)
def _check_single_action(
self,
actions: List[ExecAction],
) -> Generator[Problem, None, None]:
"""Validates each action individually for input issues.
Processes each ExecAction to validate input requirements against
the action's metadata (if available).
Args:
actions: List of ExecAction instances to validate.
Yields:
Problem: Problems found including missing required inputs
and usage of undefined inputs.
"""
for action in actions:
required_inputs = action.metadata.required_inputs if action.metadata else []
possible_inputs = action.metadata.possible_inputs if action.metadata else []
if len(action.with_) == 0:
if len(required_inputs) == 0:
continue
else:
yield from self._misses_required_input(action, required_inputs)
else:
yield from self._check_required_inputs(action, required_inputs)
yield from self._uses_non_defined_input(action, possible_inputs)
# ====================
# INPUT VALIDATION METHODS
# ====================
def _misses_required_input(
self, action: ExecAction, required_inputs: List[str]
) -> Generator[Problem, None, None]:
"""Generates an error problem for missing required inputs.
This is a helper method that creates a formatted error message
listing all required inputs for an action.
Args:
action: The action missing required inputs.
required_inputs: List of all required input names.
Yields:
Problem: Error problem with formatted list of required inputs.
"""
prettyprint_required_inputs = ", ".join(required_inputs)
yield Problem(
action.pos,
ProblemLevel.ERR,
(f"{action.uses_.string} requires inputs: " f"{prettyprint_required_inputs}"),
self.NAME,
)
def _check_required_inputs(
self, action: ExecAction, required_inputs: List[str]
) -> Generator[Problem, None, None]:
"""Validates that all required inputs for an action are provided.
Iterates through all required inputs and checks if they are present
in the action's 'with:' section. Generates problems for missing inputs.
Args:
action: The action to validate.
required_inputs: List of required input names for this action.
Yields:
Problem: Error problems for each missing required input.
"""
if not required_inputs:
return
for required_input in required_inputs:
if required_input not in action.with_:
yield from self._misses_required_input(action, required_inputs)
def _uses_non_defined_input(
self, action: ExecAction, possible_inputs: List[str]
) -> Generator[Problem, None, None]:
"""
Checks if an action uses inputs that are not defined in its metadata.
Args:
action (ExecAction): The action to validate.
possible_inputs (List[str]): The list of possible inputs.
Yields:
Problem: Error if undefined inputs are used.
"""
if not possible_inputs:
return
for action_input in action.with_:
if action_input not in possible_inputs:
yield Problem(
action.pos,
ProblemLevel.ERR,
f"{action.uses_.string} uses unknown input: {action_input.string}",
self.NAME,
)