Skip to content

Commit

Permalink
[BRE-138] - Fix Workflow Name Underscore Error and Enforce Action Ver…
Browse files Browse the repository at this point in the history
…sion Comment (#30)

* fix workflow name underscore error and enforce action version comment

* Update tests/rules/test_name_capitalized.py

* Update src/bitwarden_workflow_linter/rules/underscore_outputs.py

---------

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
  • Loading branch information
Eeebru and vgrassia authored Nov 13, 2024
1 parent e52594e commit 69f2ef2
Show file tree
Hide file tree
Showing 18 changed files with 85 additions and 11 deletions.
2 changes: 2 additions & 0 deletions src/bitwarden_workflow_linter/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@

from .utils import Colors, Settings, Action


class GitHubApiSchemaError(Exception):
"""A generic Exception to catch redefinitions of GitHub Api Schema changes."""

pass


class ActionsCmd:
"""Command to manage the pre-approved list of Actions
Expand Down
2 changes: 2 additions & 0 deletions src/bitwarden_workflow_linter/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

local_settings = Settings.factory()


def main(input_args: Optional[List[str]] = None) -> int:
"""CLI utility to lint GitHub Action Workflows.
Expand Down Expand Up @@ -48,5 +49,6 @@ def main(input_args: Optional[List[str]] = None) -> int:

return -1


if __name__ == "__main__":
sys.exit(main())
1 change: 1 addition & 0 deletions src/bitwarden_workflow_linter/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .load import WorkflowBuilder, Rules
from .utils import LintFinding, Settings


class LinterCmd:
"""Command to lint GitHub Action Workflow files
Expand Down
4 changes: 4 additions & 0 deletions src/bitwarden_workflow_linter/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@

yaml = YAML()


class WorkflowBuilderError(Exception):
"""Exception to indicate an error with the WorkflowBuilder."""

pass


class WorkflowBuilder:
"""Collection of methods to build Workflow objects."""

Expand Down Expand Up @@ -82,11 +84,13 @@ def build(
"The workflow must either be built from a file or from a CommentedMap"
)


class LoadRulesError(Exception):
"""Exception to indicate an error with loading rules."""

pass


class Rules:
"""A collection of all of the types of rules.
Expand Down
1 change: 1 addition & 0 deletions src/bitwarden_workflow_linter/models/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from .step import Step


@dataclass_json(undefined=Undefined.EXCLUDE)
@dataclass
class Job:
Expand Down
1 change: 1 addition & 0 deletions src/bitwarden_workflow_linter/models/step.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from dataclasses_json import config, dataclass_json, Undefined
from ruamel.yaml.comments import CommentedMap


@dataclass_json(undefined=Undefined.EXCLUDE)
@dataclass
class Step:
Expand Down
2 changes: 2 additions & 0 deletions src/bitwarden_workflow_linter/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
from .models.step import Step
from .utils import LintFinding, LintLevels, Settings


class RuleExecutionException(Exception):
"""Exception for the Base Rule class."""

pass


class Rule:
"""Base class of a Rule to extend to create a linting Rule."""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ..rule import Rule
from ..utils import LintLevels, Settings


class RuleJobEnvironmentPrefix(Rule):
"""Rule to enforce specific prefixes for environment variables.
Expand Down
13 changes: 10 additions & 3 deletions src/bitwarden_workflow_linter/rules/name_capitalized.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ..rule import Rule
from ..utils import LintLevels, Settings


class RuleNameCapitalized(Rule):
"""Rule to enforce all 'name' values start with a capital letter.
Expand Down Expand Up @@ -50,6 +51,12 @@ def fn(self, obj: Union[Workflow, Job, Step]) -> Tuple[bool, str]:
capitalized names. This Rule DOES NOT enforce that the name exists.
It only enforces capitalization IF it does.
"""
if obj.name:
return obj.name[0].isupper(), self.message
return True, "" # Force passing if obj.name doesn't exist
if isinstance(obj, Workflow):
if obj.name:
if obj.name[0] != "_":
return obj.name[0].isupper(), self.message
else:
if obj.name:
return obj.name[0].isupper(), self.message

return True, "" # Force passing
1 change: 1 addition & 0 deletions src/bitwarden_workflow_linter/rules/name_exists.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ..rule import Rule
from ..utils import LintLevels, Settings


class RuleNameExists(Rule):
"""Rule to enforce a 'name' key exists for every object in GitHub Actions.
Expand Down
1 change: 1 addition & 0 deletions src/bitwarden_workflow_linter/rules/pinned_job_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ..rule import Rule
from ..utils import LintLevels, Settings


class RuleJobRunnerVersionPinned(Rule):
"""Rule to enforce pinned Runner OS versions.
Expand Down
1 change: 1 addition & 0 deletions src/bitwarden_workflow_linter/rules/step_approved.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ..rule import Rule
from ..utils import LintLevels, Settings


class RuleStepUsesApproved(Rule):
"""Rule to enforce that all Actions have been pre-approved.
Expand Down
4 changes: 4 additions & 0 deletions src/bitwarden_workflow_linter/rules/step_pinned.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ..rule import Rule
from ..utils import LintLevels, Settings


class RuleStepUsesPinned(Rule):
"""Rule to contain the enforcement logic for pinning Actions versions.
Expand Down Expand Up @@ -94,4 +95,7 @@ def fn(self, obj: Step) -> Tuple[bool, str]:
if len(ref) != 40:
return False, "Please use the full commit sha to pin the action"

if not obj.uses_comment:
return False, "Please comment the version of the action commit sha"

return True, ""
13 changes: 9 additions & 4 deletions src/bitwarden_workflow_linter/rules/underscore_outputs.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
""" Rule to enforce all GitHub outputs with more than one words use an underscore."""

import re

from typing import Optional, Union, Tuple
Expand All @@ -8,8 +10,9 @@
from ..models.step import Step
from ..utils import LintLevels, Settings


class RuleUnderscoreOutputs(Rule):
"""Rule to enforce all GitHub 'outputs' more than one words contain an underscore.
"""Rule to enforce all GitHub outputs with more than one word use an underscore.
A simple standard to ensure uniformity in naming.
"""
Expand Down Expand Up @@ -98,9 +101,11 @@ def fn(self, obj: Union[Workflow, Job, Step]) -> Tuple[bool, str]:

if isinstance(obj, Step):
if obj.run:
outputs.extend(re.findall(
r"\b([a-zA-Z0-9_-]+)\s*=\s*[^=]*>>\s*\$GITHUB_OUTPUT",
obj.run))
outputs.extend(
re.findall(
r"\b([a-zA-Z0-9_-]+)\s*=\s*[^=]*>>\s*\$GITHUB_OUTPUT", obj.run
)
)

for output_name in outputs:
if "-" in output_name:
Expand Down
8 changes: 8 additions & 0 deletions src/bitwarden_workflow_linter/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

yaml = YAML()


@dataclass
class Colors:
"""Class containing color codes for printing strings to output."""
Expand All @@ -25,20 +26,23 @@ class Colors:
cyan = "36m"
white = "37m"


@dataclass
class LintLevel:
"""Class to contain the numeric level and color of linting."""

code: int
color: Colors


class LintLevels(LintLevel, Enum):
"""Collection of the different types of LintLevels available."""

NONE = 0, Colors.white
WARNING = 1, Colors.yellow
ERROR = 2, Colors.red


class LintFinding:
"""Represents a problem detected by linting."""

Expand All @@ -57,6 +61,7 @@ def __str__(self) -> str:
f"{self.description}"
)


@dataclass
class Action:
"""Collection of the metadata associated with a GitHub Action."""
Expand Down Expand Up @@ -93,13 +98,16 @@ def __ne__(self, other: Self) -> bool:
"""
return not self.__eq__(other)


class SettingsError(Exception):
"""Custom Exception to indicate an error with loading Settings."""

pass


SettingsFromFactory = TypeVar("SettingsFromFactory", bound="Settings")


class Settings:
"""Class that contains configuration-as-code for any portion of the app."""

Expand Down
23 changes: 23 additions & 0 deletions tests/rules/test_name_capitalized.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ def fixture_incorrect_workflow():
return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False)


@pytest.fixture(name="underscore_name_workflow")
def fixture_underscore_name_workflow():
workflow = """\
name: _test
on:
workflow_dispatch:
jobs:
job-key:
name: test
runs-on: ubuntu-latest
steps:
- name: test
run: echo test
"""
return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False)


@pytest.fixture(name="missing_name_workflow")
def fixture_missing_name_workflow():
workflow = """\
Expand Down Expand Up @@ -86,6 +104,11 @@ def test_rule_on_incorrect_workflow_name(rule, incorrect_workflow):
assert result is False


def test_rule_on_underscore_name_workflow(rule, underscore_name_workflow):
result, _ = rule.fn(underscore_name_workflow)
assert result is True


def test_rule_on_incorrect_job_name(rule, incorrect_workflow):
result, _ = rule.fn(incorrect_workflow.jobs["job-key"])
assert result is False
Expand Down
15 changes: 12 additions & 3 deletions tests/rules/test_step_pinned.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ def fixture_incorrect_workflow():
job-key:
runs-on: ubuntu-22.04
steps:
- name: Test 3rd Party Action without version pinned
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- name: Test External Branch
uses: actions/checkout@main
Expand Down Expand Up @@ -78,20 +81,26 @@ def test_rule_on_correct_workflow(rule, correct_workflow):
assert result is True


def test_rule_on_incorrect_workflow_external_branch(rule, incorrect_workflow):
def test_rule_on_incorrect_workflow_version_comment(rule, incorrect_workflow):
result, message = rule.fn(incorrect_workflow.jobs["job-key"].steps[0])
assert result is False
assert "the version of the action commit sha" in message


def test_rule_on_incorrect_workflow_external_branch(rule, incorrect_workflow):
result, message = rule.fn(incorrect_workflow.jobs["job-key"].steps[1])
assert result is False
assert "Please pin the action" in message


def test_rule_on_incorrect_workflow_hex(rule, incorrect_workflow):
result, message = rule.fn(incorrect_workflow.jobs["job-key"].steps[1])
result, message = rule.fn(incorrect_workflow.jobs["job-key"].steps[2])
assert result is False
assert "Please use the full commit sha" in message


def test_rule_on_incorrect_workflow_internal_commit(rule, incorrect_workflow):
result, message = rule.fn(incorrect_workflow.jobs["job-key"].steps[2])
result, message = rule.fn(incorrect_workflow.jobs["job-key"].steps[3])
assert result is False
assert "Please pin to main" in message

Expand Down
3 changes: 2 additions & 1 deletion tests/rules/test_underscore_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ def fixture_push_only_workflow():
"""
return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False)


@pytest.fixture(name="misc_workflow")
def fixture_misc_workflow():
workflow = """\
Expand All @@ -164,6 +165,7 @@ def fixture_misc_workflow():
"""
return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False)


@pytest.fixture(name="no_output_workflow")
def fixture_no_output_workflow():
workflow = """\
Expand All @@ -184,7 +186,6 @@ def fixture_no_output_workflow():
return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False)



@pytest.fixture(name="rule")
def fixture_rule():
return RuleUnderscoreOutputs()
Expand Down

0 comments on commit 69f2ef2

Please sign in to comment.