bitwarden_workflow_linter 0.4.6__py3-none-any.whl → 0.4.7__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.4.6"
3
+ __version__ = "0.4.7"
@@ -6,5 +6,6 @@ enabled_rules:
6
6
  - bitwarden_workflow_linter.rules.step_approved.RuleStepUsesApproved
7
7
  - bitwarden_workflow_linter.rules.step_pinned.RuleStepUsesPinned
8
8
  - bitwarden_workflow_linter.rules.underscore_outputs.RuleUnderscoreOutputs
9
+ - bitwarden_workflow_linter.rules.run_actionlint.RunActionlint
9
10
 
10
11
  approved_actions_path: default_actions.json
@@ -39,20 +39,32 @@ class WorkflowBuilder:
39
39
  objects (depending on their location in the file).
40
40
  """
41
41
  with open(filename, encoding="utf8") as file:
42
- return yaml.load(file)
42
+ if not file:
43
+ raise WorkflowBuilderError(f"Could not load {filename}")
44
+ try:
45
+ return yaml.load(file)
46
+ except Exception as e:
47
+ raise WorkflowBuilderError(f"Error loading YAML file {filename}: {e}")
43
48
 
44
49
  @classmethod
45
- def __build_workflow(cls, loaded_yaml: CommentedMap) -> Workflow:
50
+ def __build_workflow(cls, filename: str, loaded_yaml: CommentedMap) -> Workflow:
46
51
  """Parse the YAML and build out the workflow to run Rules against.
47
52
 
48
53
  Args:
54
+ filename:
55
+ The name of the file that the YAML was loaded from
49
56
  loaded_yaml:
50
57
  YAML that was loaded from either code or a file
51
58
 
52
59
  Returns
53
60
  A Workflow to run linting Rules against
54
61
  """
55
- return Workflow.init("", loaded_yaml)
62
+ try:
63
+ if not loaded_yaml:
64
+ raise WorkflowBuilderError("No YAML loaded")
65
+ return Workflow.init("", filename, loaded_yaml)
66
+ except Exception as e:
67
+ raise WorkflowBuilderError(f"Error building workflow: {e}")
56
68
 
57
69
  @classmethod
58
70
  def build(
@@ -76,9 +88,11 @@ class WorkflowBuilder:
76
88
  be loaded from disk
77
89
  """
78
90
  if from_file and filename is not None:
79
- return cls.__build_workflow(cls.__load_workflow_from_file(filename))
91
+ return cls.__build_workflow(
92
+ filename, cls.__load_workflow_from_file(filename)
93
+ )
80
94
  elif not from_file and workflow is not None:
81
- return cls.__build_workflow(workflow)
95
+ return cls.__build_workflow("", workflow)
82
96
 
