bitwarden_workflow_linter 0.0.3__py3-none-any.whl → 0.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,3 @@
1
1
  """Metadata for Workflow Linter."""
2
2
 
3
- __version__ = "0.0.3"
3
+ __version__ = "0.0.4"
@@ -11,13 +11,11 @@ from typing import Optional, Union
11
11
 
12
12
  from .utils import Colors, Settings, Action
13
13
 
14
-
15
14
  class GitHubApiSchemaError(Exception):
16
15
  """A generic Exception to catch redefinitions of GitHub Api Schema changes."""
17
16
 
18
17
  pass
19
18
 
20
-
21
19
  class ActionsCmd:
22
20
  """Command to manage the pre-approved list of Actions
23
21
 
@@ -9,10 +9,8 @@ from .actions import ActionsCmd
9
9
  from .lint import LinterCmd
10
10
  from .utils import Settings
11
11
 
12
-
13
12
  local_settings = Settings.factory()
14
13
 
15
-
16
14
  def main(input_args: Optional[List[str]] = None) -> int:
17
15
  """CLI utility to lint GitHub Action Workflows.
18
16
 
@@ -50,6 +48,5 @@ def main(input_args: Optional[List[str]] = None) -> int:
50
48
 
51
49
  return -1
52
50
 
53
-
54
51
  if __name__ == "__main__":
55
52
  sys.exit(main())
@@ -4,5 +4,6 @@ enabled_rules:
4
4
  - bitwarden_workflow_linter.rules.pinned_job_runner.RuleJobRunnerVersionPinned
5
5
  - bitwarden_workflow_linter.rules.job_environment_prefix.RuleJobEnvironmentPrefix
6
6
  - bitwarden_workflow_linter.rules.step_pinned.RuleStepUsesPinned
7
+ - bitwarden_workflow_linter.rules.underscore_outputs.RuleUnderscoreOutputs
7
8
 
8
9
  approved_actions_path: default_actions.json
@@ -10,7 +10,6 @@ from typing import Optional
10
10
  from .load import WorkflowBuilder, Rules
11
11
  from .utils import LintFinding, Settings
12
12
 
13
-
14
13
  class LinterCmd:
15
14
  """Command to lint GitHub Action Workflow files
16
15
 
@@ -13,16 +13,13 @@ from .models.workflow import Workflow
13
13
  from .rule import Rule
14
14
  from .utils import Settings
15
15
 
16
-
17
16
  yaml = YAML()
18
17
 
19
-
20
18
  class WorkflowBuilderError(Exception):
21
19
  """Exception to indicate an error with the WorkflowBuilder."""
22
20
 
23
21
  pass
24
22
 
25
-
26
23
  class WorkflowBuilder:
27
24
  """Collection of methods to build Workflow objects."""
28
25
 
@@ -85,13 +82,11 @@ class WorkflowBuilder:
85
82
  "The workflow must either be built from a file or from a CommentedMap"
86
83
  )
87
84
 
88
-
89
85
  class LoadRulesError(Exception):
90
86
  """Exception to indicate an error with loading rules."""
91
87
 
92
88
  pass
93
89
 
94
-
95
90
  class Rules:
96
91
  """A collection of all of the types of rules.
97
92
 
@@ -30,6 +30,7 @@ class Job:
30
30
  uses_with: Optional[CommentedMap] = field(
31
31
  metadata=config(field_name="with"), default=None
32
32
  )
33
+ outputs: Optional[CommentedMap] = None
33
34
 
34
35
  @classmethod
35
36
  def init(cls: Self, key: str, data: CommentedMap) -> Self:
@@ -39,6 +40,7 @@ class Job:
39
40
  "name": data["name"] if "name" in data else None,
40
41
  "runs-on": data["runs-on"] if "runs-on" in data else None,
41
42
  "env": data["env"] if "env" in data else None,
43
+ "outputs": data["outputs"] if "outputs" in data else None,
42
44
  }
43
45
 
44
46
  new_job = cls.from_dict(init_data)
@@ -7,13 +7,11 @@ from .models.job import Job
7
7
  from .models.step import Step
8
8
  from .utils import LintFinding, LintLevels, Settings
9
9
 
10
-
11
10
  class RuleExecutionException(Exception):
12
11
  """Exception for the Base Rule class."""
13
12
 
14
13
  pass
15
14
 
16
-
17
15
  class Rule:
18
16
  """Base class of a Rule to extend to create a linting Rule."""
19
17
 
@@ -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, ""
@@ -10,10 +10,8 @@ from typing import Optional, Self, TypeVar
10
10
 
11
11
  from ruamel.yaml import YAML
12
12
 
13
-
14
13
  yaml = YAML()
15
14
 
16
-
17
15
  @dataclass
18
16
  class Colors:
