From 40448c64ec8c1241d2c85f51cbb516f49c6fcd51 Mon Sep 17 00:00:00 2001 From: Daniel Chambers Date: Thu, 8 Aug 2024 23:06:25 +1000 Subject: [PATCH] Stop authenticating healthchecks and automatically handle the NDC version in capabilities (#36) * Stop applying authorization to the health readiness check * Return the correct NDC version in the capabilities automatically * Bump version to 6.0.0 * Update changelog --- CHANGELOG.md | 6 ++++++ package-lock.json | 4 ++-- package.json | 2 +- src/connector.ts | 19 ++++++++++++------- src/schema/index.ts | 4 +++- src/schema/version.generated.ts | 1 + src/server.ts | 19 ++++++++++++++----- typegen/regenerate-schema.sh | 2 +- typegen/src/main.rs | 32 +++++++++++++++++++++++--------- 9 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 src/schema/version.generated.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 04e848e..6c72a0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # NDC TypeScript SDK Changelog +## [6.0.0] - 2024-08-08 +Breaking changes ([#36](https://github.com/hasura/ndc-sdk-typescript/pull/36)): +- `Connector.healthCheck` has been removed and replaced with `Connector.getHealthReadiness`, which only returns whether the connector is able to accept requests, not whether any underlying connections to data sources actually work. +- The `/health` endpoint is now unauthorized, allowing healthchecks to be performed without authorization. +- `Connector.getCapabilities` now returns `Capabilities` instead of `CapabilitiesResponse`. The SDK will now take care of adding the correct NDC version to the `Capabilities` on behalf of the connector. + ## [5.2.0] - 2024-07-30 - The connector now listens on both ipv4 and ipv6 interfaces by default. This can be configured by using the `HASURA_CONNECTOR_HOST` environment variable, which sets the host the web server listens on. ([#34](https://github.com/hasura/ndc-sdk-typescript/pull/34)) - Updated to support [v0.1.5 of the NDC Spec](https://hasura.github.io/ndc-spec/specification/changelog.html#015) ([#35](https://github.com/hasura/ndc-sdk-typescript/pull/35)) diff --git a/package-lock.json b/package-lock.json index 763e6a8..4fef3dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@hasura/ndc-sdk-typescript", - "version": "5.2.0", + "version": "6.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@hasura/ndc-sdk-typescript", - "version": "5.2.0", + "version": "6.0.0", "license": "Apache-2.0", "dependencies": { "@json-schema-tools/meta-schema": "^1.7.0", diff --git a/package.json b/package.json index 864e6f6..41cd701 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hasura/ndc-sdk-typescript", - "version": "5.2.0", + "version": "6.0.0", "description": "", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/src/connector.ts b/src/connector.ts index e7bd4bc..a011da5 100644 --- a/src/connector.ts +++ b/src/connector.ts @@ -1,6 +1,6 @@ import { Registry } from "prom-client"; import { - CapabilitiesResponse, + Capabilities, QueryRequest, QueryResponse, SchemaResponse, @@ -35,6 +35,7 @@ export interface Connector { configuration: Configuration, metrics: Registry ): Promise; + /** * * Update any metrics from the state @@ -48,17 +49,21 @@ export interface Connector { * @param state */ fetchMetrics(configuration: Configuration, state: State): Promise; + /** * Check the health of the connector. * - * For example, this function should check that the connector - * is able to reach its data source over the network. + * This should simply verify that the connector is ready to start accepting + * requests. It should not verify that external data sources are available. + * + * This function is optional to implement; if left unimplemented, a default + * implementation will be used that returns healthy once the connector + * webserver is running. * - * Should throw if the check fails, else resolve * @param configuration * @param state */ - healthCheck(configuration: Configuration, state: State): Promise; + getHealthReadiness?(configuration: Configuration, state: State): Promise; /** * Get the connector's capabilities. @@ -66,10 +71,10 @@ export interface Connector { * This function implements the [capabilities endpoint](https://hasura.github.io/ndc-spec/specification/capabilities.html) * from the NDC specification. * - * This function should be syncronous + * This function should be synchronous * @param configuration */ - getCapabilities(configuration: Configuration): CapabilitiesResponse; + getCapabilities(configuration: Configuration): Capabilities; /** * Get the connector's schema. diff --git a/src/schema/index.ts b/src/schema/index.ts index 84983a1..8abd543 100644 --- a/src/schema/index.ts +++ b/src/schema/index.ts @@ -1,5 +1,6 @@ import { JSONSchemaObject } from "@json-schema-tools/meta-schema"; import schema from "./schema.generated.json"; +import { VERSION } from "./version.generated"; function schemaForType(type_name: string): JSONSchemaObject { return { @@ -29,5 +30,6 @@ export { MutationRequestSchema, MutationResponseSchema, ErrorResponseSchema, - ValidateResponseSchema + ValidateResponseSchema, + VERSION, }; diff --git a/src/schema/version.generated.ts b/src/schema/version.generated.ts new file mode 100644 index 0000000..a38e38f --- /dev/null +++ b/src/schema/version.generated.ts @@ -0,0 +1 @@ +export const VERSION = "0.1.5"; diff --git a/src/server.ts b/src/server.ts index 4a0e628..516e79b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,7 +1,5 @@ import Fastify, { FastifyRequest } from "fastify"; import opentelemetry, { - Attributes, - Span, SpanStatusCode, } from "@opentelemetry/api"; @@ -23,6 +21,7 @@ import { MutationResponse, MutationRequest, QueryRequest, + VERSION } from "./schema"; import { Options as AjvOptions } from "ajv"; @@ -98,6 +97,11 @@ export async function startServer( ); server.addHook("preHandler", (request, reply, done) => { + // Don't apply authorization to the healthcheck endpoint + if (request.routeOptions.method === "GET" && request.routeOptions.url === "/health") { + return done(); + } + const expectedAuthHeader = options.serviceTokenSecret === undefined ? undefined @@ -131,13 +135,18 @@ export async function startServer( return withActiveSpan( tracer, "getCapabilities", - () => connector.getCapabilities(configuration) + () => ({ + version: VERSION, + capabilities: connector.getCapabilities(configuration), + }) ); } ); - server.get("/health", (_request): Promise => { - return connector.healthCheck(configuration, state); + server.get("/health", async (_request): Promise => { + return connector.getHealthReadiness + ? await connector.getHealthReadiness(configuration, state) + : undefined; }); server.get("/metrics", (_request) => { diff --git a/typegen/regenerate-schema.sh b/typegen/regenerate-schema.sh index d5133a8..2954fa7 100755 --- a/typegen/regenerate-schema.sh +++ b/typegen/regenerate-schema.sh @@ -2,7 +2,7 @@ set -eu -o pipefail # Generate JSON schema from Rust types -cargo run -- ./src/schema/schema.generated.json +cargo run -- ./src/schema # Generate TypeScript types from JSON schema json2ts -i ./src/schema/schema.generated.json -o ./src/schema/schema.generated.ts --no-additionalProperties diff --git a/typegen/src/main.rs b/typegen/src/main.rs index e70f1e5..ba04b06 100644 --- a/typegen/src/main.rs +++ b/typegen/src/main.rs @@ -1,10 +1,10 @@ use ndc_models::{ CapabilitiesResponse, ErrorResponse, ExplainResponse, MutationRequest, MutationResponse, - QueryRequest, QueryResponse, SchemaResponse, + QueryRequest, QueryResponse, SchemaResponse, VERSION, }; use schemars::{schema_for, JsonSchema}; use serde_derive::{Deserialize, Serialize}; -use std::{error::Error, fs, env}; +use std::{env, error::Error, fs, path::PathBuf}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] #[schemars(title = "SchemaRoot")] @@ -30,20 +30,34 @@ struct ValidateResponse { fn main() -> Result<(), Box> { let args = env::args().collect::>(); - let path = if args.len() >= 2 { - Ok(&args[1]) + let (schema_json_path, version_path) = if args.len() >= 2 { + let schema_json_path = [args[1].as_str(), "schema.generated.json"] + .iter() + .collect::(); + let version_path = [args[1].as_str(), "version.generated.ts"] + .iter() + .collect::(); + Ok((schema_json_path, version_path)) } else { - Err("Schema file path not passed on command line") + Err("Schema directory not passed on command line") }?; - print!("Generating Schema to {path}..."); + print!( + "Generating schema JSON to {}...", + schema_json_path.to_str().unwrap() + ); let schema_json = serde_json::to_string_pretty(&schema_for!(SchemaRoot))?; + fs::write(schema_json_path, schema_json + "\n")?; + println!("done!"); + print!( + "Generating schema version to {}...", + version_path.to_str().unwrap() + ); fs::write( - path, - schema_json + "\n", + version_path, + format!("export const VERSION = \"{VERSION}\";\n"), )?; - println!("done!"); Ok(())