feat: Allow for external services and declarative port binding#69
feat: Allow for external services and declarative port binding#69scottpledger wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3d42ba6037
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if service.Type == "external_service" { | ||
| return service.WaitUntilHealthy(ctx) |
There was a problem hiding this comment.
Honor health_check_timeout for external services
When an itest_external_service has an HTTP or command health check that keeps failing and health_check_timeout is set, this early return calls WaitUntilHealthy before the timeout-wrapping block below runs. The external-service health loop therefore polls until the outer Bazel/test timeout instead of failing after the configured service timeout, which makes unreachable external dependencies hang much longer than requested.
Useful? React with 👍 / 👎.
| "so_reuseport_aware": ctx.attr.so_reuseport_aware, | ||
| "deferred": ctx.attr.deferred, | ||
| "domain": ctx.attr.domain, | ||
| "port_bindings": _compute_port_bindings(ctx), |
There was a problem hiding this comment.
Allow SO_REUSEPORT with declarative ports
When a service uses only the new ports = {":p": "name"} binding, _compute_port_bindings(ctx) still creates an autoassigned socket, but the validation above only allows so_reuseport_aware with legacy autoassign_port or named_ports. As a result, users adopting first-class itest_port targets cannot enable the collision-avoidance mode for those autoassigned ports and get an analysis failure even though the runtime path supports SoReuseportAware for all bindings.
Useful? React with 👍 / 👎.
|
Thanks for sending this! I've definitely considered a first class port target in the past and I'm excited to look at what you've cooked up here. It might take me a bit to get to it but wanted to ack the PR |
3d42ba6 to
7337a01
Compare
No problem! This is something I've been thinking about for a while, given how we use this library at my company. We currently just create multiple test targets - one for local, test, preview, and prod. However, this approach doesn't give us the flexibility to mix and match service locations (eg, a local web server with test remote APIs). |
Allows services to be described in more detail than just as a local port, so a test suite can run against either locally-managed services or production-like instances (selected with Bazel
select()).Important changes/features
itest_port— declare a port as a first-class target. The binding info (value + host) is supplied by whichever service binds it, and a port can only be bound once.itest_external_service— point at a fixed FQDN instead of a locally-spawned binary. It exposes the same provider asitest_service, so it's a drop-in replacement viaselect(). The manager never starts/stops it (optional health check only).itest_servicegains domain (default127.0.0.1) and aportsattribute.ITEST_PORTS_MAPandITEST_SERVICES_MAP(string-encoded JSON) describe every port/service with{origin, domain, port}, injected into the test and all child services.GET /v0/portsandGET /v0/serviceslist this for all services at once.I also made this fully backward-compatible: existing macros auto-create the needed ports/aliases;
port(),ASSIGNED_PORTS,GET_ASSIGNED_PORT_BIN,/v0/port, and--//pkg:svc.port=Noverrides all still work. The underlying rules are now exported for extension.Example
The test can then read the connection info from either:
ITEST_PORTS_MAP(or/v0/ports) (keyed by port target label, plus any aliases):{ "@@//myapp:db_port": { "origin": "127.0.0.1:54321", "domain": "127.0.0.1", "port": "54321" } }--//myapp:use_external_db, the same keys instead resolve to the production-like instance:{ "@@//myapp:db_port": { "origin": "db.staging.mycompany.com:5432", "domain": "db.staging.mycompany.com", "port": "5432" } }ITEST_SERVICES_MAP(or/v0/services), e.g.:{ "@@//myapp:db": { "sql": { "origin": "127.0.0.1:54321", "domain": "127.0.0.1", "port": "54321" } } }Note: in the example above,
:db_externaland:dbboth bind:db_port— that's valid becauseselect()resolves to only one of them at a time in theservice_testrule, so the port is still only bound once.Testing
Added
tests/ports, which covers internal ports, an external service, theselect()swap, the new maps/endpoints, and ensuring ports & aliases are only ever bound once. The full test suite passes (48/48), and the examples build cleanly.Caveats
/my/appor something. I don't think this would be too hard to add in a follow-up, though.Solves: #70