Skip to content

Commit

Permalink
Got to a somewhat working breakdown of the rules (not including check…
Browse files Browse the repository at this point in the history
…ing the actions)
  • Loading branch information
joseph-flinn committed May 5, 2023
1 parent 1ef4a3b commit 87adafd
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 20 deletions.
2 changes: 1 addition & 1 deletion lint-workflow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ workflows:
- assert name exists
- assert name is capitalized
jobs:
- assert runs-on is pinned
- assert name exists
- assert name is capitalized
- assert runs-on is pinned
- assert any environment variables start with "_"
steps:
shared:
Expand Down
39 changes: 38 additions & 1 deletion lint-workflow/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import logging


from src.rules import LintFinding
from src.rules import LintFinding, workflow_rules, job_rules, step_rules, uses_step_rules, run_step_rules
from src.load import get_workflow


PROBLEM_LEVELS = {
Expand Down Expand Up @@ -196,6 +197,42 @@ def get_action_update(action_id):
return update_url


def _new_lint(filename):
findings = []
max_error_level = 0

print(f"Linting: {filename}")
with open(filename) as file:
workflow = get_workflow(filename)

for rule in workflow_rules:
findings.append(rule.execute(workflow))

for job_key, job in workflow.jobs.items():
for rule in job_rules:
findings.append(rule.execute(job))

for step in job.steps:
if step.uses is not None:
for rule in [*step_rules, *uses_step_rules]:
findings.append(rule.execute(step))
else:
for rule in [*step_rules, *run_step_rules]:
findings.append(rule.execute(step))

findings = list(filter(lambda a: a is not None, findings))

if len(findings) > 0:
print("#", filename)
for finding in findings:
print_finding(finding)
print()

max_error_level = get_max_error_level(findings)

return max_error_level


def lint(filename):

findings = []
Expand Down
22 changes: 17 additions & 5 deletions lint-workflow/src/load.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from ruamel.yaml import YAML
from ruamel.yaml.comments import CommentedMap

from .models.job import Job
from .models.step import Step
Expand All @@ -8,15 +9,26 @@
yaml = YAML()


def load_workflow(filename: str) -> Workflow:
def load_workflow(filename: str) -> CommentedMap:
with open(filename) as file:
#workflow = sanitize_yaml(file.read())
workflow = yaml.load(file)
return yaml.load(file)


def build_workflow(loaded_yaml: str) -> Workflow:
return Workflow.from_dict({
**workflow,
**loaded_yaml,
"jobs": {str(job_key): Job.from_dict({
**job,
"steps": [Step.from_dict(step) for step in job["steps"]]
}) for job_key, job in workflow["jobs"].items()}
}) for job_key, job in loaded_yaml["jobs"].items()}
})


def get_workflow(filename: str) -> Workflow:
return build_workflow(load_workflow(filename))


"""
workflow = load_workflow("tests/fixtures/test.yml")
workflow["jobs"]["crowdin-pull"]["steps"][0]._yaml_comment # has the comment in it
"""
62 changes: 52 additions & 10 deletions lint-workflow/src/rules.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
from collections.abc import Callable
from dataclasses import dataclass
from typing import Union

from .models.workflow import Workflow
from .models.job import Job
from .models.step import Step


@dataclass
class LintFinding:
"""Represents a linting problem."""
description: str = "<no description>"
level: str = None


def _validate(
obj: Workflow | Job | Step,
rule: Callable[[Workflow | Job | Step], bool | None],
def validate(
obj: Union[Workflow, Job, Step],
rule: Callable[Union[Workflow, Job, Step], Union[bool, None]],
message: str,
warning_level: str
) -> LintFinding | None:
) -> Union[LintFinding, None]:
try:
if rule(obj):
return None
Expand All @@ -27,18 +30,57 @@ def _validate(
return LintFinding(f"{obj.__name__}.{obj.name} => {message}", warning_level)


class Rule:
def __init__(
self,
fn: Callable[Union[Workflow, Job, Step], Union[bool, None]],
message: str = "error",
warning_level: str = "error",
):
self.fn = fn
self.message = message
self.warning_level = warning_level

def execute(self, obj: Union[Workflow, Job, Step]):
try:
if self.fn(obj):
return None
except:
message = f"failed to apply {self.fn.__name__}"
warning_level = "error"

#return LintFinding(f"{obj.__name__}.{obj.name} => {self.message}", self.warning_level)
return LintFinding(f"{obj.name} => {self.message}", self.warning_level)


# -------- Rules ---------

def workflow_name_exists( obj: Workflow | Job | Step):
def name_exists(obj: Union[Workflow, Job, Step]):
return obj.name is not None

def workflow_name_capitalized( obj: Workflow | Job | Step):
def name_capitalized(obj: Union[Workflow, Job, Step]):
return obj.name.isupper()


def step_run_single_line(step: Step):
return True

# ----- End of Rules -----

workflow_rules = [
Rule(name_exists, "field required", "error"),
Rule(name_capitalized, "field must be capitalized", "error")
]
job_rules = [
Rule(name_exists, "field required", "error"),
Rule(name_capitalized, "field must be capitalized", "error")
]
step_rules = [
Rule(name_exists, "field required", "error"),
Rule(name_capitalized, "field must be capitalized", "error")
]
uses_step_rules = [
]
run_step_rules = [
]

findings = list(filter(lambda a: a is not None, [
_validate(workflow, workflow_name_exists, "field required", "error"),
_validate(workflow, workflow_name_capitalized, "field must be capitalized", "error")
]))
4 changes: 2 additions & 2 deletions lint-workflow/tests/fixtures/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
_CROWDIN_PROJECT_ID: "308189"
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v2.3.4
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v2.3.4

- name: Login to Azure
uses: Azure/logi@77f1b2e3fb80c0e8645114159d17008b8a2e475a
Expand All @@ -37,7 +37,7 @@ jobs:
done
- name: Download translations
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
Expand Down
13 changes: 12 additions & 1 deletion lint-workflow/tests/test_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,18 @@ def workflow_filename():


def test_load_workflow(workflow_filename):
workflow = src.load.load_workflow(workflow_filename)
loaded_yaml = src.load.load_workflow(workflow_filename)
print(loaded_yaml)
print(type(loaded_yaml))
print(dir(loaded_yaml))

assert False


def test_build_workflow(workflow_filename):
workflow = src.load.build_workflow(
src.load.load_workflow(workflow_filename)
)

assert workflow.name == "crowdin Pull"
assert len(workflow.jobs["crowdin-pull"].steps) == 4
1 change: 1 addition & 0 deletions lint-workflow/tests/test_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def step_default_data():
"run": "echo \"test\""
}


def test_step_default(step_default_data):
step = src.models.Step.from_dict(step_default_data)
assert step.name == None
Expand Down

0 comments on commit 87adafd

Please sign in to comment.