-
Notifications
You must be signed in to change notification settings - Fork 66
[ip_version/pool allocation] Refactor IP pool selection to use tagged enums (type-safe API) #9598
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
… enums (type-safe API)
This PR refactors `FloatingIpCreate`, `EphemeralIpCreate`, `ExternalIpCreate::Ephemeral`,
and `ProbeCreate` to use tagged enums for pool selection, making invalid states
unrepresentable at the type level.
Previously, these types used flat `Option` fields (`pool`, `ip_version`, `ip`)
that required runtime validation to catch invalid combinations like
`ip_version + pool` or `ip + ip_version`. The new design uses:
- `PoolSelection` enum with `Named { pool }` and `Default { ip_version }`
variants, used by `EphemeralIpCreate`, `ExternalIpCreate::Ephemeral`,
and `ProbeCreate`
- `FloatingIpAllocation` enum with `Explicit { ip, pool }` and
`Auto { pool_selection }` variants for `FloatingIpCreate`
This ensures at the type level that...
- `ip_version` can only be specified when using the default pool
- `ip_version` cannot be combined with an explicit IP address
- Named pools don't accept `ip_version` (the pool determines available IPs)
The `Explicit { ip, pool }` combination remains valid for reserving
an exact IP, validating it exists in this pool (pool is a constraint, not
a selection method).
API versioning: Added v2026010300.rs with the old flat types and `TryFrom`
implementations that validate and convert to the new enum-based types for delegation.
Older API versions continue to work via the existing conversion chain.
Note: ProbeCreate in the experimental API previously lacked ip_version support; this is now addressed using the enum approach.
|
I like the general idea. I think I prefer something more like "selector" over "allocation" for both levels. It looks like you're trying to avoid calling them both the same thing, but maybe it's ok if one is Generated diff for TS clientdiff --git a/tmp/api-diff/2e00484cf882af3c450d1cb3124677c6a4d0d374/nexus-2026010300.0.0-7599dd.json/Api.ts b/tmp/api-diff/2e00484cf882af3c450d1cb3124677c6a4d0d374/nexus-2026010500.0.0-423571.json/Api.ts
index 989e2d63..adab6be3 100644
--- a/tmp/api-diff/2e00484cf882af3c450d1cb3124677c6a4d0d374/nexus-2026010300.0.0-7599dd.json/Api.ts
+++ b/tmp/api-diff/2e00484cf882af3c450d1cb3124677c6a4d0d374/nexus-2026010500.0.0-423571.json/Api.ts
@@ -1931,14 +1931,31 @@ export type Distributionint64 = {
*/
export type IpVersion = "v4" | "v6";
+/**
+ * Pool selection for IP address allocation.
+ *
+ * This enum makes invalid states unrepresentable: `ip_version` is only relevant when using the default pool.
+ */
+export type PoolSelection =
+ /** Use the specified pool by name or ID. */
+ | {
+ /** The pool to allocate from. */
+ pool: NameOrId;
+ type: "named";
+ }
+ /** Use the default pool for the silo. */
+ | {
+ /** IP version to use when multiple default pools exist. Required if both IPv4 and IPv6 default pools are configured. */
+ ipVersion?: IpVersion | null;
+ type: "default";
+ };
+
/**
* Parameters for creating an ephemeral IP address for an instance.
*/
export type EphemeralIpCreate = {
- /** IP version to use when allocating from the default pool. Only used when `pool` is not specified. Required if multiple default pools of different IP versions exist. Allocation fails if no pool of the requested version is available. */
- ipVersion?: IpVersion | null;
- /** Name or ID of the IP pool used to allocate an address. If unspecified, the default IP pool will be used. */
- pool?: NameOrId | null;
+ /** Pool to allocate from. */
+ poolSelection?: PoolSelection;
};
export type ExternalIp =
@@ -1984,11 +2001,10 @@ SNAT addresses are ephemeral addresses used only for outbound connectivity. */
* Parameters for creating an external IP address for instances.
*/
export type ExternalIpCreate =
- /** An IP address providing both inbound and outbound access. The address is automatically assigned from the provided IP pool or the default IP pool if not specified. */
+ /** An IP address providing both inbound and outbound access. The address is automatically assigned from a pool. */
| {
- /** IP version to use when allocating from the default pool. Only used when `pool` is not specified. Required if multiple default pools of different IP versions exist. Allocation fails if no pool of the requested version is available. */
- ipVersion?: IpVersion | null;
- pool?: NameOrId | null;
+ /** Pool to allocate from. */
+ poolSelection?: PoolSelection;
type: "ephemeral";
}
/** An IP address providing both inbound and outbound access. The address is an existing floating IP object assigned to the current project.
@@ -2115,6 +2131,27 @@ export type FloatingIp = {
timeModified: Date;
};
+/**
+ * How to allocate a floating IP address.
+ *
+ * This enum makes invalid states unrepresentable: when specifying an explicit IP, there's no `ip_version` field (the IP determines its version). The `ip_version` preference is only available when auto-allocating.
+ */
+export type FloatingIpAllocation =
+ /** Reserve a specific IP address. */
+ | {
+ /** The specific IP address to reserve. Must be available in the pool. */
+ ip: string;
+ /** The pool containing this address. If not specified, the default pool for this IP's version is used. */
+ pool?: NameOrId | null;
+ type: "explicit";
+ }
+ /** Automatically allocate an IP address based on pool selection. */
+ | {
+ /** How to select the pool for allocation. */
+ poolSelection?: PoolSelection;
+ type: "auto";
+ };
+
/**
* The type of resource that a floating IP is attached to
*/
@@ -2134,14 +2171,10 @@ export type FloatingIpAttach = {
* Parameters for creating a new floating IP address for instances.
*/
export type FloatingIpCreate = {
+ /** How to allocate the floating IP address. */
+ allocation?: FloatingIpAllocation;
description: string;
- /** An IP address to reserve for use as a floating IP. This field is optional: when not set, an address will be automatically chosen from `pool`. If set, then the IP must be available in the resolved `pool`. */
- ip?: string | null;
- /** IP version to use when allocating from the default pool. Only used when both `ip` and `pool` are not specified. Required if multiple default pools of different IP versions exist. Allocation fails if no pool of the requested version is available. */
- ipVersion?: IpVersion | null;
name: Name;
- /** The parent IP pool that a floating IP is pulled from. If unset, the default pool is selected. */
- pool?: NameOrId | null;
};
/**
@@ -3537,8 +3570,9 @@ export type Probe = {
*/
export type ProbeCreate = {
description: string;
- ipPool?: NameOrId | null;
name: Name;
+ /** Pool selection for allocating an ephemeral IP to the probe. */
+ poolSelection?: PoolSelection;
sled: string;
};
@@ -7048,7 +7082,7 @@ export class Api {
* Pulled from info.version in the OpenAPI schema. Sent in the
* `api-version` header on all requests.
*/
- apiVersion = "2026010300.0.0";
+ apiVersion = "2026010500.0.0";
constructor({ host = "", baseParams = {}, token }: ApiConfig = {}) {
this.host = host;
|
…dressSelector Renames the tagged enums for IP pool and address allocation to use consistent "Selector" suffix: - `PoolSelection` -> `PoolSelector` - `FloatingIpAllocation` → `AddressSelector` - `pool_selection` → `pool_selector` (field in AddressSelector::Auto, ExternalIpCreate::Ephemeral, EphemeralIpCreate, ProbeCreate) - `allocation` → `address_selector` (field in FloatingIpCreate) This addresses review feedback requesting consistent naming between the two related enums. While "Selector" is used elsewhere in the API for path/query parameter structs (e.g., ProjectSelector, FloatingIpSelector), these enums serve a similar "selection" purpose in request bodies, and the consistent naming improves API ergonomics. Note: `selector` is a bit overused, but choosing consistency seems better here.
ahl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it! what do you think?
Pulled this change into the CLI to check it out:
@@ -278,8 +278,9 @@ impl crate::AuthenticatedCmd for CmdInstanceFromImage {
size: self.size.clone(),
})
.external_ips(vec![ExternalIpCreate::Ephemeral {
- ip_version: Some(IpVersion::V4),
- pool: None,
+ pool_selector: PoolSelector::Default {
+ ip_version: Some(IpVersion::V4),
+ },
}])
.hostname(self.hostname.clone())
.memory(self.memory.clone())Seems nice?
|
@ahl, @david-crespo good to merge? |
agreed; fire when ready |
This PR refactors
FloatingIpCreate,EphemeralIpCreate,ExternalIpCreate::Ephemeral, andProbeCreateto use tagged enums for pool selection, making invalid states unrepresentable at the type level.Previously, these types used flat
Optionfields (pool,ip_version,ip) that required runtime validation to catch invalid combinations likeip_version + poolorip + ip_version. The new design uses:PoolSelectionenum withNamed { pool }andDefault { ip_version }variants, used byEphemeralIpCreate,ExternalIpCreate::Ephemeral, andProbeCreateFloatingIpAllocationenum withExplicit { ip, pool }andAuto { pool_selection }variants forFloatingIpCreateThis ensures at the type level that...
ip_versioncan only be specified when using the default poolip_versioncannot be combined with an explicit IP addressip_version(the pool determines available IPs)The
Explicit { ip, pool }combination remains valid for reserving an exact IP, validating it exists in this pool (pool is a constraint, not a selection method).API versioning: Added v2026010300.rs with the old flat types and
TryFromimplementations that validate and convert to the new enum-based types for delegation. Older API versions continue to work via the existing conversion chain.ProbeCreatein the experimental API previously lacked ip_version support; this is now addressed using the enum approach.