From cade7d339ef2e962a019d1dd721f6cbffc05b2a2 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 20 Nov 2023 17:03:38 +0000 Subject: [PATCH 01/63] First pass at updating Helm chart. --- charts/budibase/Chart.yaml | 6 +--- charts/budibase/templates/alb-ingress.yaml | 16 ++++----- .../templates/app-service-deployment.yaml | 8 +---- .../templates/app-service-service.yaml | 10 ++---- charts/budibase/templates/couchdb-backup.yaml | 8 ----- .../minio-data-persistentvolumeclaim.yaml | 1 - .../templates/minio-service-deployment.yaml | 8 ----- .../templates/minio-service-service.yaml | 4 --- .../templates/proxy-service-deployment.yaml | 5 --- .../templates/proxy-service-service.yaml | 12 +++---- .../redis-data-persistentvolumeclaim.yaml | 1 - .../templates/redis-service-deployment.yaml | 8 ----- .../templates/redis-service-service.yaml | 4 --- .../templates/worker-service-deployment.yaml | 6 ---- .../templates/worker-service-service.yaml | 10 ++---- charts/budibase/values.yaml | 33 +++++-------------- 16 files changed, 29 insertions(+), 111 deletions(-) diff --git a/charts/budibase/Chart.yaml b/charts/budibase/Chart.yaml index 05b3f24dbd..b62c1ca248 100644 --- a/charts/budibase/Chart.yaml +++ b/charts/budibase/Chart.yaml @@ -17,10 +17,6 @@ version: 0.0.0 appVersion: 0.0.0 dependencies: - name: couchdb - version: 3.3.4 + version: 4.4.5 repository: https://apache.github.io/couchdb-helm condition: services.couchdb.enabled - - name: ingress-nginx - version: 4.0.13 - repository: https://kubernetes.github.io/ingress-nginx - condition: ingress.nginx diff --git a/charts/budibase/templates/alb-ingress.yaml b/charts/budibase/templates/alb-ingress.yaml index 6cd1cf2cba..01d7fe0bf0 100644 --- a/charts/budibase/templates/alb-ingress.yaml +++ b/charts/budibase/templates/alb-ingress.yaml @@ -1,4 +1,4 @@ -{{- if .Values.ingress.aws }} +{{- if .Values.awsAlbIngress.enabled }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: @@ -9,22 +9,22 @@ metadata: alb.ingress.kubernetes.io/target-type: ip alb.ingress.kubernetes.io/success-codes: 200,301 alb.ingress.kubernetes.io/healthcheck-path: / - {{- if .Values.ingress.certificateArn }} + {{- if .Values.awsAlbIngress.certificateArn }} alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}' alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]' - alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.certificateArn }} + alb.ingress.kubernetes.io/certificate-arn: {{ .Values.awsAlbIngress.certificateArn }} {{- end }} - {{- if .Values.ingress.sslPolicy }} - alb.ingress.kubernetes.io/actions.ssl-policy: {{ .Values.ingress.sslPolicy }} + {{- if .Values.awsAlbIngress.sslPolicy }} + alb.ingress.kubernetes.io/actions.ssl-policy: {{ .Values.awsAlbIngress.sslPolicy }} {{- end }} - {{- if .Values.ingress.securityGroups }} - alb.ingress.kubernetes.io/security-groups: {{ .Values.ingress.securityGroups }} + {{- if .Values.awsAlbIngress.securityGroups }} + alb.ingress.kubernetes.io/security-groups: {{ .Values.awsAlbIngress.securityGroups }} {{- end }} spec: rules: - http: paths: - {{- if .Values.ingress.certificateArn }} + {{- if .Values.awsAlbIngress.certificateArn }} - path: / pathType: Prefix backend: diff --git a/charts/budibase/templates/app-service-deployment.yaml b/charts/budibase/templates/app-service-deployment.yaml index 73c6d990d2..fbe9c1d363 100644 --- a/charts/budibase/templates/app-service-deployment.yaml +++ b/charts/budibase/templates/app-service-deployment.yaml @@ -2,12 +2,9 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) {{ if .Values.services.apps.deploymentAnnotations }} {{- toYaml .Values.services.apps.deploymentAnnotations | indent 4 -}} {{ end }} - creationTimestamp: null labels: io.kompose.service: app-service {{ if .Values.services.apps.deploymentLabels }} @@ -24,12 +21,9 @@ spec: template: metadata: annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) {{ if .Values.services.apps.templateAnnotations }} {{- toYaml .Values.services.apps.templateAnnotations | indent 8 -}} {{ end }} - creationTimestamp: null labels: io.kompose.service: app-service {{ if .Values.services.apps.templateLabels }} @@ -200,7 +194,7 @@ spec: {{ end }} image: budibase/apps:{{ .Values.globals.appVersion | default .Chart.AppVersion }} - imagePullPolicy: Always + imagePullPolicy: IfNotPresent {{- if .Values.services.apps.startupProbe }} {{- with .Values.services.apps.startupProbe }} startupProbe: diff --git a/charts/budibase/templates/app-service-service.yaml b/charts/budibase/templates/app-service-service.yaml index 5247b4de09..390fbfe782 100644 --- a/charts/budibase/templates/app-service-service.yaml +++ b/charts/budibase/templates/app-service-service.yaml @@ -1,18 +1,14 @@ apiVersion: v1 kind: Service metadata: - annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) - creationTimestamp: null labels: io.kompose.service: app-service name: app-service spec: ports: - - name: {{ .Values.services.apps.port | quote }} - port: {{ .Values.services.apps.port }} - targetPort: {{ .Values.services.apps.port }} + - name: {{ .Values.services.apps.port | quote }} + port: {{ .Values.services.apps.port }} + targetPort: {{ .Values.services.apps.port }} selector: io.kompose.service: app-service status: diff --git a/charts/budibase/templates/couchdb-backup.yaml b/charts/budibase/templates/couchdb-backup.yaml index 7396f97476..6f842537a7 100644 --- a/charts/budibase/templates/couchdb-backup.yaml +++ b/charts/budibase/templates/couchdb-backup.yaml @@ -2,10 +2,6 @@ apiVersion: apps/v1 kind: Deployment metadata: - annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) - creationTimestamp: null labels: app.kubernetes.io/name: couchdb-backup name: couchdb-backup @@ -18,10 +14,6 @@ spec: type: Recreate template: metadata: - annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) - creationTimestamp: null labels: app.kubernetes.io/name: couchdb-backup spec: diff --git a/charts/budibase/templates/minio-data-persistentvolumeclaim.yaml b/charts/budibase/templates/minio-data-persistentvolumeclaim.yaml index abcf341bc5..c17001a436 100644 --- a/charts/budibase/templates/minio-data-persistentvolumeclaim.yaml +++ b/charts/budibase/templates/minio-data-persistentvolumeclaim.yaml @@ -2,7 +2,6 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: - creationTimestamp: null labels: io.kompose.service: minio-data name: minio-data diff --git a/charts/budibase/templates/minio-service-deployment.yaml b/charts/budibase/templates/minio-service-deployment.yaml index f98ecc139d..28e8eb9991 100644 --- a/charts/budibase/templates/minio-service-deployment.yaml +++ b/charts/budibase/templates/minio-service-deployment.yaml @@ -2,10 +2,6 @@ apiVersion: apps/v1 kind: Deployment metadata: - annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) - creationTimestamp: null labels: io.kompose.service: minio-service name: minio-service @@ -18,10 +14,6 @@ spec: type: Recreate template: metadata: - annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) - creationTimestamp: null labels: io.kompose.service: minio-service spec: diff --git a/charts/budibase/templates/minio-service-service.yaml b/charts/budibase/templates/minio-service-service.yaml index cfdb22002b..ce89f1347c 100644 --- a/charts/budibase/templates/minio-service-service.yaml +++ b/charts/budibase/templates/minio-service-service.yaml @@ -2,10 +2,6 @@ apiVersion: v1 kind: Service metadata: - annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) - creationTimestamp: null labels: io.kompose.service: minio-service name: minio-service diff --git a/charts/budibase/templates/proxy-service-deployment.yaml b/charts/budibase/templates/proxy-service-deployment.yaml index 53bba6232d..26f4265e4c 100644 --- a/charts/budibase/templates/proxy-service-deployment.yaml +++ b/charts/budibase/templates/proxy-service-deployment.yaml @@ -2,12 +2,9 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) {{ if .Values.services.proxy.deploymentAnnotations }} {{- toYaml .Values.services.proxy.deploymentAnnotations | indent 4 -}} {{ end }} - creationTimestamp: null labels: app.kubernetes.io/name: budibase-proxy {{ if .Values.services.proxy.deploymentLabels }} @@ -24,8 +21,6 @@ spec: template: metadata: annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) {{ if .Values.services.proxy.templateAnnotations }} {{- toYaml .Values.services.proxy.templateAnnotations | indent 8 -}} {{ end }} diff --git a/charts/budibase/templates/proxy-service-service.yaml b/charts/budibase/templates/proxy-service-service.yaml index bf2b199ee5..e5dde301fa 100644 --- a/charts/budibase/templates/proxy-service-service.yaml +++ b/charts/budibase/templates/proxy-service-service.yaml @@ -1,19 +1,15 @@ apiVersion: v1 kind: Service metadata: - annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) - creationTimestamp: null labels: app.kubernetes.io/name: budibase-proxy name: proxy-service spec: ports: - - name: {{ .Values.services.proxy.port | quote }} - port: {{ .Values.services.proxy.port }} - targetPort: {{ .Values.services.proxy.port }} + - name: { { .Values.services.proxy.port | quote } } + port: { { .Values.services.proxy.port } } + targetPort: { { .Values.services.proxy.port } } selector: app.kubernetes.io/name: budibase-proxy status: - loadBalancer: {} \ No newline at end of file + loadBalancer: {} diff --git a/charts/budibase/templates/redis-data-persistentvolumeclaim.yaml b/charts/budibase/templates/redis-data-persistentvolumeclaim.yaml index 6f11492b58..e605a98376 100644 --- a/charts/budibase/templates/redis-data-persistentvolumeclaim.yaml +++ b/charts/budibase/templates/redis-data-persistentvolumeclaim.yaml @@ -2,7 +2,6 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: - creationTimestamp: null labels: io.kompose.service: redis-data name: redis-data diff --git a/charts/budibase/templates/redis-service-deployment.yaml b/charts/budibase/templates/redis-service-deployment.yaml index 9b39d14291..bca40d2237 100644 --- a/charts/budibase/templates/redis-service-deployment.yaml +++ b/charts/budibase/templates/redis-service-deployment.yaml @@ -2,10 +2,6 @@ apiVersion: apps/v1 kind: Deployment metadata: - annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) - creationTimestamp: null labels: io.kompose.service: redis-service name: redis-service @@ -18,10 +14,6 @@ spec: type: Recreate template: metadata: - annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) - creationTimestamp: null labels: io.kompose.service: redis-service spec: diff --git a/charts/budibase/templates/redis-service-service.yaml b/charts/budibase/templates/redis-service-service.yaml index 55ca40ed88..b8f998925a 100644 --- a/charts/budibase/templates/redis-service-service.yaml +++ b/charts/budibase/templates/redis-service-service.yaml @@ -2,10 +2,6 @@ apiVersion: v1 kind: Service metadata: - annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) - creationTimestamp: null labels: io.kompose.service: redis-service name: redis-service diff --git a/charts/budibase/templates/worker-service-deployment.yaml b/charts/budibase/templates/worker-service-deployment.yaml index 5e0addb9dd..6427aa70e8 100644 --- a/charts/budibase/templates/worker-service-deployment.yaml +++ b/charts/budibase/templates/worker-service-deployment.yaml @@ -2,12 +2,9 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) {{ if .Values.services.worker.deploymentAnnotations }} {{- toYaml .Values.services.worker.deploymentAnnotations | indent 4 -}} {{ end }} - creationTimestamp: null labels: io.kompose.service: worker-service {{ if .Values.services.worker.deploymentLabels }} @@ -24,12 +21,9 @@ spec: template: metadata: annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) {{ if .Values.services.worker.templateAnnotations }} {{- toYaml .Values.services.worker.templateAnnotations | indent 8 -}} {{ end }} - creationTimestamp: null labels: io.kompose.service: worker-service {{ if .Values.services.worker.templateLabels }} diff --git a/charts/budibase/templates/worker-service-service.yaml b/charts/budibase/templates/worker-service-service.yaml index a79ba1e04b..214ee7191e 100644 --- a/charts/budibase/templates/worker-service-service.yaml +++ b/charts/budibase/templates/worker-service-service.yaml @@ -1,18 +1,14 @@ apiVersion: v1 kind: Service metadata: - annotations: - kompose.cmd: kompose convert - kompose.version: 1.21.0 (992df58d8) - creationTimestamp: null labels: io.kompose.service: worker-service name: worker-service spec: ports: - - name: {{ .Values.services.worker.port | quote }} - port: {{ .Values.services.worker.port }} - targetPort: {{ .Values.services.worker.port }} + - name: { { .Values.services.worker.port | quote } } + port: { { .Values.services.worker.port } } + targetPort: { { .Values.services.worker.port } } selector: io.kompose.service: worker-service status: diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index 857067d0f1..5c750ce2ca 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -2,11 +2,6 @@ # This is a YAML-formatted file. # Declare variables to be passed into your templates. -image: - pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. - tag: "" - imagePullSecrets: [] nameOverride: "" # fullnameOverride: "" @@ -41,14 +36,10 @@ service: ingress: enabled: true - aws: false - nginx: true - certificateArn: "" className: "" annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/client-max-body-size: 150M - nginx.ingress.kubernetes.io/proxy-body-size: 50m + # nginx.ingress.kubernetes.io/client-max-body-size: 150M + # nginx.ingress.kubernetes.io/proxy-body-size: 50m hosts: - host: # change if using custom domain paths: @@ -60,6 +51,10 @@ ingress: port: number: 10000 +awsAlbIngress: + enabled: false + certificateArn: "" + autoscaling: enabled: false minReplicas: 1 @@ -111,7 +106,6 @@ globals: # globalAgentNoProxy: services: - budibaseVersion: latest dns: cluster.local # tlsRejectUnauthorized: 0 @@ -145,10 +139,6 @@ services: scheme: HTTP failureThreshold: 3 periodSeconds: 5 - # annotations: - # co.elastic.logs/module: nginx - # co.elastic.logs/fileset.stdout: access - # co.elastic.logs/fileset.stderr: error apps: port: 4002 @@ -280,7 +270,6 @@ couchdb: # adminUsername: budibase # adminPassword: budibase - # adminHash: -pbkdf2-this_is_not_necessarily_secure_either # cookieAuthSecret: admin ## When enabled, will deploy a networkpolicy that allows CouchDB pods to @@ -310,16 +299,12 @@ couchdb: ## The CouchDB image image: - repository: couchdb - tag: 3.1.1 + repository: budibase/couchdb + tag: 3.2.1 pullPolicy: IfNotPresent ## Experimental integration with Lucene-powered fulltext search - enableSearch: true - searchImage: - repository: kocolosk/couchdb-search - tag: 0.2.0 - pullPolicy: IfNotPresent + enableSearch: false initImage: repository: busybox From 08d45820a2d752de1e26e420a607aca649800ed4 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Tue, 21 Nov 2023 10:29:27 +0000 Subject: [PATCH 02/63] Update chart dependencies, fix some syntactic errors. --- charts/budibase/Chart.lock | 9 +- charts/budibase/Chart.yaml | 2 +- charts/budibase/charts/couchdb-3.3.4.tgz | Bin 9668 -> 0 bytes charts/budibase/charts/couchdb-4.3.0.tgz | Bin 0 -> 14629 bytes .../budibase/charts/ingress-nginx-4.0.13.tgz | Bin 27766 -> 0 bytes .../templates/app-service-service.yaml | 6 +- .../templates/proxy-service-service.yaml | 6 +- .../templates/worker-service-service.yaml | 6 +- charts/budibase/values.yaml | 175 +++++++++++++++--- 9 files changed, 159 insertions(+), 45 deletions(-) delete mode 100644 charts/budibase/charts/couchdb-3.3.4.tgz create mode 100644 charts/budibase/charts/couchdb-4.3.0.tgz delete mode 100644 charts/budibase/charts/ingress-nginx-4.0.13.tgz diff --git a/charts/budibase/Chart.lock b/charts/budibase/Chart.lock index 75b9de07b5..3ee752a362 100644 --- a/charts/budibase/Chart.lock +++ b/charts/budibase/Chart.lock @@ -1,9 +1,6 @@ dependencies: - name: couchdb repository: https://apache.github.io/couchdb-helm - version: 3.3.4 -- name: ingress-nginx - repository: https://kubernetes.github.io/ingress-nginx - version: 4.0.13 -digest: sha256:20892705c2d8e64c98257d181063a514ac55013e2b43399a6e54868a97f97845 -generated: "2021-12-30T18:55:30.878411Z" + version: 4.3.0 +digest: sha256:94449a7f195b186f5af33ec5aa66d58b36bede240fae710f021ca87837b30606 +generated: "2023-11-20T17:43:02.777596Z" diff --git a/charts/budibase/Chart.yaml b/charts/budibase/Chart.yaml index b62c1ca248..e2c9378f2c 100644 --- a/charts/budibase/Chart.yaml +++ b/charts/budibase/Chart.yaml @@ -17,6 +17,6 @@ version: 0.0.0 appVersion: 0.0.0 dependencies: - name: couchdb - version: 4.4.5 + version: 4.3.0 repository: https://apache.github.io/couchdb-helm condition: services.couchdb.enabled diff --git a/charts/budibase/charts/couchdb-3.3.4.tgz b/charts/budibase/charts/couchdb-3.3.4.tgz deleted file mode 100644 index f7ebfd3e9653ef2e0fba07de4e498b46311de606..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9668 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 GzyJWm|L4yD diff --git a/charts/budibase/charts/couchdb-4.3.0.tgz b/charts/budibase/charts/couchdb-4.3.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..d3cce28ee630a20d8bdc0051746b6e2dac95f7fd GIT binary patch literal 14629 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 K0YMK1@&5p2<=4Le diff --git a/charts/budibase/templates/app-service-service.yaml b/charts/budibase/templates/app-service-service.yaml index 390fbfe782..6d19590d45 100644 --- a/charts/budibase/templates/app-service-service.yaml +++ b/charts/budibase/templates/app-service-service.yaml @@ -6,9 +6,9 @@ metadata: name: app-service spec: ports: - - name: {{ .Values.services.apps.port | quote }} - port: {{ .Values.services.apps.port }} - targetPort: {{ .Values.services.apps.port }} + - name: {{ .Values.services.apps.port | quote }} + port: {{ .Values.services.apps.port }} + targetPort: {{ .Values.services.apps.port }} selector: io.kompose.service: app-service status: diff --git a/charts/budibase/templates/proxy-service-service.yaml b/charts/budibase/templates/proxy-service-service.yaml index e5dde301fa..988c540599 100644 --- a/charts/budibase/templates/proxy-service-service.yaml +++ b/charts/budibase/templates/proxy-service-service.yaml @@ -6,9 +6,9 @@ metadata: name: proxy-service spec: ports: - - name: { { .Values.services.proxy.port | quote } } - port: { { .Values.services.proxy.port } } - targetPort: { { .Values.services.proxy.port } } + - name: {{ .Values.services.proxy.port | quote }} + port: {{ .Values.services.proxy.port }} + targetPort: {{ .Values.services.proxy.port }} selector: app.kubernetes.io/name: budibase-proxy status: diff --git a/charts/budibase/templates/worker-service-service.yaml b/charts/budibase/templates/worker-service-service.yaml index 214ee7191e..c5f56ba205 100644 --- a/charts/budibase/templates/worker-service-service.yaml +++ b/charts/budibase/templates/worker-service-service.yaml @@ -6,9 +6,9 @@ metadata: name: worker-service spec: ports: - - name: { { .Values.services.worker.port | quote } } - port: { { .Values.services.worker.port } } - targetPort: { { .Values.services.worker.port } } + - name: {{ .Values.services.worker.port | quote }} + port: {{ .Values.services.worker.port }} + targetPort: {{ .Values.services.worker.port }} selector: io.kompose.service: worker-service status: diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index 5c750ce2ca..9409363bbb 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -261,22 +261,45 @@ services: # Override values in couchDB subchart couchdb: - ## clusterSize is the initial size of the CouchDB cluster. - clusterSize: 1 + # -- the initial number of nodes in the CouchDB cluster. + clusterSize: 3 + + # -- If allowAdminParty is enabled the cluster will start up without any database + # administrator account; i.e., all users will be granted administrative + # access. Otherwise, the system will look for a Secret called + # -couchdb containing `adminUsername`, `adminPassword` and + # `cookieAuthSecret` keys. See the `createAdminSecret` flag. + # ref: https://kubernetes.io/docs/concepts/configuration/secret/ allowAdminParty: false - # Secret Management + # -- If createAdminSecret is enabled a Secret called -couchdb will + # be created containing auto-generated credentials. Users who prefer to set + # these values themselves have a couple of options: + # + # 1) The `adminUsername`, `adminPassword`, `adminHash`, and `cookieAuthSecret` + # can be defined directly in the chart's values. Note that all of a chart's + # values are currently stored in plaintext in a ConfigMap in the tiller + # namespace. + # + # 2) This flag can be disabled and a Secret with the required keys can be + # created ahead of time. createAdminSecret: true - # adminUsername: budibase - # adminPassword: budibase - # cookieAuthSecret: admin + adminUsername: admin + # adminPassword: this_is_not_secure + # adminHash: -pbkdf2-this_is_not_necessarily_secure_either + # cookieAuthSecret: neither_is_this ## When enabled, will deploy a networkpolicy that allows CouchDB pods to ## communicate with each other for clustering and ingress on port 5984 networkPolicy: enabled: true + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + # Use a service account serviceAccount: enabled: true @@ -285,25 +308,28 @@ couchdb: # imagePullSecrets: # - name: myimagepullsecret - ## The storage volume used by each Pod in the StatefulSet. If a - ## persistentVolume is not enabled, the Pods will use `emptyDir` ephemeral - ## local storage. Setting the storageClass attribute to "-" disables dynamic - ## provisioning of Persistent Volumes; leaving it unset will invoke the default - ## provisioner. + # -- The storage volume used by each Pod in the StatefulSet. If a + # persistentVolume is not enabled, the Pods will use `emptyDir` ephemeral + # local storage. Setting the storageClass attribute to "-" disables dynamic + # provisioning of Persistent Volumes; leaving it unset will invoke the default + # provisioner. persistentVolume: enabled: false + # NOTE: the number of existing claims must match the cluster size + existingClaims: [] + annotations: {} accessModes: - ReadWriteOnce size: 10Gi - storageClass: "" + # storageClass: "-" ## The CouchDB image image: repository: budibase/couchdb - tag: 3.2.1 + tag: v3.2.1 pullPolicy: IfNotPresent - ## Experimental integration with Lucene-powered fulltext search + # -- Flip this to flag to include the Search container in each Pod enableSearch: false initImage: @@ -316,19 +342,52 @@ couchdb: ## `OrderedReady` podManagementPolicy: Parallel + ## To better tolerate Node failures, we can prevent Kubernetes scheduler from + ## assigning more than one Pod of CouchDB StatefulSet per Node using podAntiAffinity. + affinity: + {} + # podAntiAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # - labelSelector: + # matchExpressions: + # - key: "app" + # operator: In + # values: + # - couchdb + # topologyKey: "kubernetes.io/hostname" + + ## To control how Pods are spread across your cluster among failure-domains such as regions, + ## zones, nodes, and other user-defined topology domains use topologySpreadConstraints. + topologySpreadConstraints: + {} + # topologySpreadConstraints: + # - maxSkew: 1 + # topologyKey: "topology.kubernetes.io/zone" + # whenUnsatisfiable: ScheduleAnyway + # labelSelector: + # matchLabels: + # app: couchdb + + ## Optional pod labels + labels: {} + ## Optional pod annotations annotations: {} ## Optional tolerations tolerations: [] - affinity: {} - + ## A StatefulSet requires a headless Service to establish the stable network + ## identities of the Pods, and that Service is created automatically by this + ## chart without any additional configuration. The Service block below refers + ## to a second Service that governs how clients connect to the CouchDB cluster. service: - # annotations: + annotations: {} enabled: true type: ClusterIP externalPort: 5984 + targetPort: 5984 + labels: {} ## An Ingress resource can provide name-based virtual hosting and TLS ## termination among other things for CouchDB deployments which are accessed @@ -336,11 +395,12 @@ couchdb: ## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/ ingress: enabled: false + # className: nginx hosts: - chart-example.local path: / annotations: - [] + {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" tls: @@ -360,21 +420,35 @@ couchdb: # cpu: 56 # memory: 256Gi - ## erlangFlags is a map that is passed to the Erlang VM as flags using the - ## ERL_FLAGS env. `name` and `setcookie` flags are minimally required to - ## establish connectivity between cluster nodes. - ## ref: http://erlang.org/doc/man/erl.html#init_flags + ## Optional resource requests and limits for the CouchDB init container + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + initResources: + {} + # requests: + # cpu: 100m + # memory: 128Mi + # limits: + # cpu: 500m + # memory: 128Mi + + # -- erlangFlags is a map that is passed to the Erlang VM as flags using the + # ERL_FLAGS env. The `name` flag is required to establish connectivity + # between cluster nodes. + # ref: http://erlang.org/doc/man/erl.html#init_flags erlangFlags: name: couchdb - setcookie: monster + # Older versions of the official CouchDB image (anything prior to 3.2.1) + # do not act on the COUCHDB_ERLANG_COOKIE environment variable, so if you + # want to cluster these deployments it's necessary to pass in a cookie here + # setcookie: make-something-up - ## couchdbConfig will override default CouchDB configuration settings. - ## The contents of this map are reformatted into a .ini file laid down - ## by a ConfigMap object. - ## ref: http://docs.couchdb.org/en/latest/config/index.html + # -- couchdbConfig will override default CouchDB configuration settings. + # The contents of this map are reformatted into a .ini file laid down + # by a ConfigMap object. + # ref: http://docs.couchdb.org/en/latest/config/index.html couchdbConfig: couchdb: - uuid: budibase-couchdb # REQUIRED: Unique identifier for this CouchDB server instance + uuid: decafbaddecafbaddecafbaddecafbad # Unique identifier for this CouchDB server instance # cluster: # q: 8 # Create 8 shards for each database chttpd: @@ -382,6 +456,9 @@ couchdb: # chttpd.require_valid_user disables all the anonymous requests to the port # 5984 when is set to true. require_valid_user: false + # required to use Fauxton if chttpd.require_valid_user is set to true + # httpd: + # WWW-Authenticate: "Basic realm=\"administrator\"" # Kubernetes local cluster domain. # This is used to generate FQDNs for peers when joining the CouchDB cluster. @@ -390,16 +467,56 @@ couchdb: ## Configure liveness and readiness probe values ## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes - # FOR COUCHDB livenessProbe: + enabled: true failureThreshold: 3 initialDelaySeconds: 0 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 readinessProbe: + enabled: true failureThreshold: 3 initialDelaySeconds: 0 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 + + # Control an optional pod disruption budget + podDisruptionBudget: + # toggle creation of pod disruption budget, disabled by default + enabled: false + # minAvailable: 1 + maxUnavailable: 1 + + # CouchDB 3.2.0 adds in a metrics endpoint on the path `/_node/_local/_prometheus`. + # Optionally, a standalone, unauthenticated port can be exposed for these metrics. + prometheusPort: + enabled: false + bind_address: "0.0.0.0" + port: 17986 + + # Configure arbitrary sidecar containers for CouchDB pods created by the + # StatefulSet + sidecars: + {} + # - name: foo + # image: "busybox" + # imagePullPolicy: IfNotPresent + # resources: + # requests: + # cpu: "0.1" + # memory: 10Mi + # command: ['echo "foo";'] + # volumeMounts: + # - name: database-storage + # mountPath: /opt/couchdb/data/ + + # Placement manager to annotate each document in the nodes DB with "zone" attribute + # recording the zone where node has been scheduled + # Ref: https://docs.couchdb.org/en/stable/cluster/sharding.html#specifying-database-placement + placementConfig: + enabled: false + image: + repository: caligrafix/couchdb-autoscaler-placement-manager + tag: 0.1.0 From b7a704d06e1a6dc57d9967aa63468e2fb2998bbb Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Tue, 21 Nov 2023 17:32:58 +0000 Subject: [PATCH 03/63] Remove -name param from CouchDB image. Clouseau works without it and it fixes the image in Kubernetes. --- hosting/couchdb/clouseau/clouseau.ini | 3 --- hosting/couchdb/couch/vm.args | 1 - 2 files changed, 4 deletions(-) diff --git a/hosting/couchdb/clouseau/clouseau.ini b/hosting/couchdb/clouseau/clouseau.ini index 578a5acafa..9e03f8bc6e 100644 --- a/hosting/couchdb/clouseau/clouseau.ini +++ b/hosting/couchdb/clouseau/clouseau.ini @@ -1,8 +1,5 @@ [clouseau] -; the name of the Erlang node created by the service, leave this unchanged -name=clouseau@127.0.0.1 - ; set this to the same distributed Erlang cookie used by the CouchDB nodes cookie=monster diff --git a/hosting/couchdb/couch/vm.args b/hosting/couchdb/couch/vm.args index e9e4416863..33873b91a7 100644 --- a/hosting/couchdb/couch/vm.args +++ b/hosting/couchdb/couch/vm.args @@ -11,7 +11,6 @@ # the License. # erlang cookie for clouseau security --name couchdb@127.0.0.1 -setcookie monster # Ensure that the Erlang VM listens on a known port From 4af7bf459a64e6c041c1ac710e58c38cc1f7f386 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Tue, 21 Nov 2023 17:35:23 +0000 Subject: [PATCH 04/63] Remove extraneous creation timestamp. --- charts/budibase/templates/proxy-service-deployment.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/charts/budibase/templates/proxy-service-deployment.yaml b/charts/budibase/templates/proxy-service-deployment.yaml index 26f4265e4c..2e23f12793 100644 --- a/charts/budibase/templates/proxy-service-deployment.yaml +++ b/charts/budibase/templates/proxy-service-deployment.yaml @@ -24,7 +24,6 @@ spec: {{ if .Values.services.proxy.templateAnnotations }} {{- toYaml .Values.services.proxy.templateAnnotations | indent 8 -}} {{ end }} - creationTimestamp: null labels: app.kubernetes.io/name: budibase-proxy {{ if .Values.services.proxy.templateLabels }} From 423fa68f34007d36c83a51b3732d6fd1be8c7fb1 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 22 Nov 2023 10:35:53 +0000 Subject: [PATCH 05/63] Make the CouchDB image use the right directory on Kubernetes. --- hosting/couchdb/build-target-paths.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hosting/couchdb/build-target-paths.sh b/hosting/couchdb/build-target-paths.sh index 34227011f4..8bad974a1f 100644 --- a/hosting/couchdb/build-target-paths.sh +++ b/hosting/couchdb/build-target-paths.sh @@ -18,6 +18,11 @@ if [[ "${TARGETBUILD}" = "aas" ]]; then /etc/init.d/ssh restart sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini +elif [[ -n $KUBERNETES_SERVICE_HOST ]]; then + # In Kubernetes the directory /opt/couchdb/data has a persistent volume + # mount for storing database data. + sed -i "s#DATA_DIR#/opt/couchdb/data#g" /opt/clouseau/clouseau.ini + sed -i "s#DATA_DIR#/opt/couchdb/data#g" /opt/couchdb/etc/local.ini else sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini From 08ae4ef1328e87f8f420f0e6a494e2e4870be37f Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 22 Nov 2023 16:20:47 +0000 Subject: [PATCH 06/63] Fix the single image build working on Kubernetes. --- hosting/couchdb/build-target-paths.sh | 5 ++++- hosting/couchdb/runner.sh | 1 + hosting/single/Dockerfile.v2 | 1 - hosting/single/runner.sh | 4 ++-- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/hosting/couchdb/build-target-paths.sh b/hosting/couchdb/build-target-paths.sh index 8bad974a1f..90d9130cd4 100644 --- a/hosting/couchdb/build-target-paths.sh +++ b/hosting/couchdb/build-target-paths.sh @@ -18,7 +18,10 @@ if [[ "${TARGETBUILD}" = "aas" ]]; then /etc/init.d/ssh restart sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini -elif [[ -n $KUBERNETES_SERVICE_HOST ]]; then +elif [[ "${TARGETBUILD}" = "single" ]]; then + sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini + sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini +elif [[ -n $KUBERNETES_SERVICE_HOST ]]; then # In Kubernetes the directory /opt/couchdb/data has a persistent volume # mount for storing database data. sed -i "s#DATA_DIR#/opt/couchdb/data#g" /opt/clouseau/clouseau.ini diff --git a/hosting/couchdb/runner.sh b/hosting/couchdb/runner.sh index 4102d2a751..32951cb539 100644 --- a/hosting/couchdb/runner.sh +++ b/hosting/couchdb/runner.sh @@ -1,6 +1,7 @@ #!/bin/bash DATA_DIR=${DATA_DIR:-/data} + mkdir -p ${DATA_DIR} mkdir -p ${DATA_DIR}/couch/{dbs,views} mkdir -p ${DATA_DIR}/search diff --git a/hosting/single/Dockerfile.v2 b/hosting/single/Dockerfile.v2 index ec03a1b5a2..dd61c779b5 100644 --- a/hosting/single/Dockerfile.v2 +++ b/hosting/single/Dockerfile.v2 @@ -94,7 +94,6 @@ RUN chmod +x ./healthcheck.sh # For Azure App Service install SSH & point data locations to /home COPY hosting/single/ssh/sshd_config /etc/ COPY hosting/single/ssh/ssh_setup.sh /tmp -RUN /build-target-paths.sh # setup letsencrypt certificate diff --git a/hosting/single/runner.sh b/hosting/single/runner.sh index 87201c95c0..f4b2b5b127 100644 --- a/hosting/single/runner.sh +++ b/hosting/single/runner.sh @@ -22,11 +22,11 @@ declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONME # Azure App Service customisations if [[ "${TARGETBUILD}" = "aas" ]]; then - DATA_DIR="${DATA_DIR:-/home}" + export DATA_DIR="${DATA_DIR:-/home}" WEBSITES_ENABLE_APP_SERVICE_STORAGE=true /etc/init.d/ssh start else - DATA_DIR=${DATA_DIR:-/data} + export DATA_DIR=${DATA_DIR:-/data} fi mkdir -p ${DATA_DIR} # Mount NFS or GCP Filestore if env vars exist for it From 40fd056237099f53d627d955e2d0090ed3f4885b Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 23 Nov 2023 11:40:21 +0000 Subject: [PATCH 07/63] Make Clouseau work in both single image and Helm deployments. --- hosting/couchdb/Dockerfile | 3 +- hosting/couchdb/build-target-paths.sh | 32 --------------------- hosting/couchdb/clouseau/clouseau.ini | 3 ++ hosting/couchdb/couch/vm.args | 5 ++-- hosting/couchdb/runner.sh | 41 +++++++++++++++++++++++++-- hosting/single/Dockerfile | 1 - hosting/single/Dockerfile.v2 | 2 +- 7 files changed, 47 insertions(+), 40 deletions(-) delete mode 100644 hosting/couchdb/build-target-paths.sh diff --git a/hosting/couchdb/Dockerfile b/hosting/couchdb/Dockerfile index 792856cac7..f83df7038b 100644 --- a/hosting/couchdb/Dockerfile +++ b/hosting/couchdb/Dockerfile @@ -29,7 +29,6 @@ WORKDIR /opt/couchdb ADD couch/vm.args couch/local.ini ./etc/ WORKDIR / -ADD build-target-paths.sh . ADD runner.sh ./bbcouch-runner.sh -RUN chmod +x ./bbcouch-runner.sh /opt/clouseau/bin/clouseau ./build-target-paths.sh +RUN chmod +x ./bbcouch-runner.sh /opt/clouseau/bin/clouseau CMD ["./bbcouch-runner.sh"] diff --git a/hosting/couchdb/build-target-paths.sh b/hosting/couchdb/build-target-paths.sh deleted file mode 100644 index 90d9130cd4..0000000000 --- a/hosting/couchdb/build-target-paths.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -echo ${TARGETBUILD} > /buildtarget.txt -if [[ "${TARGETBUILD}" = "aas" ]]; then - # Azure AppService uses /home for persistent data & SSH on port 2222 - DATA_DIR="${DATA_DIR:-/home}" - WEBSITES_ENABLE_APP_SERVICE_STORAGE=true - mkdir -p $DATA_DIR/{search,minio,couch} - mkdir -p $DATA_DIR/couch/{dbs,views} - chown -R couchdb:couchdb $DATA_DIR/couch/ - apt update - apt-get install -y openssh-server - echo "root:Docker!" | chpasswd - mkdir -p /tmp - chmod +x /tmp/ssh_setup.sh \ - && (sleep 1;/tmp/ssh_setup.sh 2>&1 > /dev/null) - cp /etc/sshd_config /etc/ssh/sshd_config - /etc/init.d/ssh restart - sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini - sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini -elif [[ "${TARGETBUILD}" = "single" ]]; then - sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini - sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini -elif [[ -n $KUBERNETES_SERVICE_HOST ]]; then - # In Kubernetes the directory /opt/couchdb/data has a persistent volume - # mount for storing database data. - sed -i "s#DATA_DIR#/opt/couchdb/data#g" /opt/clouseau/clouseau.ini - sed -i "s#DATA_DIR#/opt/couchdb/data#g" /opt/couchdb/etc/local.ini -else - sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini - sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini -fi \ No newline at end of file diff --git a/hosting/couchdb/clouseau/clouseau.ini b/hosting/couchdb/clouseau/clouseau.ini index 9e03f8bc6e..578a5acafa 100644 --- a/hosting/couchdb/clouseau/clouseau.ini +++ b/hosting/couchdb/clouseau/clouseau.ini @@ -1,5 +1,8 @@ [clouseau] +; the name of the Erlang node created by the service, leave this unchanged +name=clouseau@127.0.0.1 + ; set this to the same distributed Erlang cookie used by the CouchDB nodes cookie=monster diff --git a/hosting/couchdb/couch/vm.args b/hosting/couchdb/couch/vm.args index 33873b91a7..ea2b4d26ae 100644 --- a/hosting/couchdb/couch/vm.args +++ b/hosting/couchdb/couch/vm.args @@ -11,6 +11,7 @@ # the License. # erlang cookie for clouseau security +-name couchdb@127.0.0.1 -setcookie monster # Ensure that the Erlang VM listens on a known port @@ -18,8 +19,8 @@ -kernel inet_dist_listen_max 9100 # Tell kernel and SASL not to log anything --kernel error_logger silent --sasl sasl_error_logger false +# -kernel error_logger silent +# -sasl sasl_error_logger false # Use kernel poll functionality if supported by emulator +K true diff --git a/hosting/couchdb/runner.sh b/hosting/couchdb/runner.sh index 32951cb539..2e4d26122f 100644 --- a/hosting/couchdb/runner.sh +++ b/hosting/couchdb/runner.sh @@ -6,10 +6,47 @@ mkdir -p ${DATA_DIR} mkdir -p ${DATA_DIR}/couch/{dbs,views} mkdir -p ${DATA_DIR}/search chown -R couchdb:couchdb ${DATA_DIR}/couch -/build-target-paths.sh + +echo ${TARGETBUILD} > /buildtarget.txt +if [[ "${TARGETBUILD}" = "aas" ]]; then + # Azure AppService uses /home for persistent data & SSH on port 2222 + DATA_DIR="${DATA_DIR:-/home}" + WEBSITES_ENABLE_APP_SERVICE_STORAGE=true + mkdir -p $DATA_DIR/{search,minio,couch} + mkdir -p $DATA_DIR/couch/{dbs,views} + chown -R couchdb:couchdb $DATA_DIR/couch/ + apt update + apt-get install -y openssh-server + echo "root:Docker!" | chpasswd + mkdir -p /tmp + chmod +x /tmp/ssh_setup.sh \ + && (sleep 1;/tmp/ssh_setup.sh 2>&1 > /dev/null) + cp /etc/sshd_config /etc/ssh/sshd_config + /etc/init.d/ssh restart + sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini + sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini +elif [[ "${TARGETBUILD}" = "single" ]]; then + sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini + sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini +elif [[ -n $KUBERNETES_SERVICE_HOST ]]; then + # In Kubernetes the directory /opt/couchdb/data has a persistent volume + # mount for storing database data. + sed -i "s#DATA_DIR#/opt/couchdb/data#g" /opt/clouseau/clouseau.ini + sed -i "s#DATA_DIR#/opt/couchdb/data#g" /opt/couchdb/etc/local.ini + sed -i "s/^-name .*$//g" /opt/couchdb/etc/vm.args +else + sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini + sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini +fi + /opt/clouseau/bin/clouseau > /dev/stdout 2>&1 & /docker-entrypoint.sh /opt/couchdb/bin/couchdb & -sleep 10 + +while [[ $(curl -s -w "%{http_code}\n" http://localhost:5984/_up -o /dev/null) -ne 200 ]]; do + echo 'Waiting for CouchDB to start...'; + sleep 5; +done + curl -X PUT http://${COUCHDB_USER}:${COUCHDB_PASSWORD}@localhost:5984/_users curl -X PUT http://${COUCHDB_USER}:${COUCHDB_PASSWORD}@localhost:5984/_replicator sleep infinity \ No newline at end of file diff --git a/hosting/single/Dockerfile b/hosting/single/Dockerfile index c7b90dbdc4..84c3b824fb 100644 --- a/hosting/single/Dockerfile +++ b/hosting/single/Dockerfile @@ -81,7 +81,6 @@ RUN chmod +x ./healthcheck.sh # For Azure App Service install SSH & point data locations to /home ADD hosting/single/ssh/sshd_config /etc/ ADD hosting/single/ssh/ssh_setup.sh /tmp -RUN /build-target-paths.sh # cleanup cache RUN yarn cache clean -f diff --git a/hosting/single/Dockerfile.v2 b/hosting/single/Dockerfile.v2 index dd61c779b5..afbb377a4b 100644 --- a/hosting/single/Dockerfile.v2 +++ b/hosting/single/Dockerfile.v2 @@ -39,7 +39,7 @@ COPY packages/worker/pm2.config.js packages/worker/pm2.config.js COPY packages/string-templates packages/string-templates -FROM budibase/couchdb as runner +FROM samwho/test as runner ARG TARGETARCH ENV TARGETARCH $TARGETARCH ENV NODE_MAJOR 18 From c50770da0a31e1104844cb2d7bf0b9e6a80656bf Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 23 Nov 2023 17:06:52 +0000 Subject: [PATCH 08/63] Tidy up the chart. --- charts/budibase/values.yaml | 8 +++++--- hosting/couchdb/couch/vm.args | 4 ++-- hosting/single/Dockerfile.v2 | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index 9409363bbb..5df6e980f0 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -262,7 +262,7 @@ services: # Override values in couchDB subchart couchdb: # -- the initial number of nodes in the CouchDB cluster. - clusterSize: 3 + clusterSize: 1 # -- If allowAdminParty is enabled the cluster will start up without any database # administrator account; i.e., all users will be granted administrative @@ -329,7 +329,9 @@ couchdb: tag: v3.2.1 pullPolicy: IfNotPresent - # -- Flip this to flag to include the Search container in each Pod + # This should remain false. We ship Clouseau ourselves as part of the + # budibase/couchdb image, and it's not possible to disable it because it's a + # core part of the Budibase experience. enableSearch: false initImage: @@ -448,7 +450,7 @@ couchdb: # ref: http://docs.couchdb.org/en/latest/config/index.html couchdbConfig: couchdb: - uuid: decafbaddecafbaddecafbaddecafbad # Unique identifier for this CouchDB server instance + uuid: budibase-couchdb # Unique identifier for this CouchDB server instance # cluster: # q: 8 # Create 8 shards for each database chttpd: diff --git a/hosting/couchdb/couch/vm.args b/hosting/couchdb/couch/vm.args index ea2b4d26ae..e9e4416863 100644 --- a/hosting/couchdb/couch/vm.args +++ b/hosting/couchdb/couch/vm.args @@ -19,8 +19,8 @@ -kernel inet_dist_listen_max 9100 # Tell kernel and SASL not to log anything -# -kernel error_logger silent -# -sasl sasl_error_logger false +-kernel error_logger silent +-sasl sasl_error_logger false # Use kernel poll functionality if supported by emulator +K true diff --git a/hosting/single/Dockerfile.v2 b/hosting/single/Dockerfile.v2 index afbb377a4b..dd61c779b5 100644 --- a/hosting/single/Dockerfile.v2 +++ b/hosting/single/Dockerfile.v2 @@ -39,7 +39,7 @@ COPY packages/worker/pm2.config.js packages/worker/pm2.config.js COPY packages/string-templates packages/string-templates -FROM samwho/test as runner +FROM budibase/couchdb as runner ARG TARGETARCH ENV TARGETARCH $TARGETARCH ENV NODE_MAJOR 18 From 6674bf139b08375d00f75c2380bf64afaaa8bf79 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 27 Nov 2023 17:12:13 +0000 Subject: [PATCH 09/63] Expand chart documentation, fix HPA definition. --- charts/budibase/README.md | 194 +++++- charts/budibase/README.md.gotmpl | 92 +++ .../budibase/templates/app-service-hpa.yaml | 32 + charts/budibase/templates/hpa.yaml | 28 - .../budibase/templates/proxy-service-hpa.yaml | 32 + .../templates/worker-service-hpa.yaml | 32 + charts/budibase/values.yaml | 598 ++++++++---------- 7 files changed, 630 insertions(+), 378 deletions(-) create mode 100644 charts/budibase/README.md.gotmpl create mode 100644 charts/budibase/templates/app-service-hpa.yaml delete mode 100644 charts/budibase/templates/hpa.yaml create mode 100644 charts/budibase/templates/proxy-service-hpa.yaml create mode 100644 charts/budibase/templates/worker-service-hpa.yaml diff --git a/charts/budibase/README.md b/charts/budibase/README.md index efa78ba75c..961776b977 100644 --- a/charts/budibase/README.md +++ b/charts/budibase/README.md @@ -1,39 +1,193 @@ -# Budibase +# budibase -[Budibase](https://budibase.com/) Budibase is an open source low-code platform, helping thousands of teams build apps for their workplace in minutes. - -## TL;DR; -```console -$ cd chart -$ helm install budibase . -``` - -## Introduction - -This chart bootstraps a [Budibase](https://budibase.com/) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. +Budibase is an open source low-code platform, helping thousands of teams build apps for their workplace in minutes. ## Prerequisites -- helm v3 or above +- `helm` v3 or above - Kubernetes 1.4+ -- PV provisioner support in the underlying infrastructure (with persistence storage enabled) +- A storage controller (for `PersistentVolume` creation, if you want to ensure your data does not get lost when pods restart) ## Installing the Chart -To install the chart with the release name `budi-release`: +To install the chart from our repository: ```console -$ helm install budi-release . +$ helm repo add budibase https://budibase.github.io/budibase/ +$ helm repo update +$ helm install --create-namespace --namespace budibase budibase budibase/budibase ``` -The command deploys Budibase on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. +To install the chart from this repo: -> **Tip**: List all releases using `helm list` +```console +$ git clone git@github.com:budibase/budibase.git +$ cd budibase/charts/budibase +$ helm install --create-namespace --namespace budibase budibase . +``` + +## Example minimal configuration + +Here's an example `values.yaml` that would get a Budibase instance running in a home +cluster using an nginx ingress controller and NFS as cluster storage (basically one of our +staff's homelabs). + +
+ +```yaml +ingress: + enabled: true + annotations: + nginx.ingress.kubernetes.io/client-max-body-size: 150M + nginx.ingress.kubernetes.io/proxy-body-size: 50m + className: "nginx" + hosts: + - host: budibase.local # set this to whatever DNS name you'd use + paths: + - backend: + service: + name: proxy-service + port: + number: 10000 + path: / + pathType: Prefix + +couchdb: + persistentVolume: + enabled: true + storageClass: "nfs-client" + adminPassword: admin + + image: + repository: samwho/test + tag: latest + pullPolicy: Always + + objectStore: + storageClass: "nfs-client" + redis: + storageClass: "nfs-client" +``` + +If you wanted to use this when bringing up Budibase in your own cluster, you could save it +to your hard disk and run the following: + +```console +$ helm install --create-namespace --namespace budibase budibase . -f values.yaml +``` + +
+ +## Configuration reference + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Sets the affinity for all pods created by this chart. Should not ordinarily need to be changed. See for more information on affinity. | +| awsAlbIngress.certificateArn | string | `""` | If you're wanting to use HTTPS, you'll need to create an ACM certificate and specify the ARN here. | +| awsAlbIngress.enabled | bool | `false` | Whether to create an ALB Ingress resource pointing to the Budibase proxy. Requires the AWS ALB Ingress Controller. | +| couchdb.clusterSize | int | `1` | The number of replicas to run in the CouchDB cluster. We set this to 1 by default to make things simpler, but you can set it to 3 if you need a high-availability CouchDB cluster. | +| couchdb.couchdbConfig.couchdb.uuid | string | `"budibase-couchdb"` | Unique identifier for this CouchDB server instance. You shouldn't need to change this. | +| couchdb.image | object | `{}` | We use a custom CouchDB image for running Budibase and we don't support using any other CouchDB image. You shouldn't change this, and if you do we can't guarantee that Budibase will work. | +| globals.apiEncryptionKey | string | `""` | Used for encrypting API keys and environment variables when stored in the database. You don't need to set this if `createSecrets` is true. | +| globals.appVersion | string | `""` | The version of Budibase to deploy. Defaults to what's specified by {{ .Chart.AppVersion }}. Ends up being used as the image version tag for the apps, proxy, and worker images. | +| globals.automationMaxIterations | string | `"200"` | The maximum number of iterations allows for an automation loop step. You can read more about looping here: . | +| globals.budibaseEnv | string | `"PRODUCTION"` | Sets the environment variable BUDIBASE_ENVIRONMENT for the apps and worker pods. Should not ordinarily need to be changed. | +| globals.cookieDomain | string | `""` | Sets the domain attribute of the cookie that Budibase uses to store session information. See for details on why you might want to set this. | +| globals.createSecrets | bool | `true` | Create an internal API key, JWT secret, object store access key and secret, and store them in a Kubernetes `Secret`. | +| globals.enableAnalytics | string | `"1"` | Whether to enable analytics or not. You can read more about our analytics here: . | +| globals.google | object | `{"clientId":"","secret":""}` | Google OAuth settings. These can also be set in the Budibase UI, see for details. | +| globals.google.clientId | string | `""` | Client ID of your Google OAuth app. | +| globals.google.secret | string | `""` | Client secret of your Google OAuth app. | +| globals.httpMigrations | string | `"0"` | Whether or not to enable doing data migrations over the HTTP API. If this is set to "0", migrations are run on startup. You shouldn't ordinarily need to change this. | +| globals.internalApiKey | string | `""` | API key used for internal Budibase API calls. You don't need to set this if `createSecrets` is true. | +| globals.internalApiKeyFallback | string | `""` | A fallback value for `internalApiKey`. If you're rotating your encryption key, you can set this to the old value for the duration of the rotation. | +| globals.jwtSecret | string | `""` | Secret used for signing JWTs. You don't need to set this if `createSecrets` is true. | +| globals.jwtSecretFallback | string | `""` | A fallback value for `jwtSecret`. If you're rotating your JWT secret, you can set this to the old value for the duration of the rotation. | +| globals.multiTenancy | string | `"0"` | Whether to enable the multi-tenancy feature or not. "0" means an installation can only have one tenant. "1" allows multiple tenants. | +| globals.platformUrl | string | `""` | Set the `platformUrl` binding. You can also do this in Settings > Organisation if you are self-hosting. | +| globals.smtp.enabled | bool | `false` | Whether to enable SMTP or not. | +| globals.smtp.from | string | `""` | The email address to use in the "From:" field of emails sent by Budibase. | +| globals.smtp.host | string | `""` | The hostname of your SMTP server. | +| globals.smtp.password | string | `""` | The password to use when authenticating with your SMTP server. | +| globals.smtp.port | string | `"587"` | The port of your SMTP server. | +| globals.smtp.user | string | `""` | The username to use when authenticating with your SMTP server. | +| globals.tenantFeatureFlags | string | `"*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR"` | Sets what feature flags are enabled and for which tenants. Should not ordinarily need to be changed. | +| imagePullSecrets | list | `[]` | Passed to all pods created by this chart. Should not ordinarily need to be changed. | +| ingress.className | string | `""` | What ingress class to use. | +| ingress.enabled | bool | `true` | Whether to create an Ingress resource pointing to the Budibase proxy. | +| ingress.hosts | list | `[]` | Standard hosts block for the Ingress resource. Defaults to pointing to the Budibase proxy. | +| nameOverride | string | `""` | Override the name of the deploymen. Defaults to {{ .Chart.Name }}. | +| service.port | int | `10000` | Port to expose on the service. | +| service.type | string | `"ClusterIP"` | Service type for the service that points to the main Budibase proxy pod. | +| serviceAccount.annotations | object | `{}` | Annotations to add to the service account | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created | +| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | +| services.apps.autoscaling.enabled | bool | `false` | Whether to enable horizontal pod autoscaling for the apps service. | +| services.apps.autoscaling.maxReplicas | int | `10` | | +| services.apps.autoscaling.minReplicas | int | `1` | | +| services.apps.autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage for the apps service. Note that for autoscaling to work, you will need to have metrics-server configured, and resources set for the apps pods. | +| services.apps.httpLogging | int | `1` | Whether or not to log HTTP requests to the apps service. | +| services.apps.livenessProbe | object | HTTP health checks. | Liveness probe configuration for apps pods. You shouldn't need to change this, but if you want to you can find more information here: | +| services.apps.logLevel | string | `"info"` | The log level for the apps service. | +| services.apps.readinessProbe | object | HTTP health checks. | Readiness probe configuration for apps pods. You shouldn't need to change this, but if you want to you can find more information here: | +| services.apps.replicaCount | int | `1` | The number of apps replicas to run. | +| services.apps.resources | object | `{}` | The resources to use for apps pods. See for more information on how to set these. | +| services.apps.startupProbe | object | HTTP health checks. | Startup probe configuration for apps pods. You shouldn't need to change this, but if you want to you can find more information here: | +| services.couchdb.backup.enabled | bool | `false` | Whether or not to enable periodic CouchDB backups. This works by replicating to another CouchDB instance. | +| services.couchdb.backup.interval | string | `""` | Backup interval in seconds | +| services.couchdb.backup.resources | object | `{}` | The resources to use for CouchDB backup pods. See for more information on how to set these. | +| services.couchdb.backup.target | string | `""` | Target couchDB instance to back up to, either a hostname or an IP address. | +| services.couchdb.enabled | bool | `true` | Whether or not to spin up a CouchDB instance in your cluster. True by default, and the configuration for the CouchDB instance is under the `couchdb` key at the root of this file. You can see what options are available to you by looking at the official CouchDB Helm chart: . | +| services.couchdb.port | int | `5984` | | +| services.dns | string | `"cluster.local"` | The DNS suffix to use for service discovery. You only need to change this if you've configured your cluster to use a different DNS suffix. | +| services.objectStore.accessKey | string | `""` | AWS_ACCESS_KEY if using S3 | +| services.objectStore.browser | bool | `true` | Whether to enable the Minio web console or not. If you're exposing Minio to the Internet (via a custom Ingress record, for example), you should set this to false. If you're only exposing Minio to your cluster, you can leave this as true. | +| services.objectStore.cloudfront.cdn | string | `""` | Set the url of a distribution to enable cloudfront. | +| services.objectStore.cloudfront.privateKey64 | string | `""` | Base64 encoded private key for the above public key. | +| services.objectStore.cloudfront.publicKeyId | string | `""` | ID of public key stored in cloudfront. | +| services.objectStore.minio | bool | `true` | Set to false if using another object store, such as S3. You will need to set `services.objectStore.url` to point to your bucket if you do this. | +| services.objectStore.region | string | `""` | AWS_REGION if using S3 | +| services.objectStore.resources | object | `{}` | The resources to use for Minio pods. See for more information on how to set these. | +| services.objectStore.secretKey | string | `""` | AWS_SECRET_ACCESS_KEY if using S3 | +| services.objectStore.storage | string | `"100Mi"` | How much storage to give Minio in its PersistentVolumeClaim. | +| services.objectStore.storageClass | string | `""` | If defined, storageClassName: If set to "-", storageClassName: "", which disables dynamic provisioning If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. | +| services.objectStore.url | string | `"http://minio-service:9000"` | URL to use for object storage. Only change this if you're using an external object store, such as S3. Remember to set `minio: false` if you do this. | +| services.proxy.autoscaling.enabled | bool | `false` | Whether to enable horizontal pod autoscaling for the proxy service. | +| services.proxy.autoscaling.maxReplicas | int | `10` | | +| services.proxy.autoscaling.minReplicas | int | `1` | | +| services.proxy.autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage for the proxy service. Note that for autoscaling to work, you will need to have metrics-server configured, and resources set for the proxy pods. | +| services.proxy.livenessProbe | object | HTTP health checks. | Liveness probe configuration for proxy pods. You shouldn't need to change this, but if you want to you can find more information here: | +| services.proxy.readinessProbe | object | HTTP health checks. | Readiness probe configuration for proxy pods. You shouldn't need to change this, but if you want to you can find more information here: | +| services.proxy.replicaCount | int | `1` | The number of proxy replicas to run. | +| services.proxy.resources | object | `{}` | The resources to use for proxy pods. See for more information on how to set these. | +| services.proxy.startupProbe | object | HTTP health checks. | Startup probe configuration for proxy pods. You shouldn't need to change this, but if you want to you can find more information here: | +| services.redis.enabled | bool | `true` | Whether or not to deploy a Redis pod into your cluster. | +| services.redis.password | string | `"budibase"` | The password to use when connecting to Redis. It's recommended that you change this from the default if you're running Redis in-cluster. | +| services.redis.port | int | `6379` | Port to expose Redis on. | +| services.redis.resources | object | `{}` | The resources to use for Redis pods. See for more information on how to set these. | +| services.redis.storage | string | `"100Mi"` | How much persistent storage to allocate to Redis. | +| services.redis.storageClass | string | `""` | If defined, storageClassName: If set to "-", storageClassName: "", which disables dynamic provisioning If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. | +| services.redis.url | string | `""` | If you choose to run Redis externally to this chart, you can specify the connection details here. | +| services.worker.autoscaling.enabled | bool | `false` | Whether to enable horizontal pod autoscaling for the worker service. | +| services.worker.autoscaling.maxReplicas | int | `10` | | +| services.worker.autoscaling.minReplicas | int | `1` | | +| services.worker.autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage for the worker service. Note that for autoscaling to work, you will need to have metrics-server configured, and resources set for the worker pods. | +| services.worker.httpLogging | int | `1` | Whether or not to log HTTP requests to the worker service. | +| services.worker.livenessProbe | object | HTTP health checks. | Liveness probe configuration for worker pods. You shouldn't need to change this, but if you want to you can find more information here: | +| services.worker.logLevel | string | `"info"` | The log level for the worker service. | +| services.worker.readinessProbe | object | HTTP health checks. | Readiness probe configuration for worker pods. You shouldn't need to change this, but if you want to you can find more information here: | +| services.worker.replicaCount | int | `1` | The number of worker replicas to run. | +| services.worker.resources | object | `{}` | The resources to use for worker pods. See for more information on how to set these. | +| services.worker.startupProbe | object | HTTP health checks. | Startup probe configuration for worker pods. You shouldn't need to change this, but if you want to you can find more information here: | +| tolerations | list | `[]` | Sets the tolerations for all pods created by this chart. Should not ordinarily need to be changed. See for more information on tolerations. | ## Uninstalling the Chart -To uninstall/delete the `my-release` deployment: +To uninstall the chart, assuming you named the release `budibase` (both commands in the installation section do so): ```console -$ helm delete my-release +$ helm uninstall --namespace budibase budibase ``` + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.11.3](https://github.com/norwoodj/helm-docs/releases/v1.11.3) diff --git a/charts/budibase/README.md.gotmpl b/charts/budibase/README.md.gotmpl new file mode 100644 index 0000000000..eacd832e74 --- /dev/null +++ b/charts/budibase/README.md.gotmpl @@ -0,0 +1,92 @@ +{{ template "chart.header" . }} +{{ template "chart.description" . }} + +## Prerequisites + +- `helm` v3 or above +- Kubernetes 1.4+ +- A storage controller (for `PersistentVolume` creation, if you want to ensure your data does not get lost when pods restart) + +## Installing the Chart + +To install the chart from our repository: + +```console +$ helm repo add budibase https://budibase.github.io/budibase/ +$ helm repo update +$ helm install --create-namespace --namespace budibase budibase budibase/budibase +``` + +To install the chart from this repo: + +```console +$ git clone git@github.com:budibase/budibase.git +$ cd budibase/charts/budibase +$ helm install --create-namespace --namespace budibase budibase . +``` + +## Example minimal configuration + +Here's an example `values.yaml` that would get a Budibase instance running in a home +cluster using an nginx ingress controller and NFS as cluster storage (basically one of our +staff's homelabs). + +
+ +```yaml +ingress: + enabled: true + annotations: + nginx.ingress.kubernetes.io/client-max-body-size: 150M + nginx.ingress.kubernetes.io/proxy-body-size: 50m + className: "nginx" + hosts: + - host: budibase.local # set this to whatever DNS name you'd use + paths: + - backend: + service: + name: proxy-service + port: + number: 10000 + path: / + pathType: Prefix + +couchdb: + persistentVolume: + enabled: true + storageClass: "nfs-client" + adminPassword: admin + + image: + repository: samwho/test + tag: latest + pullPolicy: Always + + objectStore: + storageClass: "nfs-client" + redis: + storageClass: "nfs-client" +``` + +If you wanted to use this when bringing up Budibase in your own cluster, you could save it +to your hard disk and run the following: + +```console +$ helm install --create-namespace --namespace budibase budibase . -f values.yaml +``` + +
+ +## Configuration reference + +{{ template "chart.valuesTable" . }} + +## Uninstalling the Chart + +To uninstall the chart, assuming you named the release `budibase` (both commands in the installation section do so): + +```console +$ helm uninstall --namespace budibase budibase +``` + +{{ template "helm-docs.versionFooter" . }} diff --git a/charts/budibase/templates/app-service-hpa.yaml b/charts/budibase/templates/app-service-hpa.yaml new file mode 100644 index 0000000000..e819ecb9e3 --- /dev/null +++ b/charts/budibase/templates/app-service-hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.services.apps.autoscaling.enabled }} +apiVersion: {{ ternary "autoscaling/v2" "autoscaling/v2beta2" (.Capabilities.APIVersions.Has "autoscaling/v2") }} +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "budibase.fullname" . }}-apps + labels: + {{- include "budibase.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: app-service + minReplicas: {{ .Values.services.apps.autoscaling.minReplicas }} + maxReplicas: {{ .Values.services.apps.autoscaling.maxReplicas }} + metrics: + {{- if .Values.services.apps.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.services.apps.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.services.apps.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.services.apps.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/budibase/templates/hpa.yaml b/charts/budibase/templates/hpa.yaml deleted file mode 100644 index 2f901b4664..0000000000 --- a/charts/budibase/templates/hpa.yaml +++ /dev/null @@ -1,28 +0,0 @@ -{{- if .Values.autoscaling.enabled }} -apiVersion: autoscaling/v2beta1 -kind: HorizontalPodAutoscaler -metadata: - name: {{ include "budibase.fullname" . }} - labels: - {{- include "budibase.labels" . | nindent 4 }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ include "budibase.fullname" . }} - minReplicas: {{ .Values.autoscaling.minReplicas }} - maxReplicas: {{ .Values.autoscaling.maxReplicas }} - metrics: - {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} - - type: Resource - resource: - name: cpu - targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} - {{- end }} - {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} - - type: Resource - resource: - name: memory - targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} - {{- end }} -{{- end }} diff --git a/charts/budibase/templates/proxy-service-hpa.yaml b/charts/budibase/templates/proxy-service-hpa.yaml new file mode 100644 index 0000000000..b6c6022008 --- /dev/null +++ b/charts/budibase/templates/proxy-service-hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.services.proxy.autoscaling.enabled }} +apiVersion: {{ ternary "autoscaling/v2" "autoscaling/v2beta2" (.Capabilities.APIVersions.Has "autoscaling/v2") }} +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "budibase.fullname" . }}-proxy + labels: + {{- include "budibase.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: proxy-service + minReplicas: {{ .Values.services.proxy.autoscaling.minReplicas }} + maxReplicas: {{ .Values.services.proxy.autoscaling.maxReplicas }} + metrics: + {{- if .Values.services.proxy.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.services.proxy.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.services.proxy.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.services.proxy.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/budibase/templates/worker-service-hpa.yaml b/charts/budibase/templates/worker-service-hpa.yaml new file mode 100644 index 0000000000..a04cc259a0 --- /dev/null +++ b/charts/budibase/templates/worker-service-hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.services.worker.autoscaling.enabled }} +apiVersion: {{ ternary "autoscaling/v2" "autoscaling/v2beta2" (.Capabilities.APIVersions.Has "autoscaling/v2") }} +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "budibase.fullname" . }}-worker + labels: + {{- include "budibase.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: worker-service + minReplicas: {{ .Values.services.worker.autoscaling.minReplicas }} + maxReplicas: {{ .Values.services.worker.autoscaling.maxReplicas }} + metrics: + {{- if .Values.services.worker.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.services.worker.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.services.worker.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.services.worker.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index 5df6e980f0..0b693a4df3 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -1,47 +1,32 @@ -# Default values for budibase. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - +# -- Passed to all pods created by this chart. Should not ordinarily need to be changed. imagePullSecrets: [] +# -- Override the name of the deploymen. Defaults to {{ .Chart.Name }}. nameOverride: "" -# fullnameOverride: "" serviceAccount: - # Specifies whether a service account should be created + # -- Specifies whether a service account should be created create: true - # Annotations to add to the service account + # -- Annotations to add to the service account annotations: {} - # The name of the service account to use. + # -- The name of the service account to use. # If not set and create is true, a name is generated using the fullname template name: "" -podAnnotations: {} - -podSecurityContext: - {} - # fsGroup: 2000 - -securityContext: - {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - service: + # -- Service type for the service that points to the main Budibase proxy pod. type: ClusterIP + # -- Port to expose on the service. port: 10000 ingress: + # -- Whether to create an Ingress resource pointing to the Budibase proxy. enabled: true + # -- What ingress class to use. className: "" - annotations: - # nginx.ingress.kubernetes.io/client-max-body-size: 150M - # nginx.ingress.kubernetes.io/proxy-body-size: 50m + # -- Standard hosts block for the Ingress resource. Defaults to pointing to the Budibase proxy. hosts: - - host: # change if using custom domain + # @ignore + - host: paths: - path: / pathType: Prefix @@ -52,473 +37,426 @@ ingress: number: 10000 awsAlbIngress: + # -- Whether to create an ALB Ingress resource pointing to the Budibase proxy. Requires the AWS ALB Ingress Controller. enabled: false + # -- If you're wanting to use HTTPS, you'll need to create an ACM certificate and specify the ARN here. certificateArn: "" -autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 100 - targetCPUUtilizationPercentage: 80 - # targetMemoryUtilizationPercentage: 80 - -nodeSelector: {} - +# -- Sets the tolerations for all pods created by this chart. Should not ordinarily need to be changed. +# See for more information +# on tolerations. tolerations: [] +# -- Sets the affinity for all pods created by this chart. Should not ordinarily +# need to be changed. See +# +# for more information on affinity. affinity: {} globals: - appVersion: "" # Use as an override to .Chart.AppVersion + # -- The version of Budibase to deploy. Defaults to what's specified by {{ .Chart.AppVersion }}. + # Ends up being used as the image version tag for the apps, proxy, and worker images. + appVersion: "" + # -- Sets the environment variable BUDIBASE_ENVIRONMENT for the apps and worker pods. Should not + # ordinarily need to be changed. budibaseEnv: PRODUCTION + # -- Sets what feature flags are enabled and for which tenants. Should not ordinarily need to be + # changed. tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR" + # -- Whether to enable analytics or not. You can read more about our analytics here: + # . enableAnalytics: "1" + # @ignore (only used if enableAnalytics is set to 1) posthogToken: "phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU" - selfHosted: "1" # set to 0 for budibase cloud environment, set to 1 for self-hosted setup - multiTenancy: "0" # set to 0 to disable multiple orgs, set to 1 to enable multiple orgs - offlineMode: "0" # set to 1 to enable offline mode + # @ignore (should not normally need to be changed, we only set this to "0" + # when deploying to our Cloud environment) + selfHosted: "1" + # -- Whether to enable the multi-tenancy feature or not. "0" means an installation can only + # have one tenant. "1" allows multiple tenants. + multiTenancy: "0" + # @ignore (only currently used to determine whether to fetch licenses offline or not, should + # not normally need to be changed) + offlineMode: "0" + # @ignore (only needs to be set in our cloud environment) accountPortalUrl: "" + # @ignore (only needs to be set in our cloud environment) accountPortalApiKey: "" + # -- Sets the domain attribute of the cookie that Budibase uses to store session information. + # See + # for details on why you might want to set this. cookieDomain: "" + # -- Set the `platformUrl` binding. You can also do this in Settings > Organisation if you are + # self-hosting. platformUrl: "" + # -- Whether or not to enable doing data migrations over the HTTP API. If this is set to "0", + # migrations are run on startup. You shouldn't ordinarily need to change this. httpMigrations: "0" + # -- Google OAuth settings. These can also be set in the Budibase UI, see + # for details. google: + # -- Client ID of your Google OAuth app. clientId: "" + # -- Client secret of your Google OAuth app. secret: "" + # -- The maximum number of iterations allows for an automation loop step. You can read more about + # looping here: . automationMaxIterations: "200" - createSecrets: true # creates an internal API key, JWT secrets and redis password for you + # -- Create an internal API key, JWT secret, object store access key and + # secret, and store them in a Kubernetes `Secret`. + createSecrets: true - # if createSecrets is set to false, you can hard-code your secrets here + # -- Used for encrypting API keys and environment variables when stored in the database. + # You don't need to set this if `createSecrets` is true. apiEncryptionKey: "" + # -- API key used for internal Budibase API calls. You don't need to set this + # if `createSecrets` is true. internalApiKey: "" + # -- Secret used for signing JWTs. You don't need to set this if `createSecrets` is true. jwtSecret: "" - cdnUrl: "" - # fallback values used during live rotation + + # -- A fallback value for `internalApiKey`. If you're rotating your encryption key, you can + # set this to the old value for the duration of the rotation. internalApiKeyFallback: "" + # -- A fallback value for `jwtSecret`. If you're rotating your JWT secret, you can set this + # to the old value for the duration of the rotation. jwtSecretFallback: "" smtp: + # -- Whether to enable SMTP or not. enabled: false - -# globalAgentHttpProxy: -# globalAgentHttpsProxy: -# globalAgentNoProxy: + # -- The hostname of your SMTP server. + host: "" + # -- The port of your SMTP server. + port: "587" + # -- The email address to use in the "From:" field of emails sent by Budibase. + from: "" + # -- The username to use when authenticating with your SMTP server. + user: "" + # -- The password to use when authenticating with your SMTP server. + password: "" services: + # -- The DNS suffix to use for service discovery. You only need to change this + # if you've configured your cluster to use a different DNS suffix. dns: cluster.local - # tlsRejectUnauthorized: 0 proxy: + # @ignore (you shouldn't need to change this) port: 10000 + # -- The number of proxy replicas to run. replicaCount: 1 + # @ignore (you should never need to change this) upstreams: apps: "http://app-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.apps.port }}" worker: "http://worker-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.worker.port }}" minio: "http://minio-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.objectStore.port }}" couchdb: "http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }}" + # -- The resources to use for proxy pods. See + # + # for more information on how to set these. resources: {} + # -- Startup probe configuration for proxy pods. You shouldn't need to + # change this, but if you want to you can find more information here: + # + # @default -- HTTP health checks. startupProbe: + # @ignore httpGet: path: /health port: 10000 scheme: HTTP + # @ignore failureThreshold: 30 + # @ignore periodSeconds: 3 + # -- Readiness probe configuration for proxy pods. You shouldn't need to + # change this, but if you want to you can find more information here: + # + # @default -- HTTP health checks. readinessProbe: + # @ignore httpGet: path: /health port: 10000 scheme: HTTP + # @ignore periodSeconds: 3 + # @ignore failureThreshold: 1 + # -- Liveness probe configuration for proxy pods. You shouldn't need to + # change this, but if you want to you can find more information here: + # + # @default -- HTTP health checks. livenessProbe: + # @ignore httpGet: path: /health port: 10000 scheme: HTTP + # @ignore failureThreshold: 3 + # @ignore periodSeconds: 5 + autoscaling: + # -- Whether to enable horizontal pod autoscaling for the proxy service. + enabled: false + minReplicas: 1 + maxReplicas: 10 + # -- Target CPU utilization percentage for the proxy service. Note that + # for autoscaling to work, you will need to have metrics-server + # configured, and resources set for the proxy pods. + targetCPUUtilizationPercentage: 80 apps: + # @ignore (you shouldn't need to change this) port: 4002 + # -- The number of apps replicas to run. replicaCount: 1 + # -- The log level for the apps service. logLevel: info + # -- Whether or not to log HTTP requests to the apps service. httpLogging: 1 + # -- The resources to use for apps pods. See + # + # for more information on how to set these. resources: {} + # -- Startup probe configuration for apps pods. You shouldn't need to + # change this, but if you want to you can find more information here: + # + # @default -- HTTP health checks. startupProbe: + # @ignore httpGet: path: /health port: 4002 scheme: HTTP + # @ignore failureThreshold: 30 + # @ignore periodSeconds: 3 + # -- Readiness probe configuration for apps pods. You shouldn't need to + # change this, but if you want to you can find more information here: + # + # @default -- HTTP health checks. readinessProbe: + # @ignore httpGet: path: /health port: 4002 scheme: HTTP + # @ignore periodSeconds: 3 + # @ignore failureThreshold: 1 + # -- Liveness probe configuration for apps pods. You shouldn't need to + # change this, but if you want to you can find more information here: + # + # @default -- HTTP health checks. livenessProbe: + # @ignore httpGet: path: /health port: 4002 scheme: HTTP + # @ignore failureThreshold: 3 + # @ignore periodSeconds: 5 - # nodeDebug: "" # set the value of NODE_DEBUG - # annotations: - # co.elastic.logs/multiline.type: pattern - # co.elastic.logs/multiline.pattern: '^[[:space:]]' - # co.elastic.logs/multiline.negate: false - # co.elastic.logs/multiline.match: after + autoscaling: + # -- Whether to enable horizontal pod autoscaling for the apps service. + enabled: false + minReplicas: 1 + maxReplicas: 10 + # -- Target CPU utilization percentage for the apps service. Note that for + # autoscaling to work, you will need to have metrics-server configured, + # and resources set for the apps pods. + targetCPUUtilizationPercentage: 80 + worker: + # @ignore (you shouldn't need to change this) port: 4003 + # -- The number of worker replicas to run. replicaCount: 1 + # -- The log level for the worker service. logLevel: info + # -- Whether or not to log HTTP requests to the worker service. httpLogging: 1 + # -- The resources to use for worker pods. See + # + # for more information on how to set these. resources: {} + # -- Startup probe configuration for worker pods. You shouldn't need to + # change this, but if you want to you can find more information here: + # + # @default -- HTTP health checks. startupProbe: + # @ignore httpGet: path: /health port: 4003 scheme: HTTP + # @ignore failureThreshold: 30 + # @ignore periodSeconds: 3 + # -- Readiness probe configuration for worker pods. You shouldn't need to + # change this, but if you want to you can find more information here: + # + # @default -- HTTP health checks. readinessProbe: + # @ignore httpGet: path: /health port: 4003 scheme: HTTP + # @ignore periodSeconds: 3 + # @ignore failureThreshold: 1 + # -- Liveness probe configuration for worker pods. You shouldn't need to + # change this, but if you want to you can find more information here: + # + # @default -- HTTP health checks. livenessProbe: + # @ignore httpGet: path: /health port: 4003 scheme: HTTP + # @ignore failureThreshold: 3 + # @ignore periodSeconds: 5 - # annotations: - # co.elastic.logs/multiline.type: pattern - # co.elastic.logs/multiline.pattern: '^[[:space:]]' - # co.elastic.logs/multiline.negate: false - # co.elastic.logs/multiline.match: after + autoscaling: + # -- Whether to enable horizontal pod autoscaling for the worker service. + enabled: false + minReplicas: 1 + maxReplicas: 10 + # -- Target CPU utilization percentage for the worker service. Note that + # for autoscaling to work, you will need to have metrics-server + # configured, and resources set for the worker pods. + targetCPUUtilizationPercentage: 80 couchdb: + # -- Whether or not to spin up a CouchDB instance in your cluster. True by + # default, and the configuration for the CouchDB instance is under the + # `couchdb` key at the root of this file. You can see what options are + # available to you by looking at the official CouchDB Helm chart: + # . enabled: true # url: "" # only change if pointing to existing couch server # user: "" # only change if pointing to existing couch server # password: "" # only change if pointing to existing couch server port: 5984 backup: + # -- Whether or not to enable periodic CouchDB backups. This works by replicating + # to another CouchDB instance. enabled: false - # target couchDB instance to back up to + # -- Target couchDB instance to back up to, either a hostname or an IP address. target: "" - # backup interval in seconds + # -- Backup interval in seconds interval: "" + # -- The resources to use for CouchDB backup pods. See + # + # for more information on how to set these. resources: {} redis: - enabled: true # disable if using external redis + # -- Whether or not to deploy a Redis pod into your cluster. + enabled: true + # -- Port to expose Redis on. port: 6379 + # @ignore (you should leave this as 1, we don't support clustering Redis) replicaCount: 1 - url: "" # only change if pointing to existing redis cluster and enabled: false - password: "budibase" # recommended to override if using built-in redis + # -- If you choose to run Redis externally to this chart, you can specify the + # connection details here. + url: "" + # -- The password to use when connecting to Redis. It's recommended that you change + # this from the default if you're running Redis in-cluster. + password: "budibase" + # -- How much persistent storage to allocate to Redis. storage: 100Mi - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. + # -- If defined, storageClassName: If set to "-", + # storageClassName: "", which disables dynamic provisioning If undefined + # (the default) or set to null, no storageClassName spec is set, choosing + # the default provisioner. storageClass: "" + # -- The resources to use for Redis pods. See + # + # for more information on how to set these. resources: {} objectStore: - # Set to false if using another object store such as S3 + # -- Set to false if using another object store, such as S3. You will need + # to set `services.objectStore.url` to point to your bucket if you do this. minio: true + # -- Whether to enable the Minio web console or not. If you're exposing + # Minio to the Internet (via a custom Ingress record, for example), you + # should set this to false. If you're only exposing Minio to your cluster, + # you can leave this as true. browser: true + # @ignore port: 9000 + # @ignore (you should leave this as 1, we don't support clustering Minio) replicaCount: 1 - accessKey: "" # AWS_ACCESS_KEY if using S3 or existing minio access key - secretKey: "" # AWS_SECRET_ACCESS_KEY if using S3 or existing minio secret - region: "" # AWS_REGION if using S3 or existing minio secret - url: "http://minio-service:9000" # only change if pointing to existing minio cluster or S3 and minio: false + # -- AWS_ACCESS_KEY if using S3 + accessKey: "" + # -- AWS_SECRET_ACCESS_KEY if using S3 + secretKey: "" + # -- AWS_REGION if using S3 + region: "" + # -- URL to use for object storage. Only change this if you're using an + # external object store, such as S3. Remember to set `minio: false` if you + # do this. + url: "http://minio-service:9000" + # -- How much storage to give Minio in its PersistentVolumeClaim. storage: 100Mi - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. + # -- If defined, storageClassName: If set to "-", + # storageClassName: "", which disables dynamic provisioning If undefined + # (the default) or set to null, no storageClassName spec is set, choosing + # the default provisioner. storageClass: "" + # -- The resources to use for Minio pods. See + # + # for more information on how to set these. resources: {} cloudfront: - # Set the url of a distribution to enable cloudfront + # -- Set the url of a distribution to enable cloudfront. cdn: "" - # ID of public key stored in cloudfront + # -- ID of public key stored in cloudfront. publicKeyId: "" - # Base64 encoded private key for the above public key + # -- Base64 encoded private key for the above public key. privateKey64: "" -# Override values in couchDB subchart +# Override values in couchDB subchart. We're only specifying the values we're changing. +# If you want to see all of the available values, see: +# https://github.com/apache/couchdb-helm/tree/couchdb-4.3.0/couchdb couchdb: - # -- the initial number of nodes in the CouchDB cluster. + # -- The number of replicas to run in the CouchDB cluster. We set this to + # 1 by default to make things simpler, but you can set it to 3 if you need + # a high-availability CouchDB cluster. clusterSize: 1 - # -- If allowAdminParty is enabled the cluster will start up without any database - # administrator account; i.e., all users will be granted administrative - # access. Otherwise, the system will look for a Secret called - # -couchdb containing `adminUsername`, `adminPassword` and - # `cookieAuthSecret` keys. See the `createAdminSecret` flag. - # ref: https://kubernetes.io/docs/concepts/configuration/secret/ - allowAdminParty: false - - # -- If createAdminSecret is enabled a Secret called -couchdb will - # be created containing auto-generated credentials. Users who prefer to set - # these values themselves have a couple of options: - # - # 1) The `adminUsername`, `adminPassword`, `adminHash`, and `cookieAuthSecret` - # can be defined directly in the chart's values. Note that all of a chart's - # values are currently stored in plaintext in a ConfigMap in the tiller - # namespace. - # - # 2) This flag can be disabled and a Secret with the required keys can be - # created ahead of time. - createAdminSecret: true - - adminUsername: admin - # adminPassword: this_is_not_secure - # adminHash: -pbkdf2-this_is_not_necessarily_secure_either - # cookieAuthSecret: neither_is_this - - ## When enabled, will deploy a networkpolicy that allows CouchDB pods to - ## communicate with each other for clustering and ingress on port 5984 - networkPolicy: - enabled: true - - ## Use an alternate scheduler, e.g. "stork". - ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ - ## - # schedulerName: - - # Use a service account - serviceAccount: - enabled: true - create: true - # name: - # imagePullSecrets: - # - name: myimagepullsecret - - # -- The storage volume used by each Pod in the StatefulSet. If a - # persistentVolume is not enabled, the Pods will use `emptyDir` ephemeral - # local storage. Setting the storageClass attribute to "-" disables dynamic - # provisioning of Persistent Volumes; leaving it unset will invoke the default - # provisioner. - persistentVolume: - enabled: false - # NOTE: the number of existing claims must match the cluster size - existingClaims: [] - annotations: {} - accessModes: - - ReadWriteOnce - size: 10Gi - # storageClass: "-" - - ## The CouchDB image + # -- We use a custom CouchDB image for running Budibase and we don't support + # using any other CouchDB image. You shouldn't change this, and if you do we + # can't guarantee that Budibase will work. image: + # @ignore repository: budibase/couchdb + # @ignore tag: v3.2.1 - pullPolicy: IfNotPresent + # @ignore + pullPolicy: Always + # @ignore # This should remain false. We ship Clouseau ourselves as part of the # budibase/couchdb image, and it's not possible to disable it because it's a # core part of the Budibase experience. enableSearch: false - initImage: - repository: busybox - tag: latest - pullPolicy: Always - - ## CouchDB is happy to spin up cluster nodes in parallel, but if you encounter - ## problems you can try setting podManagementPolicy to the StatefulSet default - ## `OrderedReady` - podManagementPolicy: Parallel - - ## To better tolerate Node failures, we can prevent Kubernetes scheduler from - ## assigning more than one Pod of CouchDB StatefulSet per Node using podAntiAffinity. - affinity: - {} - # podAntiAffinity: - # requiredDuringSchedulingIgnoredDuringExecution: - # - labelSelector: - # matchExpressions: - # - key: "app" - # operator: In - # values: - # - couchdb - # topologyKey: "kubernetes.io/hostname" - - ## To control how Pods are spread across your cluster among failure-domains such as regions, - ## zones, nodes, and other user-defined topology domains use topologySpreadConstraints. - topologySpreadConstraints: - {} - # topologySpreadConstraints: - # - maxSkew: 1 - # topologyKey: "topology.kubernetes.io/zone" - # whenUnsatisfiable: ScheduleAnyway - # labelSelector: - # matchLabels: - # app: couchdb - - ## Optional pod labels - labels: {} - - ## Optional pod annotations - annotations: {} - - ## Optional tolerations - tolerations: [] - - ## A StatefulSet requires a headless Service to establish the stable network - ## identities of the Pods, and that Service is created automatically by this - ## chart without any additional configuration. The Service block below refers - ## to a second Service that governs how clients connect to the CouchDB cluster. - service: - annotations: {} - enabled: true - type: ClusterIP - externalPort: 5984 - targetPort: 5984 - labels: {} - - ## An Ingress resource can provide name-based virtual hosting and TLS - ## termination among other things for CouchDB deployments which are accessed - ## from outside the Kubernetes cluster. - ## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/ - ingress: - enabled: false - # className: nginx - hosts: - - chart-example.local - path: / - annotations: - {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - tls: - # Secrets must be manually created in the namespace. - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - - ## Optional resource requests and limits for the CouchDB container - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - resources: - {} - # requests: - # cpu: 100m - # memory: 128Mi - # limits: - # cpu: 56 - # memory: 256Gi - - ## Optional resource requests and limits for the CouchDB init container - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - initResources: - {} - # requests: - # cpu: 100m - # memory: 128Mi - # limits: - # cpu: 500m - # memory: 128Mi - - # -- erlangFlags is a map that is passed to the Erlang VM as flags using the - # ERL_FLAGS env. The `name` flag is required to establish connectivity - # between cluster nodes. - # ref: http://erlang.org/doc/man/erl.html#init_flags - erlangFlags: - name: couchdb - # Older versions of the official CouchDB image (anything prior to 3.2.1) - # do not act on the COUCHDB_ERLANG_COOKIE environment variable, so if you - # want to cluster these deployments it's necessary to pass in a cookie here - # setcookie: make-something-up - - # -- couchdbConfig will override default CouchDB configuration settings. - # The contents of this map are reformatted into a .ini file laid down - # by a ConfigMap object. - # ref: http://docs.couchdb.org/en/latest/config/index.html couchdbConfig: couchdb: - uuid: budibase-couchdb # Unique identifier for this CouchDB server instance - # cluster: - # q: 8 # Create 8 shards for each database - chttpd: - bind_address: any - # chttpd.require_valid_user disables all the anonymous requests to the port - # 5984 when is set to true. - require_valid_user: false - # required to use Fauxton if chttpd.require_valid_user is set to true - # httpd: - # WWW-Authenticate: "Basic realm=\"administrator\"" - - # Kubernetes local cluster domain. - # This is used to generate FQDNs for peers when joining the CouchDB cluster. - dns: - clusterDomainSuffix: cluster.local - - ## Configure liveness and readiness probe values - ## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes - livenessProbe: - enabled: true - failureThreshold: 3 - initialDelaySeconds: 0 - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 1 - readinessProbe: - enabled: true - failureThreshold: 3 - initialDelaySeconds: 0 - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 1 - - # Control an optional pod disruption budget - podDisruptionBudget: - # toggle creation of pod disruption budget, disabled by default - enabled: false - # minAvailable: 1 - maxUnavailable: 1 - - # CouchDB 3.2.0 adds in a metrics endpoint on the path `/_node/_local/_prometheus`. - # Optionally, a standalone, unauthenticated port can be exposed for these metrics. - prometheusPort: - enabled: false - bind_address: "0.0.0.0" - port: 17986 - - # Configure arbitrary sidecar containers for CouchDB pods created by the - # StatefulSet - sidecars: - {} - # - name: foo - # image: "busybox" - # imagePullPolicy: IfNotPresent - # resources: - # requests: - # cpu: "0.1" - # memory: 10Mi - # command: ['echo "foo";'] - # volumeMounts: - # - name: database-storage - # mountPath: /opt/couchdb/data/ - - # Placement manager to annotate each document in the nodes DB with "zone" attribute - # recording the zone where node has been scheduled - # Ref: https://docs.couchdb.org/en/stable/cluster/sharding.html#specifying-database-placement - placementConfig: - enabled: false - image: - repository: caligrafix/couchdb-autoscaler-placement-manager - tag: 0.1.0 + # -- Unique identifier for this CouchDB server instance. You shouldn't need + # to change this. + uuid: budibase-couchdb From 7e1036251519bc97f8fdd8e8330598650af450b6 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 27 Nov 2023 17:14:52 +0000 Subject: [PATCH 10/63] More README tweaks. --- charts/budibase/README.md | 9 ++++++++- charts/budibase/README.md.gotmpl | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/charts/budibase/README.md b/charts/budibase/README.md index 961776b977..9164ace2ce 100644 --- a/charts/budibase/README.md +++ b/charts/budibase/README.md @@ -6,7 +6,14 @@ Budibase is an open source low-code platform, helping thousands of teams build a - `helm` v3 or above - Kubernetes 1.4+ -- A storage controller (for `PersistentVolume` creation, if you want to ensure your data does not get lost when pods restart) +- A storage controller (if you want to use persistent storage) +- An ingress controller (if you want to define an `Ingress` resource) + +## Chart dependencies + +This chart depends on the official Apache CouchDB chart. You can see its +documentation here: +. ## Installing the Chart diff --git a/charts/budibase/README.md.gotmpl b/charts/budibase/README.md.gotmpl index eacd832e74..23c2399a15 100644 --- a/charts/budibase/README.md.gotmpl +++ b/charts/budibase/README.md.gotmpl @@ -5,7 +5,14 @@ - `helm` v3 or above - Kubernetes 1.4+ -- A storage controller (for `PersistentVolume` creation, if you want to ensure your data does not get lost when pods restart) +- A storage controller (if you want to use persistent storage) +- An ingress controller (if you want to define an `Ingress` resource) + +## Chart dependencies + +This chart depends on the official Apache CouchDB chart. You can see its +documentation here: +. ## Installing the Chart From f917e5624560fa0b370a2a7ab12a086e820d9708 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 27 Nov 2023 17:16:06 +0000 Subject: [PATCH 11/63] Note metrics-server dependency. --- charts/budibase/README.md | 1 + charts/budibase/README.md.gotmpl | 1 + 2 files changed, 2 insertions(+) diff --git a/charts/budibase/README.md b/charts/budibase/README.md index 9164ace2ce..ac25ccf7c9 100644 --- a/charts/budibase/README.md +++ b/charts/budibase/README.md @@ -8,6 +8,7 @@ Budibase is an open source low-code platform, helping thousands of teams build a - Kubernetes 1.4+ - A storage controller (if you want to use persistent storage) - An ingress controller (if you want to define an `Ingress` resource) +- `metrics-server` (if you want to make use of horizontal pod autoscaling) ## Chart dependencies diff --git a/charts/budibase/README.md.gotmpl b/charts/budibase/README.md.gotmpl index 23c2399a15..91f9822cb3 100644 --- a/charts/budibase/README.md.gotmpl +++ b/charts/budibase/README.md.gotmpl @@ -7,6 +7,7 @@ - Kubernetes 1.4+ - A storage controller (if you want to use persistent storage) - An ingress controller (if you want to define an `Ingress` resource) +- `metrics-server` (if you want to make use of horizontal pod autoscaling) ## Chart dependencies From adeb29a6a7109cdc9e201f0aa5f50f14d1b1323a Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Tue, 28 Nov 2023 14:21:27 +0000 Subject: [PATCH 12/63] Update pro to same as master. --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 5e3d59fc40..1037b032d4 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 5e3d59fc4060fd44b14b2599269c207753d4e5be +Subproject commit 1037b032d49244678204704d1bca779a29e395eb From 7ba412d5c85a5cc8e816eb9868604301dd7f2fa9 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 29 Nov 2023 16:53:17 +0000 Subject: [PATCH 13/63] List breaking changes between 2.x and 3.0.0 in chart readme. --- charts/budibase/README.md | 29 ++++++++++++++++++++++++++--- charts/budibase/README.md.gotmpl | 29 ++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/charts/budibase/README.md b/charts/budibase/README.md index ac25ccf7c9..808943186e 100644 --- a/charts/budibase/README.md +++ b/charts/budibase/README.md @@ -16,7 +16,30 @@ This chart depends on the official Apache CouchDB chart. You can see its documentation here: . -## Installing the Chart +## Upgrading + +### `2.x` to `3.0.0` + +We made a number of breaking changes in this release to make the chart more +idiomatic and easier to use. + +1. We no longer bundle `ingress-nginx`. If you were relying on this to supply + an ingress controller to your cluster, you will now need to deploy that + separately. You'll find guidance for that here: + . +2. We've upgraded the version of the [CouchDB chart](https://github.com/apache/couchdb-helm) + we use from `3.3.4` to `4.3.0`. The primary motivation for this was to align + the CouchDB chart used with the CouchDB version used, which has also updated + from 3.1.1 to 3.2.1. +3. We've separated out the supplied AWS ALB ingress resource for those deploying + into EKS. Where previously you enabled this by setting `ingress.enabled: false` + and `ingress.aws: true`, you now set `awsAlbIngress.enabled: true` and all + configuration for it is under `awsAlbIngress`. +4. The `HorizontalPodAutoscaler` that was configured at `hpa.enabled: true` has + been split into 3 separate HPAs, one for each of `apps`, `worker`, and `proxy`. + They are configured at `services.{apps,worker,proxy}.autoscaling`. + +## Installing To install the chart from our repository: @@ -86,7 +109,7 @@ $ helm install --create-namespace --namespace budibase budibase . -f values.yaml -## Configuration reference +## Configuring | Key | Type | Default | Description | |-----|------|---------|-------------| @@ -189,7 +212,7 @@ $ helm install --create-namespace --namespace budibase budibase . -f values.yaml | services.worker.startupProbe | object | HTTP health checks. | Startup probe configuration for worker pods. You shouldn't need to change this, but if you want to you can find more information here: | | tolerations | list | `[]` | Sets the tolerations for all pods created by this chart. Should not ordinarily need to be changed. See for more information on tolerations. | -## Uninstalling the Chart +## Uninstalling To uninstall the chart, assuming you named the release `budibase` (both commands in the installation section do so): diff --git a/charts/budibase/README.md.gotmpl b/charts/budibase/README.md.gotmpl index 91f9822cb3..66c8370ab8 100644 --- a/charts/budibase/README.md.gotmpl +++ b/charts/budibase/README.md.gotmpl @@ -15,7 +15,30 @@ This chart depends on the official Apache CouchDB chart. You can see its documentation here: . -## Installing the Chart +## Upgrading + +### `2.x` to `3.0.0` + +We made a number of breaking changes in this release to make the chart more +idiomatic and easier to use. + +1. We no longer bundle `ingress-nginx`. If you were relying on this to supply + an ingress controller to your cluster, you will now need to deploy that + separately. You'll find guidance for that here: + . +2. We've upgraded the version of the [CouchDB chart](https://github.com/apache/couchdb-helm) + we use from `3.3.4` to `4.3.0`. The primary motivation for this was to align + the CouchDB chart used with the CouchDB version used, which has also updated + from 3.1.1 to 3.2.1. +3. We've separated out the supplied AWS ALB ingress resource for those deploying + into EKS. Where previously you enabled this by setting `ingress.enabled: false` + and `ingress.aws: true`, you now set `awsAlbIngress.enabled: true` and all + configuration for it is under `awsAlbIngress`. +4. The `HorizontalPodAutoscaler` that was configured at `hpa.enabled: true` has + been split into 3 separate HPAs, one for each of `apps`, `worker`, and `proxy`. + They are configured at `services.{apps,worker,proxy}.autoscaling`. + +## Installing To install the chart from our repository: @@ -85,11 +108,11 @@ $ helm install --create-namespace --namespace budibase budibase . -f values.yaml -## Configuration reference +## Configuring {{ template "chart.valuesTable" . }} -## Uninstalling the Chart +## Uninstalling To uninstall the chart, assuming you named the release `budibase` (both commands in the installation section do so): From e4b1cf77053556d7674a3a8b54f86e26f58abf58 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 29 Nov 2023 16:57:11 +0000 Subject: [PATCH 14/63] Note that we're moving to our own CouchDB image in the helm chart readme. --- charts/budibase/README.md.gotmpl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/charts/budibase/README.md.gotmpl b/charts/budibase/README.md.gotmpl index 66c8370ab8..ac3bfef77d 100644 --- a/charts/budibase/README.md.gotmpl +++ b/charts/budibase/README.md.gotmpl @@ -29,7 +29,8 @@ idiomatic and easier to use. 2. We've upgraded the version of the [CouchDB chart](https://github.com/apache/couchdb-helm) we use from `3.3.4` to `4.3.0`. The primary motivation for this was to align the CouchDB chart used with the CouchDB version used, which has also updated - from 3.1.1 to 3.2.1. + from 3.1.1 to 3.2.1. Additionally, we're moving away from the official CouchDB + to one we're building ourselves. 3. We've separated out the supplied AWS ALB ingress resource for those deploying into EKS. Where previously you enabled this by setting `ingress.enabled: false` and `ingress.aws: true`, you now set `awsAlbIngress.enabled: true` and all From 1642d18d7dcdfad9a858f3049a1fa5d69aed859a Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 29 Nov 2023 17:01:08 +0000 Subject: [PATCH 15/63] Remove incorrect image from values.yaml example. --- charts/budibase/README.md | 8 ++------ charts/budibase/README.md.gotmpl | 5 ----- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/charts/budibase/README.md b/charts/budibase/README.md index 808943186e..5d74e05e0b 100644 --- a/charts/budibase/README.md +++ b/charts/budibase/README.md @@ -30,7 +30,8 @@ idiomatic and easier to use. 2. We've upgraded the version of the [CouchDB chart](https://github.com/apache/couchdb-helm) we use from `3.3.4` to `4.3.0`. The primary motivation for this was to align the CouchDB chart used with the CouchDB version used, which has also updated - from 3.1.1 to 3.2.1. + from 3.1.1 to 3.2.1. Additionally, we're moving away from the official CouchDB + to one we're building ourselves. 3. We've separated out the supplied AWS ALB ingress resource for those deploying into EKS. Where previously you enabled this by setting `ingress.enabled: false` and `ingress.aws: true`, you now set `awsAlbIngress.enabled: true` and all @@ -89,11 +90,6 @@ couchdb: storageClass: "nfs-client" adminPassword: admin - image: - repository: samwho/test - tag: latest - pullPolicy: Always - objectStore: storageClass: "nfs-client" redis: diff --git a/charts/budibase/README.md.gotmpl b/charts/budibase/README.md.gotmpl index ac3bfef77d..d7bc25c789 100644 --- a/charts/budibase/README.md.gotmpl +++ b/charts/budibase/README.md.gotmpl @@ -89,11 +89,6 @@ couchdb: storageClass: "nfs-client" adminPassword: admin - image: - repository: samwho/test - tag: latest - pullPolicy: Always - objectStore: storageClass: "nfs-client" redis: From e1b16c06ea941ed8e07fceb5419365347d447044 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 29 Nov 2023 17:01:52 +0000 Subject: [PATCH 16/63] Remove unnecessary annotations from example values.yaml. --- charts/budibase/README.md.gotmpl | 3 --- 1 file changed, 3 deletions(-) diff --git a/charts/budibase/README.md.gotmpl b/charts/budibase/README.md.gotmpl index d7bc25c789..92e91f8e09 100644 --- a/charts/budibase/README.md.gotmpl +++ b/charts/budibase/README.md.gotmpl @@ -68,9 +68,6 @@ staff's homelabs). ```yaml ingress: enabled: true - annotations: - nginx.ingress.kubernetes.io/client-max-body-size: 150M - nginx.ingress.kubernetes.io/proxy-body-size: 50m className: "nginx" hosts: - host: budibase.local # set this to whatever DNS name you'd use From b86640772ba07a16d077d93973810a9c5f365aa4 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 29 Nov 2023 18:45:48 +0000 Subject: [PATCH 17/63] Fix for saving relationships that have the same field name used on both sides, previously this could cause a relationship to be cleared depending on how the relationship schema was configured. There is a chance when saving that this won't happen as which side of the relationship is denoted by doc1 and doc2 is random, so when this happens is random. Using the table to pick the correct side is safer than just using the field name. --- .../server/src/api/controllers/static/index.ts | 6 ++++-- .../server/src/db/linkedRows/LinkController.ts | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/server/src/api/controllers/static/index.ts b/packages/server/src/api/controllers/static/index.ts index 4c5415a6c6..2963546e7f 100644 --- a/packages/server/src/api/controllers/static/index.ts +++ b/packages/server/src/api/controllers/static/index.ts @@ -24,7 +24,7 @@ import AWS from "aws-sdk" import fs from "fs" import sdk from "../../../sdk" import * as pro from "@budibase/pro" -import { App, Ctx, ProcessAttachmentResponse, Upload } from "@budibase/types" +import { App, Ctx, ProcessAttachmentResponse } from "@budibase/types" const send = require("koa-send") @@ -212,7 +212,9 @@ export const serveBuilderPreview = async function (ctx: Ctx) { if (!env.isJest()) { let appId = context.getAppId() - const previewHbs = loadHandlebarsFile(`${__dirname}/preview.hbs`) + const templateLoc = join(__dirname, "templates") + const previewLoc = fs.existsSync(templateLoc) ? templateLoc : __dirname + const previewHbs = loadHandlebarsFile(join(previewLoc, "preview.hbs")) ctx.body = await processString(previewHbs, { clientLibPath: objectStore.clientLibraryUrl(appId!, appInfo.version), }) diff --git a/packages/server/src/db/linkedRows/LinkController.ts b/packages/server/src/db/linkedRows/LinkController.ts index c4eed1169a..f52694465f 100644 --- a/packages/server/src/db/linkedRows/LinkController.ts +++ b/packages/server/src/db/linkedRows/LinkController.ts @@ -251,9 +251,19 @@ class LinkController { // find the docs that need to be deleted let toDeleteDocs = thisFieldLinkDocs .filter(doc => { - let correctDoc = - doc.doc1.fieldName === fieldName ? doc.doc2 : doc.doc1 - return rowField.indexOf(correctDoc.rowId) === -1 + let correctDoc + if ( + doc.doc1.tableId === table._id! && + doc.doc1.fieldName === fieldName + ) { + correctDoc = doc.doc2 + } else if ( + doc.doc2.tableId === table._id! && + doc.doc2.fieldName === fieldName + ) { + correctDoc = doc.doc1 + } + return correctDoc && rowField.indexOf(correctDoc.rowId) === -1 }) .map(doc => { return { ...doc, _deleted: true } From 160fbf21258823fe0fd06941e9061fc4719699b7 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 29 Nov 2023 19:53:56 +0000 Subject: [PATCH 18/63] Adding test case and fixing issue that it revealed with external tables as well. --- .../server/src/api/routes/tests/row.spec.ts | 35 +++++++++++++++++++ .../src/sdk/app/tables/external/utils.ts | 12 ++++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index de49441f3a..ba80f36b1b 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -517,9 +517,22 @@ describe.each([ }) describe("patch", () => { + let otherTable: Table + beforeAll(async () => { const tableConfig = generateTableConfig() table = await createTable(tableConfig) + const otherTableConfig = generateTableConfig() + // need a short name of table here - for relationship tests + otherTableConfig.name = "a" + otherTableConfig.schema.relationship = { + name: "relationship", + type: FieldType.LINK, + fieldName: "relationship", + tableId: table._id!, + relationshipType: RelationshipType.ONE_TO_MANY, + } + otherTable = await createTable(otherTableConfig) }) it("should update only the fields that are supplied", async () => { @@ -615,6 +628,28 @@ describe.each([ expect(getResp.body.user1[0]._id).toEqual(user2._id) expect(getResp.body.user2[0]._id).toEqual(user2._id) }) + + it("should be able to update relationships when both columns are same name", async () => { + let row = await config.api.row.save(table._id!, { + name: "test", + description: "test", + }) + let row2 = await config.api.row.save(otherTable._id!, { + name: "test", + description: "test", + relationship: [row._id], + }) + row = (await config.api.row.get(table._id!, row._id!)).body + expect(row.relationship.length).toBe(1) + const resp = await config.api.row.patch(table._id!, { + _id: row._id!, + _rev: row._rev!, + tableId: row.tableId!, + name: "test2", + relationship: [row2._id], + }) + expect(resp.relationship.length).toBe(1) + }) }) describe("destroy", () => { diff --git a/packages/server/src/sdk/app/tables/external/utils.ts b/packages/server/src/sdk/app/tables/external/utils.ts index bde812dd3d..50ea98eb39 100644 --- a/packages/server/src/sdk/app/tables/external/utils.ts +++ b/packages/server/src/sdk/app/tables/external/utils.ts @@ -1,5 +1,6 @@ import { Datasource, + FieldType, ManyToManyRelationshipFieldMetadata, ManyToOneRelationshipFieldMetadata, OneToManyRelationshipFieldMetadata, @@ -42,10 +43,13 @@ export function cleanupRelationships( for (let [relatedKey, relatedSchema] of Object.entries( relatedTable.schema )) { - if ( - relatedSchema.type === FieldTypes.LINK && - relatedSchema.fieldName === foreignKey - ) { + if (relatedSchema.type !== FieldType.LINK) { + continue + } + // if they both have the same field name it will appear as if it needs to be removed, + // don't cleanup in this scenario + const sameFieldNameForBoth = relatedSchema.name === schema.name + if (relatedSchema.fieldName === foreignKey && !sameFieldNameForBoth) { delete relatedTable.schema[relatedKey] } } From ae9061f97882bcc465803854bf30aeac672d45e3 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 30 Nov 2023 11:27:57 +0000 Subject: [PATCH 19/63] Bump version to 2.13.24 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 491de0dd97..022fbb055a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.13.23", + "version": "2.13.24", "npmClient": "yarn", "packages": [ "packages/*" From 71a4e96d589281aacd57f1e0ee54346ce50bf9e6 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 30 Nov 2023 11:34:11 +0000 Subject: [PATCH 20/63] Move CODEOWNERS to the root to see if that fixes it. --- CODEOWNERS | 3 +++ packages/backend-core/CODEOWNERS | 1 - packages/server/CODEOWNERS | 1 - packages/worker/CODEOWNERS | 1 - 4 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 CODEOWNERS delete mode 100644 packages/backend-core/CODEOWNERS delete mode 100644 packages/server/CODEOWNERS delete mode 100644 packages/worker/CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..69d69ab7d0 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,3 @@ +/packages/server @Budibase/backend +/packages/worker @Budibase/backend +/packages/backend-core @Budibase/backend diff --git a/packages/backend-core/CODEOWNERS b/packages/backend-core/CODEOWNERS deleted file mode 100644 index 84313fb9cf..0000000000 --- a/packages/backend-core/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @Budibase/backend \ No newline at end of file diff --git a/packages/server/CODEOWNERS b/packages/server/CODEOWNERS deleted file mode 100644 index 84313fb9cf..0000000000 --- a/packages/server/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @Budibase/backend \ No newline at end of file diff --git a/packages/worker/CODEOWNERS b/packages/worker/CODEOWNERS deleted file mode 100644 index 84313fb9cf..0000000000 --- a/packages/worker/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @Budibase/backend \ No newline at end of file From 58736e02c7dd0d835792edad2d1a3649b68efd32 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 30 Nov 2023 12:43:09 +0000 Subject: [PATCH 21/63] Bump version to 2.13.25 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 022fbb055a..6569facc59 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.13.24", + "version": "2.13.25", "npmClient": "yarn", "packages": [ "packages/*" From 3131798d1d86ebf5c9bc949dea8c9f48bb835d60 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 30 Nov 2023 12:52:40 +0000 Subject: [PATCH 22/63] Bump version to 2.13.26 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 6569facc59..794a077423 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.13.25", + "version": "2.13.26", "npmClient": "yarn", "packages": [ "packages/*" From 6315806bea201486e5f2aaa52c24d1cfdf7db3a0 Mon Sep 17 00:00:00 2001 From: melohagan <101575380+melohagan@users.noreply.github.com> Date: Thu, 30 Nov 2023 14:42:38 +0000 Subject: [PATCH 23/63] Update originalIndex on select (#12480) --- .../controls/ButtonActionEditor/ButtonActionDrawer.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/ButtonActionDrawer.svelte b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/ButtonActionDrawer.svelte index 109f9f62a2..236bba58dc 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/ButtonActionDrawer.svelte +++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/ButtonActionDrawer.svelte @@ -147,6 +147,7 @@ const selectAction = action => () => { selectedAction = action + originalActionIndex = actions.findIndex(item => item.id === action.id) } const onAddAction = actionType => { From 37e065df21bba6ea7e7f2926493cfe7ee87beec3 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 30 Nov 2023 14:44:12 +0000 Subject: [PATCH 24/63] Bump version to 2.13.27 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 794a077423..3c442524fa 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.13.26", + "version": "2.13.27", "npmClient": "yarn", "packages": [ "packages/*" From 02fefa55296a24e860a41e7d00b4b0cccfcfdb41 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 30 Nov 2023 15:09:01 +0000 Subject: [PATCH 25/63] Fixes for postgres test case, there was an issue with creating tables with relationships during the creation phase. --- .../server/src/api/routes/tests/row.spec.ts | 8 +++++--- .../src/integration-test/postgres.spec.ts | 18 ++++++++++++++++++ .../src/sdk/app/tables/external/index.ts | 2 ++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index ba80f36b1b..5b3c69b87a 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -527,12 +527,14 @@ describe.each([ otherTableConfig.name = "a" otherTableConfig.schema.relationship = { name: "relationship", - type: FieldType.LINK, - fieldName: "relationship", - tableId: table._id!, relationshipType: RelationshipType.ONE_TO_MANY, + type: FieldType.LINK, + tableId: table._id!, + fieldName: "relationship", } otherTable = await createTable(otherTableConfig) + // need to set the config back to the original table + config.table = table }) it("should update only the fields that are supplied", async () => { diff --git a/packages/server/src/integration-test/postgres.spec.ts b/packages/server/src/integration-test/postgres.spec.ts index 8dc49a9489..67e4fee81c 100644 --- a/packages/server/src/integration-test/postgres.spec.ts +++ b/packages/server/src/integration-test/postgres.spec.ts @@ -934,25 +934,43 @@ describe("postgres integrations", () => { }, ], }) + const m2oRel = { + [m2oFieldName]: [ + { + _id: row._id, + }, + ], + } expect(res.body[m2oFieldName]).toEqual([ { + ...m2oRel, ...foreignRowsByType[RelationshipType.MANY_TO_ONE][0].row, [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]: row.id, }, { + ...m2oRel, ...foreignRowsByType[RelationshipType.MANY_TO_ONE][1].row, [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]: row.id, }, { + ...m2oRel, ...foreignRowsByType[RelationshipType.MANY_TO_ONE][2].row, [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]: row.id, }, ]) + const o2mRel = { + [o2mFieldName]: [ + { + _id: row._id, + }, + ], + } expect(res.body[o2mFieldName]).toEqual([ { + ...o2mRel, ...foreignRowsByType[RelationshipType.ONE_TO_MANY][0].row, _id: expect.any(String), _rev: expect.any(String), diff --git a/packages/server/src/sdk/app/tables/external/index.ts b/packages/server/src/sdk/app/tables/external/index.ts index f445fcaf08..157a683ad0 100644 --- a/packages/server/src/sdk/app/tables/external/index.ts +++ b/packages/server/src/sdk/app/tables/external/index.ts @@ -136,6 +136,8 @@ export async function save( schema.main = true } + // add in the new table for relationship purposes + tables[tableToSave.name] = tableToSave cleanupRelationships(tableToSave, tables, oldTable) const operation = tableId ? Operation.UPDATE_TABLE : Operation.CREATE_TABLE From 4326ee17d9811269a8bf25badba1c2c67e44d637 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 30 Nov 2023 17:33:41 +0000 Subject: [PATCH 26/63] Bump version to 2.13.28 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 3c442524fa..87b7c50bf7 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.13.27", + "version": "2.13.28", "npmClient": "yarn", "packages": [ "packages/*" From f71e1ac03a784e89105efe75c93a14448b5caf55 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Nov 2023 13:56:25 +0100 Subject: [PATCH 27/63] Allow locks without TTL --- packages/backend-core/src/redis/redlockImpl.ts | 17 +++++++++++++++-- packages/types/src/sdk/locks.ts | 4 ++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index 266f1fe989..cccb981399 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -4,6 +4,7 @@ import { LockOptions, LockType } from "@budibase/types" import * as context from "../context" import env from "../environment" import { logWarn } from "../logging" +import { Duration } from "../utils" async function getClient( type: LockType, @@ -105,12 +106,21 @@ export async function doWithLock( task: () => Promise ): Promise> { const redlock = await getClient(opts.type, opts.customOptions) - let lock + let lock: Redlock.Lock | undefined + let interval try { const name = getLockName(opts) + let ttl = opts.ttl || Duration.fromSeconds(15).toMs() + // create the lock - lock = await redlock.lock(name, opts.ttl) + lock = await redlock.lock(name, ttl) + + if (!opts.ttl) { + interval = setInterval(() => { + lock!.extend(ttl) + }, ttl / 2) + } // perform locked task // need to await to ensure completion before unlocking @@ -134,5 +144,8 @@ export async function doWithLock( if (lock) { await lock.unlock() } + if (interval) { + clearInterval(interval) + } } } diff --git a/packages/types/src/sdk/locks.ts b/packages/types/src/sdk/locks.ts index a35e7b379b..080574b735 100644 --- a/packages/types/src/sdk/locks.ts +++ b/packages/types/src/sdk/locks.ts @@ -36,9 +36,9 @@ export interface LockOptions { */ name: LockName /** - * The ttl to auto-expire the lock if not unlocked manually + * The ttl to auto-expire the lock if not unlocked manually. If undefined, the lock will be autoextending while the process is running. */ - ttl: number + ttl?: number /** * The individual resource to lock. This is useful for locking around very specific identifiers, e.g. a document that is prone to conflicts */ From 3073397800b5704194ac570a5bd4707cb93e94be Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Nov 2023 18:20:11 +0100 Subject: [PATCH 28/63] Fix ttl --- packages/backend-core/src/redis/redlockImpl.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index cccb981399..048d17d080 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -111,14 +111,15 @@ export async function doWithLock( try { const name = getLockName(opts) - let ttl = opts.ttl || Duration.fromSeconds(15).toMs() + let ttl = opts.ttl || Duration.fromSeconds(30).toMs() // create the lock lock = await redlock.lock(name, ttl) if (!opts.ttl) { + // No TTL is provided, so we keep extending the lock while the task is running interval = setInterval(() => { - lock!.extend(ttl) + lock?.extend(ttl / 2) }, ttl / 2) } From 5a7dbb00764131b4b312531b064b794041bf2665 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Nov 2023 18:20:54 +0100 Subject: [PATCH 29/63] Async --- packages/backend-core/src/redis/redlockImpl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index 048d17d080..9a6b200c81 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -118,8 +118,8 @@ export async function doWithLock( if (!opts.ttl) { // No TTL is provided, so we keep extending the lock while the task is running - interval = setInterval(() => { - lock?.extend(ttl / 2) + interval = setInterval(async () => { + await lock?.extend(ttl / 2) }, ttl / 2) } From 26a77298acf508b73a7f9d4df404d00a027d099a Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Nov 2023 18:31:53 +0100 Subject: [PATCH 30/63] Use timers --- packages/backend-core/src/redis/redlockImpl.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index 9a6b200c81..d57385d8fe 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -5,6 +5,7 @@ import * as context from "../context" import env from "../environment" import { logWarn } from "../logging" import { Duration } from "../utils" +import { timers } from ".." async function getClient( type: LockType, @@ -118,7 +119,7 @@ export async function doWithLock( if (!opts.ttl) { // No TTL is provided, so we keep extending the lock while the task is running - interval = setInterval(async () => { + interval = timers.set(async () => { await lock?.extend(ttl / 2) }, ttl / 2) } @@ -146,7 +147,7 @@ export async function doWithLock( await lock.unlock() } if (interval) { - clearInterval(interval) + timers.clear(interval) } } } From a32582eb8aee59ef39a892907c8eb8e442058bd0 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Nov 2023 20:50:50 +0100 Subject: [PATCH 31/63] Use autoextend as locktype --- .../backend-core/src/redis/redlockImpl.ts | 60 +++++++++++++------ packages/types/src/sdk/locks.ts | 5 +- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index d57385d8fe..3383dbff13 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -4,8 +4,7 @@ import { LockOptions, LockType } from "@budibase/types" import * as context from "../context" import env from "../environment" import { logWarn } from "../logging" -import { Duration } from "../utils" -import { timers } from ".." +import { utils } from "@budibase/shared-core" async function getClient( type: LockType, @@ -14,7 +13,11 @@ async function getClient( if (type === LockType.CUSTOM) { return newRedlock(opts) } - if (env.isTest() && type !== LockType.TRY_ONCE) { + if ( + env.isTest() && + type !== LockType.TRY_ONCE && + type !== LockType.AUTO_EXTEND + ) { return newRedlock(OPTIONS.TEST) } switch (type) { @@ -30,13 +33,16 @@ async function getClient( case LockType.DELAY_500: { return newRedlock(OPTIONS.DELAY_500) } + case LockType.AUTO_EXTEND: { + return newRedlock(OPTIONS.AUTO_EXTEND) + } default: { - throw new Error(`Could not get redlock client: ${type}`) + throw utils.unreachable(type) } } } -const OPTIONS = { +const OPTIONS: Record = { TRY_ONCE: { // immediately throws an error if the lock is already held retryCount: 0, @@ -69,10 +75,14 @@ const OPTIONS = { DELAY_500: { retryDelay: 500, }, + CUSTOM: {}, + AUTO_EXTEND: { + retryCount: -1, + }, } export async function newRedlock(opts: Redlock.Options = {}) { - let options = { ...OPTIONS.DEFAULT, ...opts } + let options = { ...OPTIONS, ...opts } const redisWrapper = await getLockClient() const client = redisWrapper.getClient() return new Redlock([client], options) @@ -108,20 +118,36 @@ export async function doWithLock( ): Promise> { const redlock = await getClient(opts.type, opts.customOptions) let lock: Redlock.Lock | undefined - let interval + let timeout try { const name = getLockName(opts) - let ttl = opts.ttl || Duration.fromSeconds(30).toMs() - // create the lock - lock = await redlock.lock(name, ttl) + lock = await redlock.lock(name, opts.ttl) - if (!opts.ttl) { + if (opts.type === LockType.AUTO_EXTEND) { // No TTL is provided, so we keep extending the lock while the task is running - interval = timers.set(async () => { - await lock?.extend(ttl / 2) - }, ttl / 2) + const extendInIntervals = (): void => { + timeout = setTimeout(async () => { + let isExpired = false + try { + lock = await lock!.extend(1000) + } catch (err: any) { + isExpired = err.message.includes("Cannot extend lock on resource") + if (isExpired) { + console.error("The lock expired", { name }) + } else { + throw err + } + } + + if (!isExpired) { + extendInIntervals() + } + }, opts.ttl / 2) + } + + extendInIntervals() } // perform locked task @@ -143,11 +169,11 @@ export async function doWithLock( throw e } } finally { + if (timeout) { + clearTimeout(timeout) + } if (lock) { await lock.unlock() } - if (interval) { - timers.clear(interval) - } } } diff --git a/packages/types/src/sdk/locks.ts b/packages/types/src/sdk/locks.ts index 080574b735..2a2e74c4cc 100644 --- a/packages/types/src/sdk/locks.ts +++ b/packages/types/src/sdk/locks.ts @@ -10,6 +10,7 @@ export enum LockType { DEFAULT = "default", DELAY_500 = "delay_500", CUSTOM = "custom", + AUTO_EXTEND = "auto_extend", } export enum LockName { @@ -36,9 +37,9 @@ export interface LockOptions { */ name: LockName /** - * The ttl to auto-expire the lock if not unlocked manually. If undefined, the lock will be autoextending while the process is running. + * The ttl to auto-expire the lock if not unlocked manually. */ - ttl?: number + ttl: number /** * The individual resource to lock. This is useful for locking around very specific identifiers, e.g. a document that is prone to conflicts */ From 4cd76ea0fc0abea383ca42baa817816d9fbfe068 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Nov 2023 21:03:36 +0100 Subject: [PATCH 32/63] Add tests --- .../src/redis/tests/redlockImpl.spec.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 packages/backend-core/src/redis/tests/redlockImpl.spec.ts diff --git a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts new file mode 100644 index 0000000000..567c160db2 --- /dev/null +++ b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts @@ -0,0 +1,44 @@ +import { LockName, LockType } from "@budibase/types" +import { doWithLock } from "../redlockImpl" +import { DBTestConfiguration } from "../../../tests" +import { Duration } from "../../utils" + +describe("redlockImpl", () => { + beforeEach(() => { + jest.useFakeTimers() + }) + + afterEach(() => { + jest.runOnlyPendingTimers() + jest.useRealTimers() + }) + + describe("doWithLock", () => { + it("should execute the task and return the result", async () => { + const mockTask = jest.fn().mockResolvedValue("mockResult") + + // Define test options + const testOpts = { + name: LockName.PERSIST_WRITETHROUGH, + type: LockType.AUTO_EXTEND, + ttl: 30000, + } + + // Call the function with the mock lock and task + const config = new DBTestConfiguration() + const result = await config.doInTenant(() => + doWithLock(testOpts, async () => { + jest.advanceTimersByTime(Duration.fromSeconds(10).toMs()) + jest.advanceTimersByTime(Duration.fromSeconds(10).toMs()) + jest.advanceTimersByTime(Duration.fromSeconds(10).toMs()) + return mockTask() + }) + ) + + // Assert the result and verify function calls + expect(result.executed).toBe(true) + expect(result.executed && result.result).toBe("mockResult") + expect(mockTask).toHaveBeenCalledTimes(1) + }) + }) +}) From f1fafc07f27400add94347fd2ad54ca906084625 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Nov 2023 21:03:49 +0100 Subject: [PATCH 33/63] Update ioredis-mock --- packages/backend-core/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index dc8d71b52c..306aabfe6a 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -72,7 +72,7 @@ "@types/tar-fs": "2.0.1", "@types/uuid": "8.3.4", "chance": "1.1.8", - "ioredis-mock": "8.7.0", + "ioredis-mock": "8.9.0", "jest": "29.6.2", "jest-environment-node": "29.6.2", "jest-serial-runner": "1.2.1", diff --git a/yarn.lock b/yarn.lock index 700c3f9456..a09ae20de6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12667,16 +12667,16 @@ invert-kv@^2.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== -ioredis-mock@8.7.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/ioredis-mock/-/ioredis-mock-8.7.0.tgz#9877a85e0d233e1b49123d1c6e320df01e9a1d36" - integrity sha512-BJcSjkR3sIMKbH93fpFzwlWi/jl1kd5I3vLvGQxnJ/W/6bD2ksrxnyQN186ljAp3Foz4p1ivViDE3rZeKEAluA== +ioredis-mock@8.9.0: + version "8.9.0" + resolved "https://registry.yarnpkg.com/ioredis-mock/-/ioredis-mock-8.9.0.tgz#5d694c4b81d3835e4291e0b527f947e260981779" + integrity sha512-yIglcCkI1lvhwJVoMsR51fotZVsPsSk07ecTCgRTRlicG0Vq3lke6aAaHklyjmRNRsdYAgswqC2A0bPtQK4LSw== dependencies: "@ioredis/as-callback" "^3.0.0" "@ioredis/commands" "^1.2.0" fengari "^0.1.4" fengari-interop "^0.1.3" - semver "^7.3.8" + semver "^7.5.4" ioredis@5.3.2: version "5.3.2" From 73fd1f66c58736ce8963226e0135d10b85e72441 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Nov 2023 22:01:25 +0100 Subject: [PATCH 34/63] Fix --- packages/backend-core/src/redis/redlockImpl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index 3383dbff13..8368696137 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -131,7 +131,7 @@ export async function doWithLock( timeout = setTimeout(async () => { let isExpired = false try { - lock = await lock!.extend(1000) + lock = await lock!.extend(opts.ttl) } catch (err: any) { isExpired = err.message.includes("Cannot extend lock on resource") if (isExpired) { From 9c12c5b62ef5e760add8e5b7c1783190dfa89c25 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Nov 2023 22:01:49 +0100 Subject: [PATCH 35/63] Fix comments --- packages/backend-core/src/redis/redlockImpl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index 8368696137..841da5fcd1 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -126,7 +126,7 @@ export async function doWithLock( lock = await redlock.lock(name, opts.ttl) if (opts.type === LockType.AUTO_EXTEND) { - // No TTL is provided, so we keep extending the lock while the task is running + // We keep extending the lock while the task is running const extendInIntervals = (): void => { timeout = setTimeout(async () => { let isExpired = false From 078384941ae68a6741ab5c0794d1b82337167d31 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Nov 2023 22:09:09 +0100 Subject: [PATCH 36/63] Clean --- packages/backend-core/src/redis/redlockImpl.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index 841da5fcd1..dec8aad032 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -169,11 +169,7 @@ export async function doWithLock( throw e } } finally { - if (timeout) { - clearTimeout(timeout) - } - if (lock) { - await lock.unlock() - } + clearTimeout(timeout) + await lock?.unlock() } } From db6517bc0c21cd69b09e1ebcc939cf51a24549b3 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 29 Nov 2023 22:42:29 +0100 Subject: [PATCH 37/63] Use real sleeps --- .../src/redis/tests/redlockImpl.spec.ts | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts index 567c160db2..0661b8f7cc 100644 --- a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts +++ b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts @@ -1,18 +1,8 @@ import { LockName, LockType } from "@budibase/types" import { doWithLock } from "../redlockImpl" import { DBTestConfiguration } from "../../../tests" -import { Duration } from "../../utils" describe("redlockImpl", () => { - beforeEach(() => { - jest.useFakeTimers() - }) - - afterEach(() => { - jest.runOnlyPendingTimers() - jest.useRealTimers() - }) - describe("doWithLock", () => { it("should execute the task and return the result", async () => { const mockTask = jest.fn().mockResolvedValue("mockResult") @@ -21,16 +11,14 @@ describe("redlockImpl", () => { const testOpts = { name: LockName.PERSIST_WRITETHROUGH, type: LockType.AUTO_EXTEND, - ttl: 30000, + ttl: 5, } // Call the function with the mock lock and task const config = new DBTestConfiguration() const result = await config.doInTenant(() => doWithLock(testOpts, async () => { - jest.advanceTimersByTime(Duration.fromSeconds(10).toMs()) - jest.advanceTimersByTime(Duration.fromSeconds(10).toMs()) - jest.advanceTimersByTime(Duration.fromSeconds(10).toMs()) + await new Promise(r => setTimeout(() => r(), 10)) return mockTask() }) ) From c86d94968068cf6f6e0c9f4c65100dc55ef0e1cc Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Nov 2023 10:33:39 +0100 Subject: [PATCH 38/63] Add and dry tests --- .../src/redis/tests/redlockImpl.spec.ts | 104 ++++++++++++++---- 1 file changed, 85 insertions(+), 19 deletions(-) diff --git a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts index 0661b8f7cc..b3dd572ba9 100644 --- a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts +++ b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts @@ -1,32 +1,98 @@ -import { LockName, LockType } from "@budibase/types" +import { LockName, LockType, LockOptions } from "@budibase/types" +import tk from "timekeeper" import { doWithLock } from "../redlockImpl" -import { DBTestConfiguration } from "../../../tests" +import { DBTestConfiguration, generator } from "../../../tests" + +tk.reset() describe("redlockImpl", () => { describe("doWithLock", () => { - it("should execute the task and return the result", async () => { - const mockTask = jest.fn().mockResolvedValue("mockResult") + const config = new DBTestConfiguration() + const lockTtl = 30 - // Define test options - const testOpts = { - name: LockName.PERSIST_WRITETHROUGH, - type: LockType.AUTO_EXTEND, - ttl: 5, - } - - // Call the function with the mock lock and task - const config = new DBTestConfiguration() - const result = await config.doInTenant(() => - doWithLock(testOpts, async () => { - await new Promise(r => setTimeout(() => r(), 10)) - return mockTask() + function runLockWithExecutionTime({ + opts, + task, + executionTimeMs, + }: { + opts: LockOptions + task: () => Promise + executionTimeMs: number + }) { + return config.doInTenant(() => + doWithLock(opts, async () => { + await new Promise(r => setTimeout(() => r(), executionTimeMs)) + return task() }) ) + } + + it.each(Object.values(LockType))( + "should return the task value", + async (lockType: LockType) => { + const expectedResult = generator.guid() + const mockTask = jest.fn().mockResolvedValue(expectedResult) + + const opts = { + name: LockName.PERSIST_WRITETHROUGH, + type: lockType, + ttl: lockTtl, + } + + const result = await runLockWithExecutionTime({ + opts, + task: mockTask, + executionTimeMs: 0, + }) + + expect(result.executed).toBe(true) + expect(result.executed && result.result).toBe(expectedResult) + expect(mockTask).toHaveBeenCalledTimes(1) + } + ) + + it("should extend when type is autoextend", async () => { + const expectedResult = generator.guid() + const mockTask = jest.fn().mockResolvedValue(expectedResult) + + const opts = { + name: LockName.PERSIST_WRITETHROUGH, + type: LockType.AUTO_EXTEND, + ttl: lockTtl, + } + + const result = await runLockWithExecutionTime({ + opts, + task: mockTask, + executionTimeMs: lockTtl * 2, + }) - // Assert the result and verify function calls expect(result.executed).toBe(true) - expect(result.executed && result.result).toBe("mockResult") + expect(result.executed && result.result).toBe(expectedResult) expect(mockTask).toHaveBeenCalledTimes(1) }) + + it.each(Object.values(LockType).filter(t => t !== LockType.AUTO_EXTEND))( + "should timeout when type is %s", + async (lockType: LockType) => { + const mockTask = jest.fn().mockResolvedValue("mockResult") + + const opts = { + name: LockName.PERSIST_WRITETHROUGH, + type: lockType, + ttl: lockTtl, + } + + await expect( + runLockWithExecutionTime({ + opts, + task: mockTask, + executionTimeMs: lockTtl * 2, + }) + ).rejects.toThrowError( + `Unable to fully release the lock on resource \"lock:${config.tenantId}_persist_writethrough\".` + ) + } + ) }) }) From dcb6933eaac9ac5ddcb7e103bd2ffd36f971634e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Nov 2023 11:20:43 +0100 Subject: [PATCH 39/63] Clean --- packages/backend-core/src/redis/tests/redlockImpl.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts index b3dd572ba9..b9d11c20a0 100644 --- a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts +++ b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts @@ -8,7 +8,7 @@ tk.reset() describe("redlockImpl", () => { describe("doWithLock", () => { const config = new DBTestConfiguration() - const lockTtl = 30 + const lockTtl = 25 function runLockWithExecutionTime({ opts, @@ -28,7 +28,7 @@ describe("redlockImpl", () => { } it.each(Object.values(LockType))( - "should return the task value", + "should return the task value and release the lock", async (lockType: LockType) => { const expectedResult = generator.guid() const mockTask = jest.fn().mockResolvedValue(expectedResult) @@ -64,7 +64,7 @@ describe("redlockImpl", () => { const result = await runLockWithExecutionTime({ opts, task: mockTask, - executionTimeMs: lockTtl * 2, + executionTimeMs: lockTtl * 3, }) expect(result.executed).toBe(true) From bbe41e04a2aa2ae704700b11e111e7df0d4809cc Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Nov 2023 11:56:04 +0100 Subject: [PATCH 40/63] Update openapi specs --- packages/server/specs/openapi.json | 2 +- packages/server/specs/openapi.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/specs/openapi.json b/packages/server/specs/openapi.json index a6900adea7..da532802ab 100644 --- a/packages/server/specs/openapi.json +++ b/packages/server/specs/openapi.json @@ -2152,7 +2152,7 @@ "/applications/{appId}/publish": { "post": { "operationId": "appPublish", - "summary": "Unpublish an application", + "summary": "Publish an application", "tags": [ "applications" ], diff --git a/packages/server/specs/openapi.yaml b/packages/server/specs/openapi.yaml index ad02a3cd9c..7543641ba6 100644 --- a/packages/server/specs/openapi.yaml +++ b/packages/server/specs/openapi.yaml @@ -1761,7 +1761,7 @@ paths: "/applications/{appId}/publish": post: operationId: appPublish - summary: Unpublish an application + summary: Publish an application tags: - applications parameters: From 57b4c08731f0562264bc64c89574379d061be966 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Nov 2023 11:57:29 +0100 Subject: [PATCH 41/63] Increase timeouts --- packages/backend-core/src/redis/tests/redlockImpl.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts index b9d11c20a0..9d312721b1 100644 --- a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts +++ b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts @@ -8,7 +8,7 @@ tk.reset() describe("redlockImpl", () => { describe("doWithLock", () => { const config = new DBTestConfiguration() - const lockTtl = 25 + const lockTtl = 50 function runLockWithExecutionTime({ opts, From 25099ee28ef8bb004d8bf5508fecb901c983dfb5 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Nov 2023 12:02:47 +0100 Subject: [PATCH 42/63] Clean redlock test settings --- packages/backend-core/src/redis/redlockImpl.ts | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index dec8aad032..a4586a4495 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -13,13 +13,7 @@ async function getClient( if (type === LockType.CUSTOM) { return newRedlock(opts) } - if ( - env.isTest() && - type !== LockType.TRY_ONCE && - type !== LockType.AUTO_EXTEND - ) { - return newRedlock(OPTIONS.TEST) - } + switch (type) { case LockType.TRY_ONCE: { return newRedlock(OPTIONS.TRY_ONCE) @@ -42,7 +36,7 @@ async function getClient( } } -const OPTIONS: Record = { +const OPTIONS: Record = { TRY_ONCE: { // immediately throws an error if the lock is already held retryCount: 0, @@ -50,11 +44,6 @@ const OPTIONS: Record = { TRY_TWICE: { retryCount: 1, }, - TEST: { - // higher retry count in unit tests - // due to high contention. - retryCount: 100, - }, DEFAULT: { // the expected clock drift; for more details // see http://redis.io/topics/distlock From e81e37b613d7fa765a2a6ff57b46f05b456db0b2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Nov 2023 12:07:14 +0100 Subject: [PATCH 43/63] Clean comments --- packages/types/src/sdk/locks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/sdk/locks.ts b/packages/types/src/sdk/locks.ts index 2a2e74c4cc..a04b8238a9 100644 --- a/packages/types/src/sdk/locks.ts +++ b/packages/types/src/sdk/locks.ts @@ -37,7 +37,7 @@ export interface LockOptions { */ name: LockName /** - * The ttl to auto-expire the lock if not unlocked manually. + * The ttl to auto-expire the lock if not unlocked manually */ ttl: number /** From a8ac4eed6dc072d8f903c2f8e17e734a827b84b9 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Nov 2023 13:54:42 +0100 Subject: [PATCH 44/63] Autoextend without ttl --- .../backend-core/src/redis/redlockImpl.ts | 13 +++++++++---- packages/types/src/sdk/locks.ts | 19 +++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index a4586a4495..2cabeccf1d 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -2,9 +2,9 @@ import Redlock from "redlock" import { getLockClient } from "./init" import { LockOptions, LockType } from "@budibase/types" import * as context from "../context" -import env from "../environment" import { logWarn } from "../logging" import { utils } from "@budibase/shared-core" +import { Duration } from "../utils" async function getClient( type: LockType, @@ -111,8 +111,13 @@ export async function doWithLock( try { const name = getLockName(opts) + const ttl = + opts.type === LockType.AUTO_EXTEND + ? Duration.fromSeconds(10).toMs() + : opts.ttl + // create the lock - lock = await redlock.lock(name, opts.ttl) + lock = await redlock.lock(name, ttl) if (opts.type === LockType.AUTO_EXTEND) { // We keep extending the lock while the task is running @@ -120,7 +125,7 @@ export async function doWithLock( timeout = setTimeout(async () => { let isExpired = false try { - lock = await lock!.extend(opts.ttl) + lock = await lock!.extend(ttl) } catch (err: any) { isExpired = err.message.includes("Cannot extend lock on resource") if (isExpired) { @@ -133,7 +138,7 @@ export async function doWithLock( if (!isExpired) { extendInIntervals() } - }, opts.ttl / 2) + }, ttl / 2) } extendInIntervals() diff --git a/packages/types/src/sdk/locks.ts b/packages/types/src/sdk/locks.ts index a04b8238a9..b9470402c1 100644 --- a/packages/types/src/sdk/locks.ts +++ b/packages/types/src/sdk/locks.ts @@ -22,7 +22,7 @@ export enum LockName { QUOTA_USAGE_EVENT = "quota_usage_event", } -export interface LockOptions { +export type LockOptions = { /** * The lock type determines which client to use */ @@ -36,10 +36,6 @@ export interface LockOptions { * The name for the lock */ name: LockName - /** - * The ttl to auto-expire the lock if not unlocked manually - */ - ttl: number /** * The individual resource to lock. This is useful for locking around very specific identifiers, e.g. a document that is prone to conflicts */ @@ -48,4 +44,15 @@ export interface LockOptions { * This is a system-wide lock - don't use tenancy in lock key */ systemLock?: boolean -} +} & ( + | { + /** + * The ttl to auto-expire the lock if not unlocked manually + */ + ttl: number + type: Exclude + } + | { + type: LockType.AUTO_EXTEND + } +) From bd89633e61c1720b6e19d2686f80faf17d306b29 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Nov 2023 18:23:35 +0100 Subject: [PATCH 45/63] Fix wrong commited code --- packages/backend-core/src/redis/redlockImpl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index 2cabeccf1d..36504ea916 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -71,7 +71,7 @@ const OPTIONS: Record = { } export async function newRedlock(opts: Redlock.Options = {}) { - let options = { ...OPTIONS, ...opts } + const options = { ...OPTIONS.DEFAULT, ...opts } const redisWrapper = await getLockClient() const client = redisWrapper.getClient() return new Redlock([client], options) From fb72b77ac19491a9f06994014eed0c9145661a70 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Nov 2023 18:34:56 +0100 Subject: [PATCH 46/63] Use jest.useFakeTimers --- .../backend-core/src/redis/redlockImpl.ts | 24 ++++------------ .../src/redis/tests/redlockImpl.spec.ts | 28 +++++++++++-------- packages/types/src/sdk/locks.ts | 1 + 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index 36504ea916..a65dcd0c3f 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -101,20 +101,20 @@ function getLockName(opts: LockOptions) { return name } +export const autoExtendPollingMs = Duration.fromSeconds(10).toMs() + export async function doWithLock( opts: LockOptions, task: () => Promise ): Promise> { const redlock = await getClient(opts.type, opts.customOptions) let lock: Redlock.Lock | undefined - let timeout + let timeout: NodeJS.Timeout | undefined try { const name = getLockName(opts) const ttl = - opts.type === LockType.AUTO_EXTEND - ? Duration.fromSeconds(10).toMs() - : opts.ttl + opts.type === LockType.AUTO_EXTEND ? autoExtendPollingMs : opts.ttl // create the lock lock = await redlock.lock(name, ttl) @@ -123,21 +123,9 @@ export async function doWithLock( // We keep extending the lock while the task is running const extendInIntervals = (): void => { timeout = setTimeout(async () => { - let isExpired = false - try { - lock = await lock!.extend(ttl) - } catch (err: any) { - isExpired = err.message.includes("Cannot extend lock on resource") - if (isExpired) { - console.error("The lock expired", { name }) - } else { - throw err - } - } + lock = await lock!.extend(ttl, () => opts.onExtend && opts.onExtend()) - if (!isExpired) { - extendInIntervals() - } + extendInIntervals() }, ttl / 2) } diff --git a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts index 9d312721b1..6f894c2951 100644 --- a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts +++ b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts @@ -1,14 +1,15 @@ import { LockName, LockType, LockOptions } from "@budibase/types" -import tk from "timekeeper" -import { doWithLock } from "../redlockImpl" +import { autoExtendPollingMs, doWithLock } from "../redlockImpl" import { DBTestConfiguration, generator } from "../../../tests" -tk.reset() - describe("redlockImpl", () => { + beforeEach(() => { + jest.useFakeTimers() + }) + describe("doWithLock", () => { const config = new DBTestConfiguration() - const lockTtl = 50 + const lockTtl = autoExtendPollingMs function runLockWithExecutionTime({ opts, @@ -21,7 +22,10 @@ describe("redlockImpl", () => { }) { return config.doInTenant(() => doWithLock(opts, async () => { - await new Promise(r => setTimeout(() => r(), executionTimeMs)) + const interval = lockTtl / 10 + for (let i = executionTimeMs; i > 0; i -= interval) { + await jest.advanceTimersByTimeAsync(interval) + } return task() }) ) @@ -33,7 +37,7 @@ describe("redlockImpl", () => { const expectedResult = generator.guid() const mockTask = jest.fn().mockResolvedValue(expectedResult) - const opts = { + const opts: LockOptions = { name: LockName.PERSIST_WRITETHROUGH, type: lockType, ttl: lockTtl, @@ -54,22 +58,24 @@ describe("redlockImpl", () => { it("should extend when type is autoextend", async () => { const expectedResult = generator.guid() const mockTask = jest.fn().mockResolvedValue(expectedResult) + const mockOnExtend = jest.fn() - const opts = { + const opts: LockOptions = { name: LockName.PERSIST_WRITETHROUGH, type: LockType.AUTO_EXTEND, - ttl: lockTtl, + onExtend: mockOnExtend, } const result = await runLockWithExecutionTime({ opts, task: mockTask, - executionTimeMs: lockTtl * 3, + executionTimeMs: lockTtl * 2.5, }) expect(result.executed).toBe(true) expect(result.executed && result.result).toBe(expectedResult) expect(mockTask).toHaveBeenCalledTimes(1) + expect(mockOnExtend).toHaveBeenCalledTimes(5) }) it.each(Object.values(LockType).filter(t => t !== LockType.AUTO_EXTEND))( @@ -77,7 +83,7 @@ describe("redlockImpl", () => { async (lockType: LockType) => { const mockTask = jest.fn().mockResolvedValue("mockResult") - const opts = { + const opts: LockOptions = { name: LockName.PERSIST_WRITETHROUGH, type: lockType, ttl: lockTtl, diff --git a/packages/types/src/sdk/locks.ts b/packages/types/src/sdk/locks.ts index b9470402c1..6ff91d4315 100644 --- a/packages/types/src/sdk/locks.ts +++ b/packages/types/src/sdk/locks.ts @@ -54,5 +54,6 @@ export type LockOptions = { } | { type: LockType.AUTO_EXTEND + onExtend?: () => void } ) From 670853a0ea074635c49d086dd2def605a8753255 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Nov 2023 18:36:36 +0100 Subject: [PATCH 47/63] Renames --- packages/backend-core/src/redis/redlockImpl.ts | 4 ++-- packages/backend-core/src/redis/tests/redlockImpl.spec.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index a65dcd0c3f..a7b2e2b4c6 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -101,7 +101,7 @@ function getLockName(opts: LockOptions) { return name } -export const autoExtendPollingMs = Duration.fromSeconds(10).toMs() +export const AUTO_EXTEND_POLLING_MS = Duration.fromSeconds(10).toMs() export async function doWithLock( opts: LockOptions, @@ -114,7 +114,7 @@ export async function doWithLock( const name = getLockName(opts) const ttl = - opts.type === LockType.AUTO_EXTEND ? autoExtendPollingMs : opts.ttl + opts.type === LockType.AUTO_EXTEND ? AUTO_EXTEND_POLLING_MS : opts.ttl // create the lock lock = await redlock.lock(name, ttl) diff --git a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts index 6f894c2951..70920366ee 100644 --- a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts +++ b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts @@ -1,5 +1,5 @@ import { LockName, LockType, LockOptions } from "@budibase/types" -import { autoExtendPollingMs, doWithLock } from "../redlockImpl" +import { AUTO_EXTEND_POLLING_MS, doWithLock } from "../redlockImpl" import { DBTestConfiguration, generator } from "../../../tests" describe("redlockImpl", () => { @@ -9,7 +9,7 @@ describe("redlockImpl", () => { describe("doWithLock", () => { const config = new DBTestConfiguration() - const lockTtl = autoExtendPollingMs + const lockTtl = AUTO_EXTEND_POLLING_MS function runLockWithExecutionTime({ opts, From 12015c79aec963229de50ec31aa4242419a08d10 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 30 Nov 2023 18:37:34 +0100 Subject: [PATCH 48/63] Add comments --- packages/backend-core/src/redis/tests/redlockImpl.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts index 70920366ee..a1e83d8e6c 100644 --- a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts +++ b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts @@ -22,6 +22,7 @@ describe("redlockImpl", () => { }) { return config.doInTenant(() => doWithLock(opts, async () => { + // Run in multiple intervals until hitting the expected time const interval = lockTtl / 10 for (let i = executionTimeMs; i > 0; i -= interval) { await jest.advanceTimersByTimeAsync(interval) From 527f96814b89995329f8d8512a0950197842f479 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Fri, 1 Dec 2023 10:39:50 +0000 Subject: [PATCH 49/63] Bump version to 2.13.29 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 87b7c50bf7..847705ab55 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.13.28", + "version": "2.13.29", "npmClient": "yarn", "packages": [ "packages/*" From 19266b4770a643a7f78442c23e081154ebd4880f Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Fri, 1 Dec 2023 11:18:49 +0000 Subject: [PATCH 50/63] Bump version to 2.13.30 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 847705ab55..e6231eb660 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.13.29", + "version": "2.13.30", "npmClient": "yarn", "packages": [ "packages/*" From fcb862c82ff7be87c89016b1ad027a10e0bc6f37 Mon Sep 17 00:00:00 2001 From: melohagan <101575380+melohagan@users.noreply.github.com> Date: Fri, 1 Dec 2023 14:00:56 +0000 Subject: [PATCH 51/63] Prevent stale bot using default 60 days (#12488) --- .github/workflows/stale_bot.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml index 49e3473e63..8f3ab9c74c 100644 --- a/.github/workflows/stale_bot.yml +++ b/.github/workflows/stale_bot.yml @@ -10,6 +10,7 @@ jobs: steps: - uses: actions/stale@v8 with: + days-before-stale: 330 operations-per-run: 1 # stale rules for PRs days-before-pr-stale: 7 From fe0efc7539620686b5f9c11ed4cb73b46786c331 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 1 Dec 2023 09:36:24 +0100 Subject: [PATCH 52/63] Remove unused test context code --- .../backend-core/src/context/mainContext.ts | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/backend-core/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts index d2259cfcab..0dba2456a9 100644 --- a/packages/backend-core/src/context/mainContext.ts +++ b/packages/backend-core/src/context/mainContext.ts @@ -145,23 +145,19 @@ export async function doInTenant( } export async function doInAppContext( - appId: string | null, + appId: string, task: () => T ): Promise { - if (!appId && !env.isTest()) { + if (!appId) { throw new Error("appId is required") } - let updates: ContextMap - if (!appId) { - updates = { appId: "" } - } else { - const tenantId = getTenantIDFromAppID(appId) - updates = { appId } - if (tenantId) { - updates.tenantId = tenantId - } + const tenantId = getTenantIDFromAppID(appId) + const updates: ContextMap = { appId } + if (tenantId) { + updates.tenantId = tenantId } + return newContext(updates, task) } @@ -182,6 +178,27 @@ export async function doInIdentityContext( return newContext(context, task) } +export async function doInAppMigrationContext( + appId: string, + task: () => T +): Promise { + if (!appId && !env.isTest()) { + throw new Error("appId is required") + } + + let updates: ContextMap + if (!appId) { + updates = { appId: "" } + } else { + const tenantId = getTenantIDFromAppID(appId) + updates = { appId } + if (tenantId) { + updates.tenantId = tenantId + } + } + return newContext(updates, task) +} + export function getIdentity(): IdentityContext | undefined { try { const context = Context.get() From f62dd56dd6f304c54082b96cbba33c2299e33947 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 1 Dec 2023 10:19:28 +0100 Subject: [PATCH 53/63] Add doInAppMigrationContext --- .../backend-core/src/context/mainContext.ts | 32 +++++++++-------- .../src/context/tests/index.spec.ts | 36 +++++++++++++++++++ packages/backend-core/src/context/types.ts | 1 + 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/packages/backend-core/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts index 0dba2456a9..1cbf1de1c3 100644 --- a/packages/backend-core/src/context/mainContext.ts +++ b/packages/backend-core/src/context/mainContext.ts @@ -147,13 +147,21 @@ export async function doInTenant( export async function doInAppContext( appId: string, task: () => T +): Promise { + return _doInAppContext(appId, task) +} + +async function _doInAppContext( + appId: string, + task: () => T, + extraContextSettings?: ContextMap ): Promise { if (!appId) { throw new Error("appId is required") } const tenantId = getTenantIDFromAppID(appId) - const updates: ContextMap = { appId } + const updates: ContextMap = { appId, ...extraContextSettings } if (tenantId) { updates.tenantId = tenantId } @@ -182,21 +190,15 @@ export async function doInAppMigrationContext( appId: string, task: () => T ): Promise { - if (!appId && !env.isTest()) { - throw new Error("appId is required") + try { + return _doInAppContext(appId, task, { + isMigrating: true, + }) + } finally { + updateContext({ + isMigrating: undefined, + }) } - - let updates: ContextMap - if (!appId) { - updates = { appId: "" } - } else { - const tenantId = getTenantIDFromAppID(appId) - updates = { appId } - if (tenantId) { - updates.tenantId = tenantId - } - } - return newContext(updates, task) } export function getIdentity(): IdentityContext | undefined { diff --git a/packages/backend-core/src/context/tests/index.spec.ts b/packages/backend-core/src/context/tests/index.spec.ts index d8bb598af1..ef7cf17e0c 100644 --- a/packages/backend-core/src/context/tests/index.spec.ts +++ b/packages/backend-core/src/context/tests/index.spec.ts @@ -1,6 +1,10 @@ import { testEnv } from "../../../tests/extra" import * as context from "../" import { DEFAULT_TENANT_ID } from "../../constants" +import { structures } from "../../../tests" +import { db } from "../.." +import Context from "../Context" +import { ContextMap } from "../types" describe("context", () => { describe("doInTenant", () => { @@ -144,4 +148,36 @@ describe("context", () => { expect(isScim).toBe(false) }) }) + + describe("doInAppMigrationContext", () => { + it("the context is set correctly", async () => { + const appId = db.generateAppID() + + await context.doInAppMigrationContext(appId, () => { + const context = Context.get() + + const expected: ContextMap = { + appId, + isMigrating: true, + } + expect(context).toEqual(expected) + }) + }) + + it("the context is set correctly when running in a tenant id", async () => { + const tenantId = structures.tenant.id() + const appId = db.generateAppID(tenantId) + + await context.doInAppMigrationContext(appId, () => { + const context = Context.get() + + const expected: ContextMap = { + appId, + isMigrating: true, + tenantId, + } + expect(context).toEqual(expected) + }) + }) + }) }) diff --git a/packages/backend-core/src/context/types.ts b/packages/backend-core/src/context/types.ts index d687a93594..a1606a17b9 100644 --- a/packages/backend-core/src/context/types.ts +++ b/packages/backend-core/src/context/types.ts @@ -8,4 +8,5 @@ export type ContextMap = { environmentVariables?: Record isScim?: boolean automationId?: string + isMigrating?: boolean } From 14fc91d58a5a8880be025c570fa0170a8f336162 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 1 Dec 2023 10:23:47 +0100 Subject: [PATCH 54/63] Add tests --- .../backend-core/src/context/mainContext.ts | 6 ------ .../src/context/tests/index.spec.ts | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/backend-core/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts index 1cbf1de1c3..1a4d6db454 100644 --- a/packages/backend-core/src/context/mainContext.ts +++ b/packages/backend-core/src/context/mainContext.ts @@ -190,15 +190,9 @@ export async function doInAppMigrationContext( appId: string, task: () => T ): Promise { - try { return _doInAppContext(appId, task, { isMigrating: true, }) - } finally { - updateContext({ - isMigrating: undefined, - }) - } } export function getIdentity(): IdentityContext | undefined { diff --git a/packages/backend-core/src/context/tests/index.spec.ts b/packages/backend-core/src/context/tests/index.spec.ts index ef7cf17e0c..4bfbea74ce 100644 --- a/packages/backend-core/src/context/tests/index.spec.ts +++ b/packages/backend-core/src/context/tests/index.spec.ts @@ -179,5 +179,23 @@ describe("context", () => { expect(context).toEqual(expected) }) }) + + it("the context is not modified outside the delegate", async () => { + const appId = db.generateAppID() + + expect(Context.get()).toBeUndefined() + + await context.doInAppMigrationContext(appId, () => { + const context = Context.get() + + const expected: ContextMap = { + appId, + isMigrating: true, + } + expect(context).toEqual(expected) + }) + + expect(Context.get()).toBeUndefined() + }) }) }) From 7f52a1e28ce622b3c2259213f26397641dbe7ed3 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 1 Dec 2023 10:39:44 +0100 Subject: [PATCH 55/63] Guard migration --- .../backend-core/src/context/mainContext.ts | 17 ++++-- .../src/context/tests/index.spec.ts | 54 +++++++++++++++++++ 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/packages/backend-core/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts index 1a4d6db454..d2fdd040df 100644 --- a/packages/backend-core/src/context/mainContext.ts +++ b/packages/backend-core/src/context/mainContext.ts @@ -99,6 +99,8 @@ function updateContext(updates: ContextMap): ContextMap { } async function newContext(updates: ContextMap, task: () => T) { + guardMigration() + // see if there already is a context setup let context: ContextMap = updateContext(updates) return Context.run(context, task) @@ -186,13 +188,22 @@ export async function doInIdentityContext( return newContext(context, task) } +function guardMigration() { + const context = Context.get() + if (context?.isMigrating) { + throw new Error( + "The context cannot be change, a migration is currently running" + ) + } +} + export async function doInAppMigrationContext( appId: string, task: () => T ): Promise { - return _doInAppContext(appId, task, { - isMigrating: true, - }) + return _doInAppContext(appId, task, { + isMigrating: true, + }) } export function getIdentity(): IdentityContext | undefined { diff --git a/packages/backend-core/src/context/tests/index.spec.ts b/packages/backend-core/src/context/tests/index.spec.ts index 4bfbea74ce..183e09a509 100644 --- a/packages/backend-core/src/context/tests/index.spec.ts +++ b/packages/backend-core/src/context/tests/index.spec.ts @@ -5,6 +5,7 @@ import { structures } from "../../../tests" import { db } from "../.." import Context from "../Context" import { ContextMap } from "../types" +import { IdentityType } from "@budibase/types" describe("context", () => { describe("doInTenant", () => { @@ -197,5 +198,58 @@ describe("context", () => { expect(Context.get()).toBeUndefined() }) + + it.each([ + [ + "doInAppMigrationContext", + () => context.doInAppMigrationContext(db.generateAppID(), () => {}), + ], + [ + "doInAppContext", + () => context.doInAppContext(db.generateAppID(), () => {}), + ], + [ + "doInAutomationContext", + () => + context.doInAutomationContext({ + appId: db.generateAppID(), + automationId: structures.generator.guid(), + task: () => {}, + }), + ], + ["doInContext", () => context.doInContext(db.generateAppID(), () => {})], + [ + "doInEnvironmentContext", + () => context.doInEnvironmentContext({}, () => {}), + ], + [ + "doInIdentityContext", + () => + context.doInIdentityContext( + { + account: undefined, + type: IdentityType.USER, + _id: structures.users.user()._id!, + }, + () => {} + ), + ], + ["doInScimContext", () => context.doInScimContext(() => {})], + [ + "doInTenant", + () => context.doInTenant(structures.tenant.id(), () => {}), + ], + ])( + "a nested context.%s function cannot run", + async (_, otherContextCall: () => Promise) => { + await expect( + context.doInAppMigrationContext(db.generateAppID(), async () => { + await otherContextCall() + }) + ).rejects.toThrowError( + "The context cannot be change, a migration is currently running" + ) + } + ) }) }) From fa7693f6df62532446e15eb8f30af2525316ef02 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 4 Dec 2023 09:09:42 +0100 Subject: [PATCH 56/63] Typo Co-authored-by: Sam Rose --- packages/backend-core/src/context/mainContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-core/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts index d2fdd040df..983a4d20e1 100644 --- a/packages/backend-core/src/context/mainContext.ts +++ b/packages/backend-core/src/context/mainContext.ts @@ -192,7 +192,7 @@ function guardMigration() { const context = Context.get() if (context?.isMigrating) { throw new Error( - "The context cannot be change, a migration is currently running" + "The context cannot be changed, a migration is currently running" ) } } From 188d5d09a2a55a6c746c6768be07ec45c87ef02c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 4 Dec 2023 09:09:59 +0100 Subject: [PATCH 57/63] Typo Co-authored-by: Sam Rose --- packages/backend-core/src/context/tests/index.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-core/src/context/tests/index.spec.ts b/packages/backend-core/src/context/tests/index.spec.ts index 183e09a509..cfc820e169 100644 --- a/packages/backend-core/src/context/tests/index.spec.ts +++ b/packages/backend-core/src/context/tests/index.spec.ts @@ -247,7 +247,7 @@ describe("context", () => { await otherContextCall() }) ).rejects.toThrowError( - "The context cannot be change, a migration is currently running" + "The context cannot be changed, a migration is currently running" ) } ) From 083ff0b7c7c223f5a0381e3cc56c207f1ac47f0e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 4 Dec 2023 09:23:01 +0100 Subject: [PATCH 58/63] Fix tests typing --- .../server/src/tests/utilities/TestConfiguration.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index 3a14a87d2a..b7886ccea4 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -137,6 +137,10 @@ class TestConfiguration { } getAppId() { + if (!this.appId) { + throw "appId has not been initialised properly" + } + return this.appId } @@ -510,7 +514,7 @@ class TestConfiguration { // create dev app // clear any old app this.appId = null - this.app = await context.doInAppContext(null, async () => { + this.app = await context.doInTenant(this.tenantId!, async () => { const app = await this._req( { name: appName }, null, @@ -519,7 +523,7 @@ class TestConfiguration { this.appId = app.appId! return app }) - return await context.doInAppContext(this.appId, async () => { + return await context.doInAppContext(this.getAppId(), async () => { // create production app this.prodApp = await this.publish() @@ -817,7 +821,7 @@ class TestConfiguration { } async getAutomationLogs() { - return context.doInAppContext(this.appId, async () => { + return context.doInAppContext(this.getAppId(), async () => { const now = new Date() return await pro.sdk.automations.logs.logSearch({ startDate: new Date(now.getTime() - 100000).toISOString(), From 98ae0014a86fc735e87e0af789117bd666ea4fa1 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 4 Dec 2023 11:47:11 +0000 Subject: [PATCH 59/63] Respond to PR comments. --- charts/budibase/README.md | 4 ---- charts/budibase/values.yaml | 5 ++--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/charts/budibase/README.md b/charts/budibase/README.md index 5d74e05e0b..164388b730 100644 --- a/charts/budibase/README.md +++ b/charts/budibase/README.md @@ -69,9 +69,6 @@ staff's homelabs). ```yaml ingress: enabled: true - annotations: - nginx.ingress.kubernetes.io/client-max-body-size: 150M - nginx.ingress.kubernetes.io/proxy-body-size: 50m className: "nginx" hosts: - host: budibase.local # set this to whatever DNS name you'd use @@ -130,7 +127,6 @@ $ helm install --create-namespace --namespace budibase budibase . -f values.yaml | globals.internalApiKeyFallback | string | `""` | A fallback value for `internalApiKey`. If you're rotating your encryption key, you can set this to the old value for the duration of the rotation. | | globals.jwtSecret | string | `""` | Secret used for signing JWTs. You don't need to set this if `createSecrets` is true. | | globals.jwtSecretFallback | string | `""` | A fallback value for `jwtSecret`. If you're rotating your JWT secret, you can set this to the old value for the duration of the rotation. | -| globals.multiTenancy | string | `"0"` | Whether to enable the multi-tenancy feature or not. "0" means an installation can only have one tenant. "1" allows multiple tenants. | | globals.platformUrl | string | `""` | Set the `platformUrl` binding. You can also do this in Settings > Organisation if you are self-hosting. | | globals.smtp.enabled | bool | `false` | Whether to enable SMTP or not. | | globals.smtp.from | string | `""` | The email address to use in the "From:" field of emails sent by Budibase. | diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index 0b693a4df3..13054e75fc 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -71,11 +71,10 @@ globals: # @ignore (should not normally need to be changed, we only set this to "0" # when deploying to our Cloud environment) selfHosted: "1" - # -- Whether to enable the multi-tenancy feature or not. "0" means an installation can only - # have one tenant. "1" allows multiple tenants. + # @ignore (doesn't work out of the box for self-hosted users, only meant for Budicloud) multiTenancy: "0" # @ignore (only currently used to determine whether to fetch licenses offline or not, should - # not normally need to be changed) + # not normally need to be changed, and only applies to Enterprise customers) offlineMode: "0" # @ignore (only needs to be set in our cloud environment) accountPortalUrl: "" From 1c0976b97a075710df385c7c1b4c2124658a808b Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Mon, 4 Dec 2023 12:31:05 +0000 Subject: [PATCH 60/63] Bump version to 2.13.31 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index e6231eb660..ccbd2d9d60 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.13.30", + "version": "2.13.31", "npmClient": "yarn", "packages": [ "packages/*" From e4237c0620704bab8c3ca116d44c3f554d856c28 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 4 Dec 2023 15:15:40 +0100 Subject: [PATCH 61/63] Remove conflicting typing --- packages/backend-core/src/redis/redlockImpl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index a7b2e2b4c6..4de2516ab2 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -109,7 +109,7 @@ export async function doWithLock( ): Promise> { const redlock = await getClient(opts.type, opts.customOptions) let lock: Redlock.Lock | undefined - let timeout: NodeJS.Timeout | undefined + let timeout try { const name = getLockName(opts) From a7410020c7a9feba80e38bd200cef2e87ed882ed Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 4 Dec 2023 16:47:41 +0000 Subject: [PATCH 62/63] Adding cookie clearing/logout for when a cross tenant session is detected, make sure that the cookie cannot be used/considered valid after the call is made. --- packages/backend-core/src/tenancy/tenancy.ts | 12 +++++- packages/server/src/middleware/currentapp.ts | 41 ++++++++++++-------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/packages/backend-core/src/tenancy/tenancy.ts b/packages/backend-core/src/tenancy/tenancy.ts index 7b17bdbe18..3603ef3462 100644 --- a/packages/backend-core/src/tenancy/tenancy.ts +++ b/packages/backend-core/src/tenancy/tenancy.ts @@ -93,11 +93,19 @@ export const getTenantIDFromCtx = ( // subdomain if (isAllowed(TenantResolutionStrategy.SUBDOMAIN)) { // e.g. budibase.app or local.com:10000 - const platformHost = new URL(getPlatformURL()).host.split(":")[0] + let platformHost + try { + platformHost = new URL(getPlatformURL()).host.split(":")[0] + } catch (err: any) { + // if invalid URL, just don't try to process subdomain + if (err.code !== "ERR_INVALID_URL") { + throw err + } + } // e.g. tenant.budibase.app or tenant.local.com const requestHost = ctx.host // parse the tenant id from the difference - if (requestHost.includes(platformHost)) { + if (platformHost && requestHost.includes(platformHost)) { const tenantId = requestHost.substring( 0, requestHost.indexOf(`.${platformHost}`) diff --git a/packages/server/src/middleware/currentapp.ts b/packages/server/src/middleware/currentapp.ts index 800d43e69c..8fca5e0c5c 100644 --- a/packages/server/src/middleware/currentapp.ts +++ b/packages/server/src/middleware/currentapp.ts @@ -5,6 +5,7 @@ import { tenancy, context, users, + auth, } from "@budibase/backend-core" import { generateUserMetadataID, isDevAppID } from "../db/utils" import { getCachedSelf } from "../utilities/global" @@ -69,28 +70,34 @@ export default async (ctx: UserCtx, next: any) => { return next() } - return context.doInAppContext(appId, async () => { - // if the user not in the right tenant then make sure they have no permissions - // need to judge this only based on the request app ID, - if ( - env.MULTI_TENANCY && - ctx.user?._id && - requestAppId && - !tenancy.isUserInAppTenant(requestAppId, ctx.user) - ) { - // don't error, simply remove the users rights (they are a public user) - ctx.user = users.cleanseUserObject(ctx.user) as ContextUser - ctx.isAuthenticated = false - roleId = roles.BUILTIN_ROLE_IDS.PUBLIC - } + const userId = ctx.user ? generateUserMetadataID(ctx.user._id!) : undefined + // if the user not in the right tenant then make to wipe their cookie + // also cleanse any information about them that has been allocated + // this avoids apps making calls to say the worker which are cross tenant, + // we simply remove the authentication + if ( + env.MULTI_TENANCY && + userId && + requestAppId && + !tenancy.isUserInAppTenant(requestAppId, ctx.user) + ) { + // clear out the user + ctx.user = users.cleanseUserObject(ctx.user) as ContextUser + ctx.isAuthenticated = false + roleId = roles.BUILTIN_ROLE_IDS.PUBLIC + // remove the cookie, so future calls are public + await auth.platformLogout({ + ctx, + userId, + }) + } + + return context.doInAppContext(appId, async () => { ctx.appId = appId if (roleId) { ctx.roleId = roleId const globalId = ctx.user ? ctx.user._id : undefined - const userId = ctx.user - ? generateUserMetadataID(ctx.user._id!) - : undefined ctx.user = { ...ctx.user!, // override userID with metadata one From c321c839160b6efee3f3aacc3a56ab95d59e9e8b Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Mon, 4 Dec 2023 17:10:19 +0000 Subject: [PATCH 63/63] Update packages/server/src/middleware/currentapp.ts Co-authored-by: Sam Rose --- packages/server/src/middleware/currentapp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/middleware/currentapp.ts b/packages/server/src/middleware/currentapp.ts index 8fca5e0c5c..984dd8e5e9 100644 --- a/packages/server/src/middleware/currentapp.ts +++ b/packages/server/src/middleware/currentapp.ts @@ -72,7 +72,7 @@ export default async (ctx: UserCtx, next: any) => { const userId = ctx.user ? generateUserMetadataID(ctx.user._id!) : undefined - // if the user not in the right tenant then make to wipe their cookie + // if the user is not in the right tenant then make sure to wipe their cookie // also cleanse any information about them that has been allocated // this avoids apps making calls to say the worker which are cross tenant, // we simply remove the authentication