diff --git a/.gitignore b/.gitignore index 7d7d83c..fcb0f42 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ packages/openpi-gen/node_modules node_modules dist -spec.json .eslintcache diff --git a/OMICRON_VERSION b/OMICRON_VERSION index ad0d5dd..2a5ae49 100644 --- a/OMICRON_VERSION +++ b/OMICRON_VERSION @@ -1 +1 @@ -a6e111cf72ab987b2ea5acd7d26610ea2a55bf0f +135be59591f1ba4bc5941f63bf3a08a0b187b1a9 diff --git a/oxide-api/src/Api.ts b/oxide-api/src/Api.ts index 38a5d48..a752e2d 100644 --- a/oxide-api/src/Api.ts +++ b/oxide-api/src/Api.ts @@ -168,6 +168,49 @@ export type AddressLotViewResponse = { lot: AddressLot; }; +/** + * The IP address version. + */ +export type IpVersion = "v4" | "v6"; + +/** + * Specify which IP pool to allocate from. + */ +export type PoolSelector = + /** Use the specified pool by name or ID. */ + | { + /** The pool to allocate from. */ + pool: NameOrId; + type: "explicit"; + } + /** 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: "auto"; + }; + +/** + * Specify how to allocate a floating IP address. + */ +export type AddressSelector = + /** Reserve a specific IP address. */ + | { + /** The IP address to reserve. Must be available in the pool. */ + ip: string; + /** The pool containing this address. If not specified, the default pool for the address's IP version is used. */ + pool?: NameOrId | null; + type: "explicit"; + } + /** Automatically allocate an IP address from a specified pool. */ + | { + /** Pool selection. + +If omitted, this field uses the silo's default pool. If the silo has default pools for both IPv4 and IPv6, the request will fail unless `ip_version` is specified in the pool selector. */ + poolSelector?: PoolSelector; + type: "auto"; + }; + /** * Describes the scope of affinity for the purposes of co-location. */ @@ -960,6 +1003,9 @@ export type BgpPeerState = /** Waiting for keepaliave or notification from peer. */ | "open_confirm" + /** There is an ongoing Connection Collision that hasn't yet been resolved. Two connections are maintained until one connection receives an Open or is able to progress into Established. */ + | "connection_collision" + /** Synchronizing with peer. */ | "session_setup" @@ -1773,7 +1819,11 @@ export type DeviceAccessTokenResultsPage = { export type DeviceAuthRequest = { clientId: string; - /** Optional lifetime for the access token in seconds. If not specified, the silo's max TTL will be used (if set). */ + /** Optional lifetime for the access token in seconds. + +This value will be validated during the confirmation step. If not specified, it defaults to the silo's max TTL, which can be seen at `/v1/auth-settings`. If specified, must not exceed the silo's max TTL. + +Some special logic applies when authenticating the confirmation request with an existing device token: the requested TTL must not produce an expiration time later than the authenticating token's expiration. If no TTL is specified, the expiration will be the lesser of the silo max and the authenticating token's expiration time. To get the longest allowed lifetime, omit the TTL and authenticate with a web console session. */ ttlSeconds?: number | null; }; @@ -1781,6 +1831,8 @@ export type DeviceAuthVerify = { userCode: string }; export type Digest = { type: "sha256"; value: string }; +export type DiskType = "distributed" | "local"; + /** * State of a Disk */ @@ -1818,6 +1870,7 @@ export type Disk = { /** human-readable free-form text about a resource */ description: string; devicePath: string; + diskType: DiskType; /** unique, immutable, system-controlled identifier for each resource */ id: string; /** ID of image from which disk was created, if any */ @@ -1836,7 +1889,7 @@ export type Disk = { }; /** - * Different sources for a disk + * Different sources for a Distributed Disk */ export type DiskSource = /** Create a blank disk */ @@ -1852,13 +1905,24 @@ export type DiskSource = /** Create a blank disk that will accept bulk writes or pull blocks from an external source. */ | { blockSize: BlockSize; type: "importing_blocks" }; +/** + * The source of a `Disk`'s blocks + */ +export type DiskBackend = + | { type: "local" } + | { + /** The initial source for this disk */ + diskSource: DiskSource; + type: "distributed"; + }; + /** * Create-time parameters for a `Disk` */ export type DiskCreate = { description: string; - /** The initial source for this disk */ - diskSource: DiskSource; + /** The source for this `Disk`'s blocks */ + diskBackend: DiskBackend; name: Name; /** The total size of the Disk (in bytes) */ size: ByteCount; @@ -1889,9 +1953,9 @@ export type Distributiondouble = { counts: number[]; max?: number | null; min?: number | null; - p50?: Quantile | null; - p90?: Quantile | null; - p99?: Quantile | null; + p50?: number | null; + p90?: number | null; + p99?: number | null; squaredMean: number; sumOfSamples: number; }; @@ -1906,9 +1970,9 @@ export type Distributionint64 = { counts: number[]; max?: number | null; min?: number | null; - p50?: Quantile | null; - p90?: Quantile | null; - p99?: Quantile | null; + p50?: number | null; + p90?: number | null; + p99?: number | null; squaredMean: number; sumOfSamples: number; }; @@ -1917,8 +1981,8 @@ export type Distributionint64 = { * Parameters for creating an ephemeral IP address for an instance. */ export type EphemeralIpCreate = { - /** 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. */ + poolSelector?: PoolSelector; }; export type ExternalIp = @@ -1964,8 +2028,12 @@ 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. */ - | { pool?: NameOrId | null; type: "ephemeral" } + /** An IP address providing both inbound and outbound access. The address is automatically assigned from a pool. */ + | { + /** Pool to allocate from. */ + poolSelector?: PoolSelector; + type: "ephemeral"; + } /** An IP address providing both inbound and outbound access. The address is an existing floating IP object assigned to the current project. The floating IP must not be in use by another instance or service. */ @@ -2109,12 +2177,10 @@ export type FloatingIpAttach = { * Parameters for creating a new floating IP address for instances. */ export type FloatingIpCreate = { + /** IP address allocation method. */ + addressSelector?: AddressSelector; 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; name: Name; - /** The parent IP pool that a floating IP is pulled from. If unset, the default pool is selected. */ - pool?: NameOrId | null; }; /** @@ -2357,8 +2423,8 @@ export type InstanceDiskAttachment = /** During instance creation, create and attach disks */ | { description: string; - /** The initial source for this disk */ - diskSource: DiskSource; + /** The source for this `Disk`'s blocks */ + diskBackend: DiskBackend; name: Name; /** The total size of the Disk (in bytes) */ size: ByteCount; @@ -2371,18 +2437,70 @@ export type InstanceDiskAttachment = type: "attach"; }; +/** + * How a VPC-private IP address is assigned to a network interface. + */ +export type Ipv4Assignment = + /** Automatically assign an IP address from the VPC Subnet. */ + | { type: "auto" } + /** Explicitly assign a specific address, if available. */ + | { type: "explicit"; value: string }; + +/** + * Configuration for a network interface's IPv4 addressing. + */ +export type PrivateIpv4StackCreate = { + /** The VPC-private address to assign to the interface. */ + ip: Ipv4Assignment; + /** Additional IP networks the interface can send / receive on. */ + transitIps?: Ipv4Net[]; +}; + +/** + * How a VPC-private IP address is assigned to a network interface. + */ +export type Ipv6Assignment = + /** Automatically assign an IP address from the VPC Subnet. */ + | { type: "auto" } + /** Explicitly assign a specific address, if available. */ + | { type: "explicit"; value: string }; + +/** + * Configuration for a network interface's IPv6 addressing. + */ +export type PrivateIpv6StackCreate = { + /** The VPC-private address to assign to the interface. */ + ip: Ipv6Assignment; + /** Additional IP networks the interface can send / receive on. */ + transitIps?: Ipv6Net[]; +}; + +/** + * Create parameters for a network interface's IP stack. + */ +export type PrivateIpStackCreate = + /** The interface has only an IPv4 stack. */ + | { type: "v4"; value: PrivateIpv4StackCreate } + /** The interface has only an IPv6 stack. */ + | { type: "v6"; value: PrivateIpv6StackCreate } + /** The interface has both an IPv4 and IPv6 stack. */ + | { + type: "dual_stack"; + value: { v4: PrivateIpv4StackCreate; v6: PrivateIpv6StackCreate }; + }; + /** * Create-time parameters for an `InstanceNetworkInterface` */ export type InstanceNetworkInterfaceCreate = { description: string; - /** The IP address for the interface. One will be auto-assigned if not provided. */ - ip?: string | null; + /** The IP stack configuration for this interface. + +If not provided, a default configuration will be used, which creates a dual-stack IPv4 / IPv6 interface. */ + ipConfig?: PrivateIpStackCreate; name: Name; /** The VPC Subnet in which to create the interface. */ subnetName: Name; - /** A set of additional networks that this interface may send and receive traffic on. */ - transitIps?: IpNet[]; /** The VPC in which to create the interface. */ vpcName: Name; }; @@ -2395,8 +2513,18 @@ export type InstanceNetworkInterfaceAttachment = If more than one interface is provided, then the first will be designated the primary interface for the instance. */ | { params: InstanceNetworkInterfaceCreate[]; type: "create" } - /** The default networking configuration for an instance is to create a single primary interface with an automatically-assigned IP address. The IP will be pulled from the Project's default VPC / VPC Subnet. */ - | { type: "default" } + /** Create a single primary interface with an automatically-assigned IPv4 address. + +The IP will be pulled from the Project's default VPC / VPC Subnet. */ + | { type: "default_ipv4" } + /** Create a single primary interface with an automatically-assigned IPv6 address. + +The IP will be pulled from the Project's default VPC / VPC Subnet. */ + | { type: "default_ipv6" } + /** Create a single primary interface with automatically-assigned IPv4 and IPv6 addresses. + +The IPs will be pulled from the Project's default VPC / VPC Subnet. */ + | { type: "default_dual_stack" } /** No network interfaces at all will be created for the instance. */ | { type: "none" }; @@ -2437,6 +2565,10 @@ By default, all instances have outbound connectivity, but no inbound connectivit hostname: Hostname; /** The amount of RAM (in bytes) to be allocated to the instance */ memory: ByteCount; + /** The multicast groups this instance should join. + +The instance will be automatically added as a member of the specified multicast groups during creation, enabling it to send and receive multicast traffic for those groups. */ + multicastGroups?: NameOrId[]; name: Name; /** The number of vCPUs to be allocated to the instance */ ncpus: InstanceCpuCount; @@ -2452,6 +2584,40 @@ If not provided, all SSH public keys from the user's profile will be sent. If an userData?: string; }; +/** + * The VPC-private IPv4 stack for a network interface + */ +export type PrivateIpv4Stack = { + /** The VPC-private IPv4 address for the interface. */ + ip: string; + /** A set of additional IPv4 networks that this interface may send and receive traffic on. */ + transitIps: Ipv4Net[]; +}; + +/** + * The VPC-private IPv6 stack for a network interface + */ +export type PrivateIpv6Stack = { + /** The VPC-private IPv6 address for the interface. */ + ip: string; + /** A set of additional IPv6 networks that this interface may send and receive traffic on. */ + transitIps: Ipv6Net[]; +}; + +/** + * The VPC-private IP stack for a network interface. + */ +export type PrivateIpStack = + /** The interface has only an IPv4 stack. */ + | { type: "v4"; value: PrivateIpv4Stack } + /** The interface has only an IPv6 stack. */ + | { type: "v6"; value: PrivateIpv6Stack } + /** The interface is dual-stack IPv4 and IPv6. */ + | { + type: "dual_stack"; + value: { v4: PrivateIpv4Stack; v6: PrivateIpv6Stack }; + }; + /** * A MAC address * @@ -2469,8 +2635,8 @@ export type InstanceNetworkInterface = { id: string; /** The Instance to which the interface belongs. */ instanceId: string; - /** The IP address assigned to this interface. */ - ip: string; + /** The VPC-private IP stack for this interface. */ + ipStack: PrivateIpStack; /** The MAC address assigned to this interface. */ mac: MacAddr; /** unique, mutable, user-controlled identifier for each resource */ @@ -2483,8 +2649,6 @@ export type InstanceNetworkInterface = { timeCreated: Date; /** timestamp when this resource was last modified */ timeModified: Date; - /** A set of additional networks that this interface may send and receive traffic on. */ - transitIps?: IpNet[]; /** The VPC to which the interface belongs. */ vpcId: string; }; @@ -2513,7 +2677,7 @@ If applied to a secondary interface, that interface will become the primary on t Note that this can only be used to select a new primary interface for an instance. Requests to change the primary interface into a secondary will return an error. */ primary?: boolean; - /** A set of additional networks that this interface may send and receive traffic on. */ + /** A set of additional networks that this interface may send and receive traffic on */ transitIps?: IpNet[]; }; @@ -2557,6 +2721,12 @@ An instance that does not have a boot disk set will use the boot options specifi cpuPlatform: InstanceCpuPlatform | null; /** The amount of RAM (in bytes) to be allocated to the instance */ memory: ByteCount; + /** Multicast groups this instance should join. + +When specified, this replaces the instance's current multicast group membership with the new set of groups. The instance will leave any groups not listed here and join any new groups that are specified. + +If not provided (None), the instance's multicast group membership will not be changed. */ + multicastGroups?: NameOrId[] | null; /** The number of vCPUs to be allocated to the instance */ ncpus: InstanceCpuCount; }; @@ -2677,11 +2847,6 @@ export type InternetGatewayResultsPage = { nextPage?: string | null; }; -/** - * The IP address version. - */ -export type IpVersion = "v4" | "v6"; - /** * Type of IP pool. */ @@ -2706,7 +2871,7 @@ export type IpPool = { ipVersion: IpVersion; /** unique, mutable, user-controlled identifier for each resource */ name: Name; - /** Type of IP pool (unicast or multicast) */ + /** Type of IP pool (unicast or multicast). */ poolType: IpPoolType; /** timestamp when this resource was created */ timeCreated: Date; @@ -2733,7 +2898,9 @@ The default is IPv4. */ }; export type IpPoolLinkSilo = { - /** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. There can be at most one default for a given silo. */ + /** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. + +A silo can have at most one default pool per combination of pool type (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 default pools total. */ isDefault: boolean; silo: NameOrId; }; @@ -2786,7 +2953,9 @@ export type IpPoolResultsPage = { */ export type IpPoolSiloLink = { ipPoolId: string; - /** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. There can be at most one default for a given silo. */ + /** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. + +A silo can have at most one default pool per combination of pool type (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 default pools total. */ isDefault: boolean; siloId: string; }; @@ -2802,7 +2971,9 @@ export type IpPoolSiloLinkResultsPage = { }; export type IpPoolSiloUpdate = { - /** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. There can be at most one default for a given silo, so when a pool is made default, an existing default will remain linked but will no longer be the default. */ + /** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. + +A silo can have at most one default pool per combination of pool type (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 default pools total. When a pool is made default, an existing default of the same type and version will remain linked but will no longer be the default. */ isDefault: boolean; }; @@ -3022,7 +3193,7 @@ export type LoopbackAddressCreate = { anycast: boolean; /** The subnet mask to use for the address. */ mask: number; - /** The containing the switch this loopback address will be configured on. */ + /** The rack containing the switch this loopback address will be configured on. */ rackId: string; /** The location of the switch within the rack this loopback address will be configured on. */ switchLocation: Name; @@ -3066,6 +3237,156 @@ export type MetricType = /** The value represents an accumulation between two points in time. */ | "cumulative"; +/** + * View of a Multicast Group + */ +export type MulticastGroup = { + /** human-readable free-form text about a resource */ + description: string; + /** unique, immutable, system-controlled identifier for each resource */ + id: string; + /** The ID of the IP pool this resource belongs to. */ + ipPoolId: string; + /** The multicast IP address held by this resource. */ + multicastIp: string; + /** Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. None means no VLAN tagging on egress. */ + mvlan?: number | null; + /** unique, mutable, user-controlled identifier for each resource */ + name: Name; + /** Source IP addresses for Source-Specific Multicast (SSM). Empty array means any source is allowed. */ + sourceIps: string[]; + /** Current state of the multicast group. */ + state: string; + /** timestamp when this resource was created */ + timeCreated: Date; + /** timestamp when this resource was last modified */ + timeModified: Date; +}; + +/** + * Create-time parameters for a multicast group. + */ +export type MulticastGroupCreate = { + description: string; + /** The multicast IP address to allocate. If None, one will be allocated from the default pool. */ + multicastIp?: string | null; + /** Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. Tags packets leaving the rack to traverse VLAN-segmented upstream networks. + +Valid range: 2-4094 (VLAN IDs 0-1 are reserved by IEEE 802.1Q standard). */ + mvlan?: number | null; + name: Name; + /** Name or ID of the IP pool to allocate from. If None, uses the default multicast pool. */ + pool?: NameOrId | null; + /** Source IP addresses for Source-Specific Multicast (SSM). + +None uses default behavior (Any-Source Multicast). Empty list explicitly allows any source (Any-Source Multicast). Non-empty list restricts to specific sources (SSM). */ + sourceIps?: string[] | null; +}; + +/** + * View of a Multicast Group Member (instance belonging to a multicast group) + */ +export type MulticastGroupMember = { + /** human-readable free-form text about a resource */ + description: string; + /** unique, immutable, system-controlled identifier for each resource */ + id: string; + /** The ID of the instance that is a member of this group. */ + instanceId: string; + /** The ID of the multicast group this member belongs to. */ + multicastGroupId: string; + /** unique, mutable, user-controlled identifier for each resource */ + name: Name; + /** Current state of the multicast group membership. */ + state: string; + /** timestamp when this resource was created */ + timeCreated: Date; + /** timestamp when this resource was last modified */ + timeModified: Date; +}; + +/** + * Parameters for adding an instance to a multicast group. + */ +export type MulticastGroupMemberAdd = { + /** Name or ID of the instance to add to the multicast group */ + instance: NameOrId; +}; + +/** + * A single page of results + */ +export type MulticastGroupMemberResultsPage = { + /** list of items on this page of results */ + items: MulticastGroupMember[]; + /** token used to fetch the next page of results (if any) */ + nextPage?: string | null; +}; + +/** + * A single page of results + */ +export type MulticastGroupResultsPage = { + /** list of items on this page of results */ + items: MulticastGroup[]; + /** token used to fetch the next page of results (if any) */ + nextPage?: string | null; +}; + +/** + * Update-time parameters for a multicast group. + */ +export type MulticastGroupUpdate = { + description?: string | null; + /** Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. Set to null to clear the MVLAN. Valid range: 2-4094 when provided. Omit the field to leave mvlan unchanged. */ + mvlan?: number | null; + name?: Name | null; + sourceIps?: string[] | null; +}; + +/** + * VPC-private IPv4 configuration for a network interface. + */ +export type PrivateIpv4Config = { + /** VPC-private IP address. */ + ip: string; + /** The IP subnet. */ + subnet: Ipv4Net; + /** Additional networks on which the interface can send / receive traffic. */ + transitIps?: Ipv4Net[]; +}; + +/** + * VPC-private IPv6 configuration for a network interface. + */ +export type PrivateIpv6Config = { + /** VPC-private IP address. */ + ip: string; + /** The IP subnet. */ + subnet: Ipv6Net; + /** Additional networks on which the interface can send / receive traffic. */ + transitIps: Ipv6Net[]; +}; + +/** + * VPC-private IP address configuration for a network interface. + */ +export type PrivateIpConfig = + /** The interface has only an IPv4 configuration. */ + | { type: "v4"; value: PrivateIpv4Config } + /** The interface has only an IPv6 configuration. */ + | { type: "v6"; value: PrivateIpv6Config } + /** The interface is dual-stack. */ + | { + type: "dual_stack"; + value: { + /** The interface's IPv4 configuration. */ + v4: PrivateIpv4Config; + /** The interface's IPv6 configuration. */ + v6: PrivateIpv6Config; + }; + }; + /** * The type of network interface */ @@ -3087,14 +3408,12 @@ export type Vni = number; */ export type NetworkInterface = { id: string; - ip: string; + ipConfig: PrivateIpConfig; kind: NetworkInterfaceKind; mac: MacAddr; name: Name; primary: boolean; slot: number; - subnet: IpNet; - transitIps?: IpNet[]; vni: Vni; }; @@ -3257,8 +3576,9 @@ export type Probe = { */ export type ProbeCreate = { description: string; - ipPool?: NameOrId | null; name: Name; + /** Pool to allocate from. */ + poolSelector?: PoolSelector; sled: string; }; @@ -3700,10 +4020,16 @@ export type SiloIpPool = { description: string; /** unique, immutable, system-controlled identifier for each resource */ id: string; - /** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. There can be at most one default for a given silo. */ + /** The IP version for the pool. */ + ipVersion: IpVersion; + /** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. + +A silo can have at most one default pool per combination of pool type (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 default pools total. */ isDefault: boolean; /** unique, mutable, user-controlled identifier for each resource */ name: Name; + /** Type of IP pool (unicast or multicast). */ + poolType: IpPoolType; /** timestamp when this resource was created */ timeCreated: Date; /** timestamp when this resource was last modified */ @@ -5659,6 +5985,32 @@ export interface InstanceEphemeralIpDetachQueryParams { project?: NameOrId; } +export interface InstanceMulticastGroupListPathParams { + instance: NameOrId; +} + +export interface InstanceMulticastGroupListQueryParams { + project?: NameOrId; +} + +export interface InstanceMulticastGroupJoinPathParams { + instance: NameOrId; + multicastGroup: NameOrId; +} + +export interface InstanceMulticastGroupJoinQueryParams { + project?: NameOrId; +} + +export interface InstanceMulticastGroupLeavePathParams { + instance: NameOrId; + multicastGroup: NameOrId; +} + +export interface InstanceMulticastGroupLeaveQueryParams { + project?: NameOrId; +} + export interface InstanceRebootPathParams { instance: NameOrId; } @@ -5855,6 +6207,51 @@ export interface SiloMetricQueryParams { project?: NameOrId; } +export interface MulticastGroupListQueryParams { + limit?: number | null; + pageToken?: string | null; + sortBy?: NameOrIdSortMode; +} + +export interface MulticastGroupViewPathParams { + multicastGroup: NameOrId; +} + +export interface MulticastGroupUpdatePathParams { + multicastGroup: NameOrId; +} + +export interface MulticastGroupDeletePathParams { + multicastGroup: NameOrId; +} + +export interface MulticastGroupMemberListPathParams { + multicastGroup: NameOrId; +} + +export interface MulticastGroupMemberListQueryParams { + limit?: number | null; + pageToken?: string | null; + sortBy?: IdSortMode; +} + +export interface MulticastGroupMemberAddPathParams { + multicastGroup: NameOrId; +} + +export interface MulticastGroupMemberAddQueryParams { + project?: NameOrId; +} + +export interface MulticastGroupMemberRemovePathParams { + instance: NameOrId; + multicastGroup: NameOrId; +} + +export interface MulticastGroupMemberRemoveQueryParams { + project?: NameOrId; +} + export interface InstanceNetworkInterfaceListQueryParams { instance?: NameOrId; limit?: number | null; @@ -6209,6 +6606,10 @@ export interface SystemMetricQueryParams { silo?: NameOrId; } +export interface LookupMulticastGroupByIpPathParams { + address: string; +} + export interface NetworkingAddressLotListQueryParams { limit?: number | null; pageToken?: string | null; @@ -6687,7 +7088,7 @@ export class Api { * Pulled from info.version in the OpenAPI schema. Sent in the * `api-version` header on all requests. */ - apiVersion = "20251008.0.0"; + apiVersion = "2026010500.0.0"; constructor({ host = "", baseParams = {}, token }: ApiConfig = {}) { this.host = host; @@ -8212,6 +8613,66 @@ export class Api { ...params, }); }, + /** + * List multicast groups for instance + */ + instanceMulticastGroupList: ( + { + path, + query = {}, + }: { + path: InstanceMulticastGroupListPathParams; + query?: InstanceMulticastGroupListQueryParams; + }, + params: FetchParams = {}, + ) => { + return this.request({ + path: `/v1/instances/${path.instance}/multicast-groups`, + method: "GET", + query, + ...params, + }); + }, + /** + * Join multicast group. + */ + instanceMulticastGroupJoin: ( + { + path, + query = {}, + }: { + path: InstanceMulticastGroupJoinPathParams; + query?: InstanceMulticastGroupJoinQueryParams; + }, + params: FetchParams = {}, + ) => { + return this.request({ + path: `/v1/instances/${path.instance}/multicast-groups/${path.multicastGroup}`, + method: "PUT", + query, + ...params, + }); + }, + /** + * Leave multicast group. + */ + instanceMulticastGroupLeave: ( + { + path, + query = {}, + }: { + path: InstanceMulticastGroupLeavePathParams; + query?: InstanceMulticastGroupLeaveQueryParams; + }, + params: FetchParams = {}, + ) => { + return this.request({ + path: `/v1/instances/${path.instance}/multicast-groups/${path.multicastGroup}`, + method: "DELETE", + query, + ...params, + }); + }, /** * Reboot an instance */ @@ -8664,6 +9125,140 @@ export class Api { ...params, }); }, + /** + * List all multicast groups. + */ + multicastGroupList: ( + { query = {} }: { query?: MulticastGroupListQueryParams }, + params: FetchParams = {}, + ) => { + return this.request({ + path: `/v1/multicast-groups`, + method: "GET", + query, + ...params, + }); + }, + /** + * Create a multicast group. + */ + multicastGroupCreate: ( + { body }: { body: MulticastGroupCreate }, + params: FetchParams = {}, + ) => { + return this.request({ + path: `/v1/multicast-groups`, + method: "POST", + body, + ...params, + }); + }, + /** + * Fetch a multicast group. + */ + multicastGroupView: ( + { path }: { path: MulticastGroupViewPathParams }, + params: FetchParams = {}, + ) => { + return this.request({ + path: `/v1/multicast-groups/${path.multicastGroup}`, + method: "GET", + ...params, + }); + }, + /** + * Update a multicast group. + */ + multicastGroupUpdate: ( + { + path, + body, + }: { path: MulticastGroupUpdatePathParams; body: MulticastGroupUpdate }, + params: FetchParams = {}, + ) => { + return this.request({ + path: `/v1/multicast-groups/${path.multicastGroup}`, + method: "PUT", + body, + ...params, + }); + }, + /** + * Delete a multicast group. + */ + multicastGroupDelete: ( + { path }: { path: MulticastGroupDeletePathParams }, + params: FetchParams = {}, + ) => { + return this.request({ + path: `/v1/multicast-groups/${path.multicastGroup}`, + method: "DELETE", + ...params, + }); + }, + /** + * List members of a multicast group. + */ + multicastGroupMemberList: ( + { + path, + query = {}, + }: { + path: MulticastGroupMemberListPathParams; + query?: MulticastGroupMemberListQueryParams; + }, + params: FetchParams = {}, + ) => { + return this.request({ + path: `/v1/multicast-groups/${path.multicastGroup}/members`, + method: "GET", + query, + ...params, + }); + }, + /** + * Add instance to a multicast group. + */ + multicastGroupMemberAdd: ( + { + path, + query = {}, + body, + }: { + path: MulticastGroupMemberAddPathParams; + query?: MulticastGroupMemberAddQueryParams; + body: MulticastGroupMemberAdd; + }, + params: FetchParams = {}, + ) => { + return this.request({ + path: `/v1/multicast-groups/${path.multicastGroup}/members`, + method: "POST", + body, + query, + ...params, + }); + }, + /** + * Remove instance from a multicast group. + */ + multicastGroupMemberRemove: ( + { + path, + query = {}, + }: { + path: MulticastGroupMemberRemovePathParams; + query?: MulticastGroupMemberRemoveQueryParams; + }, + params: FetchParams = {}, + ) => { + return this.request({ + path: `/v1/multicast-groups/${path.multicastGroup}/members/${path.instance}`, + method: "DELETE", + query, + ...params, + }); + }, /** * List network interfaces */ @@ -9685,6 +10280,19 @@ export class Api { ...params, }); }, + /** + * Look up multicast group by IP address. + */ + lookupMulticastGroupByIp: ( + { path }: { path: LookupMulticastGroupByIpPathParams }, + params: FetchParams = {}, + ) => { + return this.request({ + path: `/v1/system/multicast-groups/by-ip/${path.address}`, + method: "GET", + ...params, + }); + }, /** * List address lots */ diff --git a/oxide-openapi-gen-ts/.prettierignore b/oxide-openapi-gen-ts/.prettierignore index a3fb68a..b05c2df 100644 --- a/oxide-openapi-gen-ts/.prettierignore +++ b/oxide-openapi-gen-ts/.prettierignore @@ -1,2 +1 @@ __snapshots__ -spec.json diff --git a/oxide-openapi-gen-ts/package-lock.json b/oxide-openapi-gen-ts/package-lock.json index f78ec84..e57ce19 100644 --- a/oxide-openapi-gen-ts/package-lock.json +++ b/oxide-openapi-gen-ts/package-lock.json @@ -1,12 +1,12 @@ { "name": "@oxide/openapi-gen-ts", - "version": "0.12.0", + "version": "0.13.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@oxide/openapi-gen-ts", - "version": "0.12.0", + "version": "0.13.0", "license": "MPL-2.0", "dependencies": { "@commander-js/extra-typings": "^14.0.0", diff --git a/oxide-openapi-gen-ts/package.json b/oxide-openapi-gen-ts/package.json index 0ee93b8..2342973 100644 --- a/oxide-openapi-gen-ts/package.json +++ b/oxide-openapi-gen-ts/package.json @@ -1,6 +1,6 @@ { "name": "@oxide/openapi-gen-ts", - "version": "0.12.0", + "version": "0.13.0", "description": "OpenAPI client generator used to generate Oxide TypeScript SDK", "keywords": [ "oxide", diff --git a/oxide-openapi-gen-ts/src/__snapshots__/Api.ts b/oxide-openapi-gen-ts/src/__snapshots__/Api.ts index 6ab15cd..b15ebb7 100644 --- a/oxide-openapi-gen-ts/src/__snapshots__/Api.ts +++ b/oxide-openapi-gen-ts/src/__snapshots__/Api.ts @@ -164,6 +164,50 @@ export type AddressLotViewResponse = /** The address lot. */ "lot": AddressLot,}; +/** +* The IP address version. + */ +export type IpVersion = +"v4" +| "v6" +; + +/** +* Specify which IP pool to allocate from. + */ +export type PoolSelector = +(/** Use the specified pool by name or ID. */ +| { +/** The pool to allocate from. */ +"pool": NameOrId,"type": "explicit" +,} +/** 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": "auto" +,} +); + +/** +* Specify how to allocate a floating IP address. + */ +export type AddressSelector = +(/** Reserve a specific IP address. */ +| { +/** The IP address to reserve. Must be available in the pool. */ +"ip": string, +/** The pool containing this address. If not specified, the default pool for the address's IP version is used. */ +"pool"?: NameOrId | null,"type": "explicit" +,} +/** Automatically allocate an IP address from a specified pool. */ +| { +/** Pool selection. + +If omitted, this field uses the silo's default pool. If the silo has default pools for both IPv4 and IPv6, the request will fail unless `ip_version` is specified in the pool selector. */ +"poolSelector"?: PoolSelector,"type": "auto" +,} +); + /** * Describes the scope of affinity for the purposes of co-location. */ @@ -956,6 +1000,9 @@ export type BgpPeerState = /** Waiting for keepaliave or notification from peer. */ | "open_confirm" +/** There is an ongoing Connection Collision that hasn't yet been resolved. Two connections are maintained until one connection receives an Open or is able to progress into Established. */ +| "connection_collision" + /** Synchronizing with peer. */ | "session_setup" @@ -1844,7 +1891,11 @@ export type DeviceAccessTokenResultsPage = export type DeviceAuthRequest = {"clientId": string, -/** Optional lifetime for the access token in seconds. If not specified, the silo's max TTL will be used (if set). */ +/** Optional lifetime for the access token in seconds. + +This value will be validated during the confirmation step. If not specified, it defaults to the silo's max TTL, which can be seen at `/v1/auth-settings`. If specified, must not exceed the silo's max TTL. + +Some special logic applies when authenticating the confirmation request with an existing device token: the requested TTL must not produce an expiration time later than the authenticating token's expiration. If no TTL is specified, the expiration will be the lesser of the silo max and the authenticating token's expiration time. To get the longest allowed lifetime, omit the TTL and authenticate with a web console session. */ "ttlSeconds"?: number | null,}; export type DeviceAuthVerify = @@ -1854,6 +1905,11 @@ export type Digest = {"type": "sha256" ,"value": string,}; +export type DiskType = +"distributed" +| "local" +; + /** * State of a Disk */ @@ -1902,7 +1958,7 @@ export type DiskState = export type Disk = {"blockSize": ByteCount, /** human-readable free-form text about a resource */ -"description": string,"devicePath": string, +"description": string,"devicePath": string,"diskType": DiskType, /** unique, immutable, system-controlled identifier for each resource */ "id": string, /** ID of image from which disk was created, if any */ @@ -1917,7 +1973,7 @@ export type Disk = "timeModified": Date,}; /** -* Different sources for a disk +* Different sources for a Distributed Disk */ export type DiskSource = (/** Create a blank disk */ @@ -1936,13 +1992,25 @@ export type DiskSource = ,} ); +/** +* The source of a `Disk`'s blocks + */ +export type DiskBackend = +(| {"type": "local" +,} +| { +/** The initial source for this disk */ +"diskSource": DiskSource,"type": "distributed" +,} +); + /** * Create-time parameters for a `Disk` */ export type DiskCreate = {"description": string, -/** The initial source for this disk */ -"diskSource": DiskSource,"name": Name, +/** The source for this `Disk`'s blocks */ +"diskBackend": DiskBackend,"name": Name, /** The total size of the Disk (in bytes) */ "size": ByteCount,}; @@ -1967,7 +2035,7 @@ export type DiskResultsPage = * Min, max, and the p-* quantiles are treated as optional due to the possibility of distribution operations, like subtraction. */ export type Distributiondouble = -{"bins": (number)[],"counts": (number)[],"max"?: number | null,"min"?: number | null,"p50"?: Quantile | null,"p90"?: Quantile | null,"p99"?: Quantile | null,"squaredMean": number,"sumOfSamples": number,}; +{"bins": (number)[],"counts": (number)[],"max"?: number | null,"min"?: number | null,"p50"?: number | null,"p90"?: number | null,"p99"?: number | null,"squaredMean": number,"sumOfSamples": number,}; /** * A distribution is a sequence of bins and counts in those bins, and some statistical information tracked to compute the mean, standard deviation, and quantile estimates. @@ -1975,15 +2043,15 @@ export type Distributiondouble = * Min, max, and the p-* quantiles are treated as optional due to the possibility of distribution operations, like subtraction. */ export type Distributionint64 = -{"bins": (number)[],"counts": (number)[],"max"?: number | null,"min"?: number | null,"p50"?: Quantile | null,"p90"?: Quantile | null,"p99"?: Quantile | null,"squaredMean": number,"sumOfSamples": number,}; +{"bins": (number)[],"counts": (number)[],"max"?: number | null,"min"?: number | null,"p50"?: number | null,"p90"?: number | null,"p99"?: number | null,"squaredMean": number,"sumOfSamples": number,}; /** * Parameters for creating an ephemeral IP address for an instance. */ export type EphemeralIpCreate = { -/** 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. */ +"poolSelector"?: PoolSelector,}; export type ExternalIp = (/** A source NAT IP address. @@ -2028,8 +2096,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. */ -| {"pool"?: NameOrId | null,"type": "ephemeral" +(/** An IP address providing both inbound and outbound access. The address is automatically assigned from a pool. */ +| { +/** Pool to allocate from. */ +"poolSelector"?: PoolSelector,"type": "ephemeral" ,} /** An IP address providing both inbound and outbound access. The address is an existing floating IP object assigned to the current project. @@ -2195,11 +2265,9 @@ export type FloatingIpAttach = * Parameters for creating a new floating IP address for instances. */ export type FloatingIpCreate = -{"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,"name": Name, -/** The parent IP pool that a floating IP is pulled from. If unset, the default pool is selected. */ -"pool"?: NameOrId | null,}; +{ +/** IP address allocation method. */ +"addressSelector"?: AddressSelector,"description": string,"name": Name,}; /** * A single page of results @@ -2445,8 +2513,8 @@ If this is not present, then this instance has not been automatically restarted. export type InstanceDiskAttachment = (/** During instance creation, create and attach disks */ | {"description": string, -/** The initial source for this disk */ -"diskSource": DiskSource,"name": Name, +/** The source for this `Disk`'s blocks */ +"diskBackend": DiskBackend,"name": Name, /** The total size of the Disk (in bytes) */ "size": ByteCount,"type": "create" ,} @@ -2457,17 +2525,76 @@ export type InstanceDiskAttachment = ,} ); +/** +* How a VPC-private IP address is assigned to a network interface. + */ +export type Ipv4Assignment = +(/** Automatically assign an IP address from the VPC Subnet. */ +| {"type": "auto" +,} +/** Explicitly assign a specific address, if available. */ +| {"type": "explicit" +,"value": string,} +); + +/** +* Configuration for a network interface's IPv4 addressing. + */ +export type PrivateIpv4StackCreate = +{ +/** The VPC-private address to assign to the interface. */ +"ip": Ipv4Assignment, +/** Additional IP networks the interface can send / receive on. */ +"transitIps"?: (Ipv4Net)[],}; + +/** +* How a VPC-private IP address is assigned to a network interface. + */ +export type Ipv6Assignment = +(/** Automatically assign an IP address from the VPC Subnet. */ +| {"type": "auto" +,} +/** Explicitly assign a specific address, if available. */ +| {"type": "explicit" +,"value": string,} +); + +/** +* Configuration for a network interface's IPv6 addressing. + */ +export type PrivateIpv6StackCreate = +{ +/** The VPC-private address to assign to the interface. */ +"ip": Ipv6Assignment, +/** Additional IP networks the interface can send / receive on. */ +"transitIps"?: (Ipv6Net)[],}; + +/** +* Create parameters for a network interface's IP stack. + */ +export type PrivateIpStackCreate = +(/** The interface has only an IPv4 stack. */ +| {"type": "v4" +,"value": PrivateIpv4StackCreate,} +/** The interface has only an IPv6 stack. */ +| {"type": "v6" +,"value": PrivateIpv6StackCreate,} +/** The interface has both an IPv4 and IPv6 stack. */ +| {"type": "dual_stack" +,"value": {"v4": PrivateIpv4StackCreate,"v6": PrivateIpv6StackCreate,},} +); + /** * Create-time parameters for an `InstanceNetworkInterface` */ export type InstanceNetworkInterfaceCreate = {"description": string, -/** The IP address for the interface. One will be auto-assigned if not provided. */ -"ip"?: string | null,"name": Name, +/** The IP stack configuration for this interface. + +If not provided, a default configuration will be used, which creates a dual-stack IPv4 / IPv6 interface. */ +"ipConfig"?: PrivateIpStackCreate,"name": Name, /** The VPC Subnet in which to create the interface. */ "subnetName": Name, -/** A set of additional networks that this interface may send and receive traffic on. */ -"transitIps"?: (IpNet)[], /** The VPC in which to create the interface. */ "vpcName": Name,}; @@ -2480,8 +2607,20 @@ export type InstanceNetworkInterfaceAttachment = If more than one interface is provided, then the first will be designated the primary interface for the instance. */ | {"params": (InstanceNetworkInterfaceCreate)[],"type": "create" ,} -/** The default networking configuration for an instance is to create a single primary interface with an automatically-assigned IP address. The IP will be pulled from the Project's default VPC / VPC Subnet. */ -| {"type": "default" +/** Create a single primary interface with an automatically-assigned IPv4 address. + +The IP will be pulled from the Project's default VPC / VPC Subnet. */ +| {"type": "default_ipv4" +,} +/** Create a single primary interface with an automatically-assigned IPv6 address. + +The IP will be pulled from the Project's default VPC / VPC Subnet. */ +| {"type": "default_ipv6" +,} +/** Create a single primary interface with automatically-assigned IPv4 and IPv6 addresses. + +The IPs will be pulled from the Project's default VPC / VPC Subnet. */ +| {"type": "default_dual_stack" ,} /** No network interfaces at all will be created for the instance. */ | {"type": "none" @@ -2524,7 +2663,11 @@ By default, all instances have outbound connectivity, but no inbound connectivit /** The hostname to be assigned to the instance */ "hostname": Hostname, /** The amount of RAM (in bytes) to be allocated to the instance */ -"memory": ByteCount,"name": Name, +"memory": ByteCount, +/** The multicast groups this instance should join. + +The instance will be automatically added as a member of the specified multicast groups during creation, enabling it to send and receive multicast traffic for those groups. */ +"multicastGroups"?: (NameOrId)[],"name": Name, /** The number of vCPUs to be allocated to the instance */ "ncpus": InstanceCpuCount, /** The network interfaces to be created for this instance. */ @@ -2538,6 +2681,41 @@ If not provided, all SSH public keys from the user's profile will be sent. If an /** User data for instance initialization systems (such as cloud-init). Must be a Base64-encoded string, as specified in RFC 4648 ยง 4 (+ and / characters with padding). Maximum 32 KiB unencoded data. */ "userData"?: string,}; +/** +* The VPC-private IPv4 stack for a network interface + */ +export type PrivateIpv4Stack = +{ +/** The VPC-private IPv4 address for the interface. */ +"ip": string, +/** A set of additional IPv4 networks that this interface may send and receive traffic on. */ +"transitIps": (Ipv4Net)[],}; + +/** +* The VPC-private IPv6 stack for a network interface + */ +export type PrivateIpv6Stack = +{ +/** The VPC-private IPv6 address for the interface. */ +"ip": string, +/** A set of additional IPv6 networks that this interface may send and receive traffic on. */ +"transitIps": (Ipv6Net)[],}; + +/** +* The VPC-private IP stack for a network interface. + */ +export type PrivateIpStack = +(/** The interface has only an IPv4 stack. */ +| {"type": "v4" +,"value": PrivateIpv4Stack,} +/** The interface has only an IPv6 stack. */ +| {"type": "v6" +,"value": PrivateIpv6Stack,} +/** The interface is dual-stack IPv4 and IPv6. */ +| {"type": "dual_stack" +,"value": {"v4": PrivateIpv4Stack,"v6": PrivateIpv6Stack,},} +); + /** * A MAC address * @@ -2557,8 +2735,8 @@ export type InstanceNetworkInterface = "id": string, /** The Instance to which the interface belongs. */ "instanceId": string, -/** The IP address assigned to this interface. */ -"ip": string, +/** The VPC-private IP stack for this interface. */ +"ipStack": PrivateIpStack, /** The MAC address assigned to this interface. */ "mac": MacAddr, /** unique, mutable, user-controlled identifier for each resource */ @@ -2571,8 +2749,6 @@ export type InstanceNetworkInterface = "timeCreated": Date, /** timestamp when this resource was last modified */ "timeModified": Date, -/** A set of additional networks that this interface may send and receive traffic on. */ -"transitIps"?: (IpNet)[], /** The VPC to which the interface belongs. */ "vpcId": string,}; @@ -2599,7 +2775,7 @@ If applied to a secondary interface, that interface will become the primary on t Note that this can only be used to select a new primary interface for an instance. Requests to change the primary interface into a secondary will return an error. */ "primary"?: boolean, -/** A set of additional networks that this interface may send and receive traffic on. */ +/** A set of additional networks that this interface may send and receive traffic on */ "transitIps"?: (IpNet)[],}; /** @@ -2643,6 +2819,12 @@ An instance that does not have a boot disk set will use the boot options specifi "cpuPlatform": InstanceCpuPlatform | null, /** The amount of RAM (in bytes) to be allocated to the instance */ "memory": ByteCount, +/** Multicast groups this instance should join. + +When specified, this replaces the instance's current multicast group membership with the new set of groups. The instance will leave any groups not listed here and join any new groups that are specified. + +If not provided (None), the instance's multicast group membership will not be changed. */ +"multicastGroups"?: (NameOrId)[] | null, /** The number of vCPUs to be allocated to the instance */ "ncpus": InstanceCpuCount,}; @@ -2758,14 +2940,6 @@ export type InternetGatewayResultsPage = /** token used to fetch the next page of results (if any) */ "nextPage"?: string | null,}; -/** -* The IP address version. - */ -export type IpVersion = -"v4" -| "v6" -; - /** * Type of IP pool. */ @@ -2793,7 +2967,7 @@ export type IpPool = "ipVersion": IpVersion, /** unique, mutable, user-controlled identifier for each resource */ "name": Name, -/** Type of IP pool (unicast or multicast) */ +/** Type of IP pool (unicast or multicast). */ "poolType": IpPoolType, /** timestamp when this resource was created */ "timeCreated": Date, @@ -2818,7 +2992,9 @@ The default is IPv4. */ export type IpPoolLinkSilo = { -/** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. There can be at most one default for a given silo. */ +/** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. + +A silo can have at most one default pool per combination of pool type (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 default pools total. */ "isDefault": boolean,"silo": NameOrId,}; /** @@ -2870,7 +3046,9 @@ export type IpPoolResultsPage = */ export type IpPoolSiloLink = {"ipPoolId": string, -/** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. There can be at most one default for a given silo. */ +/** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. + +A silo can have at most one default pool per combination of pool type (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 default pools total. */ "isDefault": boolean,"siloId": string,}; /** @@ -2885,7 +3063,9 @@ export type IpPoolSiloLinkResultsPage = export type IpPoolSiloUpdate = { -/** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. There can be at most one default for a given silo, so when a pool is made default, an existing default will remain linked but will no longer be the default. */ +/** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. + +A silo can have at most one default pool per combination of pool type (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 default pools total. When a pool is made default, an existing default of the same type and version will remain linked but will no longer be the default. */ "isDefault": boolean,}; /** @@ -3111,7 +3291,7 @@ export type LoopbackAddressCreate = "anycast": boolean, /** The subnet mask to use for the address. */ "mask": number, -/** The containing the switch this loopback address will be configured on. */ +/** The rack containing the switch this loopback address will be configured on. */ "rackId": string, /** The location of the switch within the rack this loopback address will be configured on. */ "switchLocation": Name,}; @@ -3157,6 +3337,151 @@ export type MetricType = ); +/** +* View of a Multicast Group + */ +export type MulticastGroup = +{ +/** human-readable free-form text about a resource */ +"description": string, +/** unique, immutable, system-controlled identifier for each resource */ +"id": string, +/** The ID of the IP pool this resource belongs to. */ +"ipPoolId": string, +/** The multicast IP address held by this resource. */ +"multicastIp": string, +/** Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. None means no VLAN tagging on egress. */ +"mvlan"?: number | null, +/** unique, mutable, user-controlled identifier for each resource */ +"name": Name, +/** Source IP addresses for Source-Specific Multicast (SSM). Empty array means any source is allowed. */ +"sourceIps": (string)[], +/** Current state of the multicast group. */ +"state": string, +/** timestamp when this resource was created */ +"timeCreated": Date, +/** timestamp when this resource was last modified */ +"timeModified": Date,}; + +/** +* Create-time parameters for a multicast group. + */ +export type MulticastGroupCreate = +{"description": string, +/** The multicast IP address to allocate. If None, one will be allocated from the default pool. */ +"multicastIp"?: string | null, +/** Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. Tags packets leaving the rack to traverse VLAN-segmented upstream networks. + +Valid range: 2-4094 (VLAN IDs 0-1 are reserved by IEEE 802.1Q standard). */ +"mvlan"?: number | null,"name": Name, +/** Name or ID of the IP pool to allocate from. If None, uses the default multicast pool. */ +"pool"?: NameOrId | null, +/** Source IP addresses for Source-Specific Multicast (SSM). + +None uses default behavior (Any-Source Multicast). Empty list explicitly allows any source (Any-Source Multicast). Non-empty list restricts to specific sources (SSM). */ +"sourceIps"?: (string)[] | null,}; + +/** +* View of a Multicast Group Member (instance belonging to a multicast group) + */ +export type MulticastGroupMember = +{ +/** human-readable free-form text about a resource */ +"description": string, +/** unique, immutable, system-controlled identifier for each resource */ +"id": string, +/** The ID of the instance that is a member of this group. */ +"instanceId": string, +/** The ID of the multicast group this member belongs to. */ +"multicastGroupId": string, +/** unique, mutable, user-controlled identifier for each resource */ +"name": Name, +/** Current state of the multicast group membership. */ +"state": string, +/** timestamp when this resource was created */ +"timeCreated": Date, +/** timestamp when this resource was last modified */ +"timeModified": Date,}; + +/** +* Parameters for adding an instance to a multicast group. + */ +export type MulticastGroupMemberAdd = +{ +/** Name or ID of the instance to add to the multicast group */ +"instance": NameOrId,}; + +/** +* A single page of results + */ +export type MulticastGroupMemberResultsPage = +{ +/** list of items on this page of results */ +"items": (MulticastGroupMember)[], +/** token used to fetch the next page of results (if any) */ +"nextPage"?: string | null,}; + +/** +* A single page of results + */ +export type MulticastGroupResultsPage = +{ +/** list of items on this page of results */ +"items": (MulticastGroup)[], +/** token used to fetch the next page of results (if any) */ +"nextPage"?: string | null,}; + +/** +* Update-time parameters for a multicast group. + */ +export type MulticastGroupUpdate = +{"description"?: string | null, +/** Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. Set to null to clear the MVLAN. Valid range: 2-4094 when provided. Omit the field to leave mvlan unchanged. */ +"mvlan"?: number | null,"name"?: Name | null,"sourceIps"?: (string)[] | null,}; + +/** +* VPC-private IPv4 configuration for a network interface. + */ +export type PrivateIpv4Config = +{ +/** VPC-private IP address. */ +"ip": string, +/** The IP subnet. */ +"subnet": Ipv4Net, +/** Additional networks on which the interface can send / receive traffic. */ +"transitIps"?: (Ipv4Net)[],}; + +/** +* VPC-private IPv6 configuration for a network interface. + */ +export type PrivateIpv6Config = +{ +/** VPC-private IP address. */ +"ip": string, +/** The IP subnet. */ +"subnet": Ipv6Net, +/** Additional networks on which the interface can send / receive traffic. */ +"transitIps": (Ipv6Net)[],}; + +/** +* VPC-private IP address configuration for a network interface. + */ +export type PrivateIpConfig = +(/** The interface has only an IPv4 configuration. */ +| {"type": "v4" +,"value": PrivateIpv4Config,} +/** The interface has only an IPv6 configuration. */ +| {"type": "v6" +,"value": PrivateIpv6Config,} +/** The interface is dual-stack. */ +| {"type": "dual_stack" +,"value": { +/** The interface's IPv4 configuration. */ +"v4": PrivateIpv4Config, +/** The interface's IPv6 configuration. */ +"v6": PrivateIpv6Config,},} +); + /** * The type of network interface */ @@ -3182,7 +3507,7 @@ number; * Information required to construct a virtual network interface */ export type NetworkInterface = -{"id": string,"ip": string,"kind": NetworkInterfaceKind,"mac": MacAddr,"name": Name,"primary": boolean,"slot": number,"subnet": IpNet,"transitIps"?: (IpNet)[],"vni": Vni,}; +{"id": string,"ipConfig": PrivateIpConfig,"kind": NetworkInterfaceKind,"mac": MacAddr,"name": Name,"primary": boolean,"slot": number,"vni": Vni,}; /** * List of data values for one timeseries. @@ -3353,7 +3678,9 @@ export type Probe = * Create time parameters for probes. */ export type ProbeCreate = -{"description": string,"ipPool"?: NameOrId | null,"name": Name,"sled": string,}; +{"description": string,"name": Name, +/** Pool to allocate from. */ +"poolSelector"?: PoolSelector,"sled": string,}; export type ProbeExternalIpKind = "snat" @@ -3787,10 +4114,16 @@ export type SiloIpPool = "description": string, /** unique, immutable, system-controlled identifier for each resource */ "id": string, -/** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. There can be at most one default for a given silo. */ +/** The IP version for the pool. */ +"ipVersion": IpVersion, +/** When a pool is the default for a silo, floating IPs and instance ephemeral IPs will come from that pool when no other pool is specified. + +A silo can have at most one default pool per combination of pool type (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 default pools total. */ "isDefault": boolean, /** unique, mutable, user-controlled identifier for each resource */ "name": Name, +/** Type of IP pool (unicast or multicast). */ +"poolType": IpPoolType, /** timestamp when this resource was created */ "timeCreated": Date, /** timestamp when this resource was last modified */ @@ -5758,6 +6091,32 @@ export interface InstanceEphemeralIpDetachQueryParams { project?: NameOrId, } +export interface InstanceMulticastGroupListPathParams { + instance: NameOrId, +} + +export interface InstanceMulticastGroupListQueryParams { + project?: NameOrId, +} + +export interface InstanceMulticastGroupJoinPathParams { + instance: NameOrId, + multicastGroup: NameOrId, +} + +export interface InstanceMulticastGroupJoinQueryParams { + project?: NameOrId, +} + +export interface InstanceMulticastGroupLeavePathParams { + instance: NameOrId, + multicastGroup: NameOrId, +} + +export interface InstanceMulticastGroupLeaveQueryParams { + project?: NameOrId, +} + export interface InstanceRebootPathParams { instance: NameOrId, } @@ -5954,6 +6313,51 @@ export interface SiloMetricQueryParams { project?: NameOrId, } +export interface MulticastGroupListQueryParams { + limit?: number | null, + pageToken?: string | null, + sortBy?: NameOrIdSortMode, +} + +export interface MulticastGroupViewPathParams { + multicastGroup: NameOrId, +} + +export interface MulticastGroupUpdatePathParams { + multicastGroup: NameOrId, +} + +export interface MulticastGroupDeletePathParams { + multicastGroup: NameOrId, +} + +export interface MulticastGroupMemberListPathParams { + multicastGroup: NameOrId, +} + +export interface MulticastGroupMemberListQueryParams { + limit?: number | null, + pageToken?: string | null, + sortBy?: IdSortMode, +} + +export interface MulticastGroupMemberAddPathParams { + multicastGroup: NameOrId, +} + +export interface MulticastGroupMemberAddQueryParams { + project?: NameOrId, +} + +export interface MulticastGroupMemberRemovePathParams { + instance: NameOrId, + multicastGroup: NameOrId, +} + +export interface MulticastGroupMemberRemoveQueryParams { + project?: NameOrId, +} + export interface InstanceNetworkInterfaceListQueryParams { instance?: NameOrId, limit?: number | null, @@ -6308,6 +6712,10 @@ export interface SystemMetricQueryParams { silo?: NameOrId, } +export interface LookupMulticastGroupByIpPathParams { + address: string, +} + export interface NetworkingAddressLotListQueryParams { limit?: number | null, pageToken?: string | null, @@ -6786,7 +7194,7 @@ export interface ApiConfig { * Pulled from info.version in the OpenAPI schema. Sent in the * `api-version` header on all requests. */ - apiVersion = "20251008.0.0"; + apiVersion = "2026010500.0.0"; constructor({ host = "", baseParams = {}, token }: ApiConfig = {}) { this.host = host; @@ -8095,6 +8503,51 @@ params: FetchParams = {}) => { }) }, /** +* List multicast groups for instance + */ +instanceMulticastGroupList: ({ +path, query = {}, }: {path: InstanceMulticastGroupListPathParams, +query?: InstanceMulticastGroupListQueryParams, +}, +params: FetchParams = {}) => { + return this.request({ + path: `/v1/instances/${path.instance}/multicast-groups`, + method: "GET", + query, + ...params, + }) + }, +/** +* Join multicast group. + */ +instanceMulticastGroupJoin: ({ +path, query = {}, }: {path: InstanceMulticastGroupJoinPathParams, +query?: InstanceMulticastGroupJoinQueryParams, +}, +params: FetchParams = {}) => { + return this.request({ + path: `/v1/instances/${path.instance}/multicast-groups/${path.multicastGroup}`, + method: "PUT", + query, + ...params, + }) + }, +/** +* Leave multicast group. + */ +instanceMulticastGroupLeave: ({ +path, query = {}, }: {path: InstanceMulticastGroupLeavePathParams, +query?: InstanceMulticastGroupLeaveQueryParams, +}, +params: FetchParams = {}) => { + return this.request({ + path: `/v1/instances/${path.instance}/multicast-groups/${path.multicastGroup}`, + method: "DELETE", + query, + ...params, + }) + }, +/** * Reboot an instance */ instanceReboot: ({ @@ -8494,6 +8947,122 @@ params: FetchParams = {}) => { }) }, /** +* List all multicast groups. + */ +multicastGroupList: ({ +query = {}, }: {query?: MulticastGroupListQueryParams, +}, +params: FetchParams = {}) => { + return this.request({ + path: `/v1/multicast-groups`, + method: "GET", + query, + ...params, + }) + }, +/** +* Create a multicast group. + */ +multicastGroupCreate: ({ +body, }: {body: MulticastGroupCreate, +}, +params: FetchParams = {}) => { + return this.request({ + path: `/v1/multicast-groups`, + method: "POST", + body, + ...params, + }) + }, +/** +* Fetch a multicast group. + */ +multicastGroupView: ({ +path, }: {path: MulticastGroupViewPathParams, +}, +params: FetchParams = {}) => { + return this.request({ + path: `/v1/multicast-groups/${path.multicastGroup}`, + method: "GET", + ...params, + }) + }, +/** +* Update a multicast group. + */ +multicastGroupUpdate: ({ +path, body, }: {path: MulticastGroupUpdatePathParams, +body: MulticastGroupUpdate, +}, +params: FetchParams = {}) => { + return this.request({ + path: `/v1/multicast-groups/${path.multicastGroup}`, + method: "PUT", + body, + ...params, + }) + }, +/** +* Delete a multicast group. + */ +multicastGroupDelete: ({ +path, }: {path: MulticastGroupDeletePathParams, +}, +params: FetchParams = {}) => { + return this.request({ + path: `/v1/multicast-groups/${path.multicastGroup}`, + method: "DELETE", + ...params, + }) + }, +/** +* List members of a multicast group. + */ +multicastGroupMemberList: ({ +path, query = {}, }: {path: MulticastGroupMemberListPathParams, +query?: MulticastGroupMemberListQueryParams, +}, +params: FetchParams = {}) => { + return this.request({ + path: `/v1/multicast-groups/${path.multicastGroup}/members`, + method: "GET", + query, + ...params, + }) + }, +/** +* Add instance to a multicast group. + */ +multicastGroupMemberAdd: ({ +path, query = {}, body, }: {path: MulticastGroupMemberAddPathParams, +query?: MulticastGroupMemberAddQueryParams, +body: MulticastGroupMemberAdd, +}, +params: FetchParams = {}) => { + return this.request({ + path: `/v1/multicast-groups/${path.multicastGroup}/members`, + method: "POST", + body, + query, + ...params, + }) + }, +/** +* Remove instance from a multicast group. + */ +multicastGroupMemberRemove: ({ +path, query = {}, }: {path: MulticastGroupMemberRemovePathParams, +query?: MulticastGroupMemberRemoveQueryParams, +}, +params: FetchParams = {}) => { + return this.request({ + path: `/v1/multicast-groups/${path.multicastGroup}/members/${path.instance}`, + method: "DELETE", + query, + ...params, + }) + }, +/** * List network interfaces */ instanceNetworkInterfaceList: ({ @@ -9413,6 +9982,19 @@ params: FetchParams = {}) => { }) }, /** +* Look up multicast group by IP address. + */ +lookupMulticastGroupByIp: ({ +path, }: {path: LookupMulticastGroupByIpPathParams, +}, +params: FetchParams = {}) => { + return this.request({ + path: `/v1/system/multicast-groups/by-ip/${path.address}`, + method: "GET", + ...params, + }) + }, +/** * List address lots */ networkingAddressLotList: ({ diff --git a/oxide-openapi-gen-ts/src/__snapshots__/msw-handlers.ts b/oxide-openapi-gen-ts/src/__snapshots__/msw-handlers.ts index 303d258..0f4dfd9 100644 --- a/oxide-openapi-gen-ts/src/__snapshots__/msw-handlers.ts +++ b/oxide-openapi-gen-ts/src/__snapshots__/msw-handlers.ts @@ -212,6 +212,12 @@ export interface MSWHandlers { instanceEphemeralIpAttach: (params: { path: Api.InstanceEphemeralIpAttachPathParams, query: Api.InstanceEphemeralIpAttachQueryParams, body: Json, req: Request, cookies: Record }) => Promisable>, /** `DELETE /v1/instances/:instance/external-ips/ephemeral` */ instanceEphemeralIpDetach: (params: { path: Api.InstanceEphemeralIpDetachPathParams, query: Api.InstanceEphemeralIpDetachQueryParams, req: Request, cookies: Record }) => Promisable, +/** `GET /v1/instances/:instance/multicast-groups` */ + instanceMulticastGroupList: (params: { path: Api.InstanceMulticastGroupListPathParams, query: Api.InstanceMulticastGroupListQueryParams, req: Request, cookies: Record }) => Promisable>, +/** `PUT /v1/instances/:instance/multicast-groups/:multicastGroup` */ + instanceMulticastGroupJoin: (params: { path: Api.InstanceMulticastGroupJoinPathParams, query: Api.InstanceMulticastGroupJoinQueryParams, req: Request, cookies: Record }) => Promisable>, +/** `DELETE /v1/instances/:instance/multicast-groups/:multicastGroup` */ + instanceMulticastGroupLeave: (params: { path: Api.InstanceMulticastGroupLeavePathParams, query: Api.InstanceMulticastGroupLeaveQueryParams, req: Request, cookies: Record }) => Promisable, /** `POST /v1/instances/:instance/reboot` */ instanceReboot: (params: { path: Api.InstanceRebootPathParams, query: Api.InstanceRebootQueryParams, req: Request, cookies: Record }) => Promisable>, /** `GET /v1/instances/:instance/serial-console` */ @@ -270,6 +276,22 @@ export interface MSWHandlers { currentUserSshKeyDelete: (params: { path: Api.CurrentUserSshKeyDeletePathParams, req: Request, cookies: Record }) => Promisable, /** `GET /v1/metrics/:metricName` */ siloMetric: (params: { path: Api.SiloMetricPathParams, query: Api.SiloMetricQueryParams, req: Request, cookies: Record }) => Promisable>, +/** `GET /v1/multicast-groups` */ + multicastGroupList: (params: { query: Api.MulticastGroupListQueryParams, req: Request, cookies: Record }) => Promisable>, +/** `POST /v1/multicast-groups` */ + multicastGroupCreate: (params: { body: Json, req: Request, cookies: Record }) => Promisable>, +/** `GET /v1/multicast-groups/:multicastGroup` */ + multicastGroupView: (params: { path: Api.MulticastGroupViewPathParams, req: Request, cookies: Record }) => Promisable>, +/** `PUT /v1/multicast-groups/:multicastGroup` */ + multicastGroupUpdate: (params: { path: Api.MulticastGroupUpdatePathParams, body: Json, req: Request, cookies: Record }) => Promisable>, +/** `DELETE /v1/multicast-groups/:multicastGroup` */ + multicastGroupDelete: (params: { path: Api.MulticastGroupDeletePathParams, req: Request, cookies: Record }) => Promisable, +/** `GET /v1/multicast-groups/:multicastGroup/members` */ + multicastGroupMemberList: (params: { path: Api.MulticastGroupMemberListPathParams, query: Api.MulticastGroupMemberListQueryParams, req: Request, cookies: Record }) => Promisable>, +/** `POST /v1/multicast-groups/:multicastGroup/members` */ + multicastGroupMemberAdd: (params: { path: Api.MulticastGroupMemberAddPathParams, query: Api.MulticastGroupMemberAddQueryParams, body: Json, req: Request, cookies: Record }) => Promisable>, +/** `DELETE /v1/multicast-groups/:multicastGroup/members/:instance` */ + multicastGroupMemberRemove: (params: { path: Api.MulticastGroupMemberRemovePathParams, query: Api.MulticastGroupMemberRemoveQueryParams, req: Request, cookies: Record }) => Promisable, /** `GET /v1/network-interfaces` */ instanceNetworkInterfaceList: (params: { query: Api.InstanceNetworkInterfaceListQueryParams, req: Request, cookies: Record }) => Promisable>, /** `POST /v1/network-interfaces` */ @@ -398,6 +420,8 @@ export interface MSWHandlers { ipPoolServiceRangeRemove: (params: { body: Json, req: Request, cookies: Record }) => Promisable, /** `GET /v1/system/metrics/:metricName` */ systemMetric: (params: { path: Api.SystemMetricPathParams, query: Api.SystemMetricQueryParams, req: Request, cookies: Record }) => Promisable>, +/** `GET /v1/system/multicast-groups/by-ip/:address` */ + lookupMulticastGroupByIp: (params: { path: Api.LookupMulticastGroupByIpPathParams, req: Request, cookies: Record }) => Promisable>, /** `GET /v1/system/networking/address-lot` */ networkingAddressLotList: (params: { query: Api.NetworkingAddressLotListQueryParams, req: Request, cookies: Record }) => Promisable>, /** `POST /v1/system/networking/address-lot` */ @@ -781,6 +805,9 @@ http.post('/v1/instances/:instance/disks/detach', handler(handlers['instanceDisk http.get('/v1/instances/:instance/external-ips', handler(handlers['instanceExternalIpList'], schema.InstanceExternalIpListParams, null)), http.post('/v1/instances/:instance/external-ips/ephemeral', handler(handlers['instanceEphemeralIpAttach'], schema.InstanceEphemeralIpAttachParams, schema.EphemeralIpCreate)), http.delete('/v1/instances/:instance/external-ips/ephemeral', handler(handlers['instanceEphemeralIpDetach'], schema.InstanceEphemeralIpDetachParams, null)), +http.get('/v1/instances/:instance/multicast-groups', handler(handlers['instanceMulticastGroupList'], schema.InstanceMulticastGroupListParams, null)), +http.put('/v1/instances/:instance/multicast-groups/:multicastGroup', handler(handlers['instanceMulticastGroupJoin'], schema.InstanceMulticastGroupJoinParams, null)), +http.delete('/v1/instances/:instance/multicast-groups/:multicastGroup', handler(handlers['instanceMulticastGroupLeave'], schema.InstanceMulticastGroupLeaveParams, null)), http.post('/v1/instances/:instance/reboot', handler(handlers['instanceReboot'], schema.InstanceRebootParams, null)), http.get('/v1/instances/:instance/serial-console', handler(handlers['instanceSerialConsole'], schema.InstanceSerialConsoleParams, null)), http.get('/v1/instances/:instance/serial-console/stream', handler(handlers['instanceSerialConsoleStream'], schema.InstanceSerialConsoleStreamParams, null)), @@ -810,6 +837,14 @@ http.post('/v1/me/ssh-keys', handler(handlers['currentUserSshKeyCreate'], null, http.get('/v1/me/ssh-keys/:sshKey', handler(handlers['currentUserSshKeyView'], schema.CurrentUserSshKeyViewParams, null)), http.delete('/v1/me/ssh-keys/:sshKey', handler(handlers['currentUserSshKeyDelete'], schema.CurrentUserSshKeyDeleteParams, null)), http.get('/v1/metrics/:metricName', handler(handlers['siloMetric'], schema.SiloMetricParams, null)), +http.get('/v1/multicast-groups', handler(handlers['multicastGroupList'], schema.MulticastGroupListParams, null)), +http.post('/v1/multicast-groups', handler(handlers['multicastGroupCreate'], null, schema.MulticastGroupCreate)), +http.get('/v1/multicast-groups/:multicastGroup', handler(handlers['multicastGroupView'], schema.MulticastGroupViewParams, null)), +http.put('/v1/multicast-groups/:multicastGroup', handler(handlers['multicastGroupUpdate'], schema.MulticastGroupUpdateParams, schema.MulticastGroupUpdate)), +http.delete('/v1/multicast-groups/:multicastGroup', handler(handlers['multicastGroupDelete'], schema.MulticastGroupDeleteParams, null)), +http.get('/v1/multicast-groups/:multicastGroup/members', handler(handlers['multicastGroupMemberList'], schema.MulticastGroupMemberListParams, null)), +http.post('/v1/multicast-groups/:multicastGroup/members', handler(handlers['multicastGroupMemberAdd'], schema.MulticastGroupMemberAddParams, schema.MulticastGroupMemberAdd)), +http.delete('/v1/multicast-groups/:multicastGroup/members/:instance', handler(handlers['multicastGroupMemberRemove'], schema.MulticastGroupMemberRemoveParams, null)), http.get('/v1/network-interfaces', handler(handlers['instanceNetworkInterfaceList'], schema.InstanceNetworkInterfaceListParams, null)), http.post('/v1/network-interfaces', handler(handlers['instanceNetworkInterfaceCreate'], schema.InstanceNetworkInterfaceCreateParams, schema.InstanceNetworkInterfaceCreate)), http.get('/v1/network-interfaces/:interface', handler(handlers['instanceNetworkInterfaceView'], schema.InstanceNetworkInterfaceViewParams, null)), @@ -874,6 +909,7 @@ http.get('/v1/system/ip-pools-service/ranges', handler(handlers['ipPoolServiceRa http.post('/v1/system/ip-pools-service/ranges/add', handler(handlers['ipPoolServiceRangeAdd'], null, schema.IpRange)), http.post('/v1/system/ip-pools-service/ranges/remove', handler(handlers['ipPoolServiceRangeRemove'], null, schema.IpRange)), http.get('/v1/system/metrics/:metricName', handler(handlers['systemMetric'], schema.SystemMetricParams, null)), +http.get('/v1/system/multicast-groups/by-ip/:address', handler(handlers['lookupMulticastGroupByIp'], schema.LookupMulticastGroupByIpParams, null)), http.get('/v1/system/networking/address-lot', handler(handlers['networkingAddressLotList'], schema.NetworkingAddressLotListParams, null)), http.post('/v1/system/networking/address-lot', handler(handlers['networkingAddressLotCreate'], null, schema.AddressLotCreate)), http.get('/v1/system/networking/address-lot/:addressLot', handler(handlers['networkingAddressLotView'], schema.NetworkingAddressLotViewParams, null)), diff --git a/oxide-openapi-gen-ts/src/__snapshots__/type-test.ts b/oxide-openapi-gen-ts/src/__snapshots__/type-test.ts index 9d2fe9b..afb328f 100644 --- a/oxide-openapi-gen-ts/src/__snapshots__/type-test.ts +++ b/oxide-openapi-gen-ts/src/__snapshots__/type-test.ts @@ -20,6 +20,9 @@ assert>>(); assert>>(); assert>>(); assert>>(); +assert>>(); +assert>>(); +assert>>(); assert>>(); assert>>(); assert>>(); @@ -140,9 +143,11 @@ assert>>(); assert>>(); assert>>(); +assert>>(); assert>>(); assert>>(); assert>>(); +assert>>(); assert>>(); assert>>(); assert>>(); @@ -185,9 +190,17 @@ assert>>(); assert>>(); assert>>(); assert>>(); +assert>>(); +assert>>(); +assert>>(); +assert>>(); +assert>>(); assert>>(); assert>>(); assert>>(); +assert>>(); +assert>>(); +assert>>(); assert>>(); assert>>(); assert>>(); @@ -205,7 +218,6 @@ assert>> assert>>(); assert>>(); assert>>(); -assert>>(); assert>>(); assert>>(); assert>>(); @@ -238,6 +250,16 @@ assert>>(); assert>>(); assert>>(); +assert>>(); +assert>>(); +assert>>(); +assert>>(); +assert>>(); +assert>>(); +assert>>(); +assert>>(); +assert>>(); +assert>>(); assert>>(); assert>>(); assert>>(); diff --git a/oxide-openapi-gen-ts/src/__snapshots__/validate.ts b/oxide-openapi-gen-ts/src/__snapshots__/validate.ts index 2ef6092..fc09c20 100644 --- a/oxide-openapi-gen-ts/src/__snapshots__/validate.ts +++ b/oxide-openapi-gen-ts/src/__snapshots__/validate.ts @@ -133,6 +133,38 @@ export const AddressLotViewResponse = z.preprocess(processResponseBody,z.object( "lot": AddressLot, })) +/** +* The IP address version. + */ +export const IpVersion = z.preprocess(processResponseBody,z.enum(["v4","v6"])) + +/** +* Specify which IP pool to allocate from. + */ +export const PoolSelector = z.preprocess(processResponseBody,z.union([ +z.object({"pool": NameOrId, +"type": z.enum(["explicit"]), +}), +z.object({"ipVersion": IpVersion.nullable().default(null).optional(), +"type": z.enum(["auto"]), +}), +]) +) + +/** +* Specify how to allocate a floating IP address. + */ +export const AddressSelector = z.preprocess(processResponseBody,z.union([ +z.object({"ip": z.ipv4(), +"pool": NameOrId.nullable().optional(), +"type": z.enum(["explicit"]), +}), +z.object({"poolSelector": PoolSelector.default({"ipVersion":null,"type":"auto"}).optional(), +"type": z.enum(["auto"]), +}), +]) +) + /** * Describes the scope of affinity for the purposes of co-location. */ @@ -675,7 +707,7 @@ export const BgpPeerConfig = z.preprocess(processResponseBody,z.object({"linkNam /** * The current state of a BGP peer. */ -export const BgpPeerState = z.preprocess(processResponseBody,z.enum(["idle", "connect", "active", "open_sent", "open_confirm", "session_setup", "established"]) +export const BgpPeerState = z.preprocess(processResponseBody,z.enum(["idle", "connect", "active", "open_sent", "open_confirm", "connection_collision", "session_setup", "established"]) ) /** @@ -1385,6 +1417,8 @@ export const Digest = z.preprocess(processResponseBody,z.object({"type": z.enum( "value": z.string(), })) +export const DiskType = z.preprocess(processResponseBody,z.enum(["distributed","local"])) + /** * State of a Disk */ @@ -1425,6 +1459,7 @@ z.object({"state": z.enum(["faulted"]), export const Disk = z.preprocess(processResponseBody,z.object({"blockSize": ByteCount, "description": z.string(), "devicePath": z.string(), +"diskType": DiskType, "id": z.uuid(), "imageId": z.uuid().nullable().optional(), "name": Name, @@ -1437,7 +1472,7 @@ export const Disk = z.preprocess(processResponseBody,z.object({"blockSize": Byte })) /** -* Different sources for a disk +* Different sources for a Distributed Disk */ export const DiskSource = z.preprocess(processResponseBody,z.union([ z.object({"blockSize": BlockSize, @@ -1455,11 +1490,23 @@ z.object({"blockSize": BlockSize, ]) ) +/** +* The source of a `Disk`'s blocks + */ +export const DiskBackend = z.preprocess(processResponseBody,z.union([ +z.object({"type": z.enum(["local"]), +}), +z.object({"diskSource": DiskSource, +"type": z.enum(["distributed"]), +}), +]) +) + /** * Create-time parameters for a `Disk` */ export const DiskCreate = z.preprocess(processResponseBody,z.object({"description": z.string(), -"diskSource": DiskSource, +"diskBackend": DiskBackend, "name": Name, "size": ByteCount, })) @@ -1483,9 +1530,9 @@ export const Distributiondouble = z.preprocess(processResponseBody,z.object({"bi "counts": z.number().min(0).array(), "max": z.number().nullable().optional(), "min": z.number().nullable().optional(), -"p50": Quantile.nullable().optional(), -"p90": Quantile.nullable().optional(), -"p99": Quantile.nullable().optional(), +"p50": z.number().nullable().optional(), +"p90": z.number().nullable().optional(), +"p99": z.number().nullable().optional(), "squaredMean": z.number(), "sumOfSamples": z.number(), })) @@ -1499,9 +1546,9 @@ export const Distributionint64 = z.preprocess(processResponseBody,z.object({"bin "counts": z.number().min(0).array(), "max": z.number().nullable().optional(), "min": z.number().nullable().optional(), -"p50": Quantile.nullable().optional(), -"p90": Quantile.nullable().optional(), -"p99": Quantile.nullable().optional(), +"p50": z.number().nullable().optional(), +"p90": z.number().nullable().optional(), +"p99": z.number().nullable().optional(), "squaredMean": z.number(), "sumOfSamples": z.number(), })) @@ -1509,7 +1556,7 @@ export const Distributionint64 = z.preprocess(processResponseBody,z.object({"bin /** * Parameters for creating an ephemeral IP address for an instance. */ -export const EphemeralIpCreate = z.preprocess(processResponseBody,z.object({"pool": NameOrId.nullable().optional(), +export const EphemeralIpCreate = z.preprocess(processResponseBody,z.object({"poolSelector": PoolSelector.default({"ipVersion":null,"type":"auto"}).optional(), })) /** @@ -1549,7 +1596,7 @@ z.object({"description": z.string(), * Parameters for creating an external IP address for instances. */ export const ExternalIpCreate = z.preprocess(processResponseBody,z.union([ -z.object({"pool": NameOrId.nullable().optional(), +z.object({"poolSelector": PoolSelector.default({"ipVersion":null,"type":"auto"}).optional(), "type": z.enum(["ephemeral"]), }), z.object({"floatingIp": NameOrId, @@ -1687,10 +1734,9 @@ export const FloatingIpAttach = z.preprocess(processResponseBody,z.object({"kind /** * Parameters for creating a new floating IP address for instances. */ -export const FloatingIpCreate = z.preprocess(processResponseBody,z.object({"description": z.string(), -"ip": z.ipv4().nullable().optional(), +export const FloatingIpCreate = z.preprocess(processResponseBody,z.object({"addressSelector": AddressSelector.default({"poolSelector":{"ipVersion":null,"type":"auto"},"type":"auto"}).optional(), +"description": z.string(), "name": Name, -"pool": NameOrId.nullable().optional(), })) /** @@ -1869,7 +1915,7 @@ export const Instance = z.preprocess(processResponseBody,z.object({"autoRestartC */ export const InstanceDiskAttachment = z.preprocess(processResponseBody,z.union([ z.object({"description": z.string(), -"diskSource": DiskSource, +"diskBackend": DiskBackend, "name": Name, "size": ByteCount, "type": z.enum(["create"]), @@ -1880,14 +1926,69 @@ z.object({"name": Name, ]) ) +/** +* How a VPC-private IP address is assigned to a network interface. + */ +export const Ipv4Assignment = z.preprocess(processResponseBody,z.union([ +z.object({"type": z.enum(["auto"]), +}), +z.object({"type": z.enum(["explicit"]), +"value": z.ipv4(), +}), +]) +) + +/** +* Configuration for a network interface's IPv4 addressing. + */ +export const PrivateIpv4StackCreate = z.preprocess(processResponseBody,z.object({"ip": Ipv4Assignment, +"transitIps": Ipv4Net.array().default([]).optional(), +})) + +/** +* How a VPC-private IP address is assigned to a network interface. + */ +export const Ipv6Assignment = z.preprocess(processResponseBody,z.union([ +z.object({"type": z.enum(["auto"]), +}), +z.object({"type": z.enum(["explicit"]), +"value": z.ipv6(), +}), +]) +) + +/** +* Configuration for a network interface's IPv6 addressing. + */ +export const PrivateIpv6StackCreate = z.preprocess(processResponseBody,z.object({"ip": Ipv6Assignment, +"transitIps": Ipv6Net.array().default([]).optional(), +})) + +/** +* Create parameters for a network interface's IP stack. + */ +export const PrivateIpStackCreate = z.preprocess(processResponseBody,z.union([ +z.object({"type": z.enum(["v4"]), +"value": PrivateIpv4StackCreate, +}), +z.object({"type": z.enum(["v6"]), +"value": PrivateIpv6StackCreate, +}), +z.object({"type": z.enum(["dual_stack"]), +"value": z.object({"v4": PrivateIpv4StackCreate, +"v6": PrivateIpv6StackCreate, +}), +}), +]) +) + /** * Create-time parameters for an `InstanceNetworkInterface` */ export const InstanceNetworkInterfaceCreate = z.preprocess(processResponseBody,z.object({"description": z.string(), -"ip": z.ipv4().nullable().optional(), +"ipConfig": PrivateIpStackCreate.default({"type":"dual_stack","value":{"v4":{"ip":{"type":"auto"},"transitIps":[]},"v6":{"ip":{"type":"auto"},"transitIps":[]}}}).optional(), "name": Name, "subnetName": Name, -"transitIps": IpNet.array().default([]).optional(), "vpcName": Name, })) @@ -1898,7 +1999,11 @@ export const InstanceNetworkInterfaceAttachment = z.preprocess(processResponseBo z.object({"params": InstanceNetworkInterfaceCreate.array(), "type": z.enum(["create"]), }), -z.object({"type": z.enum(["default"]), +z.object({"type": z.enum(["default_ipv4"]), +}), +z.object({"type": z.enum(["default_ipv6"]), +}), +z.object({"type": z.enum(["default_dual_stack"]), }), z.object({"type": z.enum(["none"]), }), @@ -1917,14 +2022,47 @@ export const InstanceCreate = z.preprocess(processResponseBody,z.object({"antiAf "externalIps": ExternalIpCreate.array().default([]).optional(), "hostname": Hostname, "memory": ByteCount, +"multicastGroups": NameOrId.array().default([]).optional(), "name": Name, "ncpus": InstanceCpuCount, -"networkInterfaces": InstanceNetworkInterfaceAttachment.default({"type":"default"}).optional(), +"networkInterfaces": InstanceNetworkInterfaceAttachment.default({"type":"default_dual_stack"}).optional(), "sshPublicKeys": NameOrId.array().nullable().optional(), "start": SafeBoolean.default(true).optional(), "userData": z.string().default("").optional(), })) +/** +* The VPC-private IPv4 stack for a network interface + */ +export const PrivateIpv4Stack = z.preprocess(processResponseBody,z.object({"ip": z.ipv4(), +"transitIps": Ipv4Net.array(), +})) + +/** +* The VPC-private IPv6 stack for a network interface + */ +export const PrivateIpv6Stack = z.preprocess(processResponseBody,z.object({"ip": z.ipv6(), +"transitIps": Ipv6Net.array(), +})) + +/** +* The VPC-private IP stack for a network interface. + */ +export const PrivateIpStack = z.preprocess(processResponseBody,z.union([ +z.object({"type": z.enum(["v4"]), +"value": PrivateIpv4Stack, +}), +z.object({"type": z.enum(["v6"]), +"value": PrivateIpv6Stack, +}), +z.object({"type": z.enum(["dual_stack"]), +"value": z.object({"v4": PrivateIpv4Stack, +"v6": PrivateIpv6Stack, +}), +}), +]) +) + /** * A MAC address * @@ -1938,14 +2076,13 @@ export const MacAddr = z.preprocess(processResponseBody,z.string().min(5).max(17 export const InstanceNetworkInterface = z.preprocess(processResponseBody,z.object({"description": z.string(), "id": z.uuid(), "instanceId": z.uuid(), -"ip": z.ipv4(), +"ipStack": PrivateIpStack, "mac": MacAddr, "name": Name, "primary": SafeBoolean, "subnetId": z.uuid(), "timeCreated": z.coerce.date(), "timeModified": z.coerce.date(), -"transitIps": IpNet.array().default([]).optional(), "vpcId": z.uuid(), })) @@ -1988,6 +2125,7 @@ export const InstanceUpdate = z.preprocess(processResponseBody,z.object({"autoRe "bootDisk": NameOrId.nullable(), "cpuPlatform": InstanceCpuPlatform.nullable(), "memory": ByteCount, +"multicastGroups": NameOrId.array().nullable().default(null).optional(), "ncpus": InstanceCpuCount, })) @@ -2080,11 +2218,6 @@ export const InternetGatewayResultsPage = z.preprocess(processResponseBody,z.obj "nextPage": z.string().nullable().optional(), })) -/** -* The IP address version. - */ -export const IpVersion = z.preprocess(processResponseBody,z.enum(["v4","v6"])) - /** * Type of IP pool. */ @@ -2346,6 +2479,108 @@ export const MeasurementResultsPage = z.preprocess(processResponseBody,z.object( export const MetricType = z.preprocess(processResponseBody,z.enum(["gauge", "delta", "cumulative"]) ) +/** +* View of a Multicast Group + */ +export const MulticastGroup = z.preprocess(processResponseBody,z.object({"description": z.string(), +"id": z.uuid(), +"ipPoolId": z.uuid(), +"multicastIp": z.ipv4(), +"mvlan": z.number().min(0).max(65535).nullable().optional(), +"name": Name, +"sourceIps": z.ipv4().array(), +"state": z.string(), +"timeCreated": z.coerce.date(), +"timeModified": z.coerce.date(), +})) + +/** +* Create-time parameters for a multicast group. + */ +export const MulticastGroupCreate = z.preprocess(processResponseBody,z.object({"description": z.string(), +"multicastIp": z.ipv4().nullable().default(null).optional(), +"mvlan": z.number().default(null).min(0).max(65535).nullable().optional(), +"name": Name, +"pool": NameOrId.nullable().default(null).optional(), +"sourceIps": z.ipv4().array().nullable().default(null).optional(), +})) + +/** +* View of a Multicast Group Member (instance belonging to a multicast group) + */ +export const MulticastGroupMember = z.preprocess(processResponseBody,z.object({"description": z.string(), +"id": z.uuid(), +"instanceId": z.uuid(), +"multicastGroupId": z.uuid(), +"name": Name, +"state": z.string(), +"timeCreated": z.coerce.date(), +"timeModified": z.coerce.date(), +})) + +/** +* Parameters for adding an instance to a multicast group. + */ +export const MulticastGroupMemberAdd = z.preprocess(processResponseBody,z.object({"instance": NameOrId, +})) + +/** +* A single page of results + */ +export const MulticastGroupMemberResultsPage = z.preprocess(processResponseBody,z.object({"items": MulticastGroupMember.array(), +"nextPage": z.string().nullable().optional(), +})) + +/** +* A single page of results + */ +export const MulticastGroupResultsPage = z.preprocess(processResponseBody,z.object({"items": MulticastGroup.array(), +"nextPage": z.string().nullable().optional(), +})) + +/** +* Update-time parameters for a multicast group. + */ +export const MulticastGroupUpdate = z.preprocess(processResponseBody,z.object({"description": z.string().nullable().optional(), +"mvlan": z.number().min(0).max(65535).nullable().optional(), +"name": Name.nullable().optional(), +"sourceIps": z.ipv4().array().nullable().optional(), +})) + +/** +* VPC-private IPv4 configuration for a network interface. + */ +export const PrivateIpv4Config = z.preprocess(processResponseBody,z.object({"ip": z.ipv4(), +"subnet": Ipv4Net, +"transitIps": Ipv4Net.array().default([]).optional(), +})) + +/** +* VPC-private IPv6 configuration for a network interface. + */ +export const PrivateIpv6Config = z.preprocess(processResponseBody,z.object({"ip": z.ipv6(), +"subnet": Ipv6Net, +"transitIps": Ipv6Net.array(), +})) + +/** +* VPC-private IP address configuration for a network interface. + */ +export const PrivateIpConfig = z.preprocess(processResponseBody,z.union([ +z.object({"type": z.enum(["v4"]), +"value": PrivateIpv4Config, +}), +z.object({"type": z.enum(["v6"]), +"value": PrivateIpv6Config, +}), +z.object({"type": z.enum(["dual_stack"]), +"value": z.object({"v4": PrivateIpv4Config, +"v6": PrivateIpv6Config, +}), +}), +]) +) + /** * The type of network interface */ @@ -2371,14 +2606,12 @@ export const Vni = z.preprocess(processResponseBody,z.number().min(0).max(429496 * Information required to construct a virtual network interface */ export const NetworkInterface = z.preprocess(processResponseBody,z.object({"id": z.uuid(), -"ip": z.ipv4(), +"ipConfig": PrivateIpConfig, "kind": NetworkInterfaceKind, "mac": MacAddr, "name": Name, "primary": SafeBoolean, "slot": z.number().min(0).max(255), -"subnet": IpNet, -"transitIps": IpNet.array().default([]).optional(), "vni": Vni, })) @@ -2521,8 +2754,8 @@ export const Probe = z.preprocess(processResponseBody,z.object({"description": z * Create time parameters for probes. */ export const ProbeCreate = z.preprocess(processResponseBody,z.object({"description": z.string(), -"ipPool": NameOrId.nullable().optional(), "name": Name, +"poolSelector": PoolSelector.default({"ipVersion":null,"type":"auto"}).optional(), "sled": z.uuid(), })) @@ -2841,8 +3074,10 @@ export const SiloCreate = z.preprocess(processResponseBody,z.object({"adminGroup */ export const SiloIpPool = z.preprocess(processResponseBody,z.object({"description": z.string(), "id": z.uuid(), +"ipVersion": IpVersion, "isDefault": SafeBoolean, "name": Name, +"poolType": IpPoolType, "timeCreated": z.coerce.date(), "timeModified": z.coerce.date(), })) @@ -4620,6 +4855,35 @@ export const InstanceEphemeralIpDetachParams = z.preprocess(processResponseBody, }), })) +export const InstanceMulticastGroupListParams = z.preprocess(processResponseBody, z.object({ + path: z.object({ + instance: NameOrId, + }), + query: z.object({ + project: NameOrId.optional(), + }), +})) + +export const InstanceMulticastGroupJoinParams = z.preprocess(processResponseBody, z.object({ + path: z.object({ + instance: NameOrId, + multicastGroup: NameOrId, + }), + query: z.object({ + project: NameOrId.optional(), + }), +})) + +export const InstanceMulticastGroupLeaveParams = z.preprocess(processResponseBody, z.object({ + path: z.object({ + instance: NameOrId, + multicastGroup: NameOrId, + }), + query: z.object({ + project: NameOrId.optional(), + }), +})) + export const InstanceRebootParams = z.preprocess(processResponseBody, z.object({ path: z.object({ instance: NameOrId, @@ -4908,6 +5172,77 @@ export const SiloMetricParams = z.preprocess(processResponseBody, z.object({ }), })) +export const MulticastGroupListParams = z.preprocess(processResponseBody, z.object({ + path: z.object({ + }), + query: z.object({ + limit: z.number().min(1).max(4294967295).nullable().optional(), + pageToken: z.string().nullable().optional(), + sortBy: NameOrIdSortMode.optional(), + }), +})) + +export const MulticastGroupCreateParams = z.preprocess(processResponseBody, z.object({ + path: z.object({ + }), + query: z.object({ + }), +})) + +export const MulticastGroupViewParams = z.preprocess(processResponseBody, z.object({ + path: z.object({ + multicastGroup: NameOrId, + }), + query: z.object({ + }), +})) + +export const MulticastGroupUpdateParams = z.preprocess(processResponseBody, z.object({ + path: z.object({ + multicastGroup: NameOrId, + }), + query: z.object({ + }), +})) + +export const MulticastGroupDeleteParams = z.preprocess(processResponseBody, z.object({ + path: z.object({ + multicastGroup: NameOrId, + }), + query: z.object({ + }), +})) + +export const MulticastGroupMemberListParams = z.preprocess(processResponseBody, z.object({ + path: z.object({ + multicastGroup: NameOrId, + }), + query: z.object({ + limit: z.number().min(1).max(4294967295).nullable().optional(), + pageToken: z.string().nullable().optional(), + sortBy: IdSortMode.optional(), + }), +})) + +export const MulticastGroupMemberAddParams = z.preprocess(processResponseBody, z.object({ + path: z.object({ + multicastGroup: NameOrId, + }), + query: z.object({ + project: NameOrId.optional(), + }), +})) + +export const MulticastGroupMemberRemoveParams = z.preprocess(processResponseBody, z.object({ + path: z.object({ + instance: NameOrId, + multicastGroup: NameOrId, + }), + query: z.object({ + project: NameOrId.optional(), + }), +})) + export const InstanceNetworkInterfaceListParams = z.preprocess(processResponseBody, z.object({ path: z.object({ }), @@ -5488,6 +5823,14 @@ export const SystemMetricParams = z.preprocess(processResponseBody, z.object({ }), })) +export const LookupMulticastGroupByIpParams = z.preprocess(processResponseBody, z.object({ + path: z.object({ + address: z.ipv4(), + }), + query: z.object({ + }), +})) + export const NetworkingAddressLotListParams = z.preprocess(processResponseBody, z.object({ path: z.object({ }), diff --git a/oxide-openapi-gen-ts/src/gen.test.ts b/oxide-openapi-gen-ts/src/gen.test.ts index d4af56b..4fcda5b 100644 --- a/oxide-openapi-gen-ts/src/gen.test.ts +++ b/oxide-openapi-gen-ts/src/gen.test.ts @@ -12,9 +12,10 @@ import { mkdtempSync, rmSync, readFileSync } from "node:fs"; import { join, dirname } from "node:path"; import { tmpdir } from "node:os"; import { fileURLToPath } from "node:url"; +import { getSpecFilePath } from "./test-util"; const __dirname = dirname(fileURLToPath(import.meta.url)); -const SPEC_FILE = join(__dirname, "../../spec.json"); +const SPEC_FILE = getSpecFilePath(join(__dirname, "../../OMICRON_VERSION")); let tempDir: string; diff --git a/oxide-openapi-gen-ts/src/schema/types.test.ts b/oxide-openapi-gen-ts/src/schema/types.test.ts index 000bd59..145ba84 100644 --- a/oxide-openapi-gen-ts/src/schema/types.test.ts +++ b/oxide-openapi-gen-ts/src/schema/types.test.ts @@ -9,10 +9,17 @@ import { test, expect } from "vitest"; import { schemaToTypes } from "./types"; import { TestWritable, initIO } from "../io"; import * as prettier from "prettier"; +import { readFileSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { getSpecFilePath } from "../test-util"; -import spec from "../../spec.json"; import { type Schema } from "./base"; +const __dirname = dirname(fileURLToPath(import.meta.url)); +const SPEC_FILE = getSpecFilePath(join(__dirname, "../../../OMICRON_VERSION")); +const spec = JSON.parse(readFileSync(SPEC_FILE, "utf-8")); + const schemas = spec.components.schemas; function genType(schema: unknown) { @@ -39,6 +46,7 @@ test("Disk", () => { /** human-readable free-form text about a resource */ description: string; devicePath: string; + diskType: DiskType; /** unique, immutable, system-controlled identifier for each resource */ id: string; /** ID of image from which disk was created, if any */ diff --git a/oxide-openapi-gen-ts/src/schema/zod.ts b/oxide-openapi-gen-ts/src/schema/zod.ts index 5868a58..eb6a9a7 100644 --- a/oxide-openapi-gen-ts/src/schema/zod.ts +++ b/oxide-openapi-gen-ts/src/schema/zod.ts @@ -9,7 +9,15 @@ import { type IO } from "../io"; import { makeSchemaGenerator, refToSchemaName } from "./base"; import { type OpenAPIV3 } from "openapi-types"; -import { snakeToCamel } from "../util"; +import { camelify, snakeToCamel } from "../util"; + +/** + * Generate the .default() method call with transformed default value + */ +function getDefaultString(schema: OpenAPIV3.SchemaObject): string { + if (!("default" in schema)) return ""; + return `.default(${JSON.stringify(camelify(schema.default))})`; +} export const schemaToZod = makeSchemaGenerator({ ref(schema, { w0 }) { @@ -20,9 +28,7 @@ export const schemaToZod = makeSchemaGenerator({ boolean(schema, { w0 }) { w0(`SafeBoolean`); - if ("default" in schema) { - w0(`.default(${schema.default})`); - } + w0(getDefaultString(schema)); if (schema.nullable) w0(".nullable()"); }, @@ -64,7 +70,7 @@ export const schemaToZod = makeSchemaGenerator({ } if (schema.nullable) w0(".nullable()"); - if ("default" in schema) w0(`.default(${JSON.stringify(schema.default)})`); + w0(getDefaultString(schema)); }, date(schema, { w0 }) { @@ -87,9 +93,7 @@ export const schemaToZod = makeSchemaGenerator({ schemaToZod(schema.items, io); w0(".array()"); if (schema.nullable) io.w0(".nullable()"); - if ("default" in schema) { - w0(`.default(${JSON.stringify(schema.default)})`); - } + w0(getDefaultString(schema)); if (schema.uniqueItems) w0(`.refine(...uniqueItems)`); }, @@ -179,7 +183,7 @@ export const schemaToZod = makeSchemaGenerator({ } if (schema.nullable) w0(".nullable()"); - if ("default" in schema) w0(`.default(${JSON.stringify(schema.default)})`); + w0(getDefaultString(schema)); }, empty({ w0 }) { @@ -199,9 +203,7 @@ function schemaToZodInt(schema: OpenAPIV3.SchemaObject, { w0 }: IO) { w0(`z.number()`); } - if (schema.default) { - w0(`.default(${schema.default})`); - } + w0(getDefaultString(schema)); const [, unsigned, size] = schema.format?.match(/(u?)int(\d+)/) || []; if ("minimum" in schema) { diff --git a/oxide-openapi-gen-ts/src/test-util.ts b/oxide-openapi-gen-ts/src/test-util.ts new file mode 100644 index 0000000..0d11fd9 --- /dev/null +++ b/oxide-openapi-gen-ts/src/test-util.ts @@ -0,0 +1,35 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ + +import { readFileSync, existsSync } from "node:fs"; + +/** + * Gets the path to the cached OpenAPI spec file based on OMICRON_VERSION. + * + * @param omicronVersionPath - Path to the OMICRON_VERSION file + * @returns The full path to the cached spec file in /tmp + * @throws Error if the spec file doesn't exist + */ +export function getSpecFilePath(omicronVersionPath: string): string { + // Use split("\n")[0] to match bash's `head -n 1` behavior + const OMICRON_SHA = readFileSync(omicronVersionPath, "utf-8") + .split("\n")[0] + .trim(); + const SPEC_CACHE_DIR = "/tmp/openapi-gen-ts-schemas"; + const SPEC_FILE = `${SPEC_CACHE_DIR}/${OMICRON_SHA}.json`; + + // Check if the spec file exists + if (!existsSync(SPEC_FILE)) { + throw new Error( + `Spec file not found at ${SPEC_FILE}. ` + + `Please run \`npm run pretest\` or \`../tools/gen.sh\` to download the spec file first.` + ); + } + + return SPEC_FILE; +} diff --git a/oxide-openapi-gen-ts/src/util.ts b/oxide-openapi-gen-ts/src/util.ts index df01671..905a452 100644 --- a/oxide-openapi-gen-ts/src/util.ts +++ b/oxide-openapi-gen-ts/src/util.ts @@ -55,3 +55,38 @@ export const topologicalSort = ( export const extractDoc = (schema: OpenAPIV3.SchemaObject): string => [schema.title, schema.description].filter((n) => n).join("\n\n"); + +const isObjectOrArray = (o: unknown) => + typeof o === "object" && + !(o instanceof Date) && + !(o instanceof RegExp) && + !(o instanceof Error) && + o !== null; + +/** + * Recursively map (k, v) pairs using Object.entries + * + * Note that value transform function takes both k and v so we can use the key + * to decide whether to transform the value. + * + * @param kf maps key to key + * @param vf maps key + value to value + */ +const mapObj = + ( + kf: (k: string) => string, + vf: (k: string | undefined, v: unknown) => unknown = (_, v) => v + ) => + (o: unknown): unknown => { + if (!isObjectOrArray(o)) return o; + + if (Array.isArray(o)) return o.map(mapObj(kf, vf)); + + const newObj: Record = {}; + for (const [k, v] of Object.entries(o as Record)) { + newObj[kf(k)] = isObjectOrArray(v) ? mapObj(kf, vf)(v) : vf(k, v); + } + return newObj; + }; + +export const camelify = mapObj(snakeToCamel); diff --git a/tools/gen.sh b/tools/gen.sh index 99bd85f..1e6d367 100755 --- a/tools/gen.sh +++ b/tools/gen.sh @@ -22,14 +22,26 @@ EOF ) ROOT_DIR="$(dirname "$0")/.." -OMICRON_SHA=$(cat "$ROOT_DIR/OMICRON_VERSION") +OMICRON_SHA=$(head -n 1 "$ROOT_DIR/OMICRON_VERSION") DEST_DIR="$ROOT_DIR/oxide-api/src" -SPEC_URL="https://raw.githubusercontent.com/oxidecomputer/omicron/$OMICRON_SHA/openapi/nexus.json" -SPEC_FILE="./spec.json" +SPEC_BASE="https://raw.githubusercontent.com/oxidecomputer/omicron/$OMICRON_SHA/openapi/nexus" +SPEC_CACHE_DIR="/tmp/openapi-gen-ts-schemas" -# TODO: we could get rid of this DL if a test didn't rely on it -curl --fail "$SPEC_URL" -o $SPEC_FILE +# Create cache directory if it doesn't exist +mkdir -p "$SPEC_CACHE_DIR" + +SPEC_FILE="$SPEC_CACHE_DIR/$OMICRON_SHA.json" + +# Download spec only if not already cached +if [ ! -f "$SPEC_FILE" ]; then + echo "Downloading spec for $OMICRON_SHA..." + # nexus-latest.json is a symlink that contains the actual spec filename + LATEST_SPEC=$(curl --fail "$SPEC_BASE/nexus-latest.json") + curl --fail "$SPEC_BASE/$LATEST_SPEC" -o "$SPEC_FILE" +else + echo "Using cached spec for $OMICRON_SHA" +fi rm -f "$DEST_DIR/*" # remove after we add --clean flag to generator