From 65c4a8b7e011d1f042097747f935ca7a29c2f8ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20Jedlicska?= Date: Fri, 10 Jan 2025 12:02:43 +0100 Subject: [PATCH 1/4] feat(server): log subscription started messages with info --- packages/server/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/app.ts b/packages/server/app.ts index cd9fb7d80d..431cc08563 100644 --- a/packages/server/app.ts +++ b/packages/server/app.ts @@ -290,7 +290,7 @@ export function buildApolloSubscriptionServer( const ctx = baseParams.context as GraphQLContext const logger = ctx.log || subscriptionLogger - logger.debug( + logger.info( { graphql_operation_name: baseParams.operationName, userId: baseParams.context.userId, From a9b73fba145810dfb0d6f760e26da7c197f71e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20Jedlicska?= Date: Mon, 13 Jan 2025 10:49:31 +0100 Subject: [PATCH 2/4] feat(server): create projects in a default region --- .../modules/core/graph/resolvers/projects.ts | 31 +++++++++++++++++-- .../modules/multiregion/helpers/index.ts | 4 +++ packages/server/modules/multiregion/index.ts | 7 ++++- .../modules/multiregion/utils/dbSelector.ts | 24 ++++++++++++-- .../modules/workspaces/services/projects.ts | 8 +++-- 5 files changed, 66 insertions(+), 8 deletions(-) diff --git a/packages/server/modules/core/graph/resolvers/projects.ts b/packages/server/modules/core/graph/resolvers/projects.ts index a9185adf56..b5e7d551fb 100644 --- a/packages/server/modules/core/graph/resolvers/projects.ts +++ b/packages/server/modules/core/graph/resolvers/projects.ts @@ -36,6 +36,13 @@ import { insertCommitsFactory, insertStreamCommitsFactory } from '@/modules/core/repositories/commits' +import { storeModelFactory } from '@/modules/core/repositories/models' +import { + deleteProjectFactory, + getProjectFactory, + storeProjectFactory, + storeProjectRoleFactory +} from '@/modules/core/repositories/projects' import { getServerInfoFactory } from '@/modules/core/repositories/server' import { getStreamFactory, @@ -50,6 +57,7 @@ import { getUserStreamsCountFactory } from '@/modules/core/repositories/streams' import { getUserFactory, getUsersFactory } from '@/modules/core/repositories/users' +import { createNewProjectFactory } from '@/modules/core/services/projects' import { getRateLimitResult, isRateLimitBreached @@ -69,7 +77,11 @@ import { } from '@/modules/core/services/streams/management' import { createOnboardingStreamFactory } from '@/modules/core/services/streams/onboarding' import { getOnboardingBaseProjectFactory } from '@/modules/cross-server-sync/services/onboardingProject' -import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector' +import { + getDb, + getProjectDbClient, + getValidDefaultProjectRegionKey +} from '@/modules/multiregion/utils/dbSelector' import { deleteAllResourceInvitesFactory, findUserByTargetFactory, @@ -284,10 +296,23 @@ export = { throw new RateLimitError(rateLimitResult) } - const project = await createStreamReturnRecord({ + const regionKey = await getValidDefaultProjectRegionKey() + const projectDb = await getDb({ regionKey }) + + const createNewProject = createNewProjectFactory({ + storeProject: storeProjectFactory({ db: projectDb }), + getProject: getProjectFactory({ db }), + deleteProject: deleteProjectFactory({ db: projectDb }), + storeModel: storeModelFactory({ db: projectDb }), + // THIS MUST GO TO THE MAIN DB + storeProjectRole: storeProjectRoleFactory({ db }), + projectsEventsEmitter: ProjectsEmitter.emit + }) + + const project = await createNewProject({ ...(args.input || {}), ownerId: context.userId!, - ownerResourceAccessRules: context.resourceAccessRules + regionKey }) return project diff --git a/packages/server/modules/multiregion/helpers/index.ts b/packages/server/modules/multiregion/helpers/index.ts index b04c4ae48e..1fa354a7c8 100644 --- a/packages/server/modules/multiregion/helpers/index.ts +++ b/packages/server/modules/multiregion/helpers/index.ts @@ -5,3 +5,7 @@ export const isMultiRegionEnabled = () => { getFeatureFlags() return !!(FF_WORKSPACES_MODULE_ENABLED && FF_WORKSPACES_MULTI_REGION_ENABLED) } + +export const getDefaultProjectRegionKey = (): string | null => { + return process.env['DEFAULT_PROJECT_REGION_KEY'] ?? null +} diff --git a/packages/server/modules/multiregion/index.ts b/packages/server/modules/multiregion/index.ts index 5575116cbe..5eed2ead3c 100644 --- a/packages/server/modules/multiregion/index.ts +++ b/packages/server/modules/multiregion/index.ts @@ -1,5 +1,8 @@ import { moduleLogger } from '@/logging/logging' -import { initializeRegisteredRegionClients as initDb } from '@/modules/multiregion/utils/dbSelector' +import { + getValidDefaultProjectRegionKey, + initializeRegisteredRegionClients as initDb +} from '@/modules/multiregion/utils/dbSelector' import { isMultiRegionEnabled } from '@/modules/multiregion/helpers' import { SpeckleModule } from '@/modules/shared/helpers/typeHelper' import { @@ -18,6 +21,8 @@ const multiRegion: SpeckleModule = { // Init registered region clients await initDb() + // validate default project region key + await getValidDefaultProjectRegionKey() const isBlobStorageEnabled = isMultiRegionBlobStorageEnabled() if (isBlobStorageEnabled) { diff --git a/packages/server/modules/multiregion/utils/dbSelector.ts b/packages/server/modules/multiregion/utils/dbSelector.ts index 28079219cf..72ce9c2219 100644 --- a/packages/server/modules/multiregion/utils/dbSelector.ts +++ b/packages/server/modules/multiregion/utils/dbSelector.ts @@ -6,7 +6,7 @@ import { } from '@/modules/multiregion/services/projectRegion' import { Knex } from 'knex' import { getRegionFactory } from '@/modules/multiregion/repositories' -import { DatabaseError } from '@/modules/shared/errors' +import { DatabaseError, MisconfiguredEnvironmentError } from '@/modules/shared/errors' import { configureClient } from '@/knexfile' import { InitializeRegion } from '@/modules/multiregion/domain/operations' import { @@ -21,7 +21,10 @@ import { getRegisteredRegionConfigs } from '@/modules/multiregion/utils/regionSelector' import { mapValues } from 'lodash' -import { isMultiRegionEnabled } from '@/modules/multiregion/helpers' +import { + getDefaultProjectRegionKey, + isMultiRegionEnabled +} from '@/modules/multiregion/helpers' let getter: GetProjectDb | undefined = undefined @@ -72,6 +75,23 @@ export const getProjectDbClient: GetProjectDb = async ({ projectId }) => { return await getter({ projectId }) } +// the default region key is a config value, we're caching this globally +let defaultRegionKeyCache: string | null | undefined = undefined + +export const getValidDefaultProjectRegionKey = async (): Promise => { + if (defaultRegionKeyCache !== undefined) return defaultRegionKeyCache + const defaultRegionKey = getDefaultProjectRegionKey() + defaultRegionKeyCache = defaultRegionKey + + if (!defaultRegionKey) return defaultRegionKey + const registeredRegionClients = await getRegisteredRegionClients() + if (!(defaultRegionKey in registeredRegionClients)) + throw new MisconfiguredEnvironmentError( + `There is no region client registered for the default region key ${defaultRegionKey} ` + ) + return defaultRegionKey +} + type RegionClients = Record let registeredRegionClients: RegionClients | undefined = undefined diff --git a/packages/server/modules/workspaces/services/projects.ts b/packages/server/modules/workspaces/services/projects.ts index 886a28c99c..c66bfdc1cc 100644 --- a/packages/server/modules/workspaces/services/projects.ts +++ b/packages/server/modules/workspaces/services/projects.ts @@ -34,7 +34,10 @@ import { } from '@/modules/core/domain/streams/operations' import { ProjectNotFoundError } from '@/modules/core/errors/projects' import { WorkspaceProjectCreateInput } from '@/test/graphql/generated/graphql' -import { getDb } from '@/modules/multiregion/utils/dbSelector' +import { + getDb, + getValidDefaultProjectRegionKey +} from '@/modules/multiregion/utils/dbSelector' import { createNewProjectFactory } from '@/modules/core/services/projects' import { deleteProjectFactory, @@ -265,7 +268,8 @@ export const createWorkspaceProjectFactory = const workspaceDefaultRegion = await deps.getDefaultRegion({ workspaceId: input.workspaceId }) - const regionKey = workspaceDefaultRegion?.key + const regionKey = + workspaceDefaultRegion?.key ?? (await getValidDefaultProjectRegionKey()) const projectDb = await getDb({ regionKey }) const db = mainDb From b94c30219a43203943aba508e130fc9d62779c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20Jedlicska?= Date: Mon, 13 Jan 2025 11:18:57 +0100 Subject: [PATCH 3/4] feat(server): allow project default region config --- utils/helm/speckle-server/templates/_helpers.tpl | 3 +++ utils/helm/speckle-server/values.schema.json | 5 +++++ utils/helm/speckle-server/values.yaml | 2 ++ 3 files changed, 10 insertions(+) diff --git a/utils/helm/speckle-server/templates/_helpers.tpl b/utils/helm/speckle-server/templates/_helpers.tpl index 22158af8fc..7667a7ff82 100644 --- a/utils/helm/speckle-server/templates/_helpers.tpl +++ b/utils/helm/speckle-server/templates/_helpers.tpl @@ -589,6 +589,9 @@ Generate the environment variables for Speckle server and Speckle objects deploy - name: FF_WORKSPACES_MULTI_REGION_ENABLED value: {{ .Values.featureFlags.workspacesMultiRegionEnabled | quote }} +- name: DEFAULT_PROJECT_REGION_KEY + value: {{ .Values.multiRegion.config.defaultProjectRegionKey }} + {{- if .Values.featureFlags.billingIntegrationEnabled }} - name: STRIPE_API_KEY valueFrom: diff --git a/utils/helm/speckle-server/values.schema.json b/utils/helm/speckle-server/values.schema.json index 55f5d69340..b5be6979e8 100644 --- a/utils/helm/speckle-server/values.schema.json +++ b/utils/helm/speckle-server/values.schema.json @@ -563,6 +563,11 @@ "type": "string", "description": "If workspacesMultiRegionEnabled is enabled, the server will be deployed in a multi-region configuration based on the values in a secret. This allows the default secret key and filename to be overridden.", "default": "multi-region-config.json" + }, + "defaultProjectRegionKey": { + "type": "string", + "description": "forces new projects to be created in this region if not indicated otherwise", + "default": "" } } } diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml index 8922b5b002..3b8e5a7331 100644 --- a/utils/helm/speckle-server/values.yaml +++ b/utils/helm/speckle-server/values.yaml @@ -423,6 +423,8 @@ multiRegion: secretName: 'multi-region-config' ## @param multiRegion.config.secretKey If workspacesMultiRegionEnabled is enabled, the server will be deployed in a multi-region configuration based on the values in a secret. This allows the default secret key and filename to be overridden. secretKey: 'multi-region-config.json' + ## @param multiRegion.config.defaultProjectRegionKey forces new projects to be created in this region if not indicated otherwise + defaultProjectRegionKey: '' ## @section Server ## @descriptionStart From 6d49b83aa7169cb19c23cde89fdd6dcdca96a4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20Jedlicska?= Date: Mon, 13 Jan 2025 15:29:05 +0100 Subject: [PATCH 4/4] feat(server): load project region from multi region config --- packages/server/modules/multiregion/helpers/index.ts | 4 ---- packages/server/modules/multiregion/regionConfig.ts | 5 +++++ .../server/modules/multiregion/utils/dbSelector.ts | 11 +++++------ packages/shared/src/environment/multiRegionConfig.ts | 3 ++- utils/helm/speckle-server/templates/_helpers.tpl | 3 --- utils/helm/speckle-server/values.schema.json | 5 ----- utils/helm/speckle-server/values.yaml | 2 -- 7 files changed, 12 insertions(+), 21 deletions(-) diff --git a/packages/server/modules/multiregion/helpers/index.ts b/packages/server/modules/multiregion/helpers/index.ts index 1fa354a7c8..b04c4ae48e 100644 --- a/packages/server/modules/multiregion/helpers/index.ts +++ b/packages/server/modules/multiregion/helpers/index.ts @@ -5,7 +5,3 @@ export const isMultiRegionEnabled = () => { getFeatureFlags() return !!(FF_WORKSPACES_MODULE_ENABLED && FF_WORKSPACES_MULTI_REGION_ENABLED) } - -export const getDefaultProjectRegionKey = (): string | null => { - return process.env['DEFAULT_PROJECT_REGION_KEY'] ?? null -} diff --git a/packages/server/modules/multiregion/regionConfig.ts b/packages/server/modules/multiregion/regionConfig.ts index f162c26e33..6999150167 100644 --- a/packages/server/modules/multiregion/regionConfig.ts +++ b/packages/server/modules/multiregion/regionConfig.ts @@ -58,3 +58,8 @@ export const getMainRegionConfig = async (): Promise => { export const getAvailableRegionConfig: GetAvailableRegionConfig = async () => { return (await getMultiRegionConfig()).regions } + +export const getDefaultProjectRegionKey = async (): Promise => { + const defaultRegionKey = (await getMultiRegionConfig()).defaultProjectRegionKey + return defaultRegionKey ?? null +} diff --git a/packages/server/modules/multiregion/utils/dbSelector.ts b/packages/server/modules/multiregion/utils/dbSelector.ts index 72ce9c2219..ae6b210e8f 100644 --- a/packages/server/modules/multiregion/utils/dbSelector.ts +++ b/packages/server/modules/multiregion/utils/dbSelector.ts @@ -11,6 +11,7 @@ import { configureClient } from '@/knexfile' import { InitializeRegion } from '@/modules/multiregion/domain/operations' import { getAvailableRegionConfig, + getDefaultProjectRegionKey, getMainRegionConfig } from '@/modules/multiregion/regionConfig' import { ensureError, MaybeNullOrUndefined } from '@speckle/shared' @@ -21,10 +22,7 @@ import { getRegisteredRegionConfigs } from '@/modules/multiregion/utils/regionSelector' import { mapValues } from 'lodash' -import { - getDefaultProjectRegionKey, - isMultiRegionEnabled -} from '@/modules/multiregion/helpers' +import { isMultiRegionEnabled } from '@/modules/multiregion/helpers' let getter: GetProjectDb | undefined = undefined @@ -80,8 +78,7 @@ let defaultRegionKeyCache: string | null | undefined = undefined export const getValidDefaultProjectRegionKey = async (): Promise => { if (defaultRegionKeyCache !== undefined) return defaultRegionKeyCache - const defaultRegionKey = getDefaultProjectRegionKey() - defaultRegionKeyCache = defaultRegionKey + const defaultRegionKey = await getDefaultProjectRegionKey() if (!defaultRegionKey) return defaultRegionKey const registeredRegionClients = await getRegisteredRegionClients() @@ -89,6 +86,8 @@ export const getValidDefaultProjectRegionKey = async (): Promise throw new MisconfiguredEnvironmentError( `There is no region client registered for the default region key ${defaultRegionKey} ` ) + + defaultRegionKeyCache = defaultRegionKey return defaultRegionKey } diff --git a/packages/shared/src/environment/multiRegionConfig.ts b/packages/shared/src/environment/multiRegionConfig.ts index ccbf32d090..90c1999f18 100644 --- a/packages/shared/src/environment/multiRegionConfig.ts +++ b/packages/shared/src/environment/multiRegionConfig.ts @@ -41,7 +41,8 @@ const regionConfigSchema = z.object({ const multiRegionConfigSchema = z.object({ main: regionConfigSchema, - regions: z.record(z.string(), regionConfigSchema) + regions: z.record(z.string(), regionConfigSchema), + defaultProjectRegionKey: z.string().min(3).nullish() }) export type MultiRegionConfig = z.infer diff --git a/utils/helm/speckle-server/templates/_helpers.tpl b/utils/helm/speckle-server/templates/_helpers.tpl index 7667a7ff82..22158af8fc 100644 --- a/utils/helm/speckle-server/templates/_helpers.tpl +++ b/utils/helm/speckle-server/templates/_helpers.tpl @@ -589,9 +589,6 @@ Generate the environment variables for Speckle server and Speckle objects deploy - name: FF_WORKSPACES_MULTI_REGION_ENABLED value: {{ .Values.featureFlags.workspacesMultiRegionEnabled | quote }} -- name: DEFAULT_PROJECT_REGION_KEY - value: {{ .Values.multiRegion.config.defaultProjectRegionKey }} - {{- if .Values.featureFlags.billingIntegrationEnabled }} - name: STRIPE_API_KEY valueFrom: diff --git a/utils/helm/speckle-server/values.schema.json b/utils/helm/speckle-server/values.schema.json index b5be6979e8..55f5d69340 100644 --- a/utils/helm/speckle-server/values.schema.json +++ b/utils/helm/speckle-server/values.schema.json @@ -563,11 +563,6 @@ "type": "string", "description": "If workspacesMultiRegionEnabled is enabled, the server will be deployed in a multi-region configuration based on the values in a secret. This allows the default secret key and filename to be overridden.", "default": "multi-region-config.json" - }, - "defaultProjectRegionKey": { - "type": "string", - "description": "forces new projects to be created in this region if not indicated otherwise", - "default": "" } } } diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml index 3b8e5a7331..8922b5b002 100644 --- a/utils/helm/speckle-server/values.yaml +++ b/utils/helm/speckle-server/values.yaml @@ -423,8 +423,6 @@ multiRegion: secretName: 'multi-region-config' ## @param multiRegion.config.secretKey If workspacesMultiRegionEnabled is enabled, the server will be deployed in a multi-region configuration based on the values in a secret. This allows the default secret key and filename to be overridden. secretKey: 'multi-region-config.json' - ## @param multiRegion.config.defaultProjectRegionKey forces new projects to be created in this region if not indicated otherwise - defaultProjectRegionKey: '' ## @section Server ## @descriptionStart