bitwarden_workflow_linter 0.5.5__tar.gz → 0.5.7__tar.gz

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.
Files changed (80) hide show
  1. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.github/workflows/cd.yml +4 -4
  2. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.github/workflows/ci.yml +1 -1
  3. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.github/workflows/scan.yml +2 -2
  4. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.github/workflows/update_actions.yml +2 -2
  5. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/PKG-INFO +1 -1
  6. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/settings.yaml +1 -0
  7. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/__about__.py +1 -1
  8. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/default_settings.yaml +1 -0
  9. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/models/job.py +9 -0
  10. bitwarden_workflow_linter-0.5.7/src/bitwarden_workflow_linter/rules/check_pr_target.py +81 -0
  11. bitwarden_workflow_linter-0.5.7/tests/rules/test_check_pr_target.py +276 -0
  12. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.editorconfig +0 -0
  13. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.gitattributes +0 -0
  14. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.github/CODEOWNERS +0 -0
  15. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  16. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  17. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.github/renovate.json +0 -0
  18. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.github/workflows/_version_type.yml +0 -0
  19. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.github/workflows/enforce-labels.yml +0 -0
  20. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.gitignore +0 -0
  21. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.husky/pre-commit +0 -0
  22. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/.python-version +0 -0
  23. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/CONTRIBUTING.md +0 -0
  24. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/LICENSE.txt +0 -0
  25. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/Pipfile +0 -0
  26. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/Pipfile.lock +0 -0
  27. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/README.md +0 -0
  28. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/SECURITY.md +0 -0
  29. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/Taskfile.yml +0 -0
  30. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/package-lock.json +0 -0
  31. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/package.json +0 -0
  32. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/pylintrc +0 -0
  33. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/pyproject.toml +0 -0
  34. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/pyproject.toml.tpl +0 -0
  35. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/__init__.py +0 -0
  36. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/actions.py +0 -0
  37. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/cli.py +0 -0
  38. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/default_actions.json +0 -0
  39. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/lint.py +0 -0
  40. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/load.py +0 -0
  41. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/models/__init__.py +0 -0
  42. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/models/step.py +0 -0
  43. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/models/workflow.py +0 -0
  44. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/rule.py +0 -0
  45. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/rules/__init__.py +0 -0
  46. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/rules/job_environment_prefix.py +0 -0
  47. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/rules/name_capitalized.py +0 -0
  48. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/rules/name_exists.py +0 -0
  49. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/rules/pinned_job_runner.py +0 -0
  50. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/rules/run_actionlint.py +0 -0
  51. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/rules/step_approved.py +0 -0
  52. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/rules/step_pinned.py +0 -0
  53. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/rules/underscore_outputs.py +0 -0
  54. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/src/bitwarden_workflow_linter/utils.py +0 -0
  55. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/__init__.py +0 -0
  56. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/conftest.py +0 -0
  57. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/fixtures/test-alt.yml +0 -0
  58. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/fixtures/test-min-incorrect.yaml +0 -0
  59. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/fixtures/test-min.yaml +0 -0
  60. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/fixtures/test-outputs-incorrect.yml +0 -0
  61. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/fixtures/test.yml +0 -0
  62. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/fixtures/test_a.yaml +0 -0
  63. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/fixtures/test_workflow.yaml +0 -0
  64. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/fixtures/test_workflow_incorrect.yaml +0 -0
  65. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/rules/__init__.py +0 -0
  66. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/rules/test_job_environment_prefix.py +0 -0
  67. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/rules/test_name_capitalized.py +0 -0
  68. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/rules/test_name_exists.py +0 -0
  69. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/rules/test_pinned_job_runner.py +0 -0
  70. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/rules/test_run_actionlint.py +0 -0
  71. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/rules/test_step_approved.py +0 -0
  72. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/rules/test_step_pinned.py +0 -0
  73. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/rules/test_underscore_output.py +0 -0
  74. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/test_job.py +0 -0
  75. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/test_lint.py +0 -0
  76. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/test_load.py +0 -0
  77. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/test_rule.py +0 -0
  78. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/test_step.py +0 -0
  79. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/test_utils.py +0 -0
  80. {bitwarden_workflow_linter-0.5.5 → bitwarden_workflow_linter-0.5.7}/tests/test_workflow.py +0 -0
@@ -24,7 +24,7 @@ jobs:
24
24
  version: ${{ steps.get-version.outputs.version }}
