diff --git a/apps/server/src/routes/rpc/handlers/monitor/converters/index.ts b/apps/server/src/routes/rpc/handlers/monitor/converters/index.ts index 1073bb7aa..43b88f1a5 100644 --- a/apps/server/src/routes/rpc/handlers/monitor/converters/index.ts +++ b/apps/server/src/routes/rpc/handlers/monitor/converters/index.ts @@ -61,4 +61,5 @@ export { regionsToStrings, regionsToDbString, validateRegions, + getDeprecatedRegions, } from "./regions"; diff --git a/apps/server/src/routes/rpc/handlers/monitor/converters/regions.ts b/apps/server/src/routes/rpc/handlers/monitor/converters/regions.ts index cc22eb041..ac39f8cc2 100644 --- a/apps/server/src/routes/rpc/handlers/monitor/converters/regions.ts +++ b/apps/server/src/routes/rpc/handlers/monitor/converters/regions.ts @@ -1,5 +1,5 @@ import { Region } from "@openstatus/proto/monitor/v1"; -import { AVAILABLE_REGIONS } from "@openstatus/regions"; +import { AVAILABLE_REGIONS, regionDict } from "@openstatus/regions"; /** * Mapping from database region strings to proto Region enum. @@ -122,3 +122,12 @@ export function validateRegions(regions: string[]): string[] { (r) => !availableSet.has(r as (typeof AVAILABLE_REGIONS)[number]), ); } + +/** + * Return the regions that exist but are deprecated (no longer selectable). + */ +export function getDeprecatedRegions(regions: string[]): string[] { + return regions.filter( + (r) => regionDict[r as keyof typeof regionDict]?.deprecated === true, + ); +} diff --git a/apps/server/src/routes/rpc/handlers/monitor/validators.test.ts b/apps/server/src/routes/rpc/handlers/monitor/validators.test.ts index ae876638e..d52bf70e2 100644 --- a/apps/server/src/routes/rpc/handlers/monitor/validators.test.ts +++ b/apps/server/src/routes/rpc/handlers/monitor/validators.test.ts @@ -151,6 +151,17 @@ describe("validateCommonMonitorFields", () => { ).not.toThrow(); }); + test("rejects deprecated regions", () => { + expect(() => + validateCommonMonitorFields({ regions: [Region.FLY_BOM] }), + ).toThrow(/Deprecated regions: bom/); + expect(() => + validateCommonMonitorFields({ + regions: [Region.FLY_AMS, Region.FLY_BOM], + }), + ).toThrow(/Deprecated regions: bom/); + }); + test("drops an unspecified region instead of rejecting it", () => { // Documents current behaviour, which is weaker than it looks: // `regionsToStrings` filters unmapped enum values to "", so the diff --git a/apps/server/src/routes/rpc/handlers/monitor/validators.ts b/apps/server/src/routes/rpc/handlers/monitor/validators.ts index 11c90fc6c..21902c8a6 100644 --- a/apps/server/src/routes/rpc/handlers/monitor/validators.ts +++ b/apps/server/src/routes/rpc/handlers/monitor/validators.ts @@ -6,6 +6,7 @@ import type { UpdateMonitorConfigInput } from "@openstatus/services/monitor"; import { MONITOR_DEFAULTS, + getDeprecatedRegions, protoOpenTelemetryToService, periodicityToString, regionsToStrings, @@ -48,6 +49,13 @@ export function toValidMethod(value: string | undefined): MonitorMethod { export function validateCommonMonitorFields(mon: { regions?: Region[] }): void { if (mon.regions && mon.regions.length > 0) { const regionStrings = regionsToStrings(mon.regions); + const deprecatedRegions = getDeprecatedRegions(regionStrings); + if (deprecatedRegions.length > 0) { + throw new ConnectError( + `Deprecated regions: ${deprecatedRegions.join(", ")}. These regions are no longer available, please choose another region.`, + Code.InvalidArgument, + ); + } const invalidRegions = validateRegions(regionStrings); if (invalidRegions.length > 0) { throw new ConnectError( diff --git a/apps/server/static/openapi.yaml b/apps/server/static/openapi.yaml index ce106615c..6ae80571d 100644 --- a/apps/server/static/openapi.yaml +++ b/apps/server/static/openapi.yaml @@ -1785,7 +1785,9 @@ components: - REGION_RAILWAY_US_EAST4 - REGION_RAILWAY_EUROPE_WEST4 - REGION_RAILWAY_ASIA_SOUTHEAST1 - description: Geographic regions where monitors can run checks from. + description: |- + Geographic regions where monitors can run checks from. + REGION_FLY_BOM is deprecated and rejected on create/update; use REGION_FLY_SIN. openstatus.monitor.v1.RegionStatus: type: object properties: diff --git a/infra/openstatus.yaml b/infra/openstatus.yaml index 58950f1b8..4e76fdc76 100644 --- a/infra/openstatus.yaml +++ b/infra/openstatus.yaml @@ -14,7 +14,6 @@ - gru - syd - cdg - - bom - sin - fra - lhr @@ -53,7 +52,6 @@ - jnb - gru - syd - - bom - sin - fra - lhr @@ -94,7 +92,6 @@ - jnb - gru - syd - - bom - sin - fra - lhr @@ -165,7 +162,6 @@ - jnb - gru - syd - - bom - sin - fra - lhr diff --git a/packages/db/script/region-migration.test.ts b/packages/db/script/region-migration.test.ts index f52fd0eb0..299810719 100644 --- a/packages/db/script/region-migration.test.ts +++ b/packages/db/script/region-migration.test.ts @@ -51,6 +51,26 @@ describe("updateRegion", () => { }); }); + describe("bom deprecation", () => { + test("should replace bom with sin", () => { + const regions: z.infer[] = ["ams", "bom"]; + updateRegion("bom", "sin", regions); + + expect(regions).toEqual(["ams", "sin"]); + }); + + test("should drop bom when sin is already selected", () => { + const regions: z.infer[] = [ + "bom", + "sin", + "fra", + ]; + updateRegion("bom", "sin", regions); + + expect(regions).toEqual(["sin", "fra"]); + }); + }); + describe("when old region does not exist in array", () => { test("should not modify the array when old region is not found", () => { const originalRegions = [...regions]; diff --git a/packages/db/script/region-migration.ts b/packages/db/script/region-migration.ts index 3bf7d5adf..0e96cfbd7 100644 --- a/packages/db/script/region-migration.ts +++ b/packages/db/script/region-migration.ts @@ -4,42 +4,23 @@ import { db, eq, schema } from "../src"; import { selectMonitorSchema } from "../src/schema"; import type { monitorRegionSchema } from "../src/schema/constants"; -const rawMonitors = await db.select().from(schema.monitor); +// Only run against the DB when executed directly, not when imported by tests. +if (import.meta.main) { + const rawMonitors = await db.select().from(schema.monitor); -const monitors = z.array(selectMonitorSchema).parse(rawMonitors); -for (const monitor of monitors) { - const regions = monitor.regions.slice(); - // Asia Pacific - updateRegion("hkg", "sin", regions); - - // North America - updateRegion("atl", "dfw", regions); - updateRegion("mia", "dfw", regions); - updateRegion("gdl", "dfw", regions); - updateRegion("qro", "dfw", regions); - updateRegion("bos", "ewr", regions); - updateRegion("phx", "lax", regions); - updateRegion("sea", "sjc", regions); - updateRegion("yul", "yyz", regions); - updateRegion("den", "dfw", regions); - - // Europe - updateRegion("waw", "ams", regions); - updateRegion("mad", "cdg", regions); - updateRegion("otp", "fra", regions); - - // South America - updateRegion("bog", "gru", regions); - updateRegion("gig", "gru", regions); - updateRegion("scl", "gru", regions); - updateRegion("eze", "gru", regions); - const newRegions = regions.join(","); - // console.log("new regions:",newRegions) - await db - .update(schema.monitor) - .set({ regions: newRegions }) - .where(eq(schema.monitor.id, monitor.id)) - .execute(); + const monitors = z.array(selectMonitorSchema).parse(rawMonitors); + for (const monitor of monitors) { + const regions = monitor.regions.slice(); + // Asia Pacific + updateRegion("bom", "sin", regions); + const newRegions = regions.join(","); + if (newRegions === monitor.regions.join(",")) continue; + await db + .update(schema.monitor) + .set({ regions: newRegions }) + .where(eq(schema.monitor.id, monitor.id)) + .execute(); + } } export function updateRegion( diff --git a/packages/proto/api/openstatus/monitor/v1/monitor.proto b/packages/proto/api/openstatus/monitor/v1/monitor.proto index 31051a48e..f9047c6a7 100644 --- a/packages/proto/api/openstatus/monitor/v1/monitor.proto +++ b/packages/proto/api/openstatus/monitor/v1/monitor.proto @@ -28,12 +28,14 @@ enum Periodicity { } // Geographic regions where monitors can run checks from. +// REGION_FLY_BOM is deprecated and rejected on create/update; use REGION_FLY_SIN. enum Region { REGION_UNSPECIFIED = 0; // Fly.io regions REGION_FLY_AMS = 1; // Amsterdam, Netherlands REGION_FLY_ARN = 2; // Stockholm, Sweden - REGION_FLY_BOM = 3; // Mumbai, India + // Deprecated: Mumbai is no longer accepted on create/update; use REGION_FLY_SIN. + REGION_FLY_BOM = 3 [deprecated = true]; // Mumbai, India REGION_FLY_CDG = 4; // Paris, France REGION_FLY_DFW = 5; // Dallas, USA REGION_FLY_EWR = 6; // Newark, USA diff --git a/packages/proto/gen/openapi.yaml b/packages/proto/gen/openapi.yaml index ce106615c..6ae80571d 100644 --- a/packages/proto/gen/openapi.yaml +++ b/packages/proto/gen/openapi.yaml @@ -1785,7 +1785,9 @@ components: - REGION_RAILWAY_US_EAST4 - REGION_RAILWAY_EUROPE_WEST4 - REGION_RAILWAY_ASIA_SOUTHEAST1 - description: Geographic regions where monitors can run checks from. + description: |- + Geographic regions where monitors can run checks from. + REGION_FLY_BOM is deprecated and rejected on create/update; use REGION_FLY_SIN. openstatus.monitor.v1.RegionStatus: type: object properties: diff --git a/packages/proto/gen/ts/openstatus/monitor/v1/monitor_pb.ts b/packages/proto/gen/ts/openstatus/monitor/v1/monitor_pb.ts index 86512c460..add2de669 100644 --- a/packages/proto/gen/ts/openstatus/monitor/v1/monitor_pb.ts +++ b/packages/proto/gen/ts/openstatus/monitor/v1/monitor_pb.ts @@ -9,7 +9,7 @@ import { enumDesc, fileDesc } from "@bufbuild/protobuf/codegenv2"; * Describes the file openstatus/monitor/v1/monitor.proto. */ export const file_openstatus_monitor_v1_monitor: GenFile = /*@__PURE__*/ - fileDesc("CiNvcGVuc3RhdHVzL21vbml0b3IvdjEvbW9uaXRvci5wcm90bxIVb3BlbnN0YXR1cy5tb25pdG9yLnYxKoEBCg1Nb25pdG9yU3RhdHVzEh4KGk1PTklUT1JfU1RBVFVTX1VOU1BFQ0lGSUVEEAASGQoVTU9OSVRPUl9TVEFUVVNfQUNUSVZFEAESGwoXTU9OSVRPUl9TVEFUVVNfREVHUkFERUQQAhIYChRNT05JVE9SX1NUQVRVU19FUlJPUhADKqUBCgtQZXJpb2RpY2l0eRIbChdQRVJJT0RJQ0lUWV9VTlNQRUNJRklFRBAAEhMKD1BFUklPRElDSVRZXzMwUxABEhIKDlBFUklPRElDSVRZXzFNEAISEgoOUEVSSU9ESUNJVFlfNU0QAxITCg9QRVJJT0RJQ0lUWV8xME0QBBITCg9QRVJJT0RJQ0lUWV8zME0QBRISCg5QRVJJT0RJQ0lUWV8xSBAGKosFCgZSZWdpb24SFgoSUkVHSU9OX1VOU1BFQ0lGSUVEEAASEgoOUkVHSU9OX0ZMWV9BTVMQARISCg5SRUdJT05fRkxZX0FSThACEhIKDlJFR0lPTl9GTFlfQk9NEAMSEgoOUkVHSU9OX0ZMWV9DREcQBBISCg5SRUdJT05fRkxZX0RGVxAFEhIKDlJFR0lPTl9GTFlfRVdSEAYSEgoOUkVHSU9OX0ZMWV9GUkEQBxISCg5SRUdJT05fRkxZX0dSVRAIEhIKDlJFR0lPTl9GTFlfSUFEEAkSEgoOUkVHSU9OX0ZMWV9KTkIQChISCg5SRUdJT05fRkxZX0xBWBALEhIKDlJFR0lPTl9GTFlfTEhSEAwSEgoOUkVHSU9OX0ZMWV9OUlQQDRISCg5SRUdJT05fRkxZX09SRBAOEhIKDlJFR0lPTl9GTFlfU0pDEA8SEgoOUkVHSU9OX0ZMWV9TSU4QEBISCg5SRUdJT05fRkxZX1NZRBAREhIKDlJFR0lPTl9GTFlfWVlaEBISFAoQUkVHSU9OX0tPWUVCX0ZSQRATEhQKEFJFR0lPTl9LT1lFQl9QQVIQFBIUChBSRUdJT05fS09ZRUJfU0ZPEBUSFAoQUkVHSU9OX0tPWUVCX1NJThAWEhQKEFJFR0lPTl9LT1lFQl9UWU8QFxIUChBSRUdJT05fS09ZRUJfV0FTEBgSGwoXUkVHSU9OX1JBSUxXQVlfVVNfV0VTVDIQGRIbChdSRUdJT05fUkFJTFdBWV9VU19FQVNUNBAaEh8KG1JFR0lPTl9SQUlMV0FZX0VVUk9QRV9XRVNUNBAbEiIKHlJFR0lPTl9SQUlMV0FZX0FTSUFfU09VVEhFQVNUMRAcQlNaUWdpdGh1Yi5jb20vb3BlbnN0YXR1c2hxL29wZW5zdGF0dXMvcGFja2FnZXMvcHJvdG8vb3BlbnN0YXR1cy9tb25pdG9yL3YxO21vbml0b3J2MWIGcHJvdG8z"); + fileDesc("CiNvcGVuc3RhdHVzL21vbml0b3IvdjEvbW9uaXRvci5wcm90bxIVb3BlbnN0YXR1cy5tb25pdG9yLnYxKoEBCg1Nb25pdG9yU3RhdHVzEh4KGk1PTklUT1JfU1RBVFVTX1VOU1BFQ0lGSUVEEAASGQoVTU9OSVRPUl9TVEFUVVNfQUNUSVZFEAESGwoXTU9OSVRPUl9TVEFUVVNfREVHUkFERUQQAhIYChRNT05JVE9SX1NUQVRVU19FUlJPUhADKqUBCgtQZXJpb2RpY2l0eRIbChdQRVJJT0RJQ0lUWV9VTlNQRUNJRklFRBAAEhMKD1BFUklPRElDSVRZXzMwUxABEhIKDlBFUklPRElDSVRZXzFNEAISEgoOUEVSSU9ESUNJVFlfNU0QAxITCg9QRVJJT0RJQ0lUWV8xME0QBBITCg9QRVJJT0RJQ0lUWV8zME0QBRISCg5QRVJJT0RJQ0lUWV8xSBAGKo8FCgZSZWdpb24SFgoSUkVHSU9OX1VOU1BFQ0lGSUVEEAASEgoOUkVHSU9OX0ZMWV9BTVMQARISCg5SRUdJT05fRkxZX0FSThACEhYKDlJFR0lPTl9GTFlfQk9NEAMaAggBEhIKDlJFR0lPTl9GTFlfQ0RHEAQSEgoOUkVHSU9OX0ZMWV9ERlcQBRISCg5SRUdJT05fRkxZX0VXUhAGEhIKDlJFR0lPTl9GTFlfRlJBEAcSEgoOUkVHSU9OX0ZMWV9HUlUQCBISCg5SRUdJT05fRkxZX0lBRBAJEhIKDlJFR0lPTl9GTFlfSk5CEAoSEgoOUkVHSU9OX0ZMWV9MQVgQCxISCg5SRUdJT05fRkxZX0xIUhAMEhIKDlJFR0lPTl9GTFlfTlJUEA0SEgoOUkVHSU9OX0ZMWV9PUkQQDhISCg5SRUdJT05fRkxZX1NKQxAPEhIKDlJFR0lPTl9GTFlfU0lOEBASEgoOUkVHSU9OX0ZMWV9TWUQQERISCg5SRUdJT05fRkxZX1lZWhASEhQKEFJFR0lPTl9LT1lFQl9GUkEQExIUChBSRUdJT05fS09ZRUJfUEFSEBQSFAoQUkVHSU9OX0tPWUVCX1NGTxAVEhQKEFJFR0lPTl9LT1lFQl9TSU4QFhIUChBSRUdJT05fS09ZRUJfVFlPEBcSFAoQUkVHSU9OX0tPWUVCX1dBUxAYEhsKF1JFR0lPTl9SQUlMV0FZX1VTX1dFU1QyEBkSGwoXUkVHSU9OX1JBSUxXQVlfVVNfRUFTVDQQGhIfChtSRUdJT05fUkFJTFdBWV9FVVJPUEVfV0VTVDQQGxIiCh5SRUdJT05fUkFJTFdBWV9BU0lBX1NPVVRIRUFTVDEQHEJTWlFnaXRodWIuY29tL29wZW5zdGF0dXNocS9vcGVuc3RhdHVzL3BhY2thZ2VzL3Byb3RvL29wZW5zdGF0dXMvbW9uaXRvci92MTttb25pdG9ydjFiBnByb3RvMw"); /** * MonitorStatus represents the operational status of a monitor. @@ -102,6 +102,7 @@ export const PeriodicitySchema: GenEnum = /*@__PURE__*/ /** * Geographic regions where monitors can run checks from. + * REGION_FLY_BOM is deprecated and rejected on create/update; use REGION_FLY_SIN. * * @generated from enum openstatus.monitor.v1.Region */ @@ -128,9 +129,12 @@ export enum Region { FLY_ARN = 2, /** + * Deprecated: Mumbai is no longer accepted on create/update; use REGION_FLY_SIN. + * * Mumbai, India * - * @generated from enum value: REGION_FLY_BOM = 3; + * @generated from enum value: REGION_FLY_BOM = 3 [deprecated = true]; + * @deprecated */ FLY_BOM = 3, diff --git a/packages/regions/index.ts b/packages/regions/index.ts index 273ed8057..27e510649 100644 --- a/packages/regions/index.ts +++ b/packages/regions/index.ts @@ -135,7 +135,7 @@ export const regionDict: Record = { location: "Mumbai, India", flag: "🇮🇳", continent: "Asia", - deprecated: false, + deprecated: true, provider: "fly", }, bos: {