Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0a8892f
tests and no test functions
oycyc Aug 6, 2024
eafa1b6
testing this out
oycyc Aug 6, 2024
2ef04f4
testing this out
oycyc Aug 6, 2024
ed02d44
testing this out
oycyc Aug 6, 2024
437d3d8
testing this out
oycyc Aug 6, 2024
ba022c4
testing this out
oycyc Aug 6, 2024
01b5cc3
testing this out
oycyc Aug 6, 2024
c10eca6
testing this out
oycyc Aug 6, 2024
d1e1066
testing this out
oycyc Aug 6, 2024
d9dc58d
testing this out
oycyc Aug 6, 2024
00dbd46
testing this out
oycyc Aug 6, 2024
feb1a61
testing this out
oycyc Aug 6, 2024
f0354d1
testing this out
oycyc Aug 6, 2024
427e47f
testing this out
oycyc Aug 6, 2024
bcc569c
testing this out
oycyc Aug 6, 2024
009cabd
clean up unused
oycyc Aug 8, 2024
e72390e
add results, coverage, inputs, PR comment, sourcemap, build, etc
oycyc Aug 11, 2024
a6ceba3
fix coverage with nested paths
oycyc Aug 11, 2024
5879f02
fix coverage
oycyc Aug 12, 2024
0a55a73
fix coverage again
oycyc Aug 12, 2024
8e281d2
let's see
oycyc Aug 12, 2024
9d1c660
surely this will fix it
oycyc Aug 12, 2024
8033b41
hopefully now...
oycyc Aug 12, 2024
c8600cd
fix not being able to find untested
oycyc Aug 12, 2024
cd3ca0e
documentation, tests, refactor
oycyc Aug 12, 2024
2c2729e
fix readme
oycyc Aug 12, 2024
2da9159
remove max depth filtering
oycyc Aug 12, 2024
d594722
preview diagram
oycyc Aug 13, 2024
2c49e1a
diagram for docs
oycyc Aug 13, 2024
1169f8e
Readjust size
oycyc Aug 13, 2024
082602c
Gear emoji
oycyc Aug 13, 2024
5246e6f
more documentation
oycyc Aug 13, 2024
a994c29
use better bashing for test
oycyc Aug 13, 2024
fce1a9a
make opa test bashing better
oycyc Aug 13, 2024
df28c8f
DEBUG, need to revert later
oycyc Aug 13, 2024
3c568f5
DEBUG, need to revert later
oycyc Aug 13, 2024
24159d9
revert, do not exit on failure
oycyc Aug 13, 2024
0fc8473
DEBUG, coverage
oycyc Aug 13, 2024
968952c
ready
oycyc Aug 13, 2024
0647843
docs/fix
oycyc Aug 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
145 changes: 144 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,144 @@
# github-action-opa-tests-coverage
[![Masterpoint Logo](https://masterpoint-public.s3.us-west-2.amazonaws.com/v2/standard-long-fullcolor.png)](https://masterpoint.io)

# GitHub Action for OPA Rego Policy Tests <!--[![Latest Release](https://img.shields.io/github/release/masterpointio/github-action-opa-rego-test.svg)](https://github.com/masterpointio/github-action-opa-rego-test/releases/latest)-->

GitHub Action to automate testing for your OPA (Open Policy Agent) Rego policies, generates a report with coverage information, and posts the test results as a comment on your pull requests.

Use this to test your OPA Rego files for [Spacelift policies](https://docs.spacelift.io/concepts/policy), Kubernetes Admission Controller policies, Docker authorization policies, or any other use case that uses [Open Policy Agent's policy language Rego](https://www.openpolicyagent.org/docs/latest/).

<img src="./assets/opa-logo.png" alt="OPA Logo" width="300">

<img src="./assets/banner-pr-comment-example.png" alt="OPA Rego Test GitHub Comment Example" width="600">

See examples of the pull request comments below at the [Example Pull Request Comments section](#-example-pull-request-comments).

📚 Table of Contents
- [🚀 Usage](#-usage)
- [Inputs](#inputs)
- [⚙️ How It Works](#️-how-it-works)
- [🧪 Running Tests](#-running-tests)
- [🏗️ Setup & Run Locally](#️-setup--run-locally)
- [📦 Releases / Packaging for Distribution](#-releases--packaging-for-distribution)
- [🤝 Contributing](#-contributing)
- [💬 Example Pull Request Comments](#-example-pull-request-comments)

## 🚀 Usage
It's super easy to get started and use this GitHub Action to test your OPA Rego policies. In your repository/directory with the `.rego` files and the `_test.rego` files, simply checkout the repository and add the step with `uses: masterpointio/github-action-opa-rego-test@main`. It's as simple as adding the step with no required inputs!
```yaml
- name: Run OPA Rego Tests
uses: masterpointio/github-action-opa-rego-test@main
```

<details>
<summary>Expand to see full usage example!</summary>

```yaml
name: Spacelift Policy OPA Rego Tests

on:
pull_request:
types:
- opened
- edited
- synchronize
- ready_for_review
- reopened
# Optionally only trigger tests on affecting .rego files.
# paths:
# - '**.rego'

permissions:
id-token: write
contents: read
pull-requests: write # required to comment on PRs

jobs:
run-opa-tests:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4

- name: Run OPA Rego Tests
uses: masterpointio/github-action-opa-rego-test@main
with:
test_directory_path: "./config/spacelift-policies" # Path of the directory where the OPA Rego policies are stored. Optional, defaults to `.` which is the root directory.
report_untested_files: true # Flag to check & report Rego files without corresponding test files. Optional, defaults to false.
```

</details>

BE SURE TO ALWAYS APPEND THE POSTFIX `_test.rego` TO YOUR TEST FILES! This is how the GitHub Action know what test to run on files. For example, if you have a file named `my-policy.rego`, you would need a file named `my-policy_test.rego`. It does not matter where the `_test.rego` file is located, just that it is in the same directory as the `.rego` file, meaning that it can be in a subdirectory.

In the example below, all `_test.rego` files' location are valid and will be executed.

<img src="./assets/test-file-structure-example.png" alt="Masterpoint GitHub Action OPA Test File Structure" width="450">

### Inputs
| Input | Description | Required | Default |
|-------|-------------|----------|---------|
| `test_directory_path` | Path to the directory containing OPA Rego files to test | No | `.` (root directory) |
| `write_pr_comment` | Flag to write a user-friendly PR comment with test results | No | `true` |
| `pr_comment_title` | Title of the PR comment for test results | No | `🧪 OPA Rego Policy Test Results` |
| `run_coverage_report` | Flag to run OPA coverage tests and include in PR comment | No | `true` |
| `report_untested_files` | Check & report Rego files without corresponding test files | No | `false` |

## ⚙️ How It Works
This GitHub Action automates the process of testing OPA (Open Policy Agent) Rego policies and generating coverage reports. Here's a breakdown of its operation:

1. Setup: The action begins by setting up OPA using the open-policy-agent/setup-opa@v2 action, ensuring the necessary tools are available.
2. Run OPA Tests: It executes `opa test` on all .rego files in the specified directory (default is the root directory). The test results are captured and stored as an output.
3. Run OPA Coverage Tests: Enabled by default but optional, the action performs coverage tests on each .rego file that has a corresponding _test.rego file. This step identifies which parts of your policies are covered by tests.
4. Find Untested Files: Optionally if enabled, it can identify Rego files that don't have corresponding test files, helping you maintain comprehensive test coverage.
5. Parse and Format Results: A custom TypeScript script (index.ts) processes the raw test and coverage outputs. It parses the results into a structured format and generates a user-friendly summary.
6. Generate PR Comment: The formatted results are used to create or update a comment on the pull request.
7. Fail the Action if Tests Fail: If any tests fail, the action is marked as failed, which can be used to block PR merges or trigger other workflows.

![Masterpoint OPA Rego Test Action Diagram](https://lucid.app/publicSegments/view/60bf898e-2640-475f-b130-2a70d317a65d/image.png)

## 🧪 Running Tests
1. `npm install`
2. `npm run test`

<img src="./assets/readme-test-results.png" alt="NPM Test Results" width="450">


## 🏗️ Setup & Run Locally
You can use [nektos/act](https://github.com/nektos/act) to simulate and run a GitHub Actions workflow locally. To directly test the custom TypeScript action locally, you can:
1. `npm run install`
2. `node ./dist/index.js`
This is assuming you have `npm` and `node` installed already. Note: You will have to manually provide the required inputs since this is directly executing the TypeScript code.

## 📦 Releases / Packaging for Distribution
This Action executes the source from the `/dist` directory. It is generated using [@vercel/ncc](https://github.com/vercel/ncc) to easily compile the TypeScript module into a single file together with all its dependencies, gcc-style, to package it up for use and distribute.

To use, simply run the command (see the source in `package.json`):
```bash
npm run build
```

To create a new release... TODO, release please with `npm run build` and commit to /dist distribution


## 🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request or open any issues you may have.

## 💬 Example Pull Request Comments
- ![Masterpoint GitHub Actions OPA Rego Test PR Example](./assets/readme-example-1.png)
- Using `report_untested_files` to indicate policies without corresponding tests.
- ![Masterpoint GitHub Actions OPA Rego Test PR Example](./assets/readme-example-2.png)
- ![Masterpoint GitHub Actions OPA Rego Test PR Example](./assets/readme-example-3.png)


### To-Do's:
- make composite action logging better
- add debug logs
- more tests + fix tests
- lint and ci tests
- deal with issues like
- `1 error occurred: ./access/label-based-team-access.rego:35: rego_type_error: conflicting rules data.spacelift.deny found`
- need better visibliity to when this happens and fails
- right now, it just exits code 2. not helpful and someone new to sys wouldn't know where to look at.
- one way is to PR comment error occured in the execution of the tests. please tak eal ook at the logs..
- publish to marketplace
- release please.
123 changes: 123 additions & 0 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// import { parseTestOutput, parseCoverageOutput, formatResults, TestResult, CoverageResult } from '../src/index';

// describe('parseTestOutput', () => {
// it('should correctly parse test output', () => {
// const testOutput = `
// ./policy/enforce-password-length.rego:
// data.policy.enforce_password_length.test_valid_password: PASS (328.208µs)
// data.policy.enforce_password_length.test_invalid_password: PASS (163.75µs)
// ./policy/enforce-password-complexity.rego:
// data.policy.enforce_password_complexity.test_valid_password: PASS (379.25µs)
// data.policy.enforce_password_complexity.test_invalid_password: FAIL (163.75µs)
// `;

// const result = parseTestOutput(testOutput);

// expect(result).toEqual([
// {
// file: './policy/enforce-password-length.rego:',
// status: 'PASS',
// passed: 2,
// total: 2,
// details: [
// '✅ data.policy.enforce_password_length.test_valid_password',
// '✅ data.policy.enforce_password_length.test_invalid_password'
// ]
// },
// {
// file: './policy/enforce-password-complexity.rego:',
// status: 'FAIL',
// passed: 1,
// total: 2,
// details: [
// '✅ data.policy.enforce_password_complexity.test_valid_password',
// '❌ data.policy.enforce_password_complexity.test_invalid_password'
// ]
// }
// ] as TestResult[]);
// });
// });

// describe('parseCoverageOutput', () => {
// it('should correctly parse coverage output', () => {
// const coverageOutput = `
// {
// "files": {
// "enforce-password-length.rego": {
// "coverage": 85.71428571428571,
// "not_covered": [
// {
// "start": {
// "row": 25
// },
// "end": {
// "row": 25
// }
// },
// {
// "start": {
// "row": 31
// },
// "end": {
// "row": 31
// }
// }
// ]
// }
// }
// }
// `;

// const result = parseCoverageOutput(coverageOutput);

// expect(result).toEqual([
// {
// file: 'enforce-password-length.rego',
// coverage: 85.71428571428571,
// notCoveredLines: '25, 31'
// }
// ] as CoverageResult[]);
// });
// });

// describe('formatResults', () => {
// it('should format results correctly with coverage', () => {
// const testResults: TestResult[] = [
// {
// file: './policy/enforce-password-length.rego:',
// status: 'PASS',
// passed: 2,
// total: 2,
// details: ['✅ test1', '✅ test2']
// }
// ];

// const coverageResults: CoverageResult[] = [
// {
// file: 'enforce-password-length.rego',
// coverage: 85.71,
// notCoveredLines: '25, 31'
// }
// ];

// const formattedOutput = formatResults(testResults, coverageResults, true);

// expect(formattedOutput).toContain('| ./policy/enforce-password-length.rego | ✅ PASS | 2 | 2 | 85.71% <details><summary>Uncovered Lines</summary>25, 31</details> |');
// });

// it('should format results correctly without coverage', () => {
// const testResults: TestResult[] = [
// {
// file: './policy/enforce-password-length.rego:',
// status: 'PASS',
// passed: 2,
// total: 2,
// details: ['✅ test1', '✅ test2']
// }
// ];

// const formattedOutput = formatResults(testResults, [], false);

// expect(formattedOutput).toContain('| ./policy/enforce-password-length.rego | ✅ PASS | 2 | 2 |');
// });
// });
Loading