19
17
  """Class containing color codes for printing strings to output."""
@@ -27,7 +25,6 @@ class Colors:
27
25
  cyan = "36m"
28
26
  white = "37m"
29
27
 
30
-
31
28
  @dataclass
32
29
  class LintLevel:
33
30
  """Class to contain the numeric level and color of linting."""
@@ -35,7 +32,6 @@ class LintLevel:
35
32
  code: int
36
33
  color: Colors
37
34
 
38
-
39
35
  class LintLevels(LintLevel, Enum):
40
36
  """Collection of the different types of LintLevels available."""
41
37
 
@@ -43,7 +39,6 @@ class LintLevels(LintLevel, Enum):
43
39
  WARNING = 1, Colors.yellow
44
40
  ERROR = 2, Colors.red
45
41
 
46
-
47
42
  class LintFinding:
48
43
  """Represents a problem detected by linting."""
49
44
 
@@ -62,7 +57,6 @@ class LintFinding:
62
57
  f"{self.description}"
63
58
  )
64
59
 
65
-
66
60
  @dataclass
67
61
  class Action:
68
62
  """Collection of the metadata associated with a GitHub Action."""
@@ -99,16 +93,13 @@ class Action:
99
93
  """
100
94
  return not self.__eq__(other)
101
95
 
102
-
103
96
  class SettingsError(Exception):
104
97
  """Custom Exception to indicate an error with loading Settings."""
105
98
 
106
99
  pass
107
100
 
108
-
109
101
  SettingsFromFactory = TypeVar("SettingsFromFactory", bound="Settings")
110
102
 
111
-
112
103
  class Settings:
113
104
  """Class that contains configuration-as-code for any portion of the app."""
114
105
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bitwarden_workflow_linter
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
4
  Summary: Custom GitHub Action Workflow Linter
5
5
  Project-URL: Homepage, https://github.com/bitwarden/workflow-linter
6
6
  Project-URL: Issues, https://github.com/bitwarden/workflow-linter/issues
@@ -66,6 +66,7 @@ enabled_rules:
66
66
  - bitwarden_workflow_linter.rules.pinned_job_runner.RuleJobRunnerVersionPinned
67
67
  - bitwarden_workflow_linter.rules.job_environment_prefix.RuleJobEnvironmentPrefix
68
68
  - bitwarden_workflow_linter.rules.step_pinned.RuleStepUsesPinned
69
+ - bitwarden_workflow_linter.rules.underscore_outputs.RuleUnderscoreOutputs
69
70
 
70
71
  approved_actions_path: default_actions.json
71
72
  ```
@@ -177,6 +178,9 @@ By default, a new Rule needs five things:
177
178
  not support Rules that check against multiple objects at a time OR file level formatting (one empty between each step or
178
179
  two empty lines between each job)
179
180
 
181
+ To activate a rule after implementing it, add it to `settings.yaml` in the project's base folder
182
+ and `src/bitwarden_workflow_linter/default_settings.yaml` to make the rule default
183
+
180
184
  ### ToDo
181
185
 
182
186
  - [ ] Add Rule to assert correct format for single line run
@@ -1,15 +1,15 @@
1
- bitwarden_workflow_linter/__about__.py,sha256=KbbxYy_rLR2HkiKmhQYMcL4RRAhh7MG3ylAz_mbGpQo,59
1
+ bitwarden_workflow_linter/__about__.py,sha256=9USIJuK96UM5d4t_Q91z8UWTAv7q9jr2VgVwol4KYx4,59
2
2
  bitwarden_workflow_linter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- bitwarden_workflow_linter/actions.py,sha256=daob3be2Y22gfsAmGhTgBRCgGk18mlGduqc5Zn1Onz4,7897
4
- bitwarden_workflow_linter/cli.py,sha256=oM3Cjo2y5NrzN1BfqGRiOhOC6IzPDEnEh14v3bsiqak,1700
3
+ bitwarden_workflow_linter/actions.py,sha256=bjuFAP72QHXlDTmMBZffNmSCjZDzyNHGzqEC78ABAgg,7895
4
+ bitwarden_workflow_linter/cli.py,sha256=GABKYWYmcKl4LrA-cgo4ME0mWD4xaO4AgZHBeCCDMy4,1697
5
5
  bitwarden_workflow_linter/default_actions.json,sha256=BeFllnAu9v6cX6eeGeNoQf3rXs7ZOjAtjTNBQuAZ8-k,8087
6
- bitwarden_workflow_linter/default_settings.yaml,sha256=yRvAt06Hupv7mSrSGGdWKJruecvEO55CazIbxP34plg,428
7
- bitwarden_workflow_linter/lint.py,sha256=z83h7XOnULngPqTwlo5LKCK2ciUu4oalwixKVcFLWac,5461
8
- bitwarden_workflow_linter/load.py,sha256=FYbSKgjVCleqiYKR_dB92C3EcP89knNMkxVr2yZZO9o,4473
9
- bitwarden_workflow_linter/rule.py,sha256=Qb60JiUDAWN3ayrMGoSbbDCSFmw-ql8djzAkxISaob4,3250
10
- bitwarden_workflow_linter/utils.py,sha256=IN_jSZQXRwutgFQ6n7xtD1OrTpx9YS_cHLZKDOEUhfc,4696
6
+ bitwarden_workflow_linter/default_settings.yaml,sha256=EMKRFPFlbgkWtZGUA10Y758RJfAjuI8IUDeqRnRTTs8,505
7
+ bitwarden_workflow_linter/lint.py,sha256=Ftz9I3hZS0Vbpn-zk3XKp35Cth3eEo9yf-25rZHrY4k,5460
8
+ bitwarden_workflow_linter/load.py,sha256=E3DPC66rObcmktOKGu3YkGAzWPZLVgSLdZr59DQzz5w,4468
9
+ bitwarden_workflow_linter/rule.py,sha256=H1uwzxck849gh7-kigBgu9RgpJ3lCu3hizQWtZupRJE,3248
10
+ bitwarden_workflow_linter/utils.py,sha256=IyPnxgb91XKXqCifRR5RWEfmZv5F2G9qvwVQVLZd-Bs,4687
11
11
  bitwarden_workflow_linter/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- bitwarden_workflow_linter/models/job.py,sha256=YXzs5CIIM1WEtzAk918QfIioqLcdr4vCOr3fs0uVEu0,1929
12
+ bitwarden_workflow_linter/models/job.py,sha256=nBK7_VYu6RRST7WLtdLsoRErl5j4Er8W9hCw9XlSECk,2043
13
13
  bitwarden_workflow_linter/models/step.py,sha256=1bKAtKZmHcO8O1e_HuoXxR1bwHDEXUssYo7EHOjY7QI,1711
14
14
  bitwarden_workflow_linter/models/workflow.py,sha256=MkqvIY4JX2eWFODNTodS_l4I8uUq08WCHy3C4kYcL0s,1395
15
15
  bitwarden_workflow_linter/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -19,8 +19,9 @@ bitwarden_workflow_linter/rules/name_exists.py,sha256=MxcaNQz64JXeHRPiOip9BxJNgP
19
19
  bitwarden_workflow_linter/rules/pinned_job_runner.py,sha256=Dm6_sdPX0yFMji_y2LMFj4gWFaToEgauyBVpNRP2qiI,1606
20
20
  bitwarden_workflow_linter/rules/step_approved.py,sha256=UIi9_z9j75SpQUmo29MLDhjLklqd4h0D-UYqkdcaju0,3307
21
21
  bitwarden_workflow_linter/rules/step_pinned.py,sha256=JSdKW-2Z8_ozQ3aAU7oYYt2ym3GLEilMfXBVqj1QU_E,3286
22
- bitwarden_workflow_linter-0.0.3.dist-info/METADATA,sha256=5Wg-TjUZxbvnihnAX8HkbsEVEh_30-hJxnslx-pujTE,5912
23
- bitwarden_workflow_linter-0.0.3.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
24
- bitwarden_workflow_linter-0.0.3.dist-info/entry_points.txt,sha256=SA_yF9CwL4VMUvdcmCd7k9rjsQNzfeOUBuDnMnaO8QQ,60
25
- bitwarden_workflow_linter-0.0.3.dist-info/licenses/LICENSE.txt,sha256=uY-7N9tbI7xc_c0WeTIGpacSCnsB91N05eCIg3bkaRw,35140
26
- bitwarden_workflow_linter-0.0.3.dist-info/RECORD,,
22
+ bitwarden_workflow_linter/rules/underscore_outputs.py,sha256=CQbxQc-LI7H6Ihv4JrXJFFUiiQ5lDUjm4jIPdiNmNgE,3843
23
+ bitwarden_workflow_linter-0.0.4.dist-info/METADATA,sha256=FnWnF5oy4sR6O4xVLAVu4GIr6rgDsfop6SXs7kUBRv8,6172
24
+ bitwarden_workflow_linter-0.0.4.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
25
+ bitwarden_workflow_linter-0.0.4.dist-info/entry_points.txt,sha256=SA_yF9CwL4VMUvdcmCd7k9rjsQNzfeOUBuDnMnaO8QQ,60
26
+ bitwarden_workflow_linter-0.0.4.dist-info/licenses/LICENSE.txt,sha256=uY-7N9tbI7xc_c0WeTIGpacSCnsB91N05eCIg3bkaRw,35140
27
+ bitwarden_workflow_linter-0.0.4.dist-info/RECORD,,