Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 7 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
fail-fast: false
matrix:
version:
- '1.6'
- '1.10' # OpenAPI.jl 1.x floor
- '1' # automatically expands to the latest stable 1.x release of Julia
- nightly
os:
Expand All @@ -32,24 +32,15 @@ jobs:
version: 1
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v1
- uses: julia-actions/setup-julia@v2
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: actions/cache@v4
env:
cache-name: cache-artifacts
with:
path: ~/.julia/artifacts
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
restore-keys: |
${{ runner.os }}-test-${{ env.cache-name }}-
${{ runner.os }}-test-
${{ runner.os }}-
- uses: julia-actions/cache@v2
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v2
- uses: codecov/codecov-action@v4
with:
files: lcov.info
docs:
Expand All @@ -60,6 +51,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: '1'
- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-docdeploy@latest
env:
Expand Down
24 changes: 14 additions & 10 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
name = "OpenPolicyAgent"
uuid = "8f257efb-743c-4ebc-8197-d291a1f743b4"
authors = ["JuliaHub Inc.", "Tanmay Mohapatra <tanmaykm@gmail.com>"]
version = "0.4.2"
version = "0.5.0"

[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
OpenAPI = "d5e62ea6-ddf3-4d43-8e4c-ad5e6c8bfd7d"
TimeZones = "f269a46b-ccf7-5d73-abea-4c690281aa53"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[compat]
Dates = "1.6"
OpenAPI = "0.1,0.2"
TimeZones = "1"
julia = "1.6"
Base64 = "1.10"
Dates = "1.10"
HTTP = "2"
JSON = "1.7"
OpenAPI = "1.1.1"
UUIDs = "1.10"
julia = "1.10"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
OpenPolicyAgent_jll = "6ea5c882-2ec3-5826-84d1-aff636352c13"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "JSON", "HTTP", "OpenPolicyAgent_jll"]
test = ["Test", "OpenPolicyAgent_jll"]
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@

This package provides a Julia interface to the OPA server, and the client APIs to interact with the server. It also includes a command-line interface to the OPA command-line tool.