83
97
  raise WorkflowBuilderError(
84
98
  "The workflow must either be built from a file or from a CommentedMap"
@@ -23,14 +23,16 @@ class Workflow:
23
23
  """
24
24
 
25
25
  key: str = ""
26
+ filename: Optional[str] = None
26
27
  name: Optional[str] = None
27
28
  on: Optional[CommentedMap] = None
28
29
  jobs: Optional[Dict[str, Job]] = None
29
30
 
30
31
  @classmethod
31
- def init(cls: Self, key: str, data: CommentedMap) -> Self:
32
+ def init(cls: Self, key: str, filename: str, data: CommentedMap) -> Self:
32
33
  init_data = {
33
34
  "key": key,
35
+ "filename": filename,
34
36
  "name": data["name"] if "name" in data else None,
35
37
  "on": data["on"] if "on" in data else None,
36
38
  }
@@ -0,0 +1,108 @@
1
+ """A Rule to run actionlint on workflows."""
2
+
3
+ from typing import Optional, Tuple
4
+ import subprocess
5
+ import platform
6
+ import urllib.request
7
+ import os
8
+
9
+ from ..rule import Rule
10
+ from ..models.workflow import Workflow
11
+ from ..utils import LintLevels, Settings
12
+
13
+
14
+ def install_actionlint(platform_system: str) -> Tuple[bool, str]:
15
+ """If actionlint is not installed, detects OS platform
16
+ and installs actionlint"""
17
+
18
+ error = f"An error occurred when installing Actionlint on {platform_system}"
19
+
20
+ if platform_system.startswith("Linux"):
21
+ return install_actionlint_source(error)
22
+ elif platform_system == "Darwin":
23
+ try:
24
+ subprocess.run(["brew", "install", "actionlint"], check=True)
25
+ return True, ""
26
+ except (FileNotFoundError, subprocess.CalledProcessError):
27
+ return False, f"{error} : check Brew installation"
28
+ elif platform_system.startswith("Win"):
29
+ try:
30
+ subprocess.run(["choco", "install", "actionlint", "-y"], check=True)
31
+ return True, ""
32
+ except (FileNotFoundError, subprocess.CalledProcessError):
33
+ return False, f"{error} : check Choco installation"
34
+ return False, error
35
+
36
+
37
+ def install_actionlint_source(error) -> Tuple[bool, str]:
38
+ """Install Actionlint Binary from provided script"""
39
+ url = "https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash"
40
+ version = "1.6.17"
41
+ request = urllib.request.urlopen(url)
42
+ with open("download-actionlint.bash", "wb+") as fp:
43
+ fp.write(request.read())
44
+ try:
45
+ subprocess.run(["bash", "download-actionlint.bash", version], check=True)
46
+ return True, os.getcwd()
47
+ except (FileNotFoundError, subprocess.CalledProcessError):
48
+ return False, error
49
+
50
+
51
+ def check_actionlint(platform_system: str) -> Tuple[bool, str]:
52
+ """Check if the actionlint is in the system's PATH."""
53
+ try:
54
+ subprocess.run(
55
+ ["actionlint", "--version"],
56
+ stdout=subprocess.PIPE,
57
+ stderr=subprocess.PIPE,
58
+ check=True,
59
+ )
60
+ return True, ""
61
+ except subprocess.CalledProcessError:
62
+ return (
63
+ False,
64
+ "Failed to install Actionlint, \
65
+ please check your package installer or manually install it",
66
+ )
67
+ except FileNotFoundError:
68
+ return install_actionlint(platform_system)
69
+
70
+
71
+ class RunActionlint(Rule):
72
+ """Rule to run actionlint as part of workflow linter V2."""
73
+
74
+ def __init__(self, settings: Optional[Settings] = None) -> None:
75
+ self.message = "Actionlint must pass without errors"
76
+ self.on_fail = LintLevels.WARNING
77
+ self.compatibility = [Workflow]
78
+ self.settings = settings
79
+
80
+ def fn(self, obj: Workflow) -> Tuple[bool, str]:
81
+ if not obj or not obj.filename:
82
+ raise AttributeError(
83
+ "Running actionlint without a filename is not currently supported"
84
+ )
85
+
86
+ installed, location = check_actionlint(platform.system())
87
+ if installed:
88
+ if location:
89
+ result = subprocess.run(
90
+ [location + "/actionlint", obj.filename],
91
+ capture_output=True,
92
+ text=True,
93
+ check=False,
94
+ )
95
+ else:
96
+ result = subprocess.run(
97
+ ["actionlint", obj.filename],
98
+ capture_output=True,
99
+ text=True,
100
+ check=False,
101
+ )
102
+ if result.returncode == 1:
103
+ return False, result.stdout
104
+ if result.returncode > 1:
105
+ return False, result.stdout
106
+ return True, ""
107
+ else:
108
+ return False, self.message
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bitwarden_workflow_linter
3
- Version: 0.4.6
3
+ Version: 0.4.7
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
@@ -27,9 +27,8 @@ Description-Content-Type: text/markdown
27
27
  # Bitwarden Workflow Linter
28
28
 
29
29
  Bitwarden's Workflow Linter is an extensible linter to apply opinionated organization-specific
30
- GitHub Action standards. It was designed to be used alongside tools like
31
- [action-lint](https://github.com/rhysd/actionlint) and
32
- [yamllint](https://github.com/adrienverge/yamllint) to check for correct Action syntax and enforce
30
+ GitHub Action standards. It was designed to be used alongside
31
+ [yamllint](https://github.com/adrienverge/yamllint) to enforce
33
32
  specific YAML standards.
34
33
 
35
34
  To see an example of Workflow Linter in practice in GitHub Action, see the
@@ -90,6 +89,8 @@ options:
90
89
 
91
90
  - Python 3.11
92
91
  - pipenv
92
+ - Windows systems: Chocolatey package manager
93
+ - Mac OS systems: Homebrew package manager
93
94
 
94
95
  ### Setup
95
96
 
@@ -1,27 +1,28 @@
1
- bitwarden_workflow_linter/__about__.py,sha256=BUP3uYgmxDtpHKM-iXpP6XKxkDO878eATLQUvfqlFJc,59
1
+ bitwarden_workflow_linter/__about__.py,sha256=4LUUps2UFYsKpd7EPemg4Nk5kmr3e5YPodE4ravFBzE,59
2
2
  bitwarden_workflow_linter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  bitwarden_workflow_linter/actions.py,sha256=LAn3yQeMMmCOvJWeTn3dE1U2nyEJqIBMwESq3TtY9hE,9069
4
4
  bitwarden_workflow_linter/cli.py,sha256=wgkK1MlVbo6Zx3f2CZZ_tkSWq_hdsGciHJA1knX6Yuw,1699
5
5
  bitwarden_workflow_linter/default_actions.json,sha256=hqbAs08CGQF41tzzBzpEcQAx1OMK-r7KgEdWEUXZUzU,12022
6
- bitwarden_workflow_linter/default_settings.yaml,sha256=2VwOcB0g3v4A2Kt2UgGFM0TSpfd6S0oNA0SkTa5tGJA,576
6
+ bitwarden_workflow_linter/default_settings.yaml,sha256=3GgDYOgqZ13wMnEtDZUjgibQkf5xYzbcSfvOG7Yim1s,641
7
7
  bitwarden_workflow_linter/lint.py,sha256=RDHv5jGeGCf5XIHE8jyqQET3-cFykl7223SQVS4Q3pg,5525
8
- bitwarden_workflow_linter/load.py,sha256=Ece2bwSSYeQ1xQQEjjqY6DlCkwznFYLG56VW_VTxU4E,4472
8
+ bitwarden_workflow_linter/load.py,sha256=XXbja-sVUtXaJjqF9sTP2xzFto3CXcCuEBNFp3Yo4VM,5078
9
9
  bitwarden_workflow_linter/rule.py,sha256=Qb60JiUDAWN3ayrMGoSbbDCSFmw-ql8djzAkxISaob4,3250
10
10
  bitwarden_workflow_linter/utils.py,sha256=9WO3T9w9vKAow_xR6JDz2MvdMXKExV18AzucF0vh67s,4695
11
11
  bitwarden_workflow_linter/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  bitwarden_workflow_linter/models/job.py,sha256=nBK7_VYu6RRST7WLtdLsoRErl5j4Er8W9hCw9XlSECk,2043
13
13
  bitwarden_workflow_linter/models/step.py,sha256=j81iWYWcNI9x55n1MOR0N6ogKaQ_4-CKu9LnI_fwEOE,1814
14
- bitwarden_workflow_linter/models/workflow.py,sha256=MkqvIY4JX2eWFODNTodS_l4I8uUq08WCHy3C4kYcL0s,1395
14
+ bitwarden_workflow_linter/models/workflow.py,sha256=Jr1CJSCpII9Z4RT6Sh1wjzZU6ov3fZt45BmTrfLNLLE,1479
15
15
  bitwarden_workflow_linter/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  bitwarden_workflow_linter/rules/job_environment_prefix.py,sha256=sY1cBU5AeBHWSyun7gwnoS0ycRyBMjjVo_2lvanBj7U,2612
17
17
  bitwarden_workflow_linter/rules/name_capitalized.py,sha256=quuqXM_qg93UE8mQo1YQp8cQ_Fx6c2u03_19s_c0ntw,1981
18
18
  bitwarden_workflow_linter/rules/name_exists.py,sha256=MxcaNQz64JXeHRPiOip9BxJNgPdpKQa7Z51mDoNw2hU,1681
19
19
  bitwarden_workflow_linter/rules/pinned_job_runner.py,sha256=Dm6_sdPX0yFMji_y2LMFj4gWFaToEgauyBVpNRP2qiI,1606
20
+ bitwarden_workflow_linter/rules/run_actionlint.py,sha256=qzfrBUcvNMkeBKGMKkIyqRaRV18dUREcuhZlZHxjgkk,3762
20
21
  bitwarden_workflow_linter/rules/step_approved.py,sha256=6XuYoasw2ME8vQu5G0ZygUSi7X5amLLWeXH81cqvKv8,3159
21
22
  bitwarden_workflow_linter/rules/step_pinned.py,sha256=fyqBjarR0UNQ6tU_ja0ZOi2afP942BMqOz5nU_yKzmw,3413
22
23
  bitwarden_workflow_linter/rules/underscore_outputs.py,sha256=w8pP1dTJEC9I2X5fQIAHDAEiaNP1xMhb4kPiF-dn8U0,4131
23
- bitwarden_workflow_linter-0.4.6.dist-info/METADATA,sha256=LePrSQB5F-E9EVIS-ZSdpGwcI9Ekgqi6ASZ8vpfl4HQ,6173
24
- bitwarden_workflow_linter-0.4.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
- bitwarden_workflow_linter-0.4.6.dist-info/entry_points.txt,sha256=SA_yF9CwL4VMUvdcmCd7k9rjsQNzfeOUBuDnMnaO8QQ,60
26
- bitwarden_workflow_linter-0.4.6.dist-info/licenses/LICENSE.txt,sha256=uY-7N9tbI7xc_c0WeTIGpacSCnsB91N05eCIg3bkaRw,35140
27
- bitwarden_workflow_linter-0.4.6.dist-info/RECORD,,
24
+ bitwarden_workflow_linter-0.4.7.dist-info/METADATA,sha256=Ce2YVKpqB7ZJtlqK0AYkugPCJMiWe0bFXrfJrorqS7U,6164
25
+ bitwarden_workflow_linter-0.4.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ bitwarden_workflow_linter-0.4.7.dist-info/entry_points.txt,sha256=SA_yF9CwL4VMUvdcmCd7k9rjsQNzfeOUBuDnMnaO8QQ,60
27
+ bitwarden_workflow_linter-0.4.7.dist-info/licenses/LICENSE.txt,sha256=uY-7N9tbI7xc_c0WeTIGpacSCnsB91N05eCIg3bkaRw,35140
28
+ bitwarden_workflow_linter-0.4.7.dist-info/RECORD,,