25
25
  steps:
26
26
  - name: Generate GH App token
27
- uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
27
+ uses: actions/create-github-app-token@67e27a7eb7db372a1c61a7f9bdab8699e9ee57f7 # v1.11.3
28
28
  id: app-token
29
29
  with:
30
30
  app-id: ${{ secrets.BW_GHAPP_ID }}
@@ -50,7 +50,7 @@ jobs:
50
50
  token: ${{ steps.app-token.outputs.token }}
51
51
 
52
52
  - name: Set up Python
53
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
53
+ uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
54
54
  with:
55
55
  python-version-file: ".python-version"
56
56
 
@@ -101,7 +101,7 @@ jobs:
101
101
  uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
102
102
 
103
103
  - name: Create GitHub release
104
- uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0
104
+ uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0
105
105
  with:
106
106
  commit: ${{ github.sha }}
107
107
  tag: v${{ needs.version-bump.outputs.version }}
@@ -118,7 +118,7 @@ jobs:
118
118
  uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
119
119
 
120
120
  - name: Set up Python
121
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
121
+ uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
122
122
  with:
123
123
  python-version-file: ".python-version"
124
124
 
@@ -16,7 +16,7 @@ jobs:
16
16
  uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
17
17
 
18
18
  - name: Set up Python
19
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
19
+ uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
20
20
  with:
21
21
  python-version-file: ".python-version"
22
22
 
@@ -31,7 +31,7 @@ jobs:
31
31
  ref: ${{ github.event.pull_request.head.sha }}
32
32
 
33
33
  - name: Scan with Checkmarx
34
- uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41
34
+ uses: checkmarx/ast-github-action@9fda4ab4c1b67c35de380552a972a82997d97731 # 2.0.42
35
35
  env:
36
36
  INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
37
37
  with:
@@ -46,7 +46,7 @@ jobs:
46
46
  --output-path . ${{ env.INCREMENTAL }}
47
47
 
48
48
  - name: Upload Checkmarx results to GitHub
49
- uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
49
+ uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
50
50
  with:
51
51
  sarif_file: cx_result.sarif
52
52
 
@@ -39,7 +39,7 @@ jobs:
39
39
  uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
40
40
 
41
41
  - name: Set up Python 3.11
42
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
42
+ uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
43
43
  with:
44
44
  python-version: "3.11"
45
45
 
@@ -76,7 +76,7 @@ jobs:
76
76
 
77
77
  - name: Generate GH App token
78
78
  if: steps.new-changes.outputs.new_changes == 'TRUE'
79
- uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
79
+ uses: actions/create-github-app-token@67e27a7eb7db372a1c61a7f9bdab8699e9ee57f7 # v1.11.3
80
80
  id: app-token
81
81
  with:
82
82
  app-id: ${{ secrets.GH_APP_ID }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bitwarden_workflow_linter
3
- Version: 0.5.5
3
+ Version: 0.5.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
@@ -7,5 +7,6 @@ enabled_rules:
7
7
  - bitwarden_workflow_linter.rules.step_pinned.RuleStepUsesPinned
8
8
  - bitwarden_workflow_linter.rules.underscore_outputs.RuleUnderscoreOutputs
9
9
  - bitwarden_workflow_linter.rules.run_actionlint.RunActionlint
10
+ - bitwarden_workflow_linter.rules.check_pr_target.RuleCheckPrTarget
10
11
 
11
12
  approved_actions_path: default_actions.json
@@ -1,3 +1,3 @@
1
1
  """Metadata for Workflow Linter."""
2
2
 
3
- __version__ = "0.5.5"
3
+ __version__ = "0.5.7"
@@ -7,5 +7,6 @@ enabled_rules:
7
7
  - bitwarden_workflow_linter.rules.step_pinned.RuleStepUsesPinned
8
8
  - bitwarden_workflow_linter.rules.underscore_outputs.RuleUnderscoreOutputs
9
9
  - bitwarden_workflow_linter.rules.run_actionlint.RunActionlint
10
+ - bitwarden_workflow_linter.rules.check_pr_target.RuleCheckPrTarget
10
11
 
11
12
  approved_actions_path: default_actions.json
@@ -23,6 +23,7 @@ class Job:
23
23
  key: Optional[str] = None
24
24
  name: Optional[str] = None
25
25
  env: Optional[CommentedMap] = None
26
+ needs: Optional[List[str]] = None
26
27
  steps: Optional[List[Step]] = None
27
28
  uses: Optional[str] = None
28
29
  uses_path: Optional[str] = None
@@ -32,6 +33,13 @@ class Job:
32
33
  )
