From 9345d8deab5f50aaebd5b0bbf17d49c6b5e4a70c Mon Sep 17 00:00:00 2001 From: Stephan Meijer Date: Mon, 12 Jan 2026 19:16:40 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(docker)=20add=20docspec=20deployment?= =?UTF-8?q?=20and=20service=20to=20kubernetes=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added Helm templates for docspec deployment and service to enable document specification conversion in the Kubernetes environment. Updated Tiltfile, compose.yml, and Helm values to configure docspec integration alongside the backend converter service for document import functionality. --- bin/Tiltfile | 3 + compose.yml | 2 +- .../core/services/converter_services.py | 27 ++++- src/helm/env.d/dev/values.impress.yaml.gotmpl | 18 ++- .../env.d/feature/values.impress.yaml.gotmpl | 18 ++- src/helm/impress/templates/_helpers.tpl | 10 ++ .../impress/templates/docspec_deployment.yaml | 108 ++++++++++++++++++ src/helm/impress/templates/docspec_svc.yaml | 20 ++++ src/helm/impress/values.yaml | 66 +++++++++++ 9 files changed, 263 insertions(+), 9 deletions(-) create mode 100644 src/helm/impress/templates/docspec_deployment.yaml create mode 100644 src/helm/impress/templates/docspec_svc.yaml diff --git a/bin/Tiltfile b/bin/Tiltfile index 4ddc284b..edcea0a7 100644 --- a/bin/Tiltfile +++ b/bin/Tiltfile @@ -8,6 +8,7 @@ docker_build( dockerfile='../Dockerfile', only=['./src/backend', './src/mail', './docker'], target = 'backend-production', + build_args={'DOCKER_USER': '1000:1000'}, live_update=[ sync('../src/backend', '/app'), run( @@ -23,6 +24,7 @@ docker_build( dockerfile='../src/frontend/servers/y-provider/Dockerfile', only=['./src/frontend/', './docker/', './.dockerignore'], target = 'y-provider', + build_args={'DOCKER_USER': '1000:1000'}, live_update=[ sync('../src/frontend/servers/y-provider/src', '/home/frontend/servers/y-provider/src'), ] @@ -34,6 +36,7 @@ docker_build( dockerfile='../src/frontend/Dockerfile', only=['./src/frontend', './docker', './.dockerignore'], target = 'impress', + build_args={'DOCKER_USER': '1000:1000'}, live_update=[ sync('../src/frontend', '/home/frontend'), ] diff --git a/compose.yml b/compose.yml index d9062da7..c1d5be85 100644 --- a/compose.yml +++ b/compose.yml @@ -232,7 +232,7 @@ services: restart: true docspec: - image: ghcr.io/docspecio/api:2.4.4 + image: ghcr.io/docspecio/api:2.6.3 ports: - "4000:4000" diff --git a/src/backend/core/services/converter_services.py b/src/backend/core/services/converter_services.py index e6c28fe2..46c289ba 100644 --- a/src/backend/core/services/converter_services.py +++ b/src/backend/core/services/converter_services.py @@ -1,5 +1,6 @@ """Y-Provider API services.""" +import logging import typing from base64 import b64encode @@ -9,6 +10,8 @@ import requests from core.services import mime_types +logger = logging.getLogger(__name__) + class ConversionError(Exception): """Base exception for conversion-related errors.""" @@ -66,6 +69,13 @@ class DocSpecConverter: timeout=settings.CONVERSION_API_TIMEOUT, verify=settings.CONVERSION_API_SECURE, ) + if not response.ok: + logger.error( + "DocSpec API error: url=%s, status=%d, response=%s", + url, + response.status_code, + response.text[:200] if response.text else "empty", + ) response.raise_for_status() return response @@ -82,6 +92,7 @@ class DocSpecConverter: try: return self._request(settings.DOCSPEC_API_URL, data, content_type).content except requests.RequestException as err: + logger.exception("DocSpec service error: url=%s", settings.DOCSPEC_API_URL) raise ServiceUnavailableError( "Failed to connect to DocSpec conversion service", ) from err @@ -109,6 +120,13 @@ class YdocConverter: timeout=settings.CONVERSION_API_TIMEOUT, verify=settings.CONVERSION_API_SECURE, ) + if not response.ok: + logger.error( + "Y-Provider API error: url=%s, status=%d, response=%s", + url, + response.status_code, + response.text[:200] if response.text else "empty", + ) response.raise_for_status() return response @@ -118,13 +136,9 @@ class YdocConverter: if not data: raise ValidationError("Input data cannot be empty") + url = f"{settings.Y_PROVIDER_API_BASE_URL}{settings.CONVERSION_API_ENDPOINT}/" try: - response = self._request( - f"{settings.Y_PROVIDER_API_BASE_URL}{settings.CONVERSION_API_ENDPOINT}/", - data, - content_type, - accept, - ) + response = self._request(url, data, content_type, accept) if accept == mime_types.YJS: return b64encode(response.content).decode("utf-8") if accept in {mime_types.MARKDOWN, "text/html"}: @@ -133,6 +147,7 @@ class YdocConverter: return response.json() raise ValidationError("Unsupported format") except requests.RequestException as err: + logger.exception("Y-Provider service error: url=%s", url) raise ServiceUnavailableError( f"Failed to connect to YDoc conversion service {content_type}, {accept}", ) from err diff --git a/src/helm/env.d/dev/values.impress.yaml.gotmpl b/src/helm/env.d/dev/values.impress.yaml.gotmpl index 75abeb99..4230499e 100644 --- a/src/helm/env.d/dev/values.impress.yaml.gotmpl +++ b/src/helm/env.d/dev/values.impress.yaml.gotmpl @@ -67,7 +67,8 @@ backend: AWS_S3_SECRET_ACCESS_KEY: password AWS_STORAGE_BUCKET_NAME: docs-media-storage STORAGES_STATICFILES_BACKEND: django.contrib.staticfiles.storage.StaticFilesStorage - Y_PROVIDER_API_BASE_URL: http://impress-y-provider:443/api/ + DOCSPEC_API_URL: http://impress-docs-docspec:4000/conversion + Y_PROVIDER_API_BASE_URL: http://impress-docs-y-provider:443/api/ Y_PROVIDER_API_KEY: my-secret CACHES_KEY_PREFIX: "{{ now | unixEpoch }}" migrate: @@ -156,6 +157,21 @@ yProvider: COLLABORATION_SERVER_SECRET: my-secret Y_PROVIDER_API_KEY: my-secret +docSpec: + enabled: true + replicas: 1 + + image: + repository: ghcr.io/docspecio/api + pullPolicy: IfNotPresent + tag: "2.6.3" + + probes: + liveness: + path: /health + readiness: + path: /health + ingress: enabled: true host: docs.127.0.0.1.nip.io diff --git a/src/helm/env.d/feature/values.impress.yaml.gotmpl b/src/helm/env.d/feature/values.impress.yaml.gotmpl index c8b3ae8f..3e562693 100644 --- a/src/helm/env.d/feature/values.impress.yaml.gotmpl +++ b/src/helm/env.d/feature/values.impress.yaml.gotmpl @@ -68,7 +68,8 @@ backend: AWS_S3_SECRET_ACCESS_KEY: password AWS_STORAGE_BUCKET_NAME: docs-media-storage STORAGES_STATICFILES_BACKEND: django.contrib.staticfiles.storage.StaticFilesStorage - Y_PROVIDER_API_BASE_URL: http://impress-y-provider:443/api/ + DOCSPEC_API_URL: http://impress-docs-docspec:4000/conversion + Y_PROVIDER_API_BASE_URL: http://impress-docs-y-provider:443/api/ Y_PROVIDER_API_KEY: my-secret CACHES_KEY_PREFIX: "{{ now | unixEpoch }}" migrate: @@ -142,6 +143,21 @@ yProvider: COLLABORATION_SERVER_SECRET: my-secret Y_PROVIDER_API_KEY: my-secret +docSpec: + enabled: true + replicas: 1 + + image: + repository: ghcr.io/docspecio/api + pullPolicy: IfNotPresent + tag: "2.6.3" + + probes: + liveness: + path: /health + readiness: + path: /health + ingress: enabled: true host: {{ .Values.feature }}-docs.{{ .Values.domain }} diff --git a/src/helm/impress/templates/_helpers.tpl b/src/helm/impress/templates/_helpers.tpl index 63a9f4b3..48140513 100644 --- a/src/helm/impress/templates/_helpers.tpl +++ b/src/helm/impress/templates/_helpers.tpl @@ -167,6 +167,16 @@ Requires top level scope {{- end }} +{{/* +Full name for the docSpec + +Requires top level scope +*/}} +{{- define "impress.docSpec.fullname" -}} +{{ include "impress.fullname" . }}-docspec +{{- end }} + + {{/* Full name for the Celery Worker diff --git a/src/helm/impress/templates/docspec_deployment.yaml b/src/helm/impress/templates/docspec_deployment.yaml new file mode 100644 index 00000000..984b98d1 --- /dev/null +++ b/src/helm/impress/templates/docspec_deployment.yaml @@ -0,0 +1,108 @@ +{{- if .Values.docSpec.enabled -}} +{{- $envVars := include "impress.common.env" (list . .Values.docSpec) -}} +{{- $fullName := include "impress.docSpec.fullname" . -}} +{{- $component := "docspec" -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "impress.common.labels" (list . $component) | nindent 4 }} +spec: + replicas: {{ .Values.docSpec.replicas }} + selector: + matchLabels: + {{- include "impress.common.selectorLabels" (list . $component) | nindent 6 }} + template: + metadata: + labels: + {{- include "impress.common.selectorLabels" (list . $component) | nindent 8 }} + spec: + {{- if $.Values.image.credentials }} + imagePullSecrets: + - name: {{ include "impress.secret.dockerconfigjson.name" (dict "fullname" (include "impress.fullname" .) "imageCredentials" $.Values.image.credentials) }} + {{- end}} + containers: + - name: {{ .Chart.Name }}-docspec + image: "{{ .Values.docSpec.image.repository }}:{{ .Values.docSpec.image.tag }}" + imagePullPolicy: {{ .Values.docSpec.image.pullPolicy }} + {{- with .Values.docSpec.command }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.docSpec.args }} + args: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if $envVars }} + env: + {{- $envVars | indent 12 }} + {{- end }} + {{- with .Values.docSpec.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: {{ .Values.docSpec.service.targetPort }} + protocol: TCP + {{- if .Values.docSpec.probes.liveness }} + livenessProbe: + {{- include "impress.probes.abstract" (merge .Values.docSpec.probes.liveness (dict "targetPort" .Values.docSpec.service.targetPort )) | nindent 12 }} + {{- end }} + {{- if .Values.docSpec.probes.readiness }} + readinessProbe: + {{- include "impress.probes.abstract" (merge .Values.docSpec.probes.readiness (dict "targetPort" .Values.docSpec.service.targetPort )) | nindent 12 }} + {{- end }} + {{- with .Values.docSpec.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.docSpec.extraVolumeMounts }} + volumeMounts: + {{- range .Values.docSpec.extraVolumeMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + {{- if .subPath }} + subPath: {{ .subPath }} + {{- end }} + {{- if .readOnly }} + readOnly: {{ .readOnly }} + {{- end }} + {{- end }} + {{- end }} + {{- with .Values.docSpec.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.docSpec.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.docSpec.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.docSpec.extraVolumes }} + volumes: + {{- range .Values.docSpec.extraVolumes }} + - name: {{ .name }} + {{- if .persistentVolumeClaim }} + persistentVolumeClaim: + {{- toYaml .persistentVolumeClaim | nindent 12 }} + {{- else if .emptyDir }} + emptyDir: + {{- toYaml .emptyDir | nindent 12 }} + {{- else if .configMap }} + configMap: + {{- toYaml .configMap | nindent 12 }} + {{- else if .secret }} + secret: + {{- toYaml .secret | nindent 12 }} + {{- else }} + emptyDir: {} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/src/helm/impress/templates/docspec_svc.yaml b/src/helm/impress/templates/docspec_svc.yaml new file mode 100644 index 00000000..f393446c --- /dev/null +++ b/src/helm/impress/templates/docspec_svc.yaml @@ -0,0 +1,20 @@ +{{- if .Values.docSpec.enabled -}} +{{- $fullName := include "impress.docSpec.fullname" . -}} +{{- $component := "docspec" -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "impress.common.labels" (list . $component) | nindent 4 }} +spec: + type: {{ .Values.docSpec.service.type }} + ports: + - port: {{ .Values.docSpec.service.port }} + targetPort: {{ .Values.docSpec.service.targetPort }} + protocol: TCP + name: http + selector: + {{- include "impress.common.selectorLabels" (list . $component) | nindent 4 }} +{{- end }} diff --git a/src/helm/impress/values.yaml b/src/helm/impress/values.yaml index 8a5401fe..5019ecff 100644 --- a/src/helm/impress/values.yaml +++ b/src/helm/impress/values.yaml @@ -701,3 +701,69 @@ yProvider: ## @param yProvider.serviceAccountName Optional service account name to use for yProvider pods serviceAccountName: null + +## @section docSpec +docSpec: + ## @param docSpec.enabled Enable docSpec deployment + enabled: false + + ## @param docSpec.image.repository Repository to use to pull docSpec container image + ## @param docSpec.image.tag docSpec container tag + ## @param docSpec.image.pullPolicy docSpec container image pull policy + image: + repository: ghcr.io/docspecio/api + pullPolicy: IfNotPresent + tag: "2.6.3" + + ## @param docSpec.command Override the docSpec container command + command: [] + + ## @param docSpec.args Override the docSpec container args + args: [] + + ## @param docSpec.replicas Amount of docSpec replicas + replicas: 1 + + ## @param docSpec.securityContext Configure docSpec Pod security context + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + + ## @param docSpec.envVars Configure docSpec container environment variables + envVars: {} + + ## @param docSpec.service Configure docSpec service + service: + type: ClusterIP + port: 4000 + targetPort: 4000 + + ## @param docSpec.probes Configure docSpec probes + probes: + liveness: + path: /health + readiness: + path: /health + + ## @param docSpec.resources docSpec resources + resources: {} + + ## @param docSpec.nodeSelector Node selector for the docSpec Pod + nodeSelector: {} + + ## @param docSpec.tolerations Tolerations for the docSpec Pod + tolerations: [] + + ## @param docSpec.affinity Affinity for the docSpec Pod + affinity: {} + + ## @param docSpec.extraVolumeMounts Additional volumes to mount on docSpec + extraVolumeMounts: [] + + ## @param docSpec.extraVolumes Additional volumes to mount on docSpec + extraVolumes: []