diff --git a/OMICRON_VERSION b/OMICRON_VERSION index f7a1d4ae6..cb340659d 100644 --- a/OMICRON_VERSION +++ b/OMICRON_VERSION @@ -1 +1 @@ -c50cf019cd9be35f98266a7f4acacab0236b3a3d +0640bb277df110f3e740464bf4bacdf2bd24c897 diff --git a/app/api/__generated__/Api.ts b/app/api/__generated__/Api.ts index e64990f1f..02a553344 100644 --- a/app/api/__generated__/Api.ts +++ b/app/api/__generated__/Api.ts @@ -1710,6 +1710,16 @@ export type ImageResultsPage = { */ export type ImportBlocksBulkWrite = { base64EncodedData: string; offset: number } +/** + * A policy determining when an instance should be automatically restarted by the control plane. + */ +export type InstanceAutoRestartPolicy = + /** The instance should not be automatically restarted by the control plane if it fails. */ + | 'never' + + /** If this instance is running and unexpectedly fails (e.g. due to a host software crash or unexpected host reboot), the control plane will make a best-effort attempt to restart it. The control plane may choose not to restart the instance to preserve the overall availability of the system. */ + | 'best_effort' + /** * The number of CPUs in an Instance */ @@ -1761,6 +1771,10 @@ If this is not present, then either the instance has never been automatically re autoRestartCooldownExpiration?: Date /** `true` if this instance's auto-restart policy will permit the control plane to automatically restart it if it enters the `Failed` state. */ autoRestartEnabled: boolean + /** The auto-restart policy configured for this instance, or `None` if no explicit policy is configured. + +If this is not present, then this instance uses the default auto-restart policy, which may or may not allow it to be restarted. The `auto_restart_enabled` field indicates whether the instance will be automatically restarted. */ + autoRestartPolicy?: InstanceAutoRestartPolicy /** the ID of the disk used to boot this Instance, if a specific one is assigned. */ bootDiskId?: string /** human-readable free-form text about a resource */ @@ -1789,16 +1803,6 @@ If this is not present, then this instance has not been automatically restarted. timeRunStateUpdated: Date } -/** - * A policy determining when an instance should be automatically restarted by the control plane. - */ -export type InstanceAutoRestartPolicy = - /** The instance should not be automatically restarted by the control plane if it fails. */ - | 'never' - - /** If this instance is running and unexpectedly fails (e.g. due to a host software crash or unexpected host reboot), the control plane will make a best-effort attempt to restart it. The control plane may choose not to restart the instance to preserve the overall availability of the system. */ - | 'best_effort' - /** * Describe the instance's disks at creation time */ @@ -1976,12 +1980,127 @@ export type InstanceSerialConsoleData = { * Parameters of an `Instance` that can be reconfigured after creation. */ export type InstanceUpdate = { + /** The auto-restart policy for this instance. + +If not provided, unset the instance's auto-restart policy. */ + autoRestartPolicy?: InstanceAutoRestartPolicy /** Name or ID of the disk the instance should be instructed to boot from. If not provided, unset the instance's boot disk. */ bootDisk?: NameOrId } +/** + * An internet gateway provides a path between VPC networks and external networks. + */ +export type InternetGateway = { + /** human-readable free-form text about a resource */ + description: string + /** unique, immutable, system-controlled identifier for each resource */ + id: string + /** unique, mutable, user-controlled identifier for each resource */ + name: Name + /** timestamp when this resource was created */ + timeCreated: Date + /** timestamp when this resource was last modified */ + timeModified: Date + /** The VPC to which the gateway belongs. */ + vpcId: string +} + +/** + * Create-time parameters for an `InternetGateway` + */ +export type InternetGatewayCreate = { description: string; name: Name } + +/** + * An IP address that is attached to an internet gateway + */ +export type InternetGatewayIpAddress = { + /** The associated IP address, */ + address: string + /** human-readable free-form text about a resource */ + description: string + /** unique, immutable, system-controlled identifier for each resource */ + id: string + /** The associated internet gateway. */ + internetGatewayId: string + /** unique, mutable, user-controlled identifier for each resource */ + name: Name + /** timestamp when this resource was created */ + timeCreated: Date + /** timestamp when this resource was last modified */ + timeModified: Date +} + +/** + * Create-time identity-related parameters + */ +export type InternetGatewayIpAddressCreate = { + address: string + description: string + name: Name +} + +/** + * A single page of results + */ +export type InternetGatewayIpAddressResultsPage = { + /** list of items on this page of results */ + items: InternetGatewayIpAddress[] + /** token used to fetch the next page of results (if any) */ + nextPage?: string +} + +/** + * An IP pool that is attached to an internet gateway + */ +export type InternetGatewayIpPool = { + /** human-readable free-form text about a resource */ + description: string + /** unique, immutable, system-controlled identifier for each resource */ + id: string + /** The associated internet gateway. */ + internetGatewayId: string + /** The associated IP pool. */ + ipPoolId: string + /** unique, mutable, user-controlled identifier for each resource */ + name: Name + /** timestamp when this resource was created */ + timeCreated: Date + /** timestamp when this resource was last modified */ + timeModified: Date +} + +/** + * Create-time identity-related parameters + */ +export type InternetGatewayIpPoolCreate = { + description: string + ipPool: NameOrId + name: Name +} + +/** + * A single page of results + */ +export type InternetGatewayIpPoolResultsPage = { + /** list of items on this page of results */ + items: InternetGatewayIpPool[] + /** token used to fetch the next page of results (if any) */ + nextPage?: string +} + +/** + * A single page of results + */ +export type InternetGatewayResultsPage = { + /** list of items on this page of results */ + items: InternetGateway[] + /** token used to fetch the next page of results (if any) */ + nextPage?: string +} + /** * A collection of IP ranges. If a pool is linked to a silo, IP addresses from the pool can be allocated within that silo */ @@ -2610,7 +2729,7 @@ export type Route = { /** The route gateway. */ gw: string /** Local preference for route. Higher preference indictes precedence within and across protocols. */ - localPref?: number + ribPriority?: number /** VLAN id the gateway is reachable over. */ vid?: number } @@ -2687,7 +2806,7 @@ export type RouterRouteKind = export type RouterRoute = { /** human-readable free-form text about a resource */ description: string - /** Selects which traffic this routing rule will apply to. */ + /** Selects which traffic this routing rule will apply to */ destination: RouteDestination /** unique, immutable, system-controlled identifier for each resource */ id: string @@ -2695,7 +2814,7 @@ export type RouterRoute = { kind: RouterRouteKind /** unique, mutable, user-controlled identifier for each resource */ name: Name - /** The location that matched packets should be forwarded to. */ + /** The location that matched packets should be forwarded to */ target: RouteTarget /** timestamp when this resource was created */ timeCreated: Date @@ -3397,10 +3516,10 @@ export type SwitchPortRouteConfig = { gw: IpNet /** The interface name this route configuration is assigned to. */ interfaceName: string - /** Local preference indicating priority within and across protocols. */ - localPref?: number /** The port settings object this route configuration belongs to. */ portSettingsId: string + /** RIB Priority indicating priority within and across protocols. */ + ribPriority?: number /** The VLAN identifier for the route. Use this if the gateway is reachable over an 802.1Q tagged L2 segment. */ vlanId?: number } @@ -4363,6 +4482,90 @@ export interface InstanceStopQueryParams { project?: NameOrId } +export interface InternetGatewayIpAddressListQueryParams { + gateway?: NameOrId + limit?: number + pageToken?: string + project?: NameOrId + sortBy?: NameOrIdSortMode + vpc?: NameOrId +} + +export interface InternetGatewayIpAddressCreateQueryParams { + gateway: NameOrId + project?: NameOrId + vpc?: NameOrId +} + +export interface InternetGatewayIpAddressDeletePathParams { + address: NameOrId +} + +export interface InternetGatewayIpAddressDeleteQueryParams { + cascade?: boolean + gateway?: NameOrId + project?: NameOrId + vpc?: NameOrId +} + +export interface InternetGatewayIpPoolListQueryParams { + gateway?: NameOrId + limit?: number + pageToken?: string + project?: NameOrId + sortBy?: NameOrIdSortMode + vpc?: NameOrId +} + +export interface InternetGatewayIpPoolCreateQueryParams { + gateway: NameOrId + project?: NameOrId + vpc?: NameOrId +} + +export interface InternetGatewayIpPoolDeletePathParams { + pool: NameOrId +} + +export interface InternetGatewayIpPoolDeleteQueryParams { + cascade?: boolean + gateway?: NameOrId + project?: NameOrId + vpc?: NameOrId +} + +export interface InternetGatewayListQueryParams { + limit?: number + pageToken?: string + project?: NameOrId + sortBy?: NameOrIdSortMode + vpc?: NameOrId +} + +export interface InternetGatewayCreateQueryParams { + project?: NameOrId + vpc: NameOrId +} + +export interface InternetGatewayViewPathParams { + gateway: NameOrId +} + +export interface InternetGatewayViewQueryParams { + project?: NameOrId + vpc?: NameOrId +} + +export interface InternetGatewayDeletePathParams { + gateway: NameOrId +} + +export interface InternetGatewayDeleteQueryParams { + cascade?: boolean + project?: NameOrId + vpc?: NameOrId +} + export interface ProjectIpPoolListQueryParams { limit?: number pageToken?: string @@ -5103,6 +5306,9 @@ export type ApiListMethods = Pick< | 'instanceDiskList' | 'instanceExternalIpList' | 'instanceSshPublicKeyList' + | 'internetGatewayIpAddressList' + | 'internetGatewayIpPoolList' + | 'internetGatewayList' | 'projectIpPoolList' | 'currentUserSshKeyList' | 'instanceNetworkInterfaceList' @@ -5998,6 +6204,185 @@ export class Api extends HttpClient { ...params, }) }, + /** + * List IP addresses attached to internet gateway + */ + internetGatewayIpAddressList: ( + { query = {} }: { query?: InternetGatewayIpAddressListQueryParams }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateway-ip-addresses`, + method: 'GET', + query, + ...params, + }) + }, + /** + * Attach IP address to internet gateway + */ + internetGatewayIpAddressCreate: ( + { + query, + body, + }: { + query: InternetGatewayIpAddressCreateQueryParams + body: InternetGatewayIpAddressCreate + }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateway-ip-addresses`, + method: 'POST', + body, + query, + ...params, + }) + }, + /** + * Detach IP address from internet gateway + */ + internetGatewayIpAddressDelete: ( + { + path, + query = {}, + }: { + path: InternetGatewayIpAddressDeletePathParams + query?: InternetGatewayIpAddressDeleteQueryParams + }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateway-ip-addresses/${path.address}`, + method: 'DELETE', + query, + ...params, + }) + }, + /** + * List IP pools attached to internet gateway + */ + internetGatewayIpPoolList: ( + { query = {} }: { query?: InternetGatewayIpPoolListQueryParams }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateway-ip-pools`, + method: 'GET', + query, + ...params, + }) + }, + /** + * Attach IP pool to internet gateway + */ + internetGatewayIpPoolCreate: ( + { + query, + body, + }: { + query: InternetGatewayIpPoolCreateQueryParams + body: InternetGatewayIpPoolCreate + }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateway-ip-pools`, + method: 'POST', + body, + query, + ...params, + }) + }, + /** + * Detach IP pool from internet gateway + */ + internetGatewayIpPoolDelete: ( + { + path, + query = {}, + }: { + path: InternetGatewayIpPoolDeletePathParams + query?: InternetGatewayIpPoolDeleteQueryParams + }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateway-ip-pools/${path.pool}`, + method: 'DELETE', + query, + ...params, + }) + }, + /** + * List internet gateways + */ + internetGatewayList: ( + { query = {} }: { query?: InternetGatewayListQueryParams }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateways`, + method: 'GET', + query, + ...params, + }) + }, + /** + * Create VPC internet gateway + */ + internetGatewayCreate: ( + { + query, + body, + }: { query: InternetGatewayCreateQueryParams; body: InternetGatewayCreate }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateways`, + method: 'POST', + body, + query, + ...params, + }) + }, + /** + * Fetch internet gateway + */ + internetGatewayView: ( + { + path, + query = {}, + }: { path: InternetGatewayViewPathParams; query?: InternetGatewayViewQueryParams }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateways/${path.gateway}`, + method: 'GET', + query, + ...params, + }) + }, + /** + * Delete internet gateway + */ + internetGatewayDelete: ( + { + path, + query = {}, + }: { + path: InternetGatewayDeletePathParams + query?: InternetGatewayDeleteQueryParams + }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateways/${path.gateway}`, + method: 'DELETE', + query, + ...params, + }) + }, /** * List IP pools */ diff --git a/app/api/__generated__/OMICRON_VERSION b/app/api/__generated__/OMICRON_VERSION index 2c1de3ebd..7278501cd 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 -c50cf019cd9be35f98266a7f4acacab0236b3a3d +0640bb277df110f3e740464bf4bacdf2bd24c897 diff --git a/app/api/__generated__/msw-handlers.ts b/app/api/__generated__/msw-handlers.ts index 49df51a41..0e455163a 100644 --- a/app/api/__generated__/msw-handlers.ts +++ b/app/api/__generated__/msw-handlers.ts @@ -407,6 +407,73 @@ export interface MSWHandlers { req: Request cookies: Record }) => Promisable> + /** `GET /v1/internet-gateway-ip-addresses` */ + internetGatewayIpAddressList: (params: { + query: Api.InternetGatewayIpAddressListQueryParams + req: Request + cookies: Record + }) => Promisable> + /** `POST /v1/internet-gateway-ip-addresses` */ + internetGatewayIpAddressCreate: (params: { + query: Api.InternetGatewayIpAddressCreateQueryParams + body: Json + req: Request + cookies: Record + }) => Promisable> + /** `DELETE /v1/internet-gateway-ip-addresses/:address` */ + internetGatewayIpAddressDelete: (params: { + path: Api.InternetGatewayIpAddressDeletePathParams + query: Api.InternetGatewayIpAddressDeleteQueryParams + req: Request + cookies: Record + }) => Promisable + /** `GET /v1/internet-gateway-ip-pools` */ + internetGatewayIpPoolList: (params: { + query: Api.InternetGatewayIpPoolListQueryParams + req: Request + cookies: Record + }) => Promisable> + /** `POST /v1/internet-gateway-ip-pools` */ + internetGatewayIpPoolCreate: (params: { + query: Api.InternetGatewayIpPoolCreateQueryParams + body: Json + req: Request + cookies: Record + }) => Promisable> + /** `DELETE /v1/internet-gateway-ip-pools/:pool` */ + internetGatewayIpPoolDelete: (params: { + path: Api.InternetGatewayIpPoolDeletePathParams + query: Api.InternetGatewayIpPoolDeleteQueryParams + req: Request + cookies: Record + }) => Promisable + /** `GET /v1/internet-gateways` */ + internetGatewayList: (params: { + query: Api.InternetGatewayListQueryParams + req: Request + cookies: Record + }) => Promisable> + /** `POST /v1/internet-gateways` */ + internetGatewayCreate: (params: { + query: Api.InternetGatewayCreateQueryParams + body: Json + req: Request + cookies: Record + }) => Promisable> + /** `GET /v1/internet-gateways/:gateway` */ + internetGatewayView: (params: { + path: Api.InternetGatewayViewPathParams + query: Api.InternetGatewayViewQueryParams + req: Request + cookies: Record + }) => Promisable> + /** `DELETE /v1/internet-gateways/:gateway` */ + internetGatewayDelete: (params: { + path: Api.InternetGatewayDeletePathParams + query: Api.InternetGatewayDeleteQueryParams + req: Request + cookies: Record + }) => Promisable /** `GET /v1/ip-pools` */ projectIpPoolList: (params: { query: Api.ProjectIpPoolListQueryParams @@ -1685,6 +1752,74 @@ export function makeHandlers(handlers: MSWHandlers): HttpHandler[] { '/v1/instances/:instance/stop', handler(handlers['instanceStop'], schema.InstanceStopParams, null) ), + http.get( + '/v1/internet-gateway-ip-addresses', + handler( + handlers['internetGatewayIpAddressList'], + schema.InternetGatewayIpAddressListParams, + null + ) + ), + http.post( + '/v1/internet-gateway-ip-addresses', + handler( + handlers['internetGatewayIpAddressCreate'], + schema.InternetGatewayIpAddressCreateParams, + schema.InternetGatewayIpAddressCreate + ) + ), + http.delete( + '/v1/internet-gateway-ip-addresses/:address', + handler( + handlers['internetGatewayIpAddressDelete'], + schema.InternetGatewayIpAddressDeleteParams, + null + ) + ), + http.get( + '/v1/internet-gateway-ip-pools', + handler( + handlers['internetGatewayIpPoolList'], + schema.InternetGatewayIpPoolListParams, + null + ) + ), + http.post( + '/v1/internet-gateway-ip-pools', + handler( + handlers['internetGatewayIpPoolCreate'], + schema.InternetGatewayIpPoolCreateParams, + schema.InternetGatewayIpPoolCreate + ) + ), + http.delete( + '/v1/internet-gateway-ip-pools/:pool', + handler( + handlers['internetGatewayIpPoolDelete'], + schema.InternetGatewayIpPoolDeleteParams, + null + ) + ), + http.get( + '/v1/internet-gateways', + handler(handlers['internetGatewayList'], schema.InternetGatewayListParams, null) + ), + http.post( + '/v1/internet-gateways', + handler( + handlers['internetGatewayCreate'], + schema.InternetGatewayCreateParams, + schema.InternetGatewayCreate + ) + ), + http.get( + '/v1/internet-gateways/:gateway', + handler(handlers['internetGatewayView'], schema.InternetGatewayViewParams, null) + ), + http.delete( + '/v1/internet-gateways/:gateway', + handler(handlers['internetGatewayDelete'], schema.InternetGatewayDeleteParams, null) + ), http.get( '/v1/ip-pools', handler(handlers['projectIpPoolList'], schema.ProjectIpPoolListParams, null) diff --git a/app/api/__generated__/validate.ts b/app/api/__generated__/validate.ts index 77402ef0d..1fea712f6 100644 --- a/app/api/__generated__/validate.ts +++ b/app/api/__generated__/validate.ts @@ -1645,6 +1645,14 @@ export const ImportBlocksBulkWrite = z.preprocess( z.object({ base64EncodedData: z.string(), offset: z.number().min(0) }) ) +/** + * A policy determining when an instance should be automatically restarted by the control plane. + */ +export const InstanceAutoRestartPolicy = z.preprocess( + processResponseBody, + z.enum(['never', 'best_effort']) +) + /** * The number of CPUs in an Instance */ @@ -1682,6 +1690,7 @@ export const Instance = z.preprocess( z.object({ autoRestartCooldownExpiration: z.coerce.date().optional(), autoRestartEnabled: SafeBoolean, + autoRestartPolicy: InstanceAutoRestartPolicy.optional(), bootDiskId: z.string().uuid().optional(), description: z.string(), hostname: z.string(), @@ -1698,14 +1707,6 @@ export const Instance = z.preprocess( }) ) -/** - * A policy determining when an instance should be automatically restarted by the control plane. - */ -export const InstanceAutoRestartPolicy = z.preprocess( - processResponseBody, - z.enum(['never', 'best_effort']) -) - /** * Describe the instance's disks at creation time */ @@ -1852,7 +1853,105 @@ export const InstanceSerialConsoleData = z.preprocess( */ export const InstanceUpdate = z.preprocess( processResponseBody, - z.object({ bootDisk: NameOrId.optional() }) + z.object({ + autoRestartPolicy: InstanceAutoRestartPolicy.optional(), + bootDisk: NameOrId.optional(), + }) +) + +/** + * An internet gateway provides a path between VPC networks and external networks. + */ +export const InternetGateway = z.preprocess( + processResponseBody, + z.object({ + description: z.string(), + id: z.string().uuid(), + name: Name, + timeCreated: z.coerce.date(), + timeModified: z.coerce.date(), + vpcId: z.string().uuid(), + }) +) + +/** + * Create-time parameters for an `InternetGateway` + */ +export const InternetGatewayCreate = z.preprocess( + processResponseBody, + z.object({ description: z.string(), name: Name }) +) + +/** + * An IP address that is attached to an internet gateway + */ +export const InternetGatewayIpAddress = z.preprocess( + processResponseBody, + z.object({ + address: z.string().ip(), + description: z.string(), + id: z.string().uuid(), + internetGatewayId: z.string().uuid(), + name: Name, + timeCreated: z.coerce.date(), + timeModified: z.coerce.date(), + }) +) + +/** + * Create-time identity-related parameters + */ +export const InternetGatewayIpAddressCreate = z.preprocess( + processResponseBody, + z.object({ address: z.string().ip(), description: z.string(), name: Name }) +) + +/** + * A single page of results + */ +export const InternetGatewayIpAddressResultsPage = z.preprocess( + processResponseBody, + z.object({ items: InternetGatewayIpAddress.array(), nextPage: z.string().optional() }) +) + +/** + * An IP pool that is attached to an internet gateway + */ +export const InternetGatewayIpPool = z.preprocess( + processResponseBody, + z.object({ + description: z.string(), + id: z.string().uuid(), + internetGatewayId: z.string().uuid(), + ipPoolId: z.string().uuid(), + name: Name, + timeCreated: z.coerce.date(), + timeModified: z.coerce.date(), + }) +) + +/** + * Create-time identity-related parameters + */ +export const InternetGatewayIpPoolCreate = z.preprocess( + processResponseBody, + z.object({ description: z.string(), ipPool: NameOrId, name: Name }) +) + +/** + * A single page of results + */ +export const InternetGatewayIpPoolResultsPage = z.preprocess( + processResponseBody, + z.object({ items: InternetGatewayIpPool.array(), nextPage: z.string().optional() }) +) + +/** + * A single page of results + */ +export const InternetGatewayResultsPage = z.preprocess( + processResponseBody, + z.object({ items: InternetGateway.array(), nextPage: z.string().optional() }) ) /** @@ -2483,7 +2582,7 @@ export const Route = z.preprocess( z.object({ dst: IpNet, gw: z.string().ip(), - localPref: z.number().min(0).max(4294967295).optional(), + ribPriority: z.number().min(0).max(255).optional(), vid: z.number().min(0).max(65535).optional(), }) ) @@ -3138,8 +3237,8 @@ export const SwitchPortRouteConfig = z.preprocess( dst: IpNet, gw: IpNet, interfaceName: z.string(), - localPref: z.number().min(0).max(4294967295).optional(), portSettingsId: z.string().uuid(), + ribPriority: z.number().min(0).max(255).optional(), vlanId: z.number().min(0).max(65535).optional(), }) ) @@ -4348,6 +4447,142 @@ export const InstanceStopParams = z.preprocess( }) ) +export const InternetGatewayIpAddressListParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({}), + query: z.object({ + gateway: NameOrId.optional(), + limit: z.number().min(1).max(4294967295).optional(), + pageToken: z.string().optional(), + project: NameOrId.optional(), + sortBy: NameOrIdSortMode.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayIpAddressCreateParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({}), + query: z.object({ + gateway: NameOrId, + project: NameOrId.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayIpAddressDeleteParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({ + address: NameOrId, + }), + query: z.object({ + cascade: SafeBoolean.optional(), + gateway: NameOrId.optional(), + project: NameOrId.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayIpPoolListParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({}), + query: z.object({ + gateway: NameOrId.optional(), + limit: z.number().min(1).max(4294967295).optional(), + pageToken: z.string().optional(), + project: NameOrId.optional(), + sortBy: NameOrIdSortMode.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayIpPoolCreateParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({}), + query: z.object({ + gateway: NameOrId, + project: NameOrId.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayIpPoolDeleteParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({ + pool: NameOrId, + }), + query: z.object({ + cascade: SafeBoolean.optional(), + gateway: NameOrId.optional(), + project: NameOrId.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayListParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({}), + query: z.object({ + limit: z.number().min(1).max(4294967295).optional(), + pageToken: z.string().optional(), + project: NameOrId.optional(), + sortBy: NameOrIdSortMode.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayCreateParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({}), + query: z.object({ + project: NameOrId.optional(), + vpc: NameOrId, + }), + }) +) + +export const InternetGatewayViewParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({ + gateway: NameOrId, + }), + query: z.object({ + project: NameOrId.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayDeleteParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({ + gateway: NameOrId, + }), + query: z.object({ + cascade: SafeBoolean.optional(), + project: NameOrId.optional(), + vpc: NameOrId.optional(), + }), + }) +) + export const ProjectIpPoolListParams = z.preprocess( processResponseBody, z.object({ diff --git a/app/api/path-params.ts b/app/api/path-params.ts index eaeab465a..5cc088a19 100644 --- a/app/api/path-params.ts +++ b/app/api/path-params.ts @@ -19,6 +19,9 @@ export type VpcRouter = Merge export type VpcRouterRoute = Merge export type VpcSubnet = Merge export type FirewallRule = Merge +export type InternetGateway = Merge +export type InternetGatewayIpAddress = Merge +export type InternetGatewayIpPool = Merge export type Silo = { silo?: string } export type IdentityProvider = Merge export type SystemUpdate = { version: string } diff --git a/app/components/ErrorBoundary.tsx b/app/components/ErrorBoundary.tsx index 91085e08f..52db88e5c 100644 --- a/app/components/ErrorBoundary.tsx +++ b/app/components/ErrorBoundary.tsx @@ -38,5 +38,6 @@ export const ErrorBoundary = (props: { children: React.ReactNode }) => ( export function RouterDataErrorBoundary() { // TODO: validate this unknown at runtime _before_ passing to ErrorFallback const error = useRouteError() as Props['error'] + console.error(error) return } diff --git a/app/components/TopBarPicker.tsx b/app/components/TopBarPicker.tsx index 17da2c90a..dddd05b33 100644 --- a/app/components/TopBarPicker.tsx +++ b/app/components/TopBarPicker.tsx @@ -17,6 +17,7 @@ import { import { useInstanceSelector, + useInternetGatewaySelector, useIpPoolSelector, useSiloSelector, useSledParams, @@ -296,6 +297,29 @@ export function VpcRouterPicker() { ) } +/** Used when drilling down into a VPC Internet Gateway from the Silo view. */ +export function VpcGatewayPicker() { + // picker only shows up when an internet gateway is in scope + const { project, vpc, gateway } = useInternetGatewaySelector() + const { data } = useApiQuery('internetGatewayList', { + query: { project, vpc, limit: PAGE_SIZE }, + }) + const items = (data?.items || []).map((g) => ({ + label: g.name, + to: pb.vpcInternetGateway({ vpc, project, gateway: g.name }), + })) + + return ( + + ) +} + const NoProjectLogo = () => (
diff --git a/app/hooks/use-params.ts b/app/hooks/use-params.ts index 348e48d3b..d55792c21 100644 --- a/app/hooks/use-params.ts +++ b/app/hooks/use-params.ts @@ -40,6 +40,7 @@ export const getFirewallRuleSelector = requireParams('project', 'vpc', 'rule') export const getVpcRouterSelector = requireParams('project', 'vpc', 'router') export const getVpcRouterRouteSelector = requireParams('project', 'vpc', 'router', 'route') export const getVpcSubnetSelector = requireParams('project', 'vpc', 'subnet') +export const getInternetGatewaySelector = requireParams('project', 'vpc', 'gateway') export const getSiloSelector = requireParams('silo') export const getSiloImageSelector = requireParams('image') export const getIdpSelector = requireParams('silo', 'provider') @@ -84,6 +85,8 @@ export const useVpcSelector = () => useSelectedParams(getVpcSelector) export const useVpcRouterSelector = () => useSelectedParams(getVpcRouterSelector) export const useVpcRouterRouteSelector = () => useSelectedParams(getVpcRouterRouteSelector) export const useVpcSubnetSelector = () => useSelectedParams(getVpcSubnetSelector) +export const useInternetGatewaySelector = () => + useSelectedParams(getInternetGatewaySelector) export const useFirewallRuleSelector = () => useSelectedParams(getFirewallRuleSelector) export const useSiloSelector = () => useSelectedParams(getSiloSelector) export const useSiloImageSelector = () => useSelectedParams(getSiloImageSelector) diff --git a/app/layouts/ProjectLayout.tsx b/app/layouts/ProjectLayout.tsx index 5c186c213..9ce5f6811 100644 --- a/app/layouts/ProjectLayout.tsx +++ b/app/layouts/ProjectLayout.tsx @@ -30,6 +30,7 @@ import { InstancePicker, ProjectPicker, SiloSystemPicker, + VpcGatewayPicker, VpcPicker, VpcRouterPicker, } from '~/components/TopBarPicker' @@ -61,7 +62,7 @@ export function ProjectLayout({ overrideContentPane }: ProjectLayoutProps) { const projectSelector = useProjectSelector() const { data: project } = usePrefetchedApiQuery('projectView', { path: projectSelector }) - const { instance, router, vpc } = useParams() + const { gateway, instance, router, vpc } = useParams() const { pathname } = useLocation() useQuickActions( useMemo( @@ -94,6 +95,7 @@ export function ProjectLayout({ overrideContentPane }: ProjectLayoutProps) { {instance && } {vpc && } {router && } + {gateway && } diff --git a/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx b/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx new file mode 100644 index 000000000..c6a3893c3 --- /dev/null +++ b/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx @@ -0,0 +1,119 @@ +/* + * 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 { useMemo } from 'react' +import { type LoaderFunctionArgs } from 'react-router-dom' + +import { Networking16Icon, Networking24Icon } from '@oxide/design-system/icons/react' + +import { apiQueryClient, usePrefetchedApiQuery } from '~/api' +import { DocsPopover } from '~/components/DocsPopover' +import { MoreActionsMenu } from '~/components/MoreActionsMenu' +import { RouteTabs, Tab } from '~/components/RouteTabs' +import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/use-params' +import { DescriptionCell } from '~/table/cells/DescriptionCell' +import { DateTime } from '~/ui/lib/DateTime' +import { Message } from '~/ui/lib/Message' +import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' +import { PropertiesTable } from '~/ui/lib/PropertiesTable' +import { Truncate } from '~/ui/lib/Truncate' +import { ALL_ISH } from '~/util/consts' +import { docLinks } from '~/util/links' +import { pb } from '~/util/path-builder' + +InternetGatewayPage.loader = async function ({ params }: LoaderFunctionArgs) { + console.log('InternetGatewayPage.loader') + const { project, vpc, gateway } = getInternetGatewaySelector(params) + console.log({ project, vpc, gateway }) + const query = { project, vpc, gateway, limit: ALL_ISH } + await Promise.all([ + apiQueryClient.prefetchQuery('internetGatewayView', { + query: { project, vpc }, + path: { gateway }, + }), + apiQueryClient.prefetchQuery('internetGatewayIpAddressList', { query }), + apiQueryClient.prefetchQuery('internetGatewayIpPoolList', { query }), + ]) + return null +} + +export function InternetGatewayPage() { + const gatewaySelector = useInternetGatewaySelector() + const { project, vpc, gateway } = gatewaySelector + const { + data: { id, description, name, timeCreated, timeModified }, + } = usePrefetchedApiQuery('internetGatewayView', { + query: { project, vpc }, + path: { gateway }, + }) + + const actions = useMemo( + () => [ + { + label: 'Copy ID', + onActivate() { + window.navigator.clipboard.writeText(id || '') + }, + }, + ], + [id] + ) + + return ( + <> + + }>{name} +
+ } + summary="Internet gateways … 🚨🙈🚨🙉🚨🙊🚨 … just using emojis here so we spot it more easily in the PR; this copy needs eyes 👀" + links={[docLinks.vpcs, docLinks.firewallRules]} + /> + +
+
+ + + + + + + + + + + + + + + + + + + + + This is a read-only copy of this internet gateway and its IP pools and + addresses. Use the CLI to create and update internet gateways. More + functionality for internet gateways will be included in future releases of the + Oxide console. + + } + /> + + + IP Pools + IP Addresses + + + ) +} diff --git a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx new file mode 100644 index 000000000..fb7f60486 --- /dev/null +++ b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx @@ -0,0 +1,63 @@ +/* + * 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 { createColumnHelper } from '@tanstack/react-table' +import { useMemo } from 'react' +import type { LoaderFunctionArgs } from 'react-router-dom' + +import { apiQueryClient, type InternetGatewayIpAddress } from '~/api' +import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/use-params' +import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' +import { Columns } from '~/table/columns/common' +import { useQueryTable } from '~/table/QueryTable' +import { CopyableIp } from '~/ui/lib/CopyableIp' +import { EmptyMessage } from '~/ui/lib/EmptyMessage' +import { ALL_ISH } from '~/util/consts' + +InternetGatewayIpAddressesTab.loader = async function ({ params }: LoaderFunctionArgs) { + const { project, vpc, gateway } = getInternetGatewaySelector(params) + await Promise.all([ + apiQueryClient.prefetchQuery('internetGatewayIpAddressList', { + query: { project, vpc, gateway, limit: ALL_ISH }, + }), + ]) + return null +} + +const colHelper = createColumnHelper() + +export function InternetGatewayIpAddressesTab() { + const { project, vpc, gateway } = useInternetGatewaySelector() + const { Table } = useQueryTable('internetGatewayIpAddressList', { + query: { project, vpc, gateway, limit: ALL_ISH }, + }) + + const emptyState = ( + + ) + + const staticColumns = useMemo( + () => [ + colHelper.accessor('name', {}), + colHelper.accessor('description', Columns.description), + colHelper.accessor('address', { + header: 'Address', + cell: (info) => , + }), + ], + [] + ) + + const makeActions = (): MenuAction[] => [] + + const columns = useColsWithActions(staticColumns, makeActions) + return +} diff --git a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx new file mode 100644 index 000000000..609149221 --- /dev/null +++ b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx @@ -0,0 +1,86 @@ +/* + * 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 { createColumnHelper } from '@tanstack/react-table' +import { useCallback } from 'react' +import type { LoaderFunctionArgs } from 'react-router-dom' + +import { apiQueryClient, type InternetGatewayIpPool } from '~/api' +import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/use-params' +import { IpPoolCell } from '~/table/cells/IpPoolCell' +import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' +import { Columns } from '~/table/columns/common' +import { useQueryTable } from '~/table/QueryTable' +import { EmptyMessage } from '~/ui/lib/EmptyMessage' +import { ALL_ISH } from '~/util/consts' + +InternetGatewayIpPoolsTab.loader = async function ({ params }: LoaderFunctionArgs) { + const { project, vpc, gateway } = getInternetGatewaySelector(params) + await Promise.all([ + apiQueryClient.prefetchQuery('internetGatewayIpPoolList', { + query: { project, vpc, gateway, limit: ALL_ISH }, + }), + // fetch IP Pools and preload into RQ cache so fetches by ID in + // IpPoolCell can be mostly instant yet gracefully fall back to + // fetching individually if we don't fetch them all here + apiQueryClient + .fetchQuery('projectIpPoolList', { query: { limit: ALL_ISH } }) + .then((pools) => { + console.log({ pools }) + for (const pool of pools.items) { + apiQueryClient.setQueryData( + 'projectIpPoolView', + { path: { pool: pool.id } }, + pool + ) + } + }), + ]) + return null +} + +const colHelper = createColumnHelper() + +export function InternetGatewayIpPoolsTab() { + const { project, vpc, gateway } = useInternetGatewaySelector() + const { Table } = useQueryTable('internetGatewayIpPoolList', { + query: { project, vpc, gateway, limit: ALL_ISH }, + }) + + const emptyState = ( + + ) + + const staticColumns = [ + colHelper.accessor('name', {}), + colHelper.accessor('description', Columns.description), + colHelper.accessor('ipPoolId', { + header: 'IP Pool', + cell: (info) => , + }), + ] + + // The user can copy the ID of the IP Pool attached to this internet gateway + const makeActions = useCallback( + (internetGatewayIpPool: InternetGatewayIpPool): MenuAction[] => [ + { + label: 'Copy IP pool ID', + onActivate() { + window.navigator.clipboard.writeText(internetGatewayIpPool.ipPoolId) + }, + }, + ], + [] + ) + + const columns = useColsWithActions(staticColumns, makeActions) + return
+} diff --git a/app/pages/project/vpcs/VpcPage/VpcPage.tsx b/app/pages/project/vpcs/VpcPage/VpcPage.tsx index 5c5c5d912..b79fce4e8 100644 --- a/app/pages/project/vpcs/VpcPage/VpcPage.tsx +++ b/app/pages/project/vpcs/VpcPage/VpcPage.tsx @@ -103,6 +103,7 @@ export function VpcPage() { Firewall RulesSubnetsRouters + Internet Gateways ) diff --git a/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx b/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx index 262139f28..0544f6ec8 100644 --- a/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx +++ b/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx @@ -5,3 +5,64 @@ * * Copyright Oxide Computer Company */ + +import { createColumnHelper } from '@tanstack/react-table' +import { useMemo } from 'react' +import { Outlet, type LoaderFunctionArgs } from 'react-router-dom' + +import { apiQueryClient, type InternetGateway } from '~/api' +import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' +import { makeLinkCell } from '~/table/cells/LinkCell' +import { Columns } from '~/table/columns/common' +import { useQueryTable } from '~/table/QueryTable' +import { EmptyMessage } from '~/ui/lib/EmptyMessage' +import { ALL_ISH } from '~/util/consts' +import { pb } from '~/util/path-builder' + +const colHelper = createColumnHelper() + +VpcInternetGatewaysTab.loader = async ({ params }: LoaderFunctionArgs) => { + const { project, vpc } = getVpcSelector(params) + const query = { project, vpc, limit: ALL_ISH } + await Promise.all([ + apiQueryClient.prefetchQuery('internetGatewayList', { query }), + apiQueryClient.prefetchQuery('internetGatewayIpAddressList', { query }), + apiQueryClient.prefetchQuery('internetGatewayIpPoolList', { query }), + ]) + return null +} + +export function VpcInternetGatewaysTab() { + const vpcSelector = useVpcSelector() + const { project, vpc } = vpcSelector + const { Table } = useQueryTable('internetGatewayList', { + query: { project, vpc, limit: ALL_ISH }, + }) + + const emptyState = ( + + ) + + const staticColumns = useMemo( + () => [ + colHelper.accessor('name', { + cell: makeLinkCell((gateway) => pb.vpcInternetGateway({ ...vpcSelector, gateway })), + }), + colHelper.accessor('description', Columns.description), + colHelper.accessor('timeCreated', Columns.timeCreated), + ], + [vpcSelector] + ) + + return ( + <> +
+ + + ) +} diff --git a/app/routes.tsx b/app/routes.tsx index 86b159f5f..18ed21560 100644 --- a/app/routes.tsx +++ b/app/routes.tsx @@ -66,8 +66,12 @@ import { NetworkingTab } from './pages/project/instances/instance/tabs/Networkin import { StorageTab } from './pages/project/instances/instance/tabs/StorageTab' import { InstancesPage } from './pages/project/instances/InstancesPage' import { SnapshotsPage } from './pages/project/snapshots/SnapshotsPage' +import { InternetGatewayPage } from './pages/project/vpcs/InternetGatewayPage/InternetGatewayPage' +import { InternetGatewayIpAddressesTab } from './pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab' +import { InternetGatewayIpPoolsTab } from './pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab' import { RouterPage } from './pages/project/vpcs/RouterPage' import { VpcFirewallRulesTab } from './pages/project/vpcs/VpcPage/tabs/VpcFirewallRulesTab' +import { VpcInternetGatewaysTab } from './pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab' import { VpcRoutersTab } from './pages/project/vpcs/VpcPage/tabs/VpcRoutersTab' import { VpcSubnetsTab } from './pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab' import { VpcPage } from './pages/project/vpcs/VpcPage/VpcPage' @@ -93,6 +97,7 @@ import { pb } from './util/path-builder' const projectCrumb: CrumbFunc = (m) => m.params.project! const instanceCrumb: CrumbFunc = (m) => m.params.instance! const vpcCrumb: CrumbFunc = (m) => m.params.vpc! +const internetGatewayCrumb: CrumbFunc = (m) => m.params.gateway! const siloCrumb: CrumbFunc = (m) => m.params.silo! const poolCrumb: CrumbFunc = (m) => m.params.pool! @@ -412,6 +417,16 @@ export const routes = createRoutesFromElements( handle={{ crumb: 'New Router' }} /> + } + loader={VpcInternetGatewaysTab.loader} + > + + @@ -434,6 +449,30 @@ export const routes = createRoutesFromElements( handle={{ crumb: 'Edit Route' }} /> + + } loader={InternetGatewayPage.loader}> + } + loader={InternetGatewayIpPoolsTab.loader} + /> + } + loader={InternetGatewayIpPoolsTab.loader} + handle={{ crumb: 'IP Pools' }} + /> + } + loader={InternetGatewayIpAddressesTab.loader} + handle={{ crumb: 'IP Addresses' }} + /> + + } loader={FloatingIpsPage.loader}> { "vpcFirewallRuleEdit": "/projects/p/vpcs/v/firewall-rules/fr/edit", "vpcFirewallRules": "/projects/p/vpcs/v/firewall-rules", "vpcFirewallRulesNew": "/projects/p/vpcs/v/firewall-rules-new", + "vpcInternetGateway": "/projects/p/vpcs/v/internet-gateways/g", + "vpcInternetGatewayIpAddresses": "/projects/p/vpcs/v/internet-gateways/g/ip-addresses", + "vpcInternetGatewayIpPools": "/projects/p/vpcs/v/internet-gateways/g/ip-pools", + "vpcInternetGateways": "/projects/p/vpcs/v/internet-gateways", + "vpcInternetGatewaysNew": "/projects/p/vpcs/v/internet-gateways-new", "vpcRouter": "/projects/p/vpcs/v/routers/r", "vpcRouterEdit": "/projects/p/vpcs/v/routers/r/edit", "vpcRouterRouteEdit": "/projects/p/vpcs/v/routers/r/routes/rr/edit", diff --git a/app/util/path-builder.ts b/app/util/path-builder.ts index 1709b19c5..5ed1e89cf 100644 --- a/app/util/path-builder.ts +++ b/app/util/path-builder.ts @@ -25,6 +25,7 @@ type FirewallRule = Required type VpcRouter = Required type VpcRouterRoute = Required type VpcSubnet = Required +type VpcInternetGateway = Required // these are used as the basis for many routes but are not themselves routes we // ever want to link to. so we use this to build the routes but pb.project() is @@ -97,6 +98,15 @@ export const pb = { vpcSubnetsNew: (params: Vpc) => `${vpcBase(params)}/subnets-new`, vpcSubnetsEdit: (params: VpcSubnet) => `${pb.vpcSubnets(params)}/${params.subnet}/edit`, + vpcInternetGateways: (params: Vpc) => `${vpcBase(params)}/internet-gateways`, + vpcInternetGateway: (params: VpcInternetGateway) => + `${pb.vpcInternetGateways(params)}/${params.gateway}`, + vpcInternetGatewaysNew: (params: Vpc) => `${vpcBase(params)}/internet-gateways-new`, + vpcInternetGatewayIpPools: (params: VpcInternetGateway) => + `${pb.vpcInternetGateway(params)}/ip-pools`, + vpcInternetGatewayIpAddresses: (params: VpcInternetGateway) => + `${pb.vpcInternetGateway(params)}/ip-addresses`, + floatingIps: (params: Project) => `${projectBase(params)}/floating-ips`, floatingIpsNew: (params: Project) => `${projectBase(params)}/floating-ips-new`, floatingIp: (params: FloatingIp) => `${pb.floatingIps(params)}/${params.floatingIp}`, diff --git a/mock-api/index.ts b/mock-api/index.ts index e03311145..0256808bf 100644 --- a/mock-api/index.ts +++ b/mock-api/index.ts @@ -11,6 +11,7 @@ export * from './external-ip' export * from './floating-ip' export * from './image' export * from './instance' +export * from './internet-gateway' export * from './ip-pool' export * from './network-interface' export * from './physical-disk' diff --git a/mock-api/internet-gateway.ts b/mock-api/internet-gateway.ts new file mode 100644 index 000000000..a8859e4fd --- /dev/null +++ b/mock-api/internet-gateway.ts @@ -0,0 +1,151 @@ +/* + * 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 type { + InternetGateway, + InternetGatewayIpAddress, + InternetGatewayIpPool, +} from '@oxide/api' + +import { ipPools } from './ip-pool' +import type { Json } from './json-type' +import { vpc, vpc2 } from './vpc' + +const time_created = new Date(2021, 0, 1).toISOString() +const time_modified = new Date(2021, 0, 2).toISOString() + +// An internet gateway for VPC 1 +const internetGateway1: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9a', + name: 'internet-gateway-1', + description: 'internet gateway 1', + vpc_id: vpc.id, + time_created, + time_modified, +} + +// Another internet gateway for VPC 1 +const internetGateway2: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9b', + name: 'internet-gateway-2', + description: 'internet gateway 2', + vpc_id: vpc.id, + time_created, + time_modified, +} + +// An internet gateway for VPC 2 +const internetGateway3: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9c', + name: 'internet-gateway-3', + description: 'internet gateway 3', + vpc_id: vpc2.id, + time_created, + time_modified, +} + +export const internetGateways: Json[] = [ + internetGateway1, + internetGateway2, + internetGateway3, +] + +const internetGatewayIpAddress1: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9d', + address: '87.114.25.166', + description: 'the IP address for an internet gateway', + internet_gateway_id: internetGateway1.id, + name: 'internet-gateway-ip-1', + time_created, + time_modified, +} + +const internetGatewayIpAddress2: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9e', + address: '292a:a05c:3b36:a053:9166:6510:2d6b:3322', + description: 'an IPv6 address for an internet gateway', + internet_gateway_id: internetGateway1.id, + name: 'internet-gateway-ip-2', + time_created, + time_modified, +} + +const internetGatewayIpAddress3: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9f', + address: '178.125.253.126', + description: 'an IPv4 address for internet gateway 2', + internet_gateway_id: internetGateway2.id, + name: 'internet-gateway-ip-3', + time_created, + time_modified, +} + +export const internetGatewayIpAddresses: Json[] = [ + internetGatewayIpAddress1, + internetGatewayIpAddress2, + internetGatewayIpAddress3, +] + +const [defaultIpPool, ipPool1, ipPool2, ipPool3] = ipPools + +const defaultInternetGatewayIpPool1: Json = { + id: '1d5e5a1f-0b2b-4d5b-8b9d-2d4b3e0c6gb9', + name: 'default', + description: 'Default internet gateway IP pool', + internet_gateway_id: internetGateway1.id, + ip_pool_id: defaultIpPool.id, + time_created, + time_modified, +} + +const defaultInternetGatewayIpPool2: Json = { + id: 'd5e5a1f1-0b2b-4d5b-8b9d-2d4b3e0c6b9c', + name: 'default', + description: 'Default internet gateway IP pool', + internet_gateway_id: internetGateway2.id, + ip_pool_id: defaultIpPool.id, + time_created, + time_modified, +} + +const internetGatewayIpPool1: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9g', + name: 'internet-gateway-ip-pool-1', + description: 'an IP pool for an internet gateway', + internet_gateway_id: internetGateway2.id, + ip_pool_id: ipPool1.id, + time_created, + time_modified, +} + +const internetGatewayIpPool2: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9h', + name: 'internet-gateway-ip-pool-2', + description: 'a set of VPN IPs in an IP pool for an internet gateway', + internet_gateway_id: internetGateway2.id, + ip_pool_id: ipPool2.id, + time_created, + time_modified, +} + +const internetGatewayIpPool3: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9i', + name: 'internet-gateway-ip-pool-3', + description: 'another IP pool for an internet gateway', + internet_gateway_id: internetGateway2.id, + ip_pool_id: ipPool3.id, + time_created, + time_modified, +} + +export const internetGatewayIpPools: Json[] = [ + defaultInternetGatewayIpPool1, + defaultInternetGatewayIpPool2, + internetGatewayIpPool1, + internetGatewayIpPool2, + internetGatewayIpPool3, +] diff --git a/mock-api/ip-pool.ts b/mock-api/ip-pool.ts index 534125ff8..d1ac26dd4 100644 --- a/mock-api/ip-pool.ts +++ b/mock-api/ip-pool.ts @@ -11,6 +11,14 @@ import { type IpPool, type IpPoolRange, type IpPoolSiloLink } from '@oxide/api' import type { Json } from './json-type' import { defaultSilo } from './silo' +export const defaultIpPool: Json = { + id: 'cadb8535-e32f-4be2-9259-e45dec9fa3cd', + name: 'default', + description: 'default IP pool', + time_created: new Date().toISOString(), + time_modified: new Date().toISOString(), +} + export const ipPool1: Json = { id: '69b5c583-74a9-451a-823d-0741c1ec66e2', name: 'ip-pool-1', @@ -43,14 +51,19 @@ const ipPool4: Json = { time_modified: new Date().toISOString(), } -export const ipPools: Json[] = [ipPool1, ipPool2, ipPool3, ipPool4] +export const ipPools: Json[] = [defaultIpPool, ipPool1, ipPool2, ipPool3, ipPool4] export const ipPoolSilos: Json[] = [ { - ip_pool_id: ipPool1.id, + ip_pool_id: defaultIpPool.id, silo_id: defaultSilo.id, is_default: true, }, + { + ip_pool_id: ipPool1.id, + silo_id: defaultSilo.id, + is_default: false, + }, { ip_pool_id: ipPool2.id, silo_id: defaultSilo.id, @@ -59,6 +72,15 @@ export const ipPoolSilos: Json[] = [ ] export const ipPoolRanges: Json = [ + { + id: 'f6b3b9b5-7e3d-4b8c-9f6b-9b7b5e3d8c4b', + ip_pool_id: defaultIpPool.id, + range: { + first: '170.20.26.11', + last: '170.20.26.254', + }, + time_created: new Date().toISOString(), + }, { id: 'bbfcf3f2-061e-4334-a0e7-dfcd8171f87e', ip_pool_id: ipPool1.id, diff --git a/mock-api/msw/db.ts b/mock-api/msw/db.ts index f9e1b26ed..8474a88f2 100644 --- a/mock-api/msw/db.ts +++ b/mock-api/msw/db.ts @@ -214,6 +214,44 @@ export const lookup = { return subnet }, + internetGateway({ + gateway: id, + ...vpcSelector + }: PP.InternetGateway): Json { + if (!id) throw notFoundErr('no internet gateway specified') + + if (isUuid(id)) { + ensureNoParentSelectors('internet gateway', vpcSelector) + return lookupById(db.internetGateways, id) + } + + const vpc = lookup.vpc(vpcSelector) + const internetGateway = db.internetGateways.find( + (ig) => ig.vpc_id === vpc.id && ig.name === id + ) + if (!internetGateway) throw notFoundErr(`internet gateway '${id}'`) + + return internetGateway + }, + internetGatewayIpAddress({ + address: id, + ...gatewaySelector + }: PP.InternetGatewayIpAddress): Json { + if (!id) throw notFoundErr('no IP address specified') + + if (isUuid(id)) { + ensureNoParentSelectors('IP address', gatewaySelector) + return lookupById(db.internetGatewayIpAddresses, id) + } + + const gateway = lookup.internetGateway(gatewaySelector) + const ip = db.internetGatewayIpAddresses.find( + (i) => i.internet_gateway_id === gateway.id && i.name === id + ) + if (!ip) throw notFoundErr(`IP address '${id}'`) + + return ip + }, image({ image: id, project: projectId }: PP.Image): Json { if (!id) throw notFoundErr('no image specified') @@ -404,6 +442,9 @@ const initDb = { images: [...mock.images], ephemeralIps: [...mock.ephemeralIps], instances: [...mock.instances], + internetGatewayIpAddresses: [...mock.internetGatewayIpAddresses], + internetGatewayIpPools: [...mock.internetGatewayIpPools], + internetGateways: [...mock.internetGateways], ipPools: [...mock.ipPools], ipPoolSilos: [...mock.ipPoolSilos], ipPoolRanges: [...mock.ipPoolRanges], diff --git a/mock-api/msw/handlers.ts b/mock-api/msw/handlers.ts index c81f53d95..e0e391434 100644 --- a/mock-api/msw/handlers.ts +++ b/mock-api/msw/handlers.ts @@ -1155,6 +1155,26 @@ export const handlers = makeHandlers({ return { rules: R.sortBy(rules, (r) => r.name) } }, + internetGatewayList({ query }) { + const vpc = lookup.vpc(query) + const gateways = db.internetGateways.filter((g) => g.vpc_id === vpc.id) + return paginated(query, gateways) + }, + internetGatewayView: ({ path, query }) => lookup.internetGateway({ ...path, ...query }), + internetGatewayIpPoolList({ query }) { + const gateway = lookup.internetGateway(query) + const pools = db.internetGatewayIpPools.filter( + (p) => p.internet_gateway_id === gateway.id + ) + return paginated(query, pools) + }, + internetGatewayIpAddressList({ query }) { + const gateway = lookup.internetGateway(query) + const addresses = db.internetGatewayIpAddresses.filter( + (a) => a.internet_gateway_id === gateway.id + ) + return paginated(query, addresses) + }, vpcRouterList({ query }) { const vpc = lookup.vpc(query) const routers = db.vpcRouters.filter((r) => r.vpc_id === vpc.id) @@ -1519,6 +1539,12 @@ export const handlers = makeHandlers({ certificateView: NotImplemented, instanceSerialConsoleStream: NotImplemented, instanceSshPublicKeyList: NotImplemented, + internetGatewayIpAddressCreate: NotImplemented, + internetGatewayIpAddressDelete: NotImplemented, + internetGatewayIpPoolCreate: NotImplemented, + internetGatewayIpPoolDelete: NotImplemented, + internetGatewayCreate: NotImplemented, + internetGatewayDelete: NotImplemented, ipPoolServiceRangeAdd: NotImplemented, ipPoolServiceRangeList: NotImplemented, ipPoolServiceRangeRemove: NotImplemented, diff --git a/test/e2e/ip-pools.e2e.ts b/test/e2e/ip-pools.e2e.ts index df0de16b0..031090b7b 100644 --- a/test/e2e/ip-pools.e2e.ts +++ b/test/e2e/ip-pools.e2e.ts @@ -19,7 +19,7 @@ test('IP pool list', async ({ page }) => { const table = page.getByRole('table') - await expect(table.getByRole('row')).toHaveCount(5) // header + 4 rows + await expect(table.getByRole('row')).toHaveCount(6) // header + 5 rows await expectRowVisible(table, { name: 'ip-pool-1', Utilization: '6 / 24' }) await expectRowVisible(table, {