Skip to content

Commit 009545b

Browse files
authored
Implement Nats K/V Streamlining into OPA memory (#1)
* Initialize NATS plugin for OPA with LRU caching and configuration support. Add Go module and dependencies, along with README documentation and CI workflow setup. Implement core caching logic, group management, and path routing for NATS Key-Value store integration. * Refactor NATS plugin configuration to remove bucket settings and enhance group management. Update logging to use OPA's logging package. Adjust tests to reflect changes in configuration structure, focusing on group regex patterns and single group settings. * Add gjson-based caching to GroupCache with LRU support Enhance GroupCache to utilize gjson for JSON document management, allowing for efficient retrieval and updates. Implement LRU caching for recently accessed paths, with automatic invalidation of affected cache entries upon updates. Introduce comprehensive tests to validate functionality, including cache eviction and document persistence. Update go.mod and go.sum to include necessary dependencies for gjson and sjson. * Major refactor to how the nats k/v import works * Refactor bucket watcher and data transformer to support root bucket handling. Update configuration files to set max_bucket_watchers to 1. Enhance EnsureBucketLoaded method to differentiate between root and non-root buckets, and adjust related methods for consistent handling of bucket data loading and OPA store interactions. * fix launch config and dependencies * Refactor pre-commit configuration and clean up whitespace across multiple files. Remove unused go-vet hook, ensure consistent formatting, and eliminate trailing whitespace in various YAML, Go, and Markdown files. * Refactor BucketDataManager and related components to improve terminology consistency. Update comments and method names to replace "group" with "bucket" for clarity. Adjust error messages and logging to reflect the new terminology. Simplify keyToPath function by removing unnecessary error handling. * Add comprehensive tests for NATS store components Introduce new test files for additional functionality, including tests for DataTransformer, NATSClient, and MockStore. Cover various scenarios such as edge cases, error handling, and configuration validation. Refactor existing tests for clarity and consistency, ensuring robust coverage of key functionalities in the NATS store package. * Update comprehensive test for MockStore to assert registration without panic Refactor the TestMockStore_Comprehensive function to ensure that the Register method does not panic by asserting the absence of errors during registration. This change enhances the robustness of the test suite for the NATS store package. * Add NATS JetStream configuration and setup in CI workflow Enhance the CI workflow by adding a NATS service setup for JetStream, including health checks and configuration management. Introduce a new configuration file for JetStream and update the PluginFactory to manage bucket data more effectively. This change improves testing capabilities and ensures proper configuration of the NATS server during CI runs. * Add default logger initialization in PluginFactory.New method Enhance the NATS store plugin by adding a default logger instantiation in the New method of the PluginFactory. This change ensures that a logger is available for testing scenarios when none is provided, improving the robustness of the plugin's logging capabilities. * Add golangci-lint configuration and enhance CI workflow for NATS service Introduce a new golangci-lint configuration file to define linting rules and settings for the project. Update the CI workflow to include a NATS service setup with health checks, ensuring proper environment for linting. Additionally, refactor the NATS store plugin to improve error handling for bucket name and key type assertions, enhancing robustness and clarity in the codebase. * fix golangci-lint * fix pre-commit * upgrade pre-commit golangci-lint * Update golangci-lint configuration and CI workflow Refactor the golangci-lint configuration to enhance linting rules and settings, including increased confidence for revive and added staticcheck. Update the GitHub Actions workflow to use golangci-lint version v2.3.1, ensuring compatibility and improved linting performance. * Add Dockerfile and main.go for OPA with NATS plugin integration Introduce a new Dockerfile for building the OPA binary with the NATS store plugin. The Dockerfile includes multi-stage builds for dependency management and a non-root user setup. Additionally, add main.go to implement the OPA runtime with the NATS plugin, enhancing the project's capabilities. Update docker-compose.yaml to reference the new Dockerfile and adjust the command for running the OPA server. * Enhance README with project structure and configuration details Update the README.md to include a detailed project layout, highlighting the directory structure and key components. Add new configuration options for the NATS plugin, including `max_bucket_watchers` and `root_bucket`. Introduce built-in functions for interacting with NATS K/V, along with usage examples. Revise usage instructions for the custom OPA binary and Docker integration, improving clarity and completeness of documentation. * Refactor examples and update README for OPA-NATS integration Rename example directory from `handledpaths` to `opa-nats` to better reflect its purpose. Update README.md to point to the new example location and provide clearer instructions for using the OPA with NATS plugin. Add new configuration files for Docker Compose and OPA, and include a test policy in Rego to verify functionality. Remove the old `main.go` example to streamline the project structure.
1 parent c6ba38a commit 009545b

28 files changed

Lines changed: 4913 additions & 0 deletions

.cursor/rules/golang-master.mdc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
alwaysApply: true
3+
---
4+
You are an expert AI programming assistant specializing in building APIs with Go, using the standard library's net/http package and the new ServeMux introduced in Go 1.22.
5+
6+
Always use the latest stable version of Go (1.22 or newer) and be familiar with RESTful API design principles, best practices, and Go idioms.
7+
8+
- Follow the user's requirements carefully & to the letter.
9+
- First think step-by-step - describe your plan for the API structure, endpoints, and data flow in pseudocode, written out in great detail.
10+
- Confirm the plan, then write code!
11+
- Write correct, up-to-date, bug-free, fully functional, secure, and efficient Go code for APIs.
12+
- Use the standard library's net/http package for API development:
13+
- Utilize the new ServeMux introduced in Go 1.22 for routing
14+
- Implement proper handling of different HTTP methods (GET, POST, PUT, DELETE, etc.)
15+
- Use method handlers with appropriate signatures (e.g., func(w http.ResponseWriter, r *http.Request))
16+
- Leverage new features like wildcard matching and regex support in routes
17+
- Implement proper error handling, including custom error types when beneficial.
18+
- Use appropriate status codes and format JSON responses correctly.
19+
- Implement input validation for API endpoints.
20+
- Utilize Go's built-in concurrency features when beneficial for API performance.
21+
- Follow RESTful API design principles and best practices.
22+
- Include necessary imports, package declarations, and any required setup code.
23+
- Implement proper logging using the standard library's log package or a simple custom logger.
24+
- Consider implementing middleware for cross-cutting concerns (e.g., logging, authentication).
25+
- Implement rate limiting and authentication/authorization when appropriate, using standard library features or simple custom implementations.
26+
- Leave NO todos, placeholders, or missing pieces in the API implementation.
27+
- Be concise in explanations, but provide brief comments for complex logic or Go-specific idioms.
28+
- If unsure about a best practice or implementation detail, say so instead of guessing.
29+
- Offer suggestions for testing the API endpoints using Go's testing package.
30+
31+
Always prioritize security, scalability, and maintainability in your API designs and implementations. Leverage the power and simplicity of Go's standard library to create efficient and idiomatic APIs.

.cursor/rules/project-master.mdc

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
---
2+
alwaysApply: true
3+
---
4+
# Cursor Rules — NATS Plugin for OPA
5+
6+
You are editing **github.com/permitio/opa-nats**. The library implements an **LRU cache backed by NATS Key-Value (JetStream)** that *selectively* overrides OPA's runtime store for paths matched by regexes. It is **not** a full OPA storage implementation.
7+
8+
## Core Principles
9+
10+
1. **Do not change the contract**: This is a cache + path router around an embedded `storage.Store`. Only `Read`/`Write` are overridden; everything else must go to the embedded store.
11+
2. **Regex decides routing**: `handled_paths_regex` is the single source of truth. If it’s empty, nothing is routed to NATS.
12+
3. **NATS is source of truth for handled paths**: Reads fall back to NATS on cache miss. Writes go to NATS, then update the LRU cache.
13+
4. **Keep the cache simple**: No hidden global state, no additional layers unless explicitly justified.
14+
5. **Production-grade (practical) engineering**: clear errors, timeouts, reconnect logic, metrics/status surface, and structured logs.
15+
16+
## Go / Tooling
17+
18+
- **Go version**: match the `go.mod` (bump only with CI + docs update).
19+
- **Linting**: keep `golangci-lint` clean; fix or justify with inline comments.
20+
- **Static analysis**: `go vet`, `staticcheck` where possible.
21+
- **Error handling**: wrap with `%w`; give context (`fmt.Errorf("connect NATS: %w", err)`).
22+
- **Context**: every external I/O (NATS ops, watchers) must accept `context.Context`.
23+
- **Generics**: use only when it simplifies code meaningfully.
24+
25+
## Concurrency & Lifecycles
26+
27+
- Watchers/goroutines must:
28+
- Respect `ctx.Done()`.
29+
- Return on shutdown.
30+
- Avoid leaks (document ownership & stop semantics).
31+
- Reconnect logic:
32+
- Honor `max_reconnect_attempts` and `reconnect_wait`.
33+
- Surface status to OPA status API.
34+
35+
## Configuration Rules
36+
37+
- Keep config in `natsstore.Config`. Add fields only when needed, document them in README and code comments.
38+
- Validate config on startup (URLs, TTLs, bucket, auth mode exclusivity).
39+
- If both `credentials` and `token`/`username` are provided, error out with a helpful message.
40+
41+
## Path Handling
42+
43+
- **Never** route non-matching paths to NATS.
44+
- Path mapping: OPA `["data","schemas","user"]` → NATS key `data.schemas.user`. Any change here must be explicitly documented and migration noted.
45+
- Regex must be compiled once and reused.
46+
47+
## Logging & Monitoring
48+
49+
- Structured logging only (key/value). No `Printf`.
50+
- Respect `log_level` in config.
51+
- Status API should expose:
52+
- Cache size & capacity.
53+
- NATS connection state.
54+
- Trigger/watch count.
55+
- Policy count (from embedded store).
56+
57+
## Testing
58+
59+
- **Unit tests** for:
60+
- Regex routing (hit/miss).
61+
- Cache TTL behavior.
62+
- NATS read/write fallbacks.
63+
- Reconnect logic.
64+
- **Integration tests** (with JetStream) behind a build tag (e.g. `integration`).
65+
- **Race detector** (`-race`) must pass.
66+
- Maintain/raise coverage; do not merge coverage regressions without rationale.
67+
68+
## CI
69+
70+
- Do not break `.github/workflows/pr_checks.yml`.
71+
- Add new tooling steps only if they’re reproducible locally.
72+
73+
## Commits & PRs
74+
75+
- Use **Conventional Commits**:
76+
- `feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `ci:`, `chore:`
77+
- Each PR must:
78+
- Explain what path(s) it touches (NATS vs embedded store).
79+
- List any config or status API changes.
80+
- Include tests or justify why not.
81+
82+
## Docs
83+
84+
- README is the single entry point for users. If you add/rename:
85+
- Config fields
86+
- Status metrics
87+
- Path mapping logic
88+
then **update README, examples, and comments**.
89+
- Avoid marketing-y fluff and filler words.
90+
91+
## Security
92+
93+
- Never log secrets (token, username, password, creds path contents).
94+
- TLS flags must be honored; `tls_insecure` defaults to `false`.
95+
- Encourage secure defaults in examples.
96+
97+
## Troubleshooting & Errors
98+
99+
- Emit actionable errors:
100+
- Connection failures: include URL and selected auth mode (not the secret).
101+
- Bucket not found: suggest permission or creation options.
102+
- Prefer retryable errors where appropriate; otherwise fail fast with context.
103+
104+
## Style
105+
106+
- Keep files short and focused. Split out packages only when it clarifies ownership/boundaries.
107+
- Prefer small, cohesive interfaces.
108+
- Name things after what they *do* (e.g., `pathRouter`, `kvBackedCache`).
109+
110+
## Banned Phrases (do not write these)
111+
112+
- dynamic world
113+
- evolving landscape
114+
- the world of
115+
- digital age
116+
- ever-evolving
117+
- digital landscape
118+
- robust
119+
- myriad
120+
121+
## When You’re Unsure
122+
123+
- Default to the simplest change that preserves public behavior.
124+
- Add a small test that demonstrates the intent.
125+
- Leave a short comment pointing back to the README section that explains the behavior.

.github/workflows/pr_checks.yml

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
name: PR Checks
2+
3+
on:
4+
pull_request:
5+
branches: [ main ]
6+
7+
jobs:
8+
lint:
9+
name: Lint
10+
runs-on: ubuntu-latest
11+
services:
12+
nats:
13+
image: nats:2.10.22-alpine
14+
ports:
15+
- 4222:4222
16+
- 8222:8222
17+
options: >-
18+
--name nats-ci-lint-${{ github.run_id }}
19+
--health-cmd "wget --no-verbose --tries=1 --spider http://localhost:8222/healthz"
20+
--health-interval 10s
21+
--health-timeout 5s
22+
--health-retries 5
23+
steps:
24+
- uses: actions/checkout@v4
25+
- uses: actions/setup-go@v4
26+
with:
27+
go-version: '1.22'
28+
- name: Setup NATS Jetstream for Lint
29+
run: |
30+
# Setup NATS with JetStream for linting
31+
docker cp ${{ github.workspace }}/.github/workflows/utils/nats-jetstream.conf nats-ci-lint-${{ github.run_id }}:/tmp/nats-jetstream.conf
32+
docker exec nats-ci-lint-${{ github.run_id }} sh -c 'cat /tmp/nats-jetstream.conf > /etc/nats/nats-server.conf'
33+
docker restart nats-ci-lint-${{ github.run_id }}
34+
35+
# Wait for NATS to be ready
36+
timeout=30
37+
start_time=$(date +%s)
38+
while true; do
39+
if curl --fail --silent http://localhost:8222/healthz; then
40+
echo "NATS is ready for linting!"
41+
break
42+
fi
43+
current_time=$(date +%s)
44+
elapsed=$((current_time - start_time))
45+
if [ $elapsed -ge $timeout ]; then
46+
echo "Timeout waiting for NATS"
47+
exit 1
48+
fi
49+
sleep 1
50+
done
51+
- name: golangci-lint
52+
uses: golangci/golangci-lint-action@v8
53+
with:
54+
version: v2.3.1
55+
args: --timeout=10m
56+
- uses: pre-commit/action@v3.0.1
57+
58+
test:
59+
name: Test
60+
runs-on: ubuntu-latest
61+
services:
62+
nats:
63+
image: nats:2.10.22-alpine
64+
ports:
65+
- 4222:4222
66+
- 8222:8222
67+
options: >-
68+
--name nats-ci-${{ github.run_id }}
69+
--health-cmd "wget --no-verbose --tries=1 --spider http://localhost:8222/healthz"
70+
--health-interval 10s
71+
--health-timeout 5s
72+
--health-retries 5
73+
# we can't have health check becasue at the moment the nats server
74+
# is not configured with monitoring.
75+
# this is due to nats not having environment variables configuration available,
76+
# and githuhb actions not allowing to edit the CMD of the container.
77+
# see https://github.com/nats-io/nats-docker/issues/110
78+
steps:
79+
- uses: actions/checkout@v4
80+
- uses: actions/setup-go@v5
81+
with:
82+
go-version: '1.23'
83+
cache: true
84+
- name: Setup NATS Jetstream
85+
run: |
86+
# Print current entrypoint command of the container
87+
echo "Current container entrypoint command:"
88+
docker inspect nats-ci-${{ github.run_id }} --format='{{.Config.Entrypoint}} {{.Config.Cmd}}' || echo "Failed to get entrypoint info"
89+
90+
# Find and print the full path of the entrypoint file
91+
echo "Discovering entrypoint file path:"
92+
docker exec nats-ci-${{ github.run_id }} find / -name "docker-entrypoint.sh" 2>/dev/null || echo "docker-entrypoint.sh not found"
93+
docker exec nats-ci-${{ github.run_id }} which docker-entrypoint.sh 2>/dev/null || echo "docker-entrypoint.sh not in PATH"
94+
95+
# Check what's in common entrypoint locations
96+
echo "Checking common entrypoint locations:"
97+
docker exec nats-ci-${{ github.run_id }} ls -la /usr/local/bin/ | grep -E "(entrypoint|docker)" || echo "No entrypoint files in /usr/local/bin/"
98+
docker exec nats-ci-${{ github.run_id }} ls -la /docker-entrypoint.sh 2>/dev/null || echo "No /docker-entrypoint.sh"
99+
docker exec nats-ci-${{ github.run_id }} ls -la /entrypoint.sh 2>/dev/null || echo "No /entrypoint.sh"
100+
101+
# Show current NATS configuration
102+
echo "Current NATS configuration:"
103+
docker exec nats-ci-${{ github.run_id }} cat /etc/nats/nats-server.conf || echo "No config file found"
104+
105+
# Mount the JetStream configuration from utils directory
106+
echo "Mounting NATS JetStream configuration"
107+
docker cp ${{ github.workspace }}/.github/workflows/utils/nats-jetstream.conf nats-ci-${{ github.run_id }}:/tmp/nats-jetstream.conf
108+
109+
# Replace the content of the existing config file with our JetStream config
110+
echo "Replacing NATS configuration content with JetStream config"
111+
docker exec nats-ci-${{ github.run_id }} sh -c 'cat /tmp/nats-jetstream.conf > /etc/nats/nats-server.conf'
112+
113+
# Show updated configuration
114+
echo "Updated NATS configuration:"
115+
docker exec nats-ci-${{ github.run_id }} cat /etc/nats/nats-server.conf
116+
117+
# Restart the container to pick up the new configuration
118+
echo "Restarting NATS container with JetStream configuration..."
119+
docker restart nats-ci-${{ github.run_id }}
120+
121+
# Print initial NATS logs for debugging
122+
echo "Initial NATS container logs after restart:"
123+
docker logs nats-ci-${{ github.run_id }}
124+
echo "Container status:"
125+
docker ps -a | grep nats-ci-${{ github.run_id }}
126+
127+
# ensure the nats server is ready by running curl to the health endpoint
128+
# in while loop until success or timeout of 10 seconds of failures
129+
timeout=10
130+
start_time=$(date +%s)
131+
while true; do
132+
if curl --fail --silent --show-error http://localhost:8222/healthz?js-enabled-only=true; then
133+
echo "NATS Jetstream is ready!"
134+
echo "Verifying JetStream configuration:"
135+
jsz_response=$(curl -s http://localhost:8222/jsz)
136+
echo "$jsz_response" | jq '.' || echo "JetStream info not available"
137+
138+
# Check if JetStream is disabled
139+
if echo "$jsz_response" | jq -e '.disabled == true' > /dev/null 2>&1; then
140+
echo "ERROR: JetStream is disabled in the server configuration!"
141+
echo "JetStream response: $jsz_response"
142+
exit 1
143+
elif echo "$jsz_response" | jq -e '.disabled == false' > /dev/null 2>&1; then
144+
echo "SUCCESS: JetStream is enabled and configured properly"
145+
else
146+
echo "WARNING: Could not determine JetStream disabled status from response"
147+
echo "JetStream response: $jsz_response"
148+
fi
149+
150+
echo "Final NATS container logs:"
151+
docker logs nats-ci-${{ github.run_id }}
152+
break
153+
fi
154+
current_time=$(date +%s)
155+
elapsed=$((current_time - start_time))
156+
if [ $elapsed -ge $timeout ]; then
157+
echo "Timeout reached after ${timeout} seconds"
158+
echo "Container logs:"
159+
docker logs nats-ci-${{ github.run_id }}
160+
echo "Container status:"
161+
docker ps -a | grep nats-ci-${{ github.run_id }}
162+
exit 1
163+
fi
164+
sleep 1
165+
done
166+
- name: Run tests
167+
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
168+
- name: Upload coverage reports
169+
uses: codecov/codecov-action@v4
170+
with:
171+
file: ./coverage.txt
172+
fail_ci_if_error: false
173+
174+
build:
175+
name: Build
176+
runs-on: ubuntu-latest
177+
steps:
178+
- uses: actions/checkout@v4
179+
- uses: actions/setup-go@v5
180+
with:
181+
go-version: '1.23'
182+
cache: true
183+
- name: Verify dependencies
184+
run: go mod verify
185+
- name: Build
186+
run: go build -v ./...
187+
- name: Check formatting
188+
run: |
189+
gofmt_output=$(gofmt -l -d .)
190+
if [ -n "$gofmt_output" ]; then
191+
echo "Code is not properly formatted:"
192+
echo "$gofmt_output"
193+
exit 1
194+
fi
195+
196+
security:
197+
name: Security Check
198+
runs-on: ubuntu-latest
199+
steps:
200+
- uses: actions/checkout@v4
201+
- uses: actions/setup-go@v5
202+
with:
203+
go-version: '1.23'
204+
cache: true
205+
- name: Install govulncheck
206+
run: go install golang.org/x/vuln/cmd/govulncheck@latest
207+
- name: Run govulncheck
208+
run: govulncheck ./...
209+
continue-on-error: true # Make this check informational rather than blocking
210+
- name: Report vulnerabilities
211+
run: |
212+
echo "::warning ::Security vulnerabilities were found. Please review the govulncheck output above."
213+
if: ${{ failure() }}

0 commit comments

Comments
 (0)