bitwarden_workflow_linter 0.0.3__py3-none-any.whl → 0.0.5__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.5"
@@ -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
 
@@ -6,7 +6,6 @@ from ..models.job import Job
6
6
  from ..rule import Rule
7
7
  from ..utils import LintLevels, Settings
8
8
 
9
-
10
9
  class RuleJobEnvironmentPrefix(Rule):
11
10
  """Rule to enforce specific prefixes for environment variables.
12
11
 
@@ -8,7 +8,6 @@ from ..models.workflow import Workflow
8
8
  from ..rule import Rule
9
9
  from ..utils import LintLevels, Settings
10
10
 
11
-
12
11
  class RuleNameCapitalized(Rule):
13
12
  """Rule to enforce all 'name' values start with a capital letter.
14
13
 
@@ -8,7 +8,6 @@ from ..models.step import Step
8
8
  from ..rule import Rule
9
9
  from ..utils import LintLevels, Settings
10
10
 
11
-
12
11
  class RuleNameExists(Rule):
13
12
  """Rule to enforce a 'name' key exists for every object in GitHub Actions.
14
13
 
@@ -6,7 +6,6 @@ from ..models.job import Job
6
6
  from ..rule import Rule
7
7
  from ..utils import LintLevels, Settings
8
8
 
9
-
10
9
  class RuleJobRunnerVersionPinned(Rule):
11
10
  """Rule to enforce pinned Runner OS versions.
12
11
 
@@ -6,7 +6,6 @@ from ..models.step import Step
6
6
  from ..rule import Rule
7
7
  from ..utils import LintLevels, Settings
8
8
 
9
-
10
9
  class RuleStepUsesApproved(Rule):
11
10
  """Rule to enforce that all Actions have been pre-approved.
12
11
 
@@ -6,7 +6,6 @@ from ..models.step import Step
6
6
  from ..rule import Rule
7
7
  from ..utils import LintLevels, Settings
8
8
 
9
-
10
9
  class RuleStepUsesPinned(Rule):
11
10
  """Rule to contain the enforcement logic for pinning Actions versions.
12
11
 
