Skip to content

acl: add Roles, Auth Methods, Binding Rules, bootstrap/login/logout; connect: add Intentions#120

Merged
mbrulatout merged 8 commits into
criteo:masterfrom
mbrulatout:acl-phase1-endpoints
Jul 3, 2026
Merged

acl: add Roles, Auth Methods, Binding Rules, bootstrap/login/logout; connect: add Intentions#120
mbrulatout merged 8 commits into
criteo:masterfrom
mbrulatout:acl-phase1-endpoints

Conversation

@mbrulatout

Copy link
Copy Markdown
Contributor

Summary

Closes several of the gaps tracked in ENDPOINT_STATUS.md under the ACL and Connect sections. Adds the following, each as its own commit:

  • ACL Roles — full CRUD (consul/api/acl/role.py)
  • ACL Auth Methods — full CRUD (consul/api/acl/auth_method.py)
  • ACL Binding Rules — full CRUD (consul/api/acl/binding_rule.py)
  • ACL.bootstrap() / ACL.login() / ACL.logout() and Token.read_self()
  • Connect.Intentions — the current exact-match intentions model (Consul 1.9+). The deprecated pre-1.9 UUID-based CRUD model is intentionally not implemented.

Response shapes are typed via TypedDict (e.g. AclRole, AclAuthMethod, AclBindingRule, AclToken, Intention) rather than a new runtime dependency like pydantic — this keeps the library dependency-free for consumers while still giving mypy/IDE support.

Explicitly out of scope: anything Enterprise-only (Namespaces, Admin Partitions, and the Enterprise-only slices of Operator).

Test plan

  • mypy consul/ — clean
  • ruff check . / ruff format --diff . — clean
  • New integration tests in tests/api/test_acl.py and tests/api/test_connect.py, run against real dockerized Consul (1.20.6/1.21.5/1.22.0) via the existing acl_consul fixture — 60/60 passing
  • Auth Method / Binding Rule tests use a static offline RSA public key with a jwt-type auth method, so they don't require a live OIDC/Kubernetes backend
  • Full existing test suite re-run — no regressions introduced by this change

Mathias Brulatout added 8 commits July 2, 2026 09:37
Adds full CRUD for /v1/acl/role and /v1/acl/roles, matching the
CB.json()/CB.boolean() + prepare_headers() pattern used by the
existing ACL resources. Response shapes are typed via TypedDict
(AclRole, AclServiceIdentity, AclNodeIdentity), not a new runtime
dependency.
Adds full CRUD for /v1/acl/auth-method and /v1/acl/auth-methods.
Tests configure an offline "jwt" auth method with a static RSA
public key so CRUD can be exercised without a real OIDC/Kubernetes
backend.
Adds full CRUD for /v1/acl/binding-rule and /v1/acl/binding-rules,
including the "authmethod" list filter. bind_type is constrained to
the current valid enum via typing.Literal.
Adds ACL.bootstrap(), ACL.login(), ACL.logout() (top-level ACL
operations, not tied to a specific resource) and Token.read_self()
for GET /v1/acl/token/self. Also adds an AclToken TypedDict and
typed return values across the rest of token.py while the file was
already being touched for this change.
Adds Connect.Intentions (upsert/read/delete/list/check/match) using
the current exact-match intentions API introduced in Consul 1.9.
The deprecated pre-1.9 UUID-based CRUD model is intentionally not
implemented.
The mypy pre-commit hook ran with --non-interactive --install-types,
which makes mypy shell out to `python -m pip install <stub-pkgs>`
whenever it hits an import with missing type stubs. CI's tox-uv
environments are created via `uv venv`, which does not include pip,
so that subprocess fails; depending on how pre-commit batches the
file list across invocations, this crashes mypy with an unhandled
INTERNAL ERROR instead of just skipping the stub suggestion.

Removing both flags (--non-interactive only has meaning paired with
--install-types) lets mypy fall back to Any for the handful of
transitively-imported, stub-less third-party modules instead of
trying to self-heal an environment that has no pip. Verified via
`pre-commit run --all-files` locally: ruff / ruff format / pylint /
mypy all pass.
The previous fix (dropping --install-types) didn't resolve CI's
mypy INTERNAL ERROR crashes, and they're not reproducible locally
across several attempts (directory mode, explicit file-list mode,
full pre-commit run). Adding --show-traceback so the next CI run
surfaces the actual Python exception instead of just "please use
--show-traceback to print a traceback when reporting a bug".
The --show-traceback diagnostic commit revealed the actual crash:

  File "mypy/metastore.py", line 174, in connect_db
  sqlite3.OperationalError: database is locked

mypy 2.x uses a sqlite-backed cache metastore, which is failing to
acquire its lock in CI's runner environment. This wasn't
reproducible locally (tried directory mode, explicit file-list
mode, full `pre-commit run --all-files`) since a single-writer,
unconstrained local filesystem doesn't hit whatever CI's runner
does (sandboxed/overlay filesystem locking semantics, or genuine
concurrent cache writers).

--no-sqlite-cache forces mypy back to its plain JSON-file cache,
which has no OS-level file-locking requirement and sidesteps the
issue regardless of root cause. Verified locally via a full
`pre-commit run --all-files`: ruff / ruff format / pylint / mypy
all pass.
@mbrulatout
mbrulatout merged commit bfd7635 into criteo:master Jul 3, 2026
5 checks passed
@mbrulatout
mbrulatout deleted the acl-phase1-endpoints branch July 3, 2026 08:10
mbrulatout added a commit that referenced this pull request Jul 3, 2026
* api: add generic Config Entries CRUD (/v1/config)

Adds consul.config for the generic Kind/Name-based config entry
endpoint (PUT/GET/GET-list/DELETE /v1/config), covering all config
entry kinds (service-defaults, service-router, proxy-defaults, mesh,
ingress-gateway, etc.) through one implementation rather than
modeling each kind's schema individually. cas (check-and-set)
support included on both set() and delete().

Note: DELETE /v1/config/:kind/:name returns an empty object when no
cas is given, but a real JSON boolean when cas is passed -- callback
choice (CB.boolean vs CB.json) branches on that.

* api: add Discovery Chain endpoint (/v1/discovery-chain)

Adds consul.discovery_chain.get(), covering both the plain GET
(no overrides) and the POST variant Consul requires when passing
OverrideConnectTimeout/OverrideProtocol/OverrideMeshGateway.

* ci: carry over mypy --no-sqlite-cache fix from PR #120

master doesn't have this fix yet (PR #120 is still pending review),
so any branch cut from master right now hits the same
sqlite3.OperationalError: database is locked INTERNAL ERROR in CI.
Carrying the fix here too so this PR's CI is green independently.
Trivial to reconcile on rebase once #120 merges.

---------

Co-authored-by: Mathias Brulatout <m.brulatout@criteo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants