Skip to content

Commit a5bcc6b

Browse files
authored
[BRE-54] Add Warning When Output Vars Contain Hyphen (#10)
* add warning when output vars contain hyphen * apply black formatter * add method doc string * update Settings type
1 parent f01bc57 commit a5bcc6b

File tree

8 files changed

+428
-0
lines changed

8 files changed

+428
-0
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,8 @@ dist
3030
## Dev Environments
3131
Session.vim
3232
flake.*
33+
34+
35+
# Python
36+
**/__pycache__/**
37+
*.pyc

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ enabled_rules:
4040
- bitwarden_workflow_linter.rules.pinned_job_runner.RuleJobRunnerVersionPinned
4141
- bitwarden_workflow_linter.rules.job_environment_prefix.RuleJobEnvironmentPrefix
4242
- bitwarden_workflow_linter.rules.step_pinned.RuleStepUsesPinned
43+
- bitwarden_workflow_linter.rules.underscore_outputs.RuleUnderscoreOutputs
4344

4445
approved_actions_path: default_actions.json
4546
```
@@ -151,6 +152,9 @@ By default, a new Rule needs five things:
151152
not support Rules that check against multiple objects at a time OR file level formatting (one empty between each step or
152153
two empty lines between each job)
153154

155+
To activate a rule after implementing it, add it to `settings.yaml` in the project's base folder
156+
and `src/bitwarden_workflow_linter/default_settings.yaml` to make the rule default
157+
154158
### ToDo
155159

156160
- [ ] Add Rule to assert correct format for single line run

settings.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ enabled_rules:
44
- bitwarden_workflow_linter.rules.pinned_job_runner.RuleJobRunnerVersionPinned
55
- bitwarden_workflow_linter.rules.job_environment_prefix.RuleJobEnvironmentPrefix
66
- bitwarden_workflow_linter.rules.step_pinned.RuleStepUsesPinned
7+
- bitwarden_workflow_linter.rules.underscore_outputs.RuleUnderscoreOutputs
78

89
approved_actions_path: default_actions.json

src/bitwarden_workflow_linter/default_settings.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ enabled_rules:
44
- bitwarden_workflow_linter.rules.pinned_job_runner.RuleJobRunnerVersionPinned
55
- bitwarden_workflow_linter.rules.job_environment_prefix.RuleJobEnvironmentPrefix
66
- bitwarden_workflow_linter.rules.step_pinned.RuleStepUsesPinned
7+
- bitwarden_workflow_linter.rules.underscore_outputs.RuleUnderscoreOutputs
78

89
approved_actions_path: default_actions.json

src/bitwarden_workflow_linter/models/job.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class Job:
3030
uses_with: Optional[CommentedMap] = field(
3131
metadata=config(field_name="with"), default=None
3232
)
33+
outputs: Optional[CommentedMap] = None
3334

3435
@classmethod
3536
def init(cls: Self, key: str, data: CommentedMap) -> Self:
@@ -39,6 +40,7 @@ def init(cls: Self, key: str, data: CommentedMap) -> Self:
3940
"name": data["name"] if "name" in data else None,
4041
"runs-on": data["runs-on"] if "runs-on" in data else None,
4142
"env": data["env"] if "env" in data else None,
43+
"outputs": data["outputs"] if "outputs" in data else None,
4244
}
4345

4446
new_job = cls.from_dict(init_data)
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import re
2+
3+
from typing import Optional, Union, Tuple
4+
5+
from ..models.job import Job
6+
from ..rule import Rule
7+
from ..models.workflow import Workflow
8+
from ..models.step import Step
9+
from ..utils import LintLevels, Settings
10+
11+
12+
class RuleUnderscoreOutputs(Rule):
13+
"""Rule to enforce all GitHub 'outputs' more than one words contain an underscore.
14+
15+
A simple standard to ensure uniformity in naming.
16+
"""
17+
18+
def __init__(self, settings: Optional[Settings] = None) -> None:
19+
"""Constructor for RuleUnderscoreOutputs to override the Rule class.
20+
21+
Args:
22+
settings:
23+
A Settings object that contains any default, overridden, or custom settings
24+
required anywhere in the application.
25+
"""
26+
self.message = "outputs with more than one word must use an underscore"
27+
self.on_fail = LintLevels.WARNING
28+
self.compatibility = [Workflow, Job, Step]
29+
self.settings = settings
30+
31+
def fn(self, obj: Union[Workflow, Job, Step]) -> Tuple[bool, str]:
32+
"""Enforces all outputs to have an underscore in the key name.
33+
34+
This Rule checks all outputs in a Workflow, Job, or Step to ensure that
35+
the key name contains an underscore. This is to ensure that the naming
36+
convention is consistent across all outputs in the workflow configuration
37+
38+
Example:
39+
---
40+
on:
41+
workflow_dispatch:
42+
outputs:
43+
registry:
44+
value: 'Test Value'
45+
some_registry:
46+
value: 'Test Value'
47+
workflow_call:
48+
outputs:
49+
registry:
50+
value: 'Test Value'
51+
some_registry:
52+
value: 'Test Value'
53+
jobs:
54+
job-key:
55+
runs-on: ubuntu-22.04
56+
outputs:
57+
test_key_job: ${{ steps.test_output_1.outputs.test_key }}
58+
steps:
59+
- name: Test output in one-line run step
60+
id: test_output_1
61+
run: echo "test_key_1=Test-Value1" >> $GITHUB_OUTPUT
62+
63+
- name: Test output in multi-line run step
64+
id: test_output_2
65+
run: |
66+
echo
67+
fake-command=Test-Value2
68+
echo "test_key_2=$REF" >> $GITHUB_OUTPUT
69+
echo "deployed_ref=$DEPLOYED_REF" >> $GITHUB_OUTPUT
70+
71+
- name: Test step with no run
72+
id: test_output_3
73+
uses: actions/checkout@v2
74+
with:
75+
ref: ${{ github.ref }}
76+
fetch-depth: 0
77+
78+
In this example, in workflow level 'registry' and 'some_registry' are outputs
79+
that satisfy the rule in both 'workflow_dispatch' and 'workflow_call' events.
80+
In job level 'test_key_job' satisfies the rule.
81+
In step level 'test_key_1', 'test_key_2', and 'deployed_ref' satisfy the rule.
82+
83+
See tests/rules/test_underscore_outputs.py for incorrect examples.
84+
"""
85+
86+
outputs = []
87+
88+
if isinstance(obj, Workflow):
89+
if obj.on.get("workflow_dispatch"):
90+
outputs.extend(obj.on["workflow_dispatch"]["outputs"].keys())
91+
92+
if obj.on.get("workflow_call"):
93+
outputs.extend(obj.on["workflow_call"]["outputs"].keys())
94+
95+
if isinstance(obj, Job):
96+
if obj.outputs:
97+
outputs.extend(obj.outputs.keys())
98+
99+
if isinstance(obj, Step):
100+
if obj.run:
101+
outputs.extend(re.findall(
102+
r"\b([a-zA-Z0-9_-]+)\s*=\s*[^=]*>>\s*\$GITHUB_OUTPUT",
103+
obj.run))
104+
105+
for output_name in outputs:
106+
if "-" in output_name:
107+
return False, (
108+
f"Hyphen found in {obj.__class__.__name__} output: {output_name}"
109+
)
110+
111+
return True, ""
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
name: Test Incorrect Workflow
3+
on:
4+
workflow_dispatch:
5+
outputs:
6+
registry-1:
7+
value: 'Test Value'
8+
some_registry-1:
9+
value: 'Test Value'
10+
workflow-call:
11+
outputs:
12+
registry-2:
13+
value: 'Test Value'
14+
push: {}
15+
16+
jobs:
17+
job-key:
18+
name: Test Incorrect Job
19+
runs-on: ubuntu-22.04
20+
outputs:
21+
test-key-1: ${{ steps.test_output_1.outputs.test_key }}
22+
steps:
23+
- name: Test output in one-line run step
24+
id: test_output_1
25+
run: echo "test-key-1=Test-Value1" >> $GITHUB_OUTPUT
26+
27+
- name: Test output in multi-line run step
28+
id: test_output_2
29+
run: |
30+
echo
31+
fake-command
32+
echo "test-key-2=$REF" >> $GITHUB_OUTPUT
33+
echo "deployed-ref=$DEPLOYED_REF" >> $GITHUB_OUTPUT
34+
35+
- name: Test step with one-line run and no Output
36+
id: test_output_3
37+
run: echo "test-key-3"
38+
39+
- name: Test step with multi-line run and no Output
40+
id: test_output_4
41+
run: |
42+
echo
43+
fake-command=Test-Value4
44+
echo "test-key-4"
45+
echo "deployed-ref"

0 commit comments

Comments
 (0)