diff --git a/src/cloud-formation/README.md b/src/cloud-formation/README.md index eda2af265..9fe13df89 100644 --- a/src/cloud-formation/README.md +++ b/src/cloud-formation/README.md @@ -12,9 +12,13 @@ using the [template file] at `indexer.cfn.yaml` and a development-specific [stack deployment file] at `deploy-*.yaml`. Once a [stack] is configured accordingly, `git` updates will result in automatic updates. -The indexer provides a public endpoint for both REST queries and WebSocket -events at a custom subdomain under a root domain you provide, of format -``, for example `dev.data.foo.bar`. +The indexer provides a public REST endpoint and a public WebSocket endpoint +under a root domain you provide, for an environment name of your choosing: + +| Endpoint | URI | +| --------- | ------------------------------------------- | +| REST | `https://.` | +| WebSocket | `wss://ws..` | ## Template parameters @@ -232,39 +236,46 @@ deployment environment: echo $STACK_NAME ``` -1. Get the DNS name specified in the [template outputs section]: +1. Get the endpoints specified in the [template outputs section]: ```sh - DNS_NAME=$(aws cloudformation describe-stacks \ + REST_ENDPOINT=$(aws cloudformation describe-stacks \ + --output text \ + --query 'Stacks[0].Outputs[?OutputKey==`RestEndpoint`].OutputValue' \ + --stack-name $STACK_NAME + ) + WS_ENDPOINT=$(aws cloudformation describe-stacks \ --output text \ - --query 'Stacks[0].Outputs[?OutputKey==`DnsName`].OutputValue' \ + --query 'Stacks[0].Outputs[?OutputKey==`WsEndpoint`].OutputValue' \ --stack-name $STACK_NAME ) - echo $DNS_NAME + echo $REST_ENDPOINT + echo $WS_ENDPOINT ``` -1. Wait until the DNS name has resolved: +1. Wait until the DNS names have resolved: ```sh - host $DNS_NAME + host $(echo $REST_ENDPOINT | sed 's|^.*//||') + host $(echo $WS_ENDPOINT | sed 's|^.*//||') ``` -1. Connect to the WebSocket endpoint: +1. Query the REST endpoint: ```sh - websocat wss://$DNS_NAME/ws + curl "$REST_ENDPOINT/processor_status?select=last_success_version" ``` -1. Subscribe to all events: +1. Connect to the WebSocket endpoint: ```sh - {} + websocat $WS_ENDPOINT ``` -1. Check PostgREST: +1. Subscribe to all events: ```sh - curl "https://$DNS_NAME/processor_status?select=last_success_version" + {} ``` ### Bastion host connections @@ -276,66 +287,79 @@ might want to de-provision then provision the bastion host before running the below commands, in order to refresh the bastion host [user data] that stores the URLs of other resources in the stack. -Install the [EC2 Instance Connect CLI]: +1. Install the [EC2 Instance Connect CLI]: -```sh -pip install ec2instanceconnectcli -``` + ```sh + pip install ec2instanceconnectcli + ``` -Connect to the bastion host over the [EC2 Instance Connect Endpoint] using your -stack name, for example `emoji-dev`: +1. Connect to the bastion host over the [EC2 Instance Connect Endpoint] using + your stack name, for example `emoji-dev`: -```sh -STACK_NAME=emoji-dev -INSTANCE_ID=$(aws cloudformation describe-stacks \ - --output text \ - --query 'Stacks[0].Outputs[?OutputKey==`BastionHostId`].OutputValue' \ - --stack-name $STACK_NAME -) -if [ -n "$(echo -e "${INSTANCE_ID}" | tr -d '[:space:]')" ]; then - echo "Connecting to instance ${INSTANCE_ID}" - aws ec2-instance-connect ssh \ - --instance-id $INSTANCE_ID --connection-type eice -else - echo 'Error: instance ID not found or is empty.' >&2 -fi -``` + ```sh + STACK_NAME=emoji-dev + INSTANCE_ID=$(aws cloudformation describe-stacks \ + --output text \ + --query 'Stacks[0].Outputs[?OutputKey==`BastionHostId`].OutputValue' \ + --stack-name $STACK_NAME + ) + if [ -n "$(echo -e "${INSTANCE_ID}" | tr -d '[:space:]')" ]; then + echo "Connecting to instance ${INSTANCE_ID}" + aws ec2-instance-connect ssh \ + --instance-id $INSTANCE_ID --connection-type eice + else + echo 'Error: instance ID not found or is empty.' >&2 + fi + ``` -Start `psql`: +1. Start `psql`: -```sh -psql $DB_URL -``` + ```sh + psql $DB_URL + ``` -List the databases: +1. List the databases: -```sh -\list -``` + ```sh + \list + ``` -After exiting `psql`, to view WebSocket events published by the processor: +1. After exiting `psql`, to view WebSocket events published by the processor: -```sh -websocat $PROCESSOR_WS_URL -``` + ```sh + websocat $PROCESSOR_WS_URL + ``` + +1. Subscribe to all events: + + ```sh + {} + ``` + +1. Connect to the broker: + + ```sh + websocat $BROKER_WS_URL + ``` -Connect to the broker: +1. Subscribe to all events: -```sh -websocat $BROKER_WS_URL -``` + ```sh + {} + ``` -Subscribe to all events: +1. Query PostgREST: -```sh -{} -``` + ```sh + curl $POSTGREST_URL/processor_status?select=last_success_version && echo + ``` -Query PostgREST: +1. Check PostgREST through the NLB: -```sh -curl $POSTGREST_URL/market_latest_state_event?select=market_id && echo -``` + ```sh + curl http://$NLB_DNS_NAME:3000/processor_status?select=last_success_version \ + && echo + ``` ## Design notes diff --git a/src/cloud-formation/deploy-dev.yaml b/src/cloud-formation/deploy-dev.yaml index 8d1c09157..6dcdafb90 100644 --- a/src/cloud-formation/deploy-dev.yaml +++ b/src/cloud-formation/deploy-dev.yaml @@ -1,20 +1,24 @@ --- parameters: - BrokerImageVersion: '0.3.0' + BrokerImageVersion: '0.7.0' Environment: 'dev' - MaybeDeployAlb: 'false' - MaybeDeployAlbDnsRecord: 'false' - MaybeDeployBastionHost: 'false' - MaybeDeployBroker: 'false' - MaybeDeployContainers: 'false' - MaybeDeployDb: 'false' - MaybeDeployPostgrest: 'false' - MaybeDeployProcessor: 'false' - MaybeDeployRouteTables: 'false' - MaybeDeployStack: 'false' - MaybeDeployVpc: 'false' + MaybeDeployAlb: 'true' + MaybeDeployAlbDnsRecord: 'true' + MaybeDeployBastionHost: 'true' + MaybeDeployBroker: 'true' + MaybeDeployContainers: 'true' + MaybeDeployDb: 'true' + MaybeDeployNlb: 'true' + MaybeDeployNlbVpcLink: 'true' + MaybeDeployPostgrest: 'true' + MaybeDeployProcessor: 'true' + MaybeDeployRestApi: 'true' + MaybeDeployRestApiDnsRecord: 'true' + MaybeDeployRouteTables: 'true' + MaybeDeployStack: 'true' + MaybeDeployVpc: 'true' Network: 'testnet' - ProcessorImageVersion: '0.4.0' + ProcessorImageVersion: '0.5.0' tags: null template-file-path: 'src/cloud-formation/indexer.cfn.yaml' ... diff --git a/src/cloud-formation/indexer.cfn.yaml b/src/cloud-formation/indexer.cfn.yaml index 3f2b65f5d..a32fcbcdb 100644 --- a/src/cloud-formation/indexer.cfn.yaml +++ b/src/cloud-formation/indexer.cfn.yaml @@ -38,6 +38,16 @@ Conditions: - !Equals - !Ref 'MaybeDeployDb' - 'true' + MaybeDeployNlb: !And + - !Condition 'MaybeDeployVpc' + - !Equals + - !Ref 'MaybeDeployNlb' + - 'true' + MaybeDeployNlbVpcLink: !And + - !Condition 'MaybeDeployNlb' + - !Equals + - !Ref 'MaybeDeployNlbVpcLink' + - 'true' MaybeDeployPostgrest: !And - !Condition 'MaybeDeployContainers' - !Condition 'MaybeDeployProcessor' @@ -49,6 +59,17 @@ Conditions: - !Equals - !Ref 'MaybeDeployProcessor' - 'true' + MaybeDeployRestApi: !And + - !Condition 'MaybeDeployNlbVpcLink' + - !Condition 'MaybeDeployPostgrest' + - !Equals + - !Ref 'MaybeDeployRestApi' + - 'true' + MaybeDeployRestApiDnsRecord: !And + - !Condition 'MaybeDeployRestApi' + - !Equals + - !Ref 'MaybeDeployRestApiDnsRecord' + - 'true' MaybeDeployRouteTables: !And - !Condition 'MaybeDeployVpc' - !Equals @@ -81,7 +102,6 @@ Mappings: '{{resolve:ssm:/emojicoin/indexer-dns-name/hosted-zone-id}}' DnsNameRootDomain: '{{resolve:ssm:/emojicoin/indexer-dns-name/root-domain}}' - DnsNameSubdomain: 'data' PostgrestHealthCheckPort: 3001 PostgrestPort: 3000 ProcessorWebsocketPort: 3008 @@ -115,18 +135,22 @@ Outputs: Condition: 'MaybeDeployBastionHost' Description: 'The instance ID of the bastion host.' Value: !Ref 'BastionHost' - DnsName: - Condition: 'MaybeDeployAlbDnsRecord' + RestEndpoint: + Condition: 'MaybeDeployRestApiDnsRecord' Value: !Sub - - '${Environment}.${Subdomain}.${RootDomain}' + - 'https://${Environment}.${RootDomain}' - RootDomain: !FindInMap - 'Constants' - 'Networking' - 'DnsNameRootDomain' - Subdomain: !FindInMap + WsEndpoint: + Condition: 'MaybeDeployAlbDnsRecord' + Value: !Sub + - 'wss://ws.${Environment}.${RootDomain}' + - RootDomain: !FindInMap - 'Constants' - 'Networking' - - 'DnsNameSubdomain' + - 'DnsNameRootDomain' Parameters: BastionHostAmiId: Default: 'ami-030cb86c12b18e236' @@ -171,6 +195,16 @@ Parameters: - 'false' - 'true' Type: 'String' + MaybeDeployNlb: + AllowedValues: + - 'false' + - 'true' + Type: 'String' + MaybeDeployNlbVpcLink: + AllowedValues: + - 'false' + - 'true' + Type: 'String' MaybeDeployPostgrest: AllowedValues: - 'false' @@ -181,6 +215,16 @@ Parameters: - 'false' - 'true' Type: 'String' + MaybeDeployRestApi: + AllowedValues: + - 'false' + - 'true' + Type: 'String' + MaybeDeployRestApiDnsRecord: + AllowedValues: + - 'false' + - 'true' + Type: 'String' MaybeDeployRouteTables: AllowedValues: - 'false' @@ -224,41 +268,29 @@ Resources: - 'MaybeDeployBroker' - !Ref 'BrokerWsClientSecurityGroup' - !Ref 'AWS::NoValue' - - !If - - 'MaybeDeployPostgrest' - - !Ref 'PostgrestClientSecurityGroup' - - !Ref 'AWS::NoValue' Subnets: - !Ref 'PublicSubnetA' - !Ref 'PublicSubnetB' - !Ref 'PublicSubnetC' Type: 'application' Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer' - # DNS certificate for the application load balancer. + # DNS certificate for the broker's application load balancer. AlbCertificate: Condition: 'MaybeDeployAlb' Properties: DomainName: !Sub - - '${Environment}.${Subdomain}.${RootDomain}' + - 'ws.${Environment}.${RootDomain}' - RootDomain: !FindInMap - 'Constants' - 'Networking' - 'DnsNameRootDomain' - Subdomain: !FindInMap - - 'Constants' - - 'Networking' - - 'DnsNameSubdomain' DomainValidationOptions: - DomainName: !Sub - - '${Environment}.${Subdomain}.${RootDomain}' + - 'ws.${Environment}.${RootDomain}' - RootDomain: !FindInMap - 'Constants' - 'Networking' - 'DnsNameRootDomain' - Subdomain: !FindInMap - - 'Constants' - - 'Networking' - - 'DnsNameSubdomain' HostedZoneId: !FindInMap - 'Constants' - 'Networking' @@ -277,15 +309,11 @@ Resources: - 'Networking' - 'DnsNameHostedZoneId' Name: !Sub - - '${Environment}.${Subdomain}.${RootDomain}' + - 'ws.${Environment}.${RootDomain}' - RootDomain: !FindInMap - 'Constants' - 'Networking' - 'DnsNameRootDomain' - Subdomain: !FindInMap - - 'Constants' - - 'Networking' - - 'DnsNameSubdomain' Type: 'A' Type: 'AWS::Route53::RecordSet' # Application load balancer listener. @@ -295,26 +323,12 @@ Resources: Certificates: - CertificateArn: !Ref 'AlbCertificate' DefaultActions: - - TargetGroupArn: !Ref 'PostgrestAlbTargetGroup' + - TargetGroupArn: !Ref 'BrokerAlbTargetGroup' Type: 'forward' LoadBalancerArn: !Ref 'Alb' Port: 443 Protocol: 'HTTPS' Type: 'AWS::ElasticLoadBalancingV2::Listener' - # Application load balancer listener rule for broker traffic. - AlbListenerRuleBrokerTarget: - Condition: 'MaybeDeployAlb' - Properties: - Actions: - - TargetGroupArn: !Ref 'BrokerAlbTargetGroup' - Type: 'forward' - Conditions: - - Field: 'path-pattern' - Values: - - '/ws' - ListenerArn: !Ref 'AlbListener' - Priority: 1 - Type: 'AWS::ElasticLoadBalancingV2::ListenerRule' # Security group for the application load balancer. AlbSecurityGroup: Condition: 'MaybeDeployAlb' @@ -342,6 +356,10 @@ Resources: - 'MaybeDeployBroker' - !Ref 'BrokerWsClientSecurityGroup' - !Ref 'AWS::NoValue' + - !If + - 'MaybeDeployNlb' + - !Ref 'NlbClientSecurityGroup' + - !Ref 'AWS::NoValue' - !If - 'MaybeDeployPostgrest' - !Ref 'PostgrestClientSecurityGroup' @@ -364,6 +382,7 @@ Resources: chmod +x ${WebsocatBuild} mv ${WebsocatBuild} /usr/local/bin/websocat echo 'export DB_URL="${DatabaseUrl}"' >> /etc/profile + echo '${NlbDnsNameExport}' >> /etc/profile echo '${BrokerWsUrlExport}' >> /etc/profile echo '${PostgrestUrlExport}' >> /etc/profile echo '${ProcessorWsUrlExport}' >> /etc/profile @@ -380,7 +399,7 @@ Resources: - 'Constants' - 'Networking' - 'BrokerPort' - - '/ws"' + - '"' - '# BROKER_WS_URL not set (broker not provisioned)' DatabaseUrl: !Join - '' @@ -401,6 +420,14 @@ Resources: - 'Constants' - 'DatabaseConfig' - 'DatabaseName' + NlbDnsNameExport: !If + - 'MaybeDeployNlb' + - !Join + - '' + - - 'export NLB_DNS_NAME="' + - !GetAtt 'Nlb.DNSName' + - '"' + - '# NLB_DNS_NAME not set (NLB not provisioned)' PostgrestUrlExport: !If - 'MaybeDeployPostgrest' - !Join @@ -486,7 +513,7 @@ Resources: DependsOn: 'Alb' Properties: HealthCheckIntervalSeconds: 5 - HealthCheckPath: '/' + HealthCheckPath: '/health' HealthCheckPort: !FindInMap - 'Constants' - 'Networking' @@ -569,12 +596,12 @@ Resources: - 'PrivateRouteTableAssociationC' - 'PrivateRouteThroughNatGateway' # Proxy for a conditional dependency on the application load balancer - # listener rule for the broker, which associates the broker target group - # with the application load balancer. + # listener, which associates the broker target group with the application + # load balancer. Metadata: ConditionalDependencyProxy: !If - 'MaybeDeployAlb' - - !Ref 'AlbListenerRuleBrokerTarget' + - !Ref 'AlbListener' - '' Properties: Cluster: !Ref 'ContainerCluster' @@ -653,8 +680,8 @@ Resources: - 'curl' - '--fail' - !Sub - - 'http://localhost:${PORT}/' - - PORT: !FindInMap + - 'http://localhost:${Port}/live' + - Port: !FindInMap - 'Constants' - 'Networking' - 'BrokerPort' @@ -1036,38 +1063,72 @@ Resources: Properties: Domain: 'vpc' Type: 'AWS::EC2::EIP' - # Target group for application load balancer traffic to PostgREST. - PostgrestAlbTargetGroup: - Condition: 'MaybeDeployAlb' - DependsOn: 'Alb' + # Private network load balancer. + Nlb: + Condition: 'MaybeDeployNlb' Properties: - HealthCheckIntervalSeconds: 5 - HealthCheckPath: '/ready' - HealthCheckPort: !FindInMap + LoadBalancerAttributes: + - Key: 'load_balancing.cross_zone.enabled' + Value: 'true' + Scheme: 'internal' + SecurityGroups: + - !Ref 'NlbSecurityGroup' + - !If + - 'MaybeDeployBroker' + - !Ref 'BrokerWsClientSecurityGroup' + - !Ref 'AWS::NoValue' + - !If + - 'MaybeDeployPostgrest' + - !Ref 'PostgrestClientSecurityGroup' + - !Ref 'AWS::NoValue' + Subnets: + - !Ref 'PrivateSubnetA' + - !Ref 'PrivateSubnetB' + - !Ref 'PrivateSubnetC' + Type: 'network' + Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer' + # Security group for direct clients of the network load balancer. + NlbClientSecurityGroup: + Condition: 'MaybeDeployNlb' + Properties: + GroupDescription: !Ref 'AWS::StackName' + SecurityGroupEgress: + - DestinationSecurityGroupId: !Ref 'NlbSecurityGroup' + IpProtocol: -1 + VpcId: !Ref 'Vpc' + Type: 'AWS::EC2::SecurityGroup' + # Security group for the network load balancer. + NlbSecurityGroup: + Condition: 'MaybeDeployNlb' + Properties: + GroupDescription: !Ref 'AWS::StackName' + VpcId: !Ref 'Vpc' + Type: 'AWS::EC2::SecurityGroup' + # Network load balancer security group ingress policy for PostgREST traffic + # over VPC link. + NlbSecurityGroupIngressPostgrest: + Condition: 'MaybeDeployNlbVpcLink' + Properties: + CidrIp: '0.0.0.0/0' + FromPort: !FindInMap - 'Constants' - 'Networking' - - 'PostgrestHealthCheckPort' - HealthCheckProtocol: 'HTTP' - HealthCheckTimeoutSeconds: 2 - HealthyThresholdCount: 2 - Name: !Sub '${AWS::StackName}-postgrest' - Port: !FindInMap + - 'PostgrestPort' + GroupId: !Ref 'NlbSecurityGroup' + IpProtocol: 'tcp' + ToPort: !FindInMap - 'Constants' - 'Networking' - 'PostgrestPort' - Protocol: 'HTTP' - ProtocolVersion: 'HTTP1' - TargetGroupAttributes: - - Key: 'deregistration_delay.timeout_seconds' - Value: '30' - - Key: 'stickiness.enabled' - Value: 'false' - - Key: 'load_balancing.algorithm.type' - Value: 'round_robin' - TargetType: 'ip' - UnhealthyThresholdCount: 2 - VpcId: !Ref 'Vpc' - Type: 'AWS::ElasticLoadBalancingV2::TargetGroup' + Type: 'AWS::EC2::SecurityGroupIngress' + # Connection to network load balancer through VPC link. + NlbVpcLink: + Condition: 'MaybeDeployNlbVpcLink' + Properties: + Name: !Ref 'AWS::StackName' + TargetArns: + - !Ref 'Nlb' + Type: 'AWS::ApiGateway::VpcLink' # Scaling policy for PostgREST service. PostgrestAutoScalingPolicy: Condition: 'MaybeDeployPostgrest' @@ -1107,6 +1168,49 @@ Resources: IpProtocol: -1 VpcId: !Ref 'Vpc' Type: 'AWS::EC2::SecurityGroup' + # Listener for network load balancer traffic to PostgREST. + PostgrestNlbListener: + Condition: 'MaybeDeployNlb' + Properties: + DefaultActions: + - TargetGroupArn: !Ref 'PostgrestNlbTargetGroup' + Type: 'forward' + LoadBalancerArn: !Ref 'Nlb' + Port: !FindInMap + - 'Constants' + - 'Networking' + - 'PostgrestPort' + Protocol: 'TCP' + Type: 'AWS::ElasticLoadBalancingV2::Listener' + # Target group for network load balancer traffic to PostgREST. + PostgrestNlbTargetGroup: + Condition: 'MaybeDeployNlb' + DependsOn: 'Nlb' + Properties: + HealthCheckIntervalSeconds: 5 + HealthCheckPath: '/ready' + HealthCheckPort: !FindInMap + - 'Constants' + - 'Networking' + - 'PostgrestHealthCheckPort' + HealthCheckProtocol: 'HTTP' + HealthCheckTimeoutSeconds: 2 + HealthyThresholdCount: 2 + Name: !Sub '${AWS::StackName}-postgrest' + Port: !FindInMap + - 'Constants' + - 'Networking' + - 'PostgrestPort' + Protocol: 'TCP' + TargetGroupAttributes: + - Key: 'deregistration_delay.timeout_seconds' + Value: '30' + - Key: 'stickiness.enabled' + Value: 'false' + TargetType: 'ip' + UnhealthyThresholdCount: 2 + VpcId: !Ref 'Vpc' + Type: 'AWS::ElasticLoadBalancingV2::TargetGroup' # Service for running PostgREST. PostgrestRunner: Condition: 'MaybeDeployPostgrest' @@ -1123,12 +1227,13 @@ Resources: - 'PrivateRouteTableAssociationB' - 'PrivateRouteTableAssociationC' - 'PrivateRouteThroughNatGateway' - # Proxy for a conditional dependency on the application load balancer - # listener, which associates the PostgREST target group with the balancer. + # Proxy for a conditional dependency on the network load balancer listener + # for PostgREST, which associates the PostgREST target group with the + # network load balancer. Metadata: ConditionalDependencyProxy: !If - - 'MaybeDeployAlb' - - !Ref 'AlbListener' + - 'MaybeDeployNlb' + - !Ref 'PostgrestNlbListener' - '' Properties: Cluster: !Ref 'ContainerCluster' @@ -1138,13 +1243,13 @@ Resources: Rollback: true LaunchType: 'FARGATE' LoadBalancers: !If - - 'MaybeDeployAlb' + - 'MaybeDeployNlb' - - ContainerName: !Sub '${AWS::StackName}-postgrest' ContainerPort: !FindInMap - 'Constants' - 'Networking' - 'PostgrestPort' - TargetGroupArn: !Ref 'PostgrestAlbTargetGroup' + TargetGroupArn: !Ref 'PostgrestNlbTargetGroup' - !Ref 'AWS::NoValue' NetworkConfiguration: AwsvpcConfiguration: @@ -1423,7 +1528,12 @@ Resources: - 'CMD' - 'curl' - '--fail' - - 'http://localhost:8084/metrics' + - !Sub + - 'http://localhost:${Port}/' + - Port: !FindInMap + - 'Constants' + - 'Networking' + - 'ProcessorWebsocketPort' Interval: 5 Retries: 1 StartPeriod: 0 @@ -1506,6 +1616,156 @@ Resources: GatewayId: !Ref 'InternetGateway' RouteTableId: !Ref 'PublicRouteTable' Type: 'AWS::EC2::Route' + # REST API endpoint. + RestApi: + Condition: 'MaybeDeployRestApi' + Properties: + EndpointConfiguration: + Types: + - 'REGIONAL' + Name: !Ref 'AWS::StackName' + Type: 'AWS::ApiGateway::RestApi' + # DNS certificate for the REST API endpoint. + RestApiCertificate: + Condition: 'MaybeDeployRestApiDnsRecord' + Properties: + DomainName: !Sub + - '${Environment}.${RootDomain}' + - RootDomain: !FindInMap + - 'Constants' + - 'Networking' + - 'DnsNameRootDomain' + DomainValidationOptions: + - DomainName: !Sub + - '${Environment}.${RootDomain}' + - RootDomain: !FindInMap + - 'Constants' + - 'Networking' + - 'DnsNameRootDomain' + HostedZoneId: !FindInMap + - 'Constants' + - 'Networking' + - 'DnsNameHostedZoneId' + ValidationMethod: 'DNS' + Type: 'AWS::CertificateManager::Certificate' + # Deployment for the REST API endpoint. + RestApiDeployment: + Condition: 'MaybeDeployRestApi' + DependsOn: + - 'RestApiMethodGeneral' + - 'RestApiMethodRoot' + Properties: + RestApiId: !Ref 'RestApi' + Type: 'AWS::ApiGateway::Deployment' + # DNS record for the REST API. + RestApiDnsRecord: + Condition: 'MaybeDeployRestApiDnsRecord' + Properties: + AliasTarget: + DNSName: !GetAtt 'RestApiDomainName.RegionalDomainName' + HostedZoneId: !GetAtt 'RestApiDomainName.RegionalHostedZoneId' + HostedZoneId: !FindInMap + - 'Constants' + - 'Networking' + - 'DnsNameHostedZoneId' + Name: !Sub + - '${Environment}.${RootDomain}' + - RootDomain: !FindInMap + - 'Constants' + - 'Networking' + - 'DnsNameRootDomain' + Type: 'A' + Type: 'AWS::Route53::RecordSet' + # Domain mapping for the REST API endpoint. + RestApiDomainMapping: + Condition: 'MaybeDeployRestApiDnsRecord' + Properties: + DomainName: !Ref 'RestApiDomainName' + RestApiId: !Ref 'RestApi' + Stage: !Ref 'RestApiStage' + Type: 'AWS::ApiGateway::BasePathMapping' + # Custom domain name for the REST API endpoint. + RestApiDomainName: + Condition: 'MaybeDeployRestApiDnsRecord' + Properties: + DomainName: !Sub + - '${Environment}.${RootDomain}' + - RootDomain: !FindInMap + - 'Constants' + - 'Networking' + - 'DnsNameRootDomain' + EndpointConfiguration: + Types: + - 'REGIONAL' + RegionalCertificateArn: !Ref 'RestApiCertificate' + SecurityPolicy: 'TLS_1_2' + Type: 'AWS::ApiGateway::DomainName' + # Rest API endpoint general method. + RestApiMethodGeneral: + Condition: 'MaybeDeployRestApi' + Properties: + AuthorizationType: 'NONE' + HttpMethod: 'GET' + Integration: + ConnectionId: !Ref 'NlbVpcLink' + ConnectionType: 'VPC_LINK' + IntegrationHttpMethod: 'GET' + RequestParameters: + integration.request.path.proxy: 'method.request.path.proxy' + Type: 'HTTP_PROXY' + Uri: !Sub + - 'http://${NlbDnsName}:${PostgrestPort}/{proxy}' + - NlbDnsName: !GetAtt 'Nlb.DNSName' + PostgrestPort: !FindInMap + - 'Constants' + - 'Networking' + - 'PostgrestPort' + RequestParameters: + method.request.path.proxy: true + ResourceId: !Ref 'RestApiProxyResource' + RestApiId: !Ref 'RestApi' + Type: 'AWS::ApiGateway::Method' + # Rest API endpoint root method. + RestApiMethodRoot: + Condition: 'MaybeDeployRestApi' + Properties: + AuthorizationType: 'NONE' + HttpMethod: 'GET' + Integration: + ConnectionId: !Ref 'NlbVpcLink' + ConnectionType: 'VPC_LINK' + IntegrationHttpMethod: 'GET' + RequestParameters: + integration.request.path.proxy: 'method.request.path.proxy' + Type: 'HTTP_PROXY' + Uri: !Sub + - 'http://${NlbDnsName}:${PostgrestPort}/' + - NlbDnsName: !GetAtt 'Nlb.DNSName' + PostgrestPort: !FindInMap + - 'Constants' + - 'Networking' + - 'PostgrestPort' + RequestParameters: + method.request.path.proxy: true + ResourceId: !GetAtt 'RestApi.RootResourceId' + RestApiId: !Ref 'RestApi' + Type: 'AWS::ApiGateway::Method' + # Proxy resource for the REST API endpoint. + RestApiProxyResource: + Condition: 'MaybeDeployRestApi' + Properties: + ParentId: !GetAtt 'RestApi.RootResourceId' + PathPart: '{proxy+}' + RestApiId: !Ref 'RestApi' + Type: 'AWS::ApiGateway::Resource' + # Stage for the REST API endpoint. + RestApiStage: + Condition: 'MaybeDeployRestApi' + Properties: + DeploymentId: !Ref 'RestApiDeployment' + RestApiId: !Ref 'RestApi' + StageName: !Ref 'AWS::StackName' + Type: 'AWS::ApiGateway::Stage' # Virtual private cloud for internal networking. Vpc: Condition: 'MaybeDeployVpc' diff --git a/src/docker/compose.yaml b/src/docker/compose.yaml index e02e4d516..bca353284 100644 --- a/src/docker/compose.yaml +++ b/src/docker/compose.yaml @@ -16,7 +16,7 @@ services: RUST_LOG: 'info,broker=trace' image: 'econialabs/emojicoin-dot-fun-indexer-broker' healthcheck: - test: 'curl -f http://localhost:${BROKER_PORT}/ || exit 1' + test: 'curl -f http://localhost:${BROKER_PORT}/live || exit 1' interval: '2m' timeout: '5s' retries: '1' diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 6526cdaf0..5ad0cf53b 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -515,7 +515,7 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "broker" -version = "0.3.0" +version = "0.7.0" dependencies = [ "async-stream", "axum 0.7.5", diff --git a/src/rust/broker/Cargo.toml b/src/rust/broker/Cargo.toml index af85adda4..5dc7b3f33 100644 --- a/src/rust/broker/Cargo.toml +++ b/src/rust/broker/Cargo.toml @@ -21,4 +21,4 @@ ws = [] edition = "2021" name = "broker" resolver = "2" -version = "0.3.0" +version = "0.7.0" diff --git a/src/rust/broker/Dockerfile b/src/rust/broker/Dockerfile index e8d11372e..71d106f50 100644 --- a/src/rust/broker/Dockerfile +++ b/src/rust/broker/Dockerfile @@ -13,14 +13,14 @@ RUN cargo chef prepare --bin broker FROM base AS builder ARG FEATURES RUN apt-get update && apt-get install -y --no-install-recommends \ - libudev-dev=252.26-1~deb12u2 \ - build-essential=12.9 \ - libclang-dev=1:14.0-55.7~deb12u1 \ - libpq-dev=15.8-0+deb12u1 \ - libssl-dev=3.0.13-1~deb12u1 \ + libudev-dev=252.* \ + build-essential=12.* \ + libclang-dev=1:14.* \ + libpq-dev=15.* \ + libssl-dev=3.* \ libdw-dev=0.188-2.1 \ - pkg-config=1.8.1-1 \ - lld=1:14.0-55.7~deb12u1 \ + pkg-config=1.8.* \ + lld=1:14.* \ && rm -rf /var/lib/apt/lists/* COPY --from=planner app/recipe.json recipe.json RUN ls -la @@ -36,9 +36,9 @@ RUN cargo build --bin broker --package broker --release --features $FEATURES # Install runtime dependencies, copy over binary. FROM debian:bookworm-slim AS runtime RUN apt-get update && apt-get install -y --no-install-recommends \ - curl=7.88.1-10+deb12u6 \ - libssl-dev=3.0.13-1~deb12u1 \ - libpq-dev=15.8-0+deb12u1 \ + curl=7.* \ + libssl-dev=3.* \ + libpq-dev=15.* \ && rm -rf /var/lib/apt/lists/* \ && mkdir app COPY --from=builder /app/target/release/broker /app diff --git a/src/rust/broker/README.md b/src/rust/broker/README.md index 338daac79..5303e8a59 100644 --- a/src/rust/broker/README.md +++ b/src/rust/broker/README.md @@ -22,7 +22,7 @@ Connect to the server with `websocat`: ```shell # Where `3009` here is the `BROKER_PORT` in `compose.yaml`. -websocat -t 'ws://127.0.0.1:3009/ws' +websocat -t 'ws://127.0.0.1:3009' ``` This will bring you into a shell where everything you send is sent directly diff --git a/src/rust/broker/src/server.rs b/src/rust/broker/src/server.rs index 54652dcda..943f43d26 100644 --- a/src/rust/broker/src/server.rs +++ b/src/rust/broker/src/server.rs @@ -25,12 +25,12 @@ fn prepare_app(app: Router>) -> Router> { #[cfg(all(feature = "ws", not(feature = "sse")))] fn prepare_app(app: Router>) -> Router> { - app.route("/ws", get(ws::handler)) + app.route("/", get(ws::handler)) } #[cfg(all(feature = "ws", feature = "sse"))] fn prepare_app(app: Router>) -> Router> { - app.route("/ws", get(ws::handler)) + app.route("/", get(ws::handler)) .route("/sse", get(sse::handler)) } @@ -39,7 +39,7 @@ fn prepare_app(app: Router>) -> Router> { app } -async fn root() {} +async fn live() {} async fn health(State(state): State>) -> StatusCode { match *state.processor_connection_health.read().await { @@ -60,7 +60,7 @@ pub async fn server( let app = prepare_app( Router::new() - .route("/", get(root)) + .route("/live", get(live)) .route("/health", get(health)), ); let app = app.with_state(Arc::new(app_state));