33
34
  outputs: Optional[CommentedMap] = None
34
35
 
36
+ @classmethod
37
+ def parse_needs(cls: Self, value):
38
+ """Parser to make all needs values lists that can be searched by linter."""
39
+ if isinstance(value, str):
40
+ return [value]
41
+ return value
42
+
35
43
  @classmethod
36
44
  def init(cls: Self, key: str, data: CommentedMap) -> Self:
37
45
  """Custom dataclass constructor to map job data to a Job."""
@@ -40,6 +48,7 @@ class Job:
40
48
  "name": data["name"] if "name" in data else None,
41
49
  "runs-on": data["runs-on"] if "runs-on" in data else None,
42
50
  "env": data["env"] if "env" in data else None,
51
+ "needs": Job.parse_needs(data["needs"]) if "needs" in data else None,
43
52
  "outputs": data["outputs"] if "outputs" in data else None,
44
53
  }
45
54
 
@@ -0,0 +1,81 @@
1
+ """A Rule to enforce check-run is run when workflow uses pull_request_target."""
2
+
3
+ from typing import Optional, Tuple
4
+
5
+ from ..models.workflow import Workflow
6
+ from ..rule import Rule
7
+ from ..utils import LintLevels, Settings
8
+
9
+
10
+ class RuleCheckPrTarget(Rule):
11
+ def __init__(self, settings: Optional[Settings] = None) -> None:
12
+ """
13
+ To ensure pull_request_target is safe to use, the check-run step is added
14
+ to all jobs as a dependency.
15
+
16
+ Once a branch is pushed to Github, it already opens up a vulnerability
17
+ even if the check-run scan fails to detect this.
18
+
19
+ In order to prevent a vulnerable branch from being used for an attack
20
+ prior to being caught through vetting, all pull_request_target workflows
21
+ should only be run by users with appropriate permissions.
22
+
23
+ This greatly reduces the risk as community contributors can't use a fork to run a compromised workflow that uses pull_request_target.
24
+ """
25
+ self.message = "A check-run job must be included as a direct job dependency when pull_request_target is used and the workflow may only apply to runs on the main branch"
26
+ self.on_fail = LintLevels.WARNING
27
+ self.compatibility = [Workflow]
28
+ self.settings = settings
29
+
30
+ def targets_main_branch(self, obj:Workflow) -> bool:
31
+ if obj.on["pull_request_target"].get("branches"):
32
+ branches_list = obj.on["pull_request_target"].get("branches")
33
+ if isinstance(branches_list, str):
34
+ branches_list = [branches_list]
35
+ if any(branch != 'main' for branch in branches_list):
36
+ return False
37
+ else:
38
+ return False
39
+ return True
40
+
41
+ def has_check_run(self, obj: Workflow) -> Tuple[bool, str]:
42
+ for name, job in obj.jobs.items():
43
+ if job.uses == "bitwarden/gh-actions/.github/workflows/check-run.yml@main":
44
+ return True, name
45
+ return False, ""
46
+
47
+ def check_run_required(self, obj:Workflow, check_job:str) -> list:
48
+ missing_jobs = []
49
+ for job in list(obj.jobs.keys()):
50
+ if job is not check_job:
51
+ job_list = obj.jobs[job].needs
52
+ if job_list:
53
+ if check_job not in job_list:
54
+ missing_jobs.append(job)
55
+ else:
56
+ missing_jobs.append(job)
57
+ return missing_jobs
58
+
59
+ def fn(self, obj: Workflow) -> Tuple[bool, str]:
60
+ Errors = []
61
+ if obj.on.get("pull_request_target"):
62
+ result, check_job = self.has_check_run(obj)
63
+ main_branch_only = self.targets_main_branch(obj)
64
+ if not main_branch_only:
65
+ Errors.append("Workflows using pull_request_target can only target the main branch")
66
+ if result:
67
+ missing_jobs = self.check_run_required(obj, check_job)
68
+ if missing_jobs:
69
+ job_list = ', '.join(missing_jobs)
70
+ message = f"Check-run is missing from the following jobs in the workflow: {job_list}"
71
+ Errors.append(message)
72
+ else:
73
+ message = f"A check-run job must be included as a direct job dependency when pull_request_target is used"
74
+ Errors.append(message)
75
+ if Errors:
76
+ self.message = "\n".join(Errors)
77
+ return False, f"{self.message}"
78
+ else:
79
+ return True, ""
80
+ else:
81
+ return True, ""
@@ -0,0 +1,276 @@
1
+ """Test src/bitwarden_workflow_linter/rules/check_pr_target."""
2
+
3
+ import pytest
4
+
5
+ from ruamel.yaml import YAML
6
+
7
+ from src.bitwarden_workflow_linter.load import WorkflowBuilder
8
+ from src.bitwarden_workflow_linter.rules.check_pr_target import (
9
+ RuleCheckPrTarget,
10
+ )
11
+
12
+ yaml = YAML()
13
+ message = "A check-run job must be included as a direct job dependency when pull_request_target is used and the workflow may only apply to runs on the main branch"
14
+
15
+ @pytest.fixture(name="correct_workflow")
16
+ def fixture_correct_workflow():
17
+ workflow = """\
18
+ ---
19
+ on:
20
+ workflow_dispatch:
21
+ pull_request_target:
22
+ types: [opened, synchronize]
23
+ branches:
24
+ - 'main'
25
+
26
+ jobs:
27
+ check-run:
28
+ name: Check PR run
29
+ uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
30
+
31
+ quality:
32
+ name: Quality scan
33
+ needs: check-run
34
+ steps:
35
+ - run: echo test
36
+
37
+ dependent-job:
38
+ name: Another Dependent Job
39
+ needs:
40
+ - quality
41
+ - check-run
42
+ steps:
43
+ - run: echo another dependent job
44
+ """
45
+ return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False)
46
+
47
+ @pytest.fixture(name="no_check_workflow")
48
+ def fixture_no_check_workflow():
49
+ workflow = """\
50
+ ---
51
+ on:
52
+ workflow_dispatch:
53
+ pull_request_target:
54
+ types: [opened, synchronize]
55
+ branches:
56
+ - 'main'
57
+
58
+ jobs:
59
+ job-key:
60
+ runs-on: ubuntu-22.04
61
+ steps:
62
+ - run: echo test
63
+ """
64
+ return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False)
65
+
66
+ @pytest.fixture(name="no_needs_workflow")
67
+ def fixture_no_needs_workflow():
68
+ workflow = """\
69
+ ---
70
+ on:
71
+ workflow_dispatch:
72
+ pull_request_target:
73
+ types: [opened, synchronize]
74
+ branches: main
75
+
76
+ jobs:
77
+ check-run:
78
+ name: Check PR run
79
+ uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
80
+
81
+ job-key:
82
+ runs-on: ubuntu-22.04
83
+ steps:
84
+ - run: echo test
85
+
86
+ quality:
87
+ name: Quality scan
88
+ steps:
89
+ - run: echo test
90
+
91
+ """
92
+ return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False)
93
+
94
+ @pytest.fixture(name="no_target_workflow")
95
+ def fixture_no_target_workflow():
96
+ workflow = """\
97
+ ---
98
+ on:
99
+ workflow_dispatch:
100
+
101
+ jobs:
102
+ job-key:
103
+ runs-on: ubuntu-22.04
104
+ steps:
105
+ - run: echo test
106
+ """
107
+ return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False)
108
+
109
+ @pytest.fixture(name="dependent_missing_check_workflow")
110
+ def dependent_missing_check_workflow():
111
+ workflow = """\
112
+ ---
113
+ on:
114
+ workflow_dispatch:
115
+ pull_request_target:
116
+ types: [opened, synchronize]
117
+ branches:
118
+ - 'main'
119
+
120
+ jobs:
121
+ check-run:
122
+ name: Check PR run
123
+ uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
124
+
125
+ quality:
126
+ name: Quality scan
127
+ needs: check-run
128
+ steps:
129
+ - run: echo test
130
+
131
+ dependent-job:
132
+ name: Another Dependent Job
133
+ needs:
134
+ - quality
135
+ steps:
136
+ - run: echo another dependent job
137
+ """
138
+ return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False)
139
+
140
+ @pytest.fixture(name="no_branches_workflow")
141
+ def fixture_no_branches_workflow():
142
+ workflow = """\
143
+ ---
144
+ on:
145
+ workflow_dispatch:
146
+ pull_request_target:
147
+ types: [opened, synchronize]
148
+
149
+ jobs:
150
+ check-run:
151
+ name: Check PR run
152
+ uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
153
+
154
+ quality:
155
+ name: Quality scan
156
+ needs: check-run
157
+ steps:
158
+ - run: echo test
159
+
160
+ dependent-job:
161
+ name: Another Dependent Job
162
+ needs:
163
+ - quality
164
+ - check-run
165
+ steps:
166
+ - run: echo another dependent job
167
+ """
168
+ return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False)
169
+
170
+ @pytest.fixture(name="bad_branches_workflow")
171
+ def fixture_bad_branches_workflow():
172
+ workflow = """\
173
+ ---
174
+ on:
175
+ workflow_dispatch:
176
+ pull_request_target:
177
+ types: [opened, synchronize]
178
+ branches:
179
+ - 'main'
180
+ - 'not main'
181
+
182
+ jobs:
183
+ check-run:
184
+ name: Check PR run
185
+ uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
186
+
187
+ quality:
188
+ name: Quality scan
189
+ needs: check-run
190
+ steps:
191
+ - run: echo test
192
+
193
+ dependent-job:
194
+ name: Another Dependent Job
195
+ needs:
196
+ - quality
197
+ - check-run
198
+ steps:
199
+ - run: echo another dependent job
200
+ """
201
+ return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False)
202
+
203
+ @pytest.fixture(name="two_failures_workflow")
204
+ def fixture_two_failures_workflow():
205
+ workflow = """\
206
+ ---
207
+ on:
208
+ workflow_dispatch:
209
+ pull_request_target:
210
+ types: [opened, synchronize]
211
+
212
+ jobs:
213
+ check-run:
214
+ name: Check PR run
215
+ uses: bitwarden/some-other-repo/.github/workflows/check-run.yml@main
216
+
217
+ quality:
218
+ name: Quality scan
219
+ needs: check-run
220
+ steps:
221
+ - run: echo test
222
+
223
+ dependent-job:
224
+ name: Another Dependent Job
225
+ needs:
226
+ - quality
227
+ - check-run
228
+ steps:
229
+ - run: echo another dependent job
230
+ """
231
+ return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False)
232
+
233
+ @pytest.fixture(name="rule")
234
+ def fixture_rule():
235
+ return RuleCheckPrTarget()
236
+
237
+ def test_rule_on_correct_workflow(rule, correct_workflow):
238
+ result, message = rule.fn(correct_workflow)
239
+ assert result is True
240
+ assert message == ""
241
+
242
+
243
+ def test_rule_on_no_checkworkflow(rule, no_check_workflow):
244
+ result, message = rule.fn(no_check_workflow)
245
+ assert result is False
246
+ assert message == message
247
+
248
+ def test_rule_on_no_target_workflow(rule, no_target_workflow):
249
+ result, message = rule.fn(no_target_workflow)
250
+ assert result is True
251
+ assert message == ""
252
+
253
+ def test_rule_on_jobs_without_needs(rule, no_needs_workflow):
254
+ result, message = rule.fn(no_needs_workflow)
255
+ assert result is False
256
+ assert message == message, "check-run is missing from the following jobs in the workflow: quality"
257
+
258
+ def test_rule_on_dependencies_without_check(rule, dependent_missing_check_workflow):
259
+ result, message = rule.fn(dependent_missing_check_workflow)
260
+ assert result is False
261
+ assert message == message, "check-run is missing from the following jobs in the workflow: dependent-job"
262
+
263
+ def test_rule_on_no_branches_workflow(rule, no_branches_workflow):
264
+ result, message = rule.fn(no_branches_workflow)
265
+ assert result is False
266
+ assert message == "Workflows using pull_request_target can only target the main branch"
267
+
268
+ def test_rule_on_only_target_main(rule, bad_branches_workflow):
269
+ result, message = rule.fn(bad_branches_workflow)
270
+ assert result is False
271
+ assert message == "Workflows using pull_request_target can only target the main branch"
272
+
273
+ def test_rule_on_two_failures(rule, two_failures_workflow):
274
+ result, message = rule.fn(two_failures_workflow)
275
+ assert result is False
276
+ assert message == "Workflows using pull_request_target can only target the main branch\nA check-run job must be included as a direct job dependency when pull_request_target is used"