Skip to content

Commit 9bdc6d5

Browse files
n1ru4ldotansimha
andauthored
chore: hive gateway as entrypoint for the public api (#6703)
Co-authored-by: Dotan Simha <[email protected]>
1 parent 3cb068e commit 9bdc6d5

File tree

4 files changed

+170
-3
lines changed

4 files changed

+170
-3
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// @ts-expect-error not a dependency
2+
import { createOtlpHttpExporter, defineConfig } from '@graphql-hive/gateway';
3+
4+
const defaultQuery = `#
5+
# Welcome to the Hive Console GraphQL API.
6+
#
7+
`;
8+
9+
export const gatewayConfig = defineConfig({
10+
transportEntries: {
11+
graphql: {
12+
location: process.env['GRAPHQL_SERVICE_ENDPOINT'],
13+
},
14+
},
15+
supergraph: {
16+
type: 'hive',
17+
endpoint: process.env['SUPERGRAPH_ENDPOINT'],
18+
key: process.env['HIVE_CDN_ACCESS_TOKEN'],
19+
},
20+
graphiql: {
21+
title: 'Hive Console - GraphQL API',
22+
defaultQuery,
23+
},
24+
propagateHeaders: {
25+
fromClientToSubgraphs({ request }) {
26+
return {
27+
'x-request-id': request.headers.get('x-request-id'),
28+
authorization: request.headers.get('authorization'),
29+
};
30+
},
31+
},
32+
disableWebsockets: true,
33+
prometheus: true,
34+
openTelemetry: process.env['OPENTELEMETRY_COLLECTOR_ENDPOINT']
35+
? {
36+
serviceName: 'public-graphql-api-gateway',
37+
exporters: [
38+
createOtlpHttpExporter({
39+
url: process.env['OPENTELEMETRY_COLLECTOR_ENDPOINT'],
40+
}),
41+
],
42+
}
43+
: false,
44+
demandControl: {
45+
maxCost: 1000,
46+
includeExtensionMetadata: true,
47+
},
48+
maxTokens: 1_000,
49+
maxDepth: 20,
50+
});

deployment/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { deployObservability } from './services/observability';
1717
import { deploySchemaPolicy } from './services/policy';
1818
import { deployPostgres } from './services/postgres';
1919
import { deployProxy } from './services/proxy';
20+
import { deployPublicGraphQLAPIGateway } from './services/public-graphql-api-gateway';
2021
import { deployRedis } from './services/redis';
2122
import { deployS3, deployS3AuditLog, deployS3Mirror } from './services/s3';
2223
import { deploySchema } from './services/schema';
@@ -285,12 +286,20 @@ const app = deployApp({
285286
sentry,
286287
});
287288

289+
const publicGraphQLAPIGateway = deployPublicGraphQLAPIGateway({
290+
environment,
291+
graphql,
292+
docker,
293+
observability,
294+
});
295+
288296
const proxy = deployProxy({
289297
observability,
290298
app,
291299
graphql,
292300
usage,
293301
environment,
302+
publicGraphQLAPIGateway,
294303
});
295304

296305
deployCloudFlareSecurityTransform({

deployment/services/proxy.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { App } from './app';
55
import { Environment } from './environment';
66
import { GraphQL } from './graphql';
77
import { Observability } from './observability';
8+
import { type PublicGraphQLAPIGateway } from './public-graphql-api-gateway';
89
import { Usage } from './usage';
910

1011
export function deployProxy({
@@ -13,12 +14,14 @@ export function deployProxy({
1314
usage,
1415
environment,
1516
observability,
17+
publicGraphQLAPIGateway,
1618
}: {
1719
observability: Observability;
1820
environment: Environment;
1921
graphql: GraphQL;
2022
app: App;
2123
usage: Usage;
24+
publicGraphQLAPIGateway: PublicGraphQLAPIGateway;
2225
}) {
2326
const { tlsIssueName } = new CertManager().deployCertManagerAndIssuer();
2427
const commonConfig = new pulumi.Config('common');
@@ -103,10 +106,10 @@ export function deployProxy({
103106
])
104107
.registerService({ record: environment.apiDns }, [
105108
{
106-
name: 'graphql-api',
109+
name: 'public-graphql-api',
107110
path: '/graphql',
108-
customRewrite: '/graphql-public',
109-
service: graphql.service,
111+
customRewrite: '/graphql',
112+
service: publicGraphQLAPIGateway.service,
110113
requestTimeout: '60s',
111114
retriable: true,
112115
},
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
import * as kx from '@pulumi/kubernetesx';
4+
import * as pulumi from '@pulumi/pulumi';
5+
import { serviceLocalEndpoint } from '../utils/local-endpoint';
6+
import { ServiceSecret } from '../utils/secrets';
7+
import { ServiceDeployment } from '../utils/service-deployment';
8+
import { type Docker } from './docker';
9+
import { type Environment } from './environment';
10+
import { type GraphQL } from './graphql';
11+
import { type Observability } from './observability';
12+
13+
/**
14+
* Hive Gateway Docker Image Version
15+
* Bump this to update the used gateway version.
16+
*/
17+
const dockerImage = 'ghcr.io/graphql-hive/gateway:1.13.6';
18+
19+
const gatewayConfigDirectory = path.resolve(
20+
__dirname,
21+
'..',
22+
'config',
23+
'public-graphql-api-gateway',
24+
);
25+
const gatewayConfigPath = path.join(gatewayConfigDirectory, 'gateway.config.ts');
26+
// On global scope to fail early in case of a read error
27+
const gwConfigFile = fs.readFileSync(gatewayConfigPath, 'utf-8');
28+
29+
export function deployPublicGraphQLAPIGateway(args: {
30+
environment: Environment;
31+
graphql: GraphQL;
32+
docker: Docker;
33+
observability: Observability;
34+
}) {
35+
const apiConfig = new pulumi.Config('api');
36+
37+
// Note: The persisted documents cdn endpoint can also be used for reading the contract schema
38+
const cdnEndpoint =
39+
apiConfig.requireObject<Record<string, string>>('env')['HIVE_PERSISTED_DOCUMENTS_CDN_ENDPOINT'];
40+
41+
if (!cdnEndpoint) {
42+
throw new Error("Missing cdn endpoint variable 'HIVE_PERSISTED_DOCUMENTS_CDN_ENDPOINT'.");
43+
}
44+
45+
const supergraphEndpoint = cdnEndpoint + '/contracts/public';
46+
47+
// Note: The persisted documents access key is also valid for reading the supergraph
48+
const publicGraphQLAPISecret = new ServiceSecret('public-graphql-api-secret', {
49+
cdnAccessKeyId: apiConfig.requireSecret('hivePersistedDocumentsCdnAccessKeyId'),
50+
});
51+
52+
const configMap = new kx.ConfigMap('public-graphql-api-gateway-config', {
53+
data: {
54+
'gateway.config.ts': gwConfigFile,
55+
},
56+
});
57+
58+
return new ServiceDeployment(
59+
'public-graphql-api-gateway',
60+
{
61+
imagePullSecret: args.docker.secret,
62+
image: dockerImage,
63+
replicas: args.environment.isProduction ? 3 : 1,
64+
availabilityOnEveryNode: true,
65+
env: {
66+
GRAPHQL_SERVICE_ENDPOINT: serviceLocalEndpoint(args.graphql.service).apply(
67+
value => `${value}/graphql-public`,
68+
),
69+
SUPERGRAPH_ENDPOINT: supergraphEndpoint,
70+
OPENTELEMETRY_COLLECTOR_ENDPOINT: args.observability.tracingEndpoint ?? '',
71+
},
72+
port: 4000,
73+
args: ['-c', '/config/gateway.config.ts', 'supergraph'],
74+
volumes: [
75+
{
76+
name: 'gateway-config',
77+
configMap: {
78+
name: configMap.metadata.name,
79+
},
80+
},
81+
],
82+
volumeMounts: [
83+
{
84+
mountPath: '/config/',
85+
name: 'gateway-config',
86+
readOnly: true,
87+
},
88+
],
89+
readinessProbe: '/readiness',
90+
livenessProbe: '/healthcheck',
91+
startupProbe: {
92+
endpoint: '/healthcheck',
93+
initialDelaySeconds: 60,
94+
failureThreshold: 10,
95+
periodSeconds: 15,
96+
timeoutSeconds: 15,
97+
},
98+
},
99+
[args.graphql.deployment, args.graphql.service],
100+
)
101+
.withSecret('HIVE_CDN_ACCESS_TOKEN', publicGraphQLAPISecret, 'cdnAccessKeyId')
102+
.deploy();
103+
}
104+
105+
export type PublicGraphQLAPIGateway = ReturnType<typeof deployPublicGraphQLAPIGateway>;

0 commit comments

Comments
 (0)