diff --git a/OMICRON_VERSION b/OMICRON_VERSION index ad0d5dd2e..05e386415 100644 --- a/OMICRON_VERSION +++ b/OMICRON_VERSION @@ -1 +1 @@ -a6e111cf72ab987b2ea5acd7d26610ea2a55bf0f +f65b0e47d593c7e6a7f328d7130b0361d85cab9a diff --git a/app/api/__generated__/Api.ts b/app/api/__generated__/Api.ts index e4aa49ac4..f2be319a4 100644 --- a/app/api/__generated__/Api.ts +++ b/app/api/__generated__/Api.ts @@ -1769,7 +1769,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 } @@ -1777,6 +1781,8 @@ export type DeviceAuthVerify = { userCode: string } export type Digest = { type: 'sha256'; value: string } +export type DiskType = 'crucible' + /** * State of a Disk */ @@ -1814,6 +1820,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 */ @@ -1885,9 +1892,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 } @@ -1902,9 +1909,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 } @@ -2427,6 +2434,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 @@ -2547,6 +2558,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 } @@ -3012,7 +3029,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 @@ -3056,6 +3073,113 @@ 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 +} + /** * The type of network interface */ @@ -5624,6 +5748,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 } @@ -5820,6 +5970,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 @@ -6174,6 +6369,10 @@ export interface SystemMetricQueryParams { silo?: NameOrId } +export interface LookupMulticastGroupByIpPathParams { + address: string +} + export interface NetworkingAddressLotListQueryParams { limit?: number | null pageToken?: string | null @@ -6652,7 +6851,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 = '2025112000.0.0' constructor({ host = '', baseParams = {}, token }: ApiConfig = {}) { this.host = host @@ -8102,6 +8301,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 */ @@ -8542,6 +8801,137 @@ 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 */ @@ -9500,6 +9890,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/app/api/__generated__/OMICRON_VERSION b/app/api/__generated__/OMICRON_VERSION index c092490dd..feecf019a 100644 --- a/app/api/__generated__/OMICRON_VERSION +++ b/app/api/__generated__/OMICRON_VERSION @@ -1,2 +1,2 @@ # generated file. do not update manually. see docs/update-pinned-api.md -a6e111cf72ab987b2ea5acd7d26610ea2a55bf0f +f65b0e47d593c7e6a7f328d7130b0361d85cab9a diff --git a/app/api/__generated__/msw-handlers.ts b/app/api/__generated__/msw-handlers.ts index 7f8f3563b..3d0aa5ace 100644 --- a/app/api/__generated__/msw-handlers.ts +++ b/app/api/__generated__/msw-handlers.ts @@ -628,6 +628,27 @@ export interface MSWHandlers { 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 @@ -815,6 +836,59 @@ export interface MSWHandlers { 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 @@ -1231,6 +1305,12 @@ export interface MSWHandlers { 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 @@ -2405,6 +2485,30 @@ export function makeHandlers(handlers: MSWHandlers): HttpHandler[] { 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) @@ -2567,6 +2671,54 @@ export function makeHandlers(handlers: MSWHandlers): HttpHandler[] { '/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( @@ -2902,6 +3054,14 @@ export function makeHandlers(handlers: MSWHandlers): HttpHandler[] { '/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( diff --git a/app/api/__generated__/validate.ts b/app/api/__generated__/validate.ts index a1af9104e..9833d2d00 100644 --- a/app/api/__generated__/validate.ts +++ b/app/api/__generated__/validate.ts @@ -1640,6 +1640,8 @@ export const Digest = z.preprocess( z.object({ type: z.enum(['sha256']), value: z.string() }) ) +export const DiskType = z.preprocess(processResponseBody, z.enum(['crucible'])) + /** * State of a Disk */ @@ -1670,6 +1672,7 @@ export const Disk = z.preprocess( blockSize: ByteCount, description: z.string(), devicePath: z.string(), + diskType: DiskType, id: z.uuid(), imageId: z.uuid().nullable().optional(), name: Name, @@ -1725,9 +1728,9 @@ export const Distributiondouble = z.preprocess( 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(), }) @@ -1745,9 +1748,9 @@ export const Distributionint64 = z.preprocess( 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(), }) @@ -2234,12 +2237,13 @@ export const InstanceCreate = z.preprocess( externalIps: ExternalIpCreate.array().default([]).optional(), hostname: Hostname, memory: ByteCount, + multicastGroups: NameOrId.array().default([]).optional(), name: Name, ncpus: InstanceCpuCount, networkInterfaces: InstanceNetworkInterfaceAttachment.default({ type: 'default', }).optional(), - sshPublicKeys: NameOrId.array().optional(), + sshPublicKeys: NameOrId.array().nullable().optional(), start: SafeBoolean.default(true).optional(), userData: z.string().default('').optional(), }) @@ -2332,6 +2336,7 @@ export const InstanceUpdate = z.preprocess( bootDisk: NameOrId.nullable(), cpuPlatform: InstanceCpuPlatform.nullable(), memory: ByteCount, + multicastGroups: NameOrId.array().nullable().default(null).optional(), ncpus: InstanceCpuCount, }) ) @@ -2700,7 +2705,7 @@ export const ManagementAddress = z.preprocess( z.object({ addr: NetworkAddress, interfaceNum: InterfaceNum, - oid: z.number().min(0).max(255).array().optional(), + oid: z.number().min(0).max(255).array().nullable().optional(), }) ) @@ -2791,6 +2796,97 @@ export const MetricType = z.preprocess( 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().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(), + }) +) + /** * The type of network interface */ @@ -2864,7 +2960,7 @@ export const Values = z.preprocess( export const Points = z.preprocess( processResponseBody, z.object({ - startTimes: z.coerce.date().array().optional(), + startTimes: z.coerce.date().array().nullable().optional(), timestamps: z.coerce.date().array(), values: Values.array(), }) @@ -4329,9 +4425,9 @@ export const VpcFirewallRuleProtocol = z.preprocess( export const VpcFirewallRuleFilter = z.preprocess( processResponseBody, z.object({ - hosts: VpcFirewallRuleHostFilter.array().optional(), - ports: L4PortRange.array().optional(), - protocols: VpcFirewallRuleProtocol.array().optional(), + hosts: VpcFirewallRuleHostFilter.array().nullable().optional(), + ports: L4PortRange.array().nullable().optional(), + protocols: VpcFirewallRuleProtocol.array().nullable().optional(), }) ) @@ -5639,6 +5735,44 @@ export const InstanceEphemeralIpDetachParams = z.preprocess( }) ) +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({ @@ -5993,6 +6127,95 @@ export const SiloMetricParams = z.preprocess( }) ) +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({ @@ -6711,6 +6934,16 @@ export const SystemMetricParams = z.preprocess( }) ) +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({ diff --git a/mock-api/disk.ts b/mock-api/disk.ts index 2a6f8e003..330b1dff3 100644 --- a/mock-api/disk.ts +++ b/mock-api/disk.ts @@ -64,6 +64,7 @@ export const disk1: Json = { device_path: '/abc', size: 2 * GiB, block_size: 2048, + disk_type: 'crucible', } export const disk2: Json = { @@ -77,6 +78,7 @@ export const disk2: Json = { device_path: '/def', size: 4 * GiB, block_size: 2048, + disk_type: 'crucible', } export const disks: Json[] = [ @@ -94,6 +96,7 @@ export const disks: Json[] = [ device_path: '/ghi', size: 6 * GiB, block_size: 2048, + disk_type: 'crucible', }, { id: '5695b16d-e1d6-44b0-a75c-7b4299831540', @@ -106,6 +109,7 @@ export const disks: Json[] = [ device_path: '/jkl', size: 64 * GiB, block_size: 2048, + disk_type: 'crucible', }, { id: '4d6f4c76-675f-4cda-b609-f3b8b301addb', @@ -118,6 +122,7 @@ export const disks: Json[] = [ device_path: '/jkl', size: 128 * GiB, block_size: 2048, + disk_type: 'crucible', }, { id: '41481936-5a6b-4dcd-8dec-26c3bdc343bd', @@ -130,6 +135,7 @@ export const disks: Json[] = [ device_path: '/jkl', size: 20 * GiB, block_size: 2048, + disk_type: 'crucible', }, { id: '704cd392-9f6b-4a2b-8410-1f1e0794db80', @@ -142,6 +148,7 @@ export const disks: Json[] = [ device_path: '/jkl', size: 24 * GiB, block_size: 2048, + disk_type: 'crucible', }, { id: '305ee9c7-1930-4a8f-86d7-ed9eece9598e', @@ -154,6 +161,7 @@ export const disks: Json[] = [ device_path: '/jkl', size: 16 * GiB, block_size: 2048, + disk_type: 'crucible', }, { id: 'ccad8d48-df21-4a80-8c16-683ee6bfb290', @@ -166,6 +174,7 @@ export const disks: Json[] = [ device_path: '/jkl', size: 32 * GiB, block_size: 2048, + disk_type: 'crucible', }, { id: 'a028160f-603c-4562-bb71-d2d76f1ac2a8', @@ -178,6 +187,7 @@ export const disks: Json[] = [ device_path: '/jkl', size: 24 * GiB, block_size: 2048, + disk_type: 'crucible', }, { id: '3f23c80f-c523-4d86-8292-2ca3f807bb12', @@ -190,6 +200,7 @@ export const disks: Json[] = [ device_path: '/jkl', size: 12 * GiB, block_size: 2048, + disk_type: 'crucible', }, // put a ton of disks in project 2 so we can use it to test comboboxes ...Array.from({ length: 1010 }).map((_, i) => { @@ -205,6 +216,7 @@ export const disks: Json[] = [ device_path: '/jkl', size: 12 * GiB, block_size: 2048, + disk_type: 'crucible' as const, } }), ] diff --git a/mock-api/msw/handlers.ts b/mock-api/msw/handlers.ts index b6995b6f2..c6d53693d 100644 --- a/mock-api/msw/handlers.ts +++ b/mock-api/msw/handlers.ts @@ -161,6 +161,7 @@ export const handlers = makeHandlers({ // TODO: for non-blank disk sources, look up image or snapshot by ID and // pull block size from there block_size: disk_source.type === 'blank' ? disk_source.block_size : 512, + disk_type: 'crucible', ...getTimestamps(), } db.disks.push(newDisk) @@ -490,6 +491,7 @@ export const handlers = makeHandlers({ state: { state: 'attached', instance: instanceId }, device_path: '/mnt/disk', block_size: disk_source.type === 'blank' ? disk_source.block_size : 4096, + disk_type: 'crucible', ...getTimestamps(), } db.disks.push(newDisk) @@ -1961,6 +1963,9 @@ export const handlers = makeHandlers({ certificateDelete: NotImplemented, certificateList: NotImplemented, certificateView: NotImplemented, + instanceMulticastGroupJoin: NotImplemented, + instanceMulticastGroupLeave: NotImplemented, + instanceMulticastGroupList: NotImplemented, instanceSerialConsole: NotImplemented, instanceSerialConsoleStream: NotImplemented, instanceSshPublicKeyList: NotImplemented, @@ -1978,6 +1983,15 @@ export const handlers = makeHandlers({ localIdpUserDelete: NotImplemented, localIdpUserSetPassword: NotImplemented, loginSaml: NotImplemented, + lookupMulticastGroupByIp: NotImplemented, + multicastGroupCreate: NotImplemented, + multicastGroupDelete: NotImplemented, + multicastGroupList: NotImplemented, + multicastGroupMemberAdd: NotImplemented, + multicastGroupMemberList: NotImplemented, + multicastGroupMemberRemove: NotImplemented, + multicastGroupUpdate: NotImplemented, + multicastGroupView: NotImplemented, networkingAddressLotBlockList: NotImplemented, networkingAddressLotCreate: NotImplemented, networkingAddressLotDelete: NotImplemented, diff --git a/package-lock.json b/package-lock.json index ffe4b0ec7..ca5287b90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,7 @@ "@eslint/js": "^9.38.0", "@ianvs/prettier-plugin-sort-imports": "^4.7.0", "@mswjs/http-middleware": "^0.10.3", - "@oxide/openapi-gen-ts": "~0.11.0", + "@oxide/openapi-gen-ts": "~0.12.0", "@playwright/test": "^1.56.1", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", @@ -1831,9 +1831,9 @@ } }, "node_modules/@oxide/openapi-gen-ts": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@oxide/openapi-gen-ts/-/openapi-gen-ts-0.11.0.tgz", - "integrity": "sha512-qBmDgTxT0gVTUgNM7b+/1qxZFxaLcLBLAYRyzm6CaBG40phPRQsxCaPfFJJL+REJZHpJJG0YMQbj60FI+11esw==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@oxide/openapi-gen-ts/-/openapi-gen-ts-0.12.0.tgz", + "integrity": "sha512-lebNC+PMbtXc7Ao4fPbJskEGkz6w7yfpaOh/tqboXgo6T3pznSRPF+GJFerGVnU0TRb1SgqItIBccPogsqZiJw==", "dev": true, "license": "MPL-2.0", "dependencies": { diff --git a/package.json b/package.json index 6711b2e07..362313dab 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "@eslint/js": "^9.38.0", "@ianvs/prettier-plugin-sort-imports": "^4.7.0", "@mswjs/http-middleware": "^0.10.3", - "@oxide/openapi-gen-ts": "~0.11.0", + "@oxide/openapi-gen-ts": "~0.12.0", "@playwright/test": "^1.56.1", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", diff --git a/tools/generate_api_client.sh b/tools/generate_api_client.sh index ab0086e7d..08e58f455 100755 --- a/tools/generate_api_client.sh +++ b/tools/generate_api_client.sh @@ -12,7 +12,7 @@ set -o xtrace OMICRON_SHA=$(head -n 1 OMICRON_VERSION) GEN_DIR="$PWD/app/api/__generated__" -SPEC_URL="https://raw.githubusercontent.com/oxidecomputer/omicron/$OMICRON_SHA/openapi/nexus.json" +SPEC_BASE="https://raw.githubusercontent.com/oxidecomputer/omicron/$OMICRON_SHA/openapi/nexus" HEADER=$(cat <<'EOF' /** @@ -25,8 +25,10 @@ HEADER=$(cat <<'EOF' EOF) +LATEST_SPEC=$(curl "$SPEC_BASE/nexus-latest.json") + # use versions of these packages specified in dev deps -npm run openapi-gen-ts -- $SPEC_URL $GEN_DIR --features msw +npm run openapi-gen-ts -- "$SPEC_BASE/$LATEST_SPEC" $GEN_DIR --features msw for f in Api.ts msw-handlers.ts validate.ts; do (printf '%s\n\n' "$HEADER"; cat "$GEN_DIR/$f") > "$GEN_DIR/$f.tmp"