From 6e170d0c4faea5480846e925771da3cb9e781c81 Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Sat, 15 Mar 2025 10:43:40 +0000 Subject: [PATCH 1/3] fix(docker compose): preview service can be used with docker compose --- docker-compose-speckle.yml | 23 ++++-- .../server/modules/previews/rest/router.ts | 8 +- .../modules/shared/helpers/envHelper.ts | 75 +++++++++---------- 3 files changed, 61 insertions(+), 45 deletions(-) diff --git a/docker-compose-speckle.yml b/docker-compose-speckle.yml index d1f6790f9..446204c58 100644 --- a/docker-compose-speckle.yml +++ b/docker-compose-speckle.yml @@ -1,4 +1,3 @@ -version: '2.4' services: speckle-ingress: build: @@ -22,12 +21,16 @@ services: restart: always environment: NUXT_PUBLIC_SERVER_NAME: 'local' + #TODO: Change this to the URL of your server. This is the URL of the server as accessed by users. NUXT_PUBLIC_API_ORIGIN: 'http://127.0.0.1' + #TODO: Change this to the URL of your server. This is the URL of the server as accessed by users. NUXT_PUBLIC_BASE_URL: 'http://127.0.0.1' + # This is the URL of the server as accessed via this docker compose network. NUXT_PUBLIC_BACKEND_API_ORIGIN: 'http://speckle-server:3000' NUXT_PUBLIC_LOG_LEVEL: 'warn' NUXT_REDIS_URL: 'redis://redis' LOG_LEVEL: 'info' + LOG_PRETTY: 'true' speckle-server: build: @@ -47,14 +50,20 @@ services: retries: 3 start_period: 90s environment: - # TODO: Change this to the URL of the speckle server, as accessed from the network + # TODO. Change this to the url of your server. This is the URL of the server as accessed by users. CANONICAL_URL: 'http://127.0.0.1' + # This is the URL of the server as accessed by other Speckle services within this docker compose network, such as preview-service. + # This will be the same value as NUXT_PUBLIC_BACKEND_API_ORIGIN as defined in the frontend-2 service. + PRIVATE_SERVER_URL: 'http://speckle-server:3000' # TODO: Change this to a unique secret for this server SESSION_SECRET: 'TODO:Replace' + # This is the authentication strategy to use. Local (i.e. username & password) is the default strategy. STRATEGY_LOCAL: 'true' + LOG_LEVEL: 'info' + LOG_PRETTY: 'true' POSTGRES_URL: 'postgres' POSTGRES_USER: 'speckle' @@ -62,6 +71,7 @@ services: POSTGRES_DB: 'speckle' REDIS_URL: 'redis://redis' + PREVIEW_SERVICE_REDIS_URL: 'redis://redis' S3_ENDPOINT: 'http://minio:9000' S3_ACCESS_KEY: 'minioadmin' @@ -85,10 +95,11 @@ services: mem_limit: '3000m' memswap_limit: '3000m' environment: - HOST: '127.0.0.1' # Only accept connections from localhost, as preview service does not need to be exposed outside the container. - METRICS_HOST: '127.0.0.1' # Amend if you want to expose Prometheus metrics outside of the container + HOST: '127.0.0.1' # The preview service does not need to be exposed outside the container. + PORT: '3001' LOG_LEVEL: 'info' - PG_CONNECTION_STRING: 'postgres://speckle:speckle@postgres/speckle' + LOG_PRETTY: 'true' + REDIS_URL: 'redis://redis' webhook-service: build: @@ -99,6 +110,7 @@ services: restart: always environment: LOG_LEVEL: 'info' + LOG_PRETTY: 'true' PG_CONNECTION_STRING: 'postgres://speckle:speckle@postgres/speckle' fileimport-service: @@ -110,6 +122,7 @@ services: restart: always environment: LOG_LEVEL: 'info' + LOG_PRETTY: 'true' PG_CONNECTION_STRING: 'postgres://speckle:speckle@postgres/speckle' SPECKLE_SERVER_URL: 'http://speckle-server:3000' FILE_IMPORT_TIME_LIMIT_MIN: 10 diff --git a/packages/server/modules/previews/rest/router.ts b/packages/server/modules/previews/rest/router.ts index 964b7279b..92374d73b 100644 --- a/packages/server/modules/previews/rest/router.ts +++ b/packages/server/modules/previews/rest/router.ts @@ -37,7 +37,10 @@ import { storeTokenScopesFactory, storeUserServerAppTokenFactory } from '@/modules/core/repositories/tokens' -import { getServerOrigin } from '@/modules/shared/helpers/envHelper' +import { + getPrivateServerOrigin, + getServerOrigin +} from '@/modules/shared/helpers/envHelper' import { requestObjectPreviewFactory } from '@/modules/previews/queues/previews' import type { Queue } from 'bull' import type { Knex } from 'knex' @@ -61,7 +64,8 @@ const buildCreateObjectPreviewFunction = ({ queue: previewRequestQueue, responseQueue: responseQueueName }), - serverOrigin: getServerOrigin(), + // use the private server origin if defined, otherwise use the public server origin + serverOrigin: getPrivateServerOrigin() || getServerOrigin(), storeObjectPreview: storeObjectPreviewFactory({ db: projectDb }), getStreamCollaborators: getStreamCollaboratorsFactory({ db }), createAppToken: createAppTokenFactory({ diff --git a/packages/server/modules/shared/helpers/envHelper.ts b/packages/server/modules/shared/helpers/envHelper.ts index 7303fd2c0..9d398f0d9 100644 --- a/packages/server/modules/shared/helpers/envHelper.ts +++ b/packages/server/modules/shared/helpers/envHelper.ts @@ -1,7 +1,7 @@ import { MisconfiguredEnvironmentError } from '@/modules/shared/errors' import { trimEnd } from 'lodash' import * as Environment from '@speckle/shared/dist/commonjs/environment/index.js' -import { ensureError } from '@speckle/shared' +import { ensureError, Nullable } from '@speckle/shared' export function getStringFromEnv( envVarKey: string, @@ -28,6 +28,32 @@ export function getBooleanFromEnv(envVarKey: string, aDefault = false): boolean return ['1', 'true', true].includes(process.env[envVarKey] || aDefault.toString()) } +function mustGetUrlFromEnv(name: string, trimTrailingSlash: boolean = false): URL { + const url = getUrlFromEnv(name, trimTrailingSlash) + if (!url) throw new MisconfiguredEnvironmentError(`${name} env var not configured`) + return url +} + +function getUrlFromEnv( + name: string, + trimTrailingSlash: boolean = false +): Nullable { + const value = process.env[name] + if (!value) { + return null + } + try { + return new URL(trimTrailingSlash ? trimEnd(value, '/') : value) + } catch (e: unknown) { + const err = ensureError(e, 'Unknown error parsing URL') + if (err instanceof TypeError && err.message === 'Invalid URL') + throw new MisconfiguredEnvironmentError(`${name} has to be a valid URL`, { + cause: err + }) + throw new MisconfiguredEnvironmentError(`Error parsing ${name} URL`, { cause: err }) + } +} + export function getSessionSecret() { if (!process.env.SESSION_SECRET) { throw new MisconfiguredEnvironmentError('SESSION_SECRET env var not configured') @@ -198,29 +224,16 @@ export function getFrontendOrigin() { * Get server app origin/base URL */ export function getServerOrigin() { - if (!process.env.CANONICAL_URL) { - throw new MisconfiguredEnvironmentError( - 'Server origin environment variable (CANONICAL_URL) not configured' - ) - } + return mustGetUrlFromEnv('CANONICAL_URL', true).origin +} +export function getPrivateServerOrigin() { try { - return new URL(trimEnd(process.env.CANONICAL_URL, '/')).origin - } catch (e) { - const err = ensureError(e) - if (e instanceof TypeError && e.message === 'Invalid URL') { - throw new MisconfiguredEnvironmentError( - `Server origin environment variable (CANONICAL_URL) is not a valid URL: ${process.env.CANONICAL_URL} ${err.message}`, - { - cause: e, - info: { - value: process.env.CANONICAL_URL - } - } - ) - } - - throw err + const url = getUrlFromEnv('PRIVATE_SERVER_URL', true) + if (!url) return url + return url.origin + } catch { + return null } } @@ -239,26 +252,12 @@ export function isSSLServer() { return /^https:\/\//.test(getServerOrigin()) } -function parseUrlVar(value: string, name: string) { - try { - return new URL(value) - } catch (err: unknown) { - if (err instanceof TypeError && err.message === 'Invalid URL') - throw new MisconfiguredEnvironmentError(`${name} has to be a valid URL`) - throw err - } -} - export function getServerMovedFrom() { - const value = process.env.MIGRATION_SERVER_MOVED_FROM - if (!value) return value - return parseUrlVar(value, 'MIGRATION_SERVER_MOVED_FROM') + return getUrlFromEnv('MIGRATION_SERVER_MOVED_FROM') } export function getServerMovedTo() { - const value = process.env.MIGRATION_SERVER_MOVED_TO - if (!value) return value - return parseUrlVar(value, 'MIGRATION_SERVER_MOVED_TO') + return getUrlFromEnv('MIGRATION_SERVER_MOVED_TO') } export function adminOverrideEnabled() { From 2dc32094d0e2e2e3112e2237e257910c2c741322 Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Sat, 15 Mar 2025 11:31:40 +0000 Subject: [PATCH 2/3] feat(helm chart): can be configured to deploy preview service in cluster --- docker-compose-speckle.yml | 3 ++- .../server/modules/previews/rest/router.ts | 9 +++++--- .../modules/shared/helpers/envHelper.ts | 21 +++++++++++-------- .../speckle-server/templates/_helpers.tpl | 11 +++++++++- .../templates/preview_service/deployment.yml | 3 ++- .../preview_service/networkpolicy.cilium.yml | 2 ++ .../networkpolicy.kubernetes.yml | 4 +++- .../templates/preview_service/service.yml | 2 ++ .../preview_service/serviceaccount.yml | 2 ++ utils/helm/speckle-server/values.schema.json | 5 +++++ utils/helm/speckle-server/values.yaml | 2 ++ 11 files changed, 48 insertions(+), 16 deletions(-) diff --git a/docker-compose-speckle.yml b/docker-compose-speckle.yml index 446204c58..658d2ccc2 100644 --- a/docker-compose-speckle.yml +++ b/docker-compose-speckle.yml @@ -54,7 +54,7 @@ services: CANONICAL_URL: 'http://127.0.0.1' # This is the URL of the server as accessed by other Speckle services within this docker compose network, such as preview-service. # This will be the same value as NUXT_PUBLIC_BACKEND_API_ORIGIN as defined in the frontend-2 service. - PRIVATE_SERVER_URL: 'http://speckle-server:3000' + PRIVATE_OBJECTS_SERVER_URL: 'http://speckle-server:3000' # TODO: Change this to a unique secret for this server SESSION_SECRET: 'TODO:Replace' @@ -71,6 +71,7 @@ services: POSTGRES_DB: 'speckle' REDIS_URL: 'redis://redis' + PREVIEW_SERVICE_USE_PRIVATE_OBJECTS_SERVER_URL: 'true' PREVIEW_SERVICE_REDIS_URL: 'redis://redis' S3_ENDPOINT: 'http://minio:9000' diff --git a/packages/server/modules/previews/rest/router.ts b/packages/server/modules/previews/rest/router.ts index 92374d73b..ea2002154 100644 --- a/packages/server/modules/previews/rest/router.ts +++ b/packages/server/modules/previews/rest/router.ts @@ -38,8 +38,9 @@ import { storeUserServerAppTokenFactory } from '@/modules/core/repositories/tokens' import { - getPrivateServerOrigin, - getServerOrigin + getPrivateObjectsServerOrigin, + getServerOrigin, + previewServiceShouldUsePrivateObjectsServerUrl } from '@/modules/shared/helpers/envHelper' import { requestObjectPreviewFactory } from '@/modules/previews/queues/previews' import type { Queue } from 'bull' @@ -65,7 +66,9 @@ const buildCreateObjectPreviewFunction = ({ responseQueue: responseQueueName }), // use the private server origin if defined, otherwise use the public server origin - serverOrigin: getPrivateServerOrigin() || getServerOrigin(), + serverOrigin: previewServiceShouldUsePrivateObjectsServerUrl() + ? getPrivateObjectsServerOrigin() + : getServerOrigin(), storeObjectPreview: storeObjectPreviewFactory({ db: projectDb }), getStreamCollaborators: getStreamCollaboratorsFactory({ db }), createAppToken: createAppTokenFactory({ diff --git a/packages/server/modules/shared/helpers/envHelper.ts b/packages/server/modules/shared/helpers/envHelper.ts index 9d398f0d9..7e3093ba7 100644 --- a/packages/server/modules/shared/helpers/envHelper.ts +++ b/packages/server/modules/shared/helpers/envHelper.ts @@ -112,6 +112,10 @@ export function getRedisUrl() { return getStringFromEnv('REDIS_URL') } +export const previewServiceShouldUsePrivateObjectsServerUrl = (): boolean => { + return getBooleanFromEnv('PREVIEW_SERVICE_USE_PRIVATE_OBJECTS_SERVER_URL') +} + export const getPreviewServiceRedisUrl = (): string | undefined => { return process.env['PREVIEW_SERVICE_REDIS_URL'] } @@ -221,20 +225,19 @@ export function getFrontendOrigin() { } /** - * Get server app origin/base URL + * Get server app origin/base URL. + * This is the public server URL, i.e. 'canonical url', used for external communication. */ export function getServerOrigin() { return mustGetUrlFromEnv('CANONICAL_URL', true).origin } -export function getPrivateServerOrigin() { - try { - const url = getUrlFromEnv('PRIVATE_SERVER_URL', true) - if (!url) return url - return url.origin - } catch { - return null - } +/** + * + * @returns the private server origin, which is used for internal communication between services + */ +export function getPrivateObjectsServerOrigin() { + return mustGetUrlFromEnv('PRIVATE_OBJECTS_SERVER_URL', true).origin } export function getBindAddress(aDefault: string = '127.0.0.1') { diff --git a/utils/helm/speckle-server/templates/_helpers.tpl b/utils/helm/speckle-server/templates/_helpers.tpl index 1e733bbed..748779d6d 100644 --- a/utils/helm/speckle-server/templates/_helpers.tpl +++ b/utils/helm/speckle-server/templates/_helpers.tpl @@ -528,7 +528,6 @@ Retrieve the s3 parameters from ConfigMap if enabled, or default to retrieving t {{- end }} {{- end }} - {{/* Generate the environment variables for Speckle server and Speckle objects deployments */}} @@ -542,6 +541,10 @@ Generate the environment variables for Speckle server and Speckle objects deploy - name: PORT value: {{ include "server.port" $ | quote }} + +- name: PRIVATE_OBJECTS_SERVER_URL + value: {{ printf "http://%s:%s" ( include "objects.service.fqdn" $ ) ( include "objects.port" $ ) }} + - name: LOG_LEVEL value: {{ .Values.server.logLevel }} - name: LOG_PRETTY @@ -799,6 +802,12 @@ Generate the environment variables for Speckle server and Speckle objects deploy value: {{ .Values.server.gendoAI.ratelimiting.burstRenderRequestPeriodSeconds | quote }} {{- end }} +# *** Preview service *** +{{- if .Values.preview_service.deployInCluster }} +- name: PREVIEW_SERVICE_USE_PRIVATE_OBJECTS_SERVER_URL + value: "true" +{{- end }} + # *** Redis *** - name: REDIS_URL valueFrom: diff --git a/utils/helm/speckle-server/templates/preview_service/deployment.yml b/utils/helm/speckle-server/templates/preview_service/deployment.yml index 01bba8ef6..7ac6f8041 100644 --- a/utils/helm/speckle-server/templates/preview_service/deployment.yml +++ b/utils/helm/speckle-server/templates/preview_service/deployment.yml @@ -1,3 +1,4 @@ +{{- if .Values.preview_service.deployInCluster }} apiVersion: apps/v1 kind: Deployment metadata: @@ -121,4 +122,4 @@ spec: # Should be > preview generation time ( 1 hour for good measure ) terminationGracePeriodSeconds: 3600 - +{{- end }} diff --git a/utils/helm/speckle-server/templates/preview_service/networkpolicy.cilium.yml b/utils/helm/speckle-server/templates/preview_service/networkpolicy.cilium.yml index 828509cb4..5b4e799c0 100644 --- a/utils/helm/speckle-server/templates/preview_service/networkpolicy.cilium.yml +++ b/utils/helm/speckle-server/templates/preview_service/networkpolicy.cilium.yml @@ -1,3 +1,4 @@ +{{- if .Values.preview_service.deployInCluster }} {{- if (and (.Values.preview_service.networkPolicy.enabled) (eq .Values.networkPlugin.type "cilium")) -}} apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy @@ -38,3 +39,4 @@ spec: # postgres {{ include "speckle.networkpolicy.egress.postgres.cilium" $ | indent 4 }} {{- end }} +{{- end }} diff --git a/utils/helm/speckle-server/templates/preview_service/networkpolicy.kubernetes.yml b/utils/helm/speckle-server/templates/preview_service/networkpolicy.kubernetes.yml index b4c386b11..d5dbff0ab 100644 --- a/utils/helm/speckle-server/templates/preview_service/networkpolicy.kubernetes.yml +++ b/utils/helm/speckle-server/templates/preview_service/networkpolicy.kubernetes.yml @@ -1,3 +1,4 @@ +{{- if .Values.preview_service.deployInCluster }} {{- if (and (.Values.preview_service.networkPolicy.enabled) (eq .Values.networkPlugin.type "kubernetes")) -}} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy @@ -38,4 +39,5 @@ spec: protocol: UDP # postgres {{ include "speckle.networkpolicy.egress.postgres" $ | indent 4 }} -{{- end -}} +{{- end }} +{{- end }} diff --git a/utils/helm/speckle-server/templates/preview_service/service.yml b/utils/helm/speckle-server/templates/preview_service/service.yml index b0b186306..e2156a71c 100644 --- a/utils/helm/speckle-server/templates/preview_service/service.yml +++ b/utils/helm/speckle-server/templates/preview_service/service.yml @@ -1,3 +1,4 @@ +{{- if .Values.preview_service.deployInCluster }} apiVersion: v1 kind: Service metadata: @@ -14,3 +15,4 @@ spec: name: web port: {{ .Values.preview_service.port }} targetPort: metrics +{{- end }} diff --git a/utils/helm/speckle-server/templates/preview_service/serviceaccount.yml b/utils/helm/speckle-server/templates/preview_service/serviceaccount.yml index 279e661e2..e078289ed 100644 --- a/utils/helm/speckle-server/templates/preview_service/serviceaccount.yml +++ b/utils/helm/speckle-server/templates/preview_service/serviceaccount.yml @@ -1,3 +1,4 @@ +{{- if .Values.preview_service.deployInCluster }} {{- if .Values.preview_service.serviceAccount.create -}} apiVersion: v1 kind: ServiceAccount @@ -21,3 +22,4 @@ secrets: - name: {{ default .Values.secretName .Values.redis.previewServiceConnectionString.secretName }} {{- end }} {{- end -}} +{{- end }} diff --git a/utils/helm/speckle-server/values.schema.json b/utils/helm/speckle-server/values.schema.json index 09b6e39ea..25d17d47e 100644 --- a/utils/helm/speckle-server/values.schema.json +++ b/utils/helm/speckle-server/values.schema.json @@ -1884,6 +1884,11 @@ "preview_service": { "type": "object", "properties": { + "deployInCluster": { + "type": "boolean", + "description": "If enabled, the Preview Service will be deployed within the cluster and speckle-server will be configured to send the kubernetes service url of the objects server to the Preview Service.", + "default": true + }, "dedicatedPreviewsQueue": { "type": "boolean", "description": "Allows using a dedicated redis url for the preview service job queue", diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml index 3b7476913..de2fb8246 100644 --- a/utils/helm/speckle-server/values.yaml +++ b/utils/helm/speckle-server/values.yaml @@ -1101,6 +1101,8 @@ frontend_2: ## @descriptionEnd ## preview_service: + ## @param preview_service.deployInCluster If enabled, the Preview Service will be deployed within the cluster and speckle-server will be configured to send the kubernetes service url of the objects server to the Preview Service. + deployInCluster: true ## @param preview_service.dedicatedPreviewsQueue Allows using a dedicated redis url for the preview service job queue ## dedicatedPreviewsQueue: false From 6c11fd5561d307deb80e70bef6cc157a8951aa37 Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Tue, 18 Mar 2025 12:00:28 +0000 Subject: [PATCH 3/3] feat(fileimport service): allows nodejs inspection to be configured --- packages/fileimport-service/Dockerfile | 3 ++- .../templates/fileimport_service/deployment.yml | 5 +++++ utils/helm/speckle-server/values.schema.json | 15 +++++++++++++++ utils/helm/speckle-server/values.yaml | 6 ++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/fileimport-service/Dockerfile b/packages/fileimport-service/Dockerfile index c3191926f..579a9f1c5 100644 --- a/packages/fileimport-service/Dockerfile +++ b/packages/fileimport-service/Dockerfile @@ -74,4 +74,5 @@ ENV IFC_DOTNET_DLL_PATH='/speckle-server/packages/fileimport-service/src/ifc-dot WORKDIR /speckle-server/packages/fileimport-service -ENTRYPOINT [ "tini", "--", "node", "--loader=./dist/src/aliasLoader.js", "bin/www.js" ] +ENTRYPOINT [ "tini", "--", "node", "--loader=./dist/src/aliasLoader.js" ] +CMD ["bin/www.js"] diff --git a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml index 8d851f337..0cac3d8c9 100644 --- a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml +++ b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml @@ -23,6 +23,11 @@ spec: - name: main image: {{ default (printf "speckle/speckle-fileimport-service:%s" .Values.docker_image_tag) .Values.fileimport_service.image }} imagePullPolicy: {{ .Values.imagePullPolicy }} + args: #overwrites the Dockerfile CMD statement + {{- if .Values.fileimport_service.inspect.enabled }} + - {{ printf "--inspect=%s" .Values.fileimport_service.inspect.port }} + {{- end }} + - "bin/www.js" ports: - name: metrics diff --git a/utils/helm/speckle-server/values.schema.json b/utils/helm/speckle-server/values.schema.json index 09b6e39ea..bcc371d8e 100644 --- a/utils/helm/speckle-server/values.schema.json +++ b/utils/helm/speckle-server/values.schema.json @@ -2138,6 +2138,21 @@ "description": "The maximum number of connections that the File Import Service postgres client will make to the Postgres database.", "default": 1 }, + "inspect": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, indicates that the File Import service should be deployed with the nodejs inspect feature enabled", + "default": false + }, + "port": { + "type": "string", + "description": "The port on which the nodejs inspect feature should be exposed", + "default": "7000" + } + } + }, "requests": { "type": "object", "properties": { diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml index 3b7476913..1bd10b02a 100644 --- a/utils/helm/speckle-server/values.yaml +++ b/utils/helm/speckle-server/values.yaml @@ -1279,6 +1279,12 @@ fileimport_service: ## postgresMaxConnections: 1 + inspect: + ## @param fileimport_service.inspect.enabled If enabled, indicates that the File Import service should be deployed with the nodejs inspect feature enabled + enabled: false + ## @param fileimport_service.inspect.port The port on which the nodejs inspect feature should be exposed + port: '7000' + requests: ## @param fileimport_service.requests.cpu The CPU that should be available on a node when scheduling this pod. ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/