@@ -0,0 +1,110 @@
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
+ class RuleUnderscoreOutputs(Rule):
12
+ """Rule to enforce all GitHub 'outputs' more than one words contain an underscore.
13
+
14
+ A simple standard to ensure uniformity in naming.
15
+ """
16
+
17
+ def __init__(self, settings: Optional[Settings] = None) -> None:
18
+ """Constructor for RuleUnderscoreOutputs to override the Rule class.
19
+
20
+ Args:
21
+ settings:
22
+ A Settings object that contains any default, overridden, or custom settings
23
+ required anywhere in the application.
24
+ """
25
+ self.message = "outputs with more than one word must use an underscore"
26
+ self.on_fail = LintLevels.WARNING
27
+ self.compatibility = [Workflow, Job, Step]
28
+ self.settings = settings
29
+
30
+ def fn(self, obj: Union[Workflow, Job, Step]) -> Tuple[bool, str]:
31
+ """Enforces all outputs to have an underscore in the key name.
32
+
33
+ This Rule checks all outputs in a Workflow, Job, or Step to ensure that
34
+ the key name contains an underscore. This is to ensure that the naming
35
+ convention is consistent across all outputs in the workflow configuration
36
+
37
+ Example:
38
+ ---
39
+ on:
40
+ workflow_dispatch:
41
+ outputs:
42
+ registry:
43
+ value: 'Test Value'
44
+ some_registry:
45
+ value: 'Test Value'
46
+ workflow_call:
47
+ outputs:
48
+ registry:
49
+ value: 'Test Value'
50
+ some_registry:
51
+ value: 'Test Value'
52
+ jobs:
53
+ job-key:
54
+ runs-on: ubuntu-22.04
55
+ outputs:
56
+ test_key_job: ${{ steps.test_output_1.outputs.test_key }}
57
+ steps:
58
+ - name: Test output in one-line run step
59
+ id: test_output_1
60
+ run: echo "test_key_1=Test-Value1" >> $GITHUB_OUTPUT
61
+
62
+ - name: Test output in multi-line run step
63
+ id: test_output_2
64
+ run: |
65
+ echo
66
+ fake-command=Test-Value2
67
+ echo "test_key_2=$REF" >> $GITHUB_OUTPUT
68
+ echo "deployed_ref=$DEPLOYED_REF" >> $GITHUB_OUTPUT
69
+
70
+ - name: Test step with no run
71
+ id: test_output_3
72
+ uses: actions/checkout@v2
73
+ with:
74
+ ref: ${{ github.ref }}
75
+ fetch-depth: 0
76
+
77
+ In this example, in workflow level 'registry' and 'some_registry' are outputs
78
+ that satisfy the rule in both 'workflow_dispatch' and 'workflow_call' events.
79
+ In job level 'test_key_job' satisfies the rule.
80
+ In step level 'test_key_1', 'test_key_2', and 'deployed_ref' satisfy the rule.
81
+
82
+ See tests/rules/test_underscore_outputs.py for incorrect examples.
83
+ """
84
+
85
+ outputs = []
86
+
87
+ if isinstance(obj, Workflow):
88
+ if obj.on.get("workflow_dispatch"):
89
+ outputs.extend(obj.on["workflow_dispatch"]["outputs"].keys())
90
+
91
+ if obj.on.get("workflow_call"):
92
+ outputs.extend(obj.on["workflow_call"]["outputs"].keys())
93
+
94
+ if isinstance(obj, Job):
95
+ if obj.outputs:
96
+ outputs.extend(obj.outputs.keys())
97
+
98
+ if isinstance(obj, Step):
99
+ if obj.run:
100
+ outputs.extend(re.findall(
101
+ r"\b([a-zA-Z0-9_-]+)\s*=\s*[^=]*>>\s*\$GITHUB_OUTPUT",
102
+ obj.run))
103
+
104
+ for output_name in outputs:
105
+ if "-" in output_name:
106
+ return False, (
107
+ f"Hyphen found in {obj.__class__.__name__} output: {output_name}"
108
+ )
109
+
110
+ 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.5
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
@@ -0,0 +1,27 @@
1
+ bitwarden_workflow_linter/__about__.py,sha256=ASh7ksrvZJZdX82GKysjxR3GTi0NRQuBQV1FKiLzJqQ,59
2
+ bitwarden_workflow_linter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ bitwarden_workflow_linter/actions.py,sha256=bjuFAP72QHXlDTmMBZffNmSCjZDzyNHGzqEC78ABAgg,7895
4
+ bitwarden_workflow_linter/cli.py,sha256=GABKYWYmcKl4LrA-cgo4ME0mWD4xaO4AgZHBeCCDMy4,1697
5
+ bitwarden_workflow_linter/default_actions.json,sha256=BeFllnAu9v6cX6eeGeNoQf3rXs7ZOjAtjTNBQuAZ8-k,8087
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
+ bitwarden_workflow_linter/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ bitwarden_workflow_linter/models/job.py,sha256=nBK7_VYu6RRST7WLtdLsoRErl5j4Er8W9hCw9XlSECk,2043
13
+ bitwarden_workflow_linter/models/step.py,sha256=1bKAtKZmHcO8O1e_HuoXxR1bwHDEXUssYo7EHOjY7QI,1711
14
+ bitwarden_workflow_linter/models/workflow.py,sha256=MkqvIY4JX2eWFODNTodS_l4I8uUq08WCHy3C4kYcL0s,1395
15
+ bitwarden_workflow_linter/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ bitwarden_workflow_linter/rules/job_environment_prefix.py,sha256=lAjvnn2PU1uVyHKRtJiehHmPHfQVaMNY-MLPPRuFvoY,2414
17
+ bitwarden_workflow_linter/rules/name_capitalized.py,sha256=VlxPpM82-vg2PlJGU5GWzdamYaz8DJ_AWuMgYzHNgsQ,1751
18
+ bitwarden_workflow_linter/rules/name_exists.py,sha256=eEPtU5y5T0aRQrICdiW1vju1Ny31VUCh_gOf6C6yFSI,1680
19
+ bitwarden_workflow_linter/rules/pinned_job_runner.py,sha256=hg8R6JDAAAhoiEgA9ihWhwLznIicbSw0knJvYI1TYEI,1605
20
+ bitwarden_workflow_linter/rules/step_approved.py,sha256=KhQTmqLkYjwZRZHspUwPBWXy1DExLoOQLp3sEoGM9qI,3306
21
+ bitwarden_workflow_linter/rules/step_pinned.py,sha256=BBCSbC-XyeB_ZILtA1q59NOB3IbY1QY8qpYt_gQ4YvM,3285
22
+ bitwarden_workflow_linter/rules/underscore_outputs.py,sha256=FiX2D27ywhDAcJV-_KsQj4-3YpaEGeF61_kDskX1EhQ,3842
23
+ bitwarden_workflow_linter-0.0.5.dist-info/METADATA,sha256=E4y-NDB1BvmjNP_ZahXGhdz3jbxJWP1b38dTttmdous,6172
24
+ bitwarden_workflow_linter-0.0.5.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
25
+ bitwarden_workflow_linter-0.0.5.dist-info/entry_points.txt,sha256=SA_yF9CwL4VMUvdcmCd7k9rjsQNzfeOUBuDnMnaO8QQ,60
26
+ bitwarden_workflow_linter-0.0.5.dist-info/licenses/LICENSE.txt,sha256=uY-7N9tbI7xc_c0WeTIGpacSCnsB91N05eCIg3bkaRw,35140
27
+ bitwarden_workflow_linter-0.0.5.dist-info/RECORD,,
@@ -1,26 +0,0 @@
1
- bitwarden_workflow_linter/__about__.py,sha256=KbbxYy_rLR2HkiKmhQYMcL4RRAhh7MG3ylAz_mbGpQo,59
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
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
11
- bitwarden_workflow_linter/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- bitwarden_workflow_linter/models/job.py,sha256=YXzs5CIIM1WEtzAk918QfIioqLcdr4vCOr3fs0uVEu0,1929
13
- bitwarden_workflow_linter/models/step.py,sha256=1bKAtKZmHcO8O1e_HuoXxR1bwHDEXUssYo7EHOjY7QI,1711
14
- bitwarden_workflow_linter/models/workflow.py,sha256=MkqvIY4JX2eWFODNTodS_l4I8uUq08WCHy3C4kYcL0s,1395
15
- bitwarden_workflow_linter/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- bitwarden_workflow_linter/rules/job_environment_prefix.py,sha256=QCYNj-sX5VB5v49OkBdmFGDkm1E2ebcfiAvlkDVNx7g,2415
17
- bitwarden_workflow_linter/rules/name_capitalized.py,sha256=3IXvE_vCZ097P7gtHbhQZlwi83Rt239iJfVjmgG26to,1752
18
- bitwarden_workflow_linter/rules/name_exists.py,sha256=MxcaNQz64JXeHRPiOip9BxJNgPdpKQa7Z51mDoNw2hU,1681
19
- bitwarden_workflow_linter/rules/pinned_job_runner.py,sha256=Dm6_sdPX0yFMji_y2LMFj4gWFaToEgauyBVpNRP2qiI,1606
20
- bitwarden_workflow_linter/rules/step_approved.py,sha256=UIi9_z9j75SpQUmo29MLDhjLklqd4h0D-UYqkdcaju0,3307
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,,