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: []