The REST client is generated from the OPA OpenAPI specification with [OpenAPI.jl](https://github.com/JuliaComputing/OpenAPI.jl) 1.x (Julia 1.10 or newer). Version 0.5 changed the client API; see the Client page of the documentation for a migration table.

[![](https://img.shields.io/badge/docs-latest-blue.svg)](https://JuliaComputing.github.io/OpenPolicyAgent.jl)
9 changes: 4 additions & 5 deletions docs/src/ast_walker.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ import OpenPolicyAgent.ASTWalker.AST: ASTVisitor
import OpenPolicyAgent.ASTWalker.SQL: SQLVisitor, SQLCondition, UnconditionalInclude, UnconditionalExclude

# invoke the partial evaluation endpoint
client = OpenPolicyAgent.Client.Client("http://localhost:8181")
partial_query_schema = OpenPolicyAgent.Client.PartialQuerySchema(; ...)
response, _http_resp = OpenPolicyAgent.Client.post_compile(
compile_client;
partial_query_schema = partial_query_schema,
)
response = OpenPolicyAgent.Client.postcompile(; body = partial_query_schema, client)
result = response.result

# crete a Julia representation of the AST
# create a Julia representation of the AST
ast = OpenPolicyAgent.ASTWalker.walk(ASTVisitor(), result)

# Provide a mapping of schema names and table names that can be used to convert policy paths to SQL table names
Expand Down
61 changes: 54 additions & 7 deletions docs/src/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,65 @@ OPA exposes domain-agnostic APIs that your service can call to manage and enforc
- **Config API** - view instance configuration.
- **Status API** - view instance status state.

The `OpenPolicyAgent.Client` module includes methods to help interact with the OPA server using the OpenAPI client.
The `OpenPolicyAgent.Client` module is generated from the OPA OpenAPI specification by [OpenAPI.jl](https://github.com/JuliaComputing/OpenAPI.jl). Each API operation is a function named after its `operationId`, and each schema is a Julia struct.

```julia
opa_client = OpenPolicyAgent.Client.DataApi(openapi_client)
import OpenPolicyAgent: Client

response, _http_resp = OpenPolicyAgent.Client.get_document(
opa_client,
"policies/server/rest/allowed"
);
# One client per OPA server; pass it to every call as the `client` keyword.
client = Client.Client("http://localhost:8181")

response = Client.getdocument("policies/server/rest/allowed"; client)
@test response.result == false

# Evaluate a rule with an input document
body = Client.InputSchema(; input = Dict("name" => "bob"))
response = Client.getdocumentwithpath("policies/server/rest/allowed", body; client)
@test response.result == true
```

Path parameters are positional, a request body is the last positional argument, and every other parameter is a keyword. Optional model fields default to `Client.ABSENT`, which is distinct from an explicit JSON `null`.

## Responses and errors

An operation returns the decoded body of a successful response. Pass `with_http_info = true` to get an `ApiResponse` with the status, headers and typed body instead.

```julia
resp = Client.getdocument("servers"; with_http_info = true, client)
resp.status # 200
resp.body # a Client.GetDocumentSuccessResponse
```

A non-2xx response raises `Client.ApiError`. Its `status` field holds the HTTP status, and `decoded` holds the documented error body (an `OpenPolicyAgent.Client.ServerErrorResponse` for most OPA errors) when it could be decoded:

```julia
try
Client.getstatus(; client)
catch ex
ex isa Client.ApiError || rethrow()
ex.status # 500
ex.decoded.code # "internal_error"
end
```

## Client options

`Client.Client(server; kwargs...)` accepts `headers` applied to every request and `request_options`, a named tuple passed through to `HTTP.request` (for example `(request_timeout = 5,)`). Request and response validation against the specification is on by default and can be disabled per client with `validate_requests = false` / `validate_responses = false`.

Complete reference is available in the Reference section.

OpenAPI [API Documents](https://github.com/JuliaComputing/OpenPolicyAgent.jl/blob/main/src/client/README.md) also give more details on the API methods.
## Migrating from OpenPolicyAgent 0.4

Version 0.5 replaced the client generated by openapi-generator (OpenAPI.jl 0.2) with one generated by OpenAPI.jl 1.x.

| 0.4 | 0.5 |
| --- | --- |
| `OpenAPI.Clients.Client(url; escape_path_params=false)` | `Client.Client(url)` |
| `api = Client.DataApi(openapi_client)` | no per-tag API structs; pass `client` as a keyword |
| `Client.get_document_with_path(api, path, Dict("input" => x))` | `Client.getdocumentwithpath(path, Client.InputSchema(; input = x); client)` |
| `response, http_resp = Client.get_document(api, path)` | `response = Client.getdocument(path; client)` (`with_http_info = true` for the status) |
| `Client.post_compile(api; partial_query_schema = s)` | `Client.postcompile(; body = s, client)` |
| `explain = true` | `explain = Client.ExplainMode("full")` |
| error bodies returned as the result (e.g. `ServerErrorResponse`) | `Client.ApiError` is thrown; the body is in `ex.decoded` |
| `OpenAPI.Clients.ApiException` | `Client.ApiError` |
| `metrics::Dict{String,Any}` | metrics structs; the entries are in `metrics.additional_properties` |
52 changes: 28 additions & 24 deletions docs/src/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,56 +11,60 @@ CurrentModule = OpenPolicyAgent

## Client

### PolicyApi
```@docs
OpenPolicyAgent.Client
```

### Policy API

```@docs
OpenPolicyAgent.Client.get_policies
OpenPolicyAgent.Client.get_policy_module
OpenPolicyAgent.Client.put_policy_module
OpenPolicyAgent.Client.delete_policy_module
OpenPolicyAgent.Client.getpolicies
OpenPolicyAgent.Client.getpolicymodule
OpenPolicyAgent.Client.putpolicymodule
OpenPolicyAgent.Client.deletepolicymodule
```

### DataApi
### Data API

```@docs
OpenPolicyAgent.Client.get_document
OpenPolicyAgent.Client.get_document_with_path
OpenPolicyAgent.Client.get_document_from_webhook
OpenPolicyAgent.Client.create_document
OpenPolicyAgent.Client.patch_document
OpenPolicyAgent.Client.delete_document
OpenPolicyAgent.Client.getdocument
OpenPolicyAgent.Client.getdocumentwithpath
OpenPolicyAgent.Client.getdocumentfromwebhook
OpenPolicyAgent.Client.createdocument
OpenPolicyAgent.Client.patchdocument
OpenPolicyAgent.Client.deletedocument
```

### QueryApi
### Query API

```@docs
OpenPolicyAgent.Client.query_get
OpenPolicyAgent.Client.query_post
OpenPolicyAgent.Client.simple_query
OpenPolicyAgent.Client.queryget
OpenPolicyAgent.Client.querypost
OpenPolicyAgent.Client.simplequery
```

### CompileApi
### Compile API

```@docs
OpenPolicyAgent.Client.post_compile
OpenPolicyAgent.Client.postcompile
```

### HealthApi
### Health API

```@docs
OpenPolicyAgent.Client.get_health
OpenPolicyAgent.Client.gethealth
```

### ConfigApi
### Config API

```@docs
OpenPolicyAgent.Client.get_config
OpenPolicyAgent.Client.getconfig
```

### StatusApi
### Status API

```@docs
OpenPolicyAgent.Client.get_status
OpenPolicyAgent.Client.getstatus
```

## Server
Expand Down
8 changes: 5 additions & 3 deletions specs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ OPA OpenAPI client and command line interface code is mostly generated from spec

## CLI

The CLI interface is generated using [FigCLIGen.j;](https://github.com/tanmaykm/FigCLIGen.jl), using a [specification](cli/opa.json) derived from the OPA fig specification. To regenerate, install the `FigCLIGen` package and run `cli/generate.jl`.
The CLI interface is generated using [FigCLIGen.jl](https://github.com/tanmaykm/FigCLIGen.jl), using a [specification](cli/opa.json) derived from the OPA fig specification. To regenerate, install the `FigCLIGen` package and run `cli/generate.jl`.

```bash
$ julia cli/generate.jl
```

## OpenAPI Client

The OpenAPI client is generated using [openapi-generator](https://github.com/OpenAPITools/openapi-generator), using the OpenAPI [specification](openapi/open_policy_agent.yaml) included in this repo. To regenerate, install `openapi-generator` and run `openapi/generate.sh`.
The OpenAPI client (`src/client/OPAClient.jl`) is generated using [OpenAPI.jl](https://github.com/JuliaComputing/OpenAPI.jl), using the OpenAPI [specification](openapi/open_policy_agent.yaml) included in this repo. To regenerate, run `openapi/generate.jl`; it uses the pinned generator version in `openapi/Project.toml`.

```bash
$ openapi/generate.sh
$ julia openapi/generate.jl
```

Generation is strict: a specification defect fails the run instead of degrading a generated type. The generated file is committed, so a change to the specification or to the pinned generator version shows up as a diff in `src/client/OPAClient.jl`.
3 changes: 1 addition & 2 deletions specs/openapi/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
OpenPolicyAgent
*.jar
Manifest.toml
12 changes: 12 additions & 0 deletions specs/openapi/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Environment for specs/openapi/generate.jl.
#
# OpenAPI is pinned exactly: the generator version determines the emitted
# source byte for byte, so bumping it regenerates src/client/OPAClient.jl and
# is a deliberate change.

[deps]
OpenAPI = "d5e62ea6-ddf3-4d43-8e4c-ad5e6c8bfd7d"

[compat]
OpenAPI = "=1.1.0"
julia = "1.10"
29 changes: 29 additions & 0 deletions specs/openapi/generate.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env julia
#
# Regenerates the OPA REST API client (src/client/OPAClient.jl) from
# open_policy_agent.yaml using OpenAPI.jl's native generator.
#
# julia specs/openapi/generate.jl
#
# The script activates and instantiates the pinned environment in this
# directory (see Project.toml), so no manual setup is needed beyond registry
# access. Planning runs in strict mode: a specification defect fails the run
# instead of silently degrading a generated type.
#
# The module is named `OPAClient` rather than `Client` because it defines the
# `Client(server; kwargs...)` constructor, and a module cannot share a name
# with one of its own bindings. `OpenPolicyAgent.Client` is an alias of it.

import Pkg
Pkg.activate(@__DIR__)
Pkg.instantiate()

using OpenAPI

const SPEC = joinpath(@__DIR__, "open_policy_agent.yaml")
const DEST = normpath(joinpath(@__DIR__, "..", "..", "src", "client", "OPAClient.jl"))

plan = OpenAPI.plan(SPEC; name = "OPAClient", strict = true)
mkpath(dirname(DEST))
OpenAPI.client(plan; path = DEST)
@info "generated" DEST
18 changes: 0 additions & 18 deletions specs/openapi/generate.sh

This file was deleted.

Loading
Loading