feat(api): add HTTP and WebSocket APIs (API Gateway v2) to the API Gateway page
CI / checks (pull_request) Successful in 26m49s
CI / checks (pull_request) Successful in 26m49s
API Gateway v2 is a separate AWS API from REST APIs, so HTTP and WebSocket APIs made with the SDK, SAM, CDK or Terraform never showed up. The API Gateway page now lists all three kinds with a Type column, creates each from the same form, and opens HTTP and WebSocket APIs in their own panel: - Quick create: an HTTP API with a Lambda gets a catch-all route on an auto-deployed $default stage, plus the Lambda permission. - Routes to Lambda (payload 2.0) or HTTP backends; deleting a route removes the integration and Lambda permission only it used. - Stages with or without automatic deployment, and deploy on demand. - JWT authorizers filled in from a Cognito pool's OpenID Connect discovery document and an app client, or from any issuer; protect routes with them. - CORS configuration. - A tester that sends a real request to a stage, and for WebSocket APIs one that connects, sends a message and collects the replies. Capabilities are advertised under a new httpApiActions group, covered by the cloudProxy guard test. Verified end to end against TheCloud's runtime.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type {CapabilitySchema, ResourceActionName, ServiceSchema} from './types'
|
||||
import type {CapabilitySchema, HttpApiActionName, ResourceActionName, ServiceSchema} from './types'
|
||||
|
||||
const resourceActions: CapabilitySchema<ResourceActionName>[] = [
|
||||
{name: 'list', label: 'List APIs', enabled: true, status: 'available', runtimeRequired: true},
|
||||
@@ -7,12 +7,42 @@ const resourceActions: CapabilitySchema<ResourceActionName>[] = [
|
||||
{name: 'inspect', label: 'Inspect API', enabled: true, status: 'available', runtimeRequired: false},
|
||||
]
|
||||
|
||||
const httpApiActions: CapabilitySchema<HttpApiActionName>[] = [
|
||||
{name: 'describe', label: 'Show routes, stages and authorizers', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'createRoute', label: 'Add a route', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'deleteRoute', label: 'Delete a route', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'setRouteAuthorizer', label: 'Protect a route with an authorizer', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'createStage', label: 'Add a stage', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'deleteStage', label: 'Delete a stage', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'deploy', label: 'Deploy to a stage', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'createAuthorizer', label: 'Add a JWT authorizer', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'deleteAuthorizer', label: 'Delete an authorizer', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'updateCors', label: 'Configure CORS', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'sendRequest', label: 'Send a request to a stage', enabled: true, status: 'available', runtimeRequired: true},
|
||||
{name: 'testWebSocket', label: 'Connect and send a WebSocket message', enabled: true, status: 'available', runtimeRequired: true},
|
||||
]
|
||||
|
||||
const httpOnly = {field: 'protocol', values: ['HTTP']}
|
||||
const webSocketOnly = {field: 'protocol', values: ['WEBSOCKET']}
|
||||
|
||||
export function awsApiGatewaySchema(): ServiceSchema {
|
||||
return {
|
||||
cloud: 'aws',
|
||||
service: 'apigateway',
|
||||
displayName: 'AWS API Gateway',
|
||||
fields: [
|
||||
{
|
||||
name: 'protocol',
|
||||
label: 'API type',
|
||||
type: 'select',
|
||||
required: true,
|
||||
defaultValue: 'REST',
|
||||
options: [
|
||||
{label: 'REST API (resources, methods and stages)', value: 'REST'},
|
||||
{label: 'HTTP API (routes to Lambda or HTTP backends, JWT authorizers)', value: 'HTTP'},
|
||||
{label: 'WebSocket API (two-way messages routed to Lambda)', value: 'WEBSOCKET'},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: 'API Name',
|
||||
@@ -30,20 +60,47 @@ export function awsApiGatewaySchema(): ServiceSchema {
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
required: false,
|
||||
description: 'Optional description for the REST API.',
|
||||
description: 'Optional description for the API.',
|
||||
validation: {
|
||||
maxLength: 1024,
|
||||
message: 'Description must be 1024 characters or fewer.',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'functionName',
|
||||
label: 'Lambda function',
|
||||
type: 'text',
|
||||
required: false,
|
||||
visibleWhen: httpOnly,
|
||||
description: 'Optional. Sends every request to this function through a catch-all route, on a $default stage that deploys automatically. Leave empty to add routes yourself.',
|
||||
},
|
||||
{
|
||||
name: 'routeSelectionExpression',
|
||||
label: 'Route selection expression',
|
||||
type: 'text',
|
||||
required: false,
|
||||
defaultValue: '$request.body.action',
|
||||
visibleWhen: webSocketOnly,
|
||||
description: 'Which field of each message picks its route. With $request.body.action, {"action": "sendMessage"} goes to the sendMessage route.',
|
||||
},
|
||||
{
|
||||
name: 'stageName',
|
||||
label: 'Stage',
|
||||
type: 'text',
|
||||
required: false,
|
||||
defaultValue: 'dev',
|
||||
visibleWhen: webSocketOnly,
|
||||
description: 'Created with automatic deployment, so route changes go live right away.',
|
||||
validation: {pattern: '^[A-Za-z0-9_-]{1,128}$', message: 'Use letters, numbers, hyphens and underscores'},
|
||||
},
|
||||
],
|
||||
actions: ['list', 'create', 'delete', 'inspect'],
|
||||
capabilities: {resourceActions},
|
||||
capabilities: {resourceActions, httpApiActions},
|
||||
filters: [{name: 'search', label: 'Search', type: 'text', required: false}],
|
||||
columns: [
|
||||
{name: 'name', label: 'API Name'},
|
||||
{name: 'type', label: 'Type'},
|
||||
{name: 'createdAt', label: 'Created At'},
|
||||
{name: 'protocol', label: 'Type', path: 'metadata.protocol', format: 'badge'},
|
||||
{name: 'createdAt', label: 'Created At', format: 'relative'},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +182,19 @@ export type UserPoolActionName =
|
||||
| 'updateClientOAuth'
|
||||
export type DistributionActionName = 'setEnabled' | 'createInvalidation'
|
||||
export type MonitoringActionName = 'listMetrics' | 'getMetricData' | 'alarmHistory' | 'setAlarmState' | 'setAlarmActions'
|
||||
export type HttpApiActionName =
|
||||
| 'describe'
|
||||
| 'createRoute'
|
||||
| 'deleteRoute'
|
||||
| 'setRouteAuthorizer'
|
||||
| 'createStage'
|
||||
| 'deleteStage'
|
||||
| 'deploy'
|
||||
| 'createAuthorizer'
|
||||
| 'deleteAuthorizer'
|
||||
| 'updateCors'
|
||||
| 'sendRequest'
|
||||
| 'testWebSocket'
|
||||
export type CapabilityStatus = 'available' | 'blocked' | 'partial' | 'coming_soon'
|
||||
|
||||
export interface CapabilitySchema<TAction extends string> {
|
||||
@@ -228,6 +241,8 @@ export interface ServiceSchema {
|
||||
userPoolActions?: CapabilitySchema<UserPoolActionName>[]
|
||||
distributionActions?: CapabilitySchema<DistributionActionName>[]
|
||||
monitoringActions?: CapabilitySchema<MonitoringActionName>[]
|
||||
/** API Gateway v2 (HTTP and WebSocket APIs): what the routes, stages and security panel can do. */
|
||||
httpApiActions?: CapabilitySchema<HttpApiActionName>[]
|
||||
// Child levels advertise separately from the resource level because a
|
||||
// store can be readable at the leaf and writable at the collection —
|
||||
// CloudWatch Logs creates streams but cannot delete an event.
|
||||
@@ -769,6 +784,107 @@ export interface ApiGatewayCorsResult {
|
||||
proxyMethods: string[]
|
||||
}
|
||||
|
||||
export type HttpApiProtocol = 'HTTP' | 'WEBSOCKET'
|
||||
|
||||
export interface HttpApiIntegrationOverview {
|
||||
integrationId: string
|
||||
type: string
|
||||
uri: string | null
|
||||
/** The Lambda function a proxy integration invokes, when it is one. */
|
||||
functionName: string | null
|
||||
payloadFormatVersion: string | null
|
||||
}
|
||||
|
||||
export interface HttpApiRouteOverview {
|
||||
routeId: string
|
||||
/** `GET /orders/{id}`, `$default`, or a WebSocket key such as `$connect` or `sendMessage`. */
|
||||
routeKey: string
|
||||
integration: HttpApiIntegrationOverview | null
|
||||
authorizationType: string
|
||||
authorizerId: string | null
|
||||
authorizationScopes: string[]
|
||||
/** WebSocket only: whether the integration's response is sent back to the client. */
|
||||
returnsResponse: boolean
|
||||
}
|
||||
|
||||
export interface HttpApiStageOverview {
|
||||
stageName: string
|
||||
autoDeploy: boolean
|
||||
deploymentId: string | null
|
||||
lastUpdatedAt: string | null
|
||||
/** Where the stage is served on the runtime endpoint: /execute-api/{api}/{stage} or, for WebSocket, /ws/{api}/{stage}. */
|
||||
invokePath: string
|
||||
}
|
||||
|
||||
export interface HttpApiAuthorizerOverview {
|
||||
authorizerId: string
|
||||
name: string
|
||||
type: string
|
||||
identitySource: string[]
|
||||
issuer: string | null
|
||||
audience: string[]
|
||||
}
|
||||
|
||||
export interface HttpApiCors {
|
||||
allowOrigins: string[]
|
||||
allowMethods: string[]
|
||||
allowHeaders: string[]
|
||||
exposeHeaders: string[]
|
||||
maxAgeSeconds: number | null
|
||||
allowCredentials: boolean
|
||||
}
|
||||
|
||||
export interface HttpApiOverview {
|
||||
apiId: string
|
||||
protocol: HttpApiProtocol
|
||||
apiEndpoint: string | null
|
||||
routeSelectionExpression: string | null
|
||||
routes: HttpApiRouteOverview[]
|
||||
stages: HttpApiStageOverview[]
|
||||
authorizers: HttpApiAuthorizerOverview[]
|
||||
cors: HttpApiCors | null
|
||||
}
|
||||
|
||||
export interface HttpApiRouteInput {
|
||||
routeKey: string
|
||||
/** A Lambda function name; exactly one of functionName and httpUrl. */
|
||||
functionName?: string
|
||||
/** An HTTP backend the route proxies to; HTTP APIs only. */
|
||||
httpUrl?: string
|
||||
authorizerId?: string
|
||||
/** WebSocket only: send the integration's response back to the client. */
|
||||
returnsResponse?: boolean
|
||||
}
|
||||
|
||||
export interface HttpApiStageInput {
|
||||
stageName: string
|
||||
autoDeploy: boolean
|
||||
}
|
||||
|
||||
export interface HttpApiAuthorizerInput {
|
||||
name: string
|
||||
/** Fill the issuer and audience from a Cognito user pool and one of its app clients. */
|
||||
userPoolId?: string
|
||||
userPoolClientId?: string
|
||||
issuer?: string
|
||||
audience?: string[]
|
||||
}
|
||||
|
||||
export interface HttpApiWebSocketTestInput {
|
||||
stage: string
|
||||
message: string
|
||||
/** Sent as query string parameters on the connection request, for a $connect authorizer. */
|
||||
query?: string
|
||||
}
|
||||
|
||||
export interface HttpApiWebSocketTestResult {
|
||||
connected: boolean
|
||||
messages: string[]
|
||||
closeCode: number | null
|
||||
error: string | null
|
||||
durationMs: number
|
||||
}
|
||||
|
||||
export interface S3LambdaTriggerInput {
|
||||
functionName: string
|
||||
events: string[]
|
||||
@@ -963,6 +1079,17 @@ export interface CloudServiceAdapter {
|
||||
listApiGatewayRoutes?(apiId: string): Promise<ApiGatewayRoutesResult>
|
||||
deleteApiGatewayRoute?(apiId: string, input: ApiGatewayDeleteRouteInput): Promise<void>
|
||||
enableApiGatewayCors?(apiId: string, input: ApiGatewayCorsInput): Promise<ApiGatewayCorsResult>
|
||||
describeHttpApi?(apiId: string): Promise<HttpApiOverview>
|
||||
createHttpApiRoute?(apiId: string, input: HttpApiRouteInput): Promise<HttpApiRouteOverview>
|
||||
deleteHttpApiRoute?(apiId: string, routeId: string): Promise<void>
|
||||
setHttpApiRouteAuthorizer?(apiId: string, routeId: string, authorizerId: string | null): Promise<HttpApiRouteOverview>
|
||||
createHttpApiStage?(apiId: string, input: HttpApiStageInput): Promise<HttpApiStageOverview>
|
||||
deleteHttpApiStage?(apiId: string, stageName: string): Promise<void>
|
||||
deployHttpApi?(apiId: string, stageName: string): Promise<HttpApiStageOverview>
|
||||
createHttpApiAuthorizer?(apiId: string, input: HttpApiAuthorizerInput): Promise<HttpApiAuthorizerOverview>
|
||||
deleteHttpApiAuthorizer?(apiId: string, authorizerId: string): Promise<void>
|
||||
updateHttpApiCors?(apiId: string, cors: HttpApiCors | null): Promise<HttpApiCors | null>
|
||||
testWebSocketApi?(apiId: string, input: HttpApiWebSocketTestInput): Promise<HttpApiWebSocketTestResult>
|
||||
createS3LambdaTrigger?(bucketName: string, input: S3LambdaTriggerInput): Promise<S3LambdaTriggerOverview>
|
||||
listS3LambdaTriggers?(bucketName: string): Promise<S3LambdaTriggerOverview[]>
|
||||
deleteS3LambdaTrigger?(bucketName: string, triggerId: string): Promise<void>
|
||||
|
||||
Reference in New Issue
Block a user