diff --git a/.eslintignore b/.eslintignore index 68d88aaaf1..91f5433596 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,4 +4,6 @@ dist packages/server/builder packages/server/coverage packages/server/client -packages/builder/.routify \ No newline at end of file +packages/builder/.routify +packages/builder/cypress/support/queryLevelTransformerFunction.js +packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 7abfe537e9..2a57d6f388 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -79,6 +79,8 @@ Component libraries are collections of components as well as the definition of t ### Getting Started For Contributors #### 1. Prerequisites +NodeJS Version `14.x.x` + *yarn -* `npm install -g yarn` *jest* - `npm install -g jest` @@ -135,7 +137,7 @@ If you wish to delete all the apps created in development and reset the environm ### Backend -For the backend we run [Redis](https://redis.io/), [CouchDB](https://couchdb.apache.org/), [MinIO](https://min.io/) and [Envoy](https://www.envoyproxy.io/) in Docker compose. This means that to develop Budibase you will need Docker and Docker compose installed. The backend services are then ran separately as Node services with nodemon so that they can be debugged outside of Docker. +For the backend we run [Redis](https://redis.io/), [CouchDB](https://couchdb.apache.org/), [MinIO](https://min.io/) and [NGINX](https://www.nginx.com/) in Docker compose. This means that to develop Budibase you will need Docker and Docker compose installed. The backend services are then ran separately as Node services with nodemon so that they can be debugged outside of Docker. ### Data Storage @@ -177,36 +179,7 @@ To enable this mode, use: yarn mode:account ``` ### CI - -#### PR Job - -After your pr is submitted a github action (can be found at `.github/workflows/budibase_ci.yml`) will run to perform some checks against the changes such as linting, build and test. - -The job will run when changes are pushed to or targetted at `master` and `develop` -#### Release Develop - -To test changes before a release, a prerelease action (can be found at `.github/workflows/release-develop.yml`) will run to build and release develop versions of npm packages and docker images. On each subsequent commit to develop a new alpha version of npm packages will be created and released. - -For example: - -- `feature1` -> `develop` = `v0.9.160-alpha.1` -- `feature2` -> `develop` = `v0.9.160-alpha.0` - -The job will run when changes are pushed to `develop` -#### Release Job - -To release changes a release job (can be found at `.github/workflows/release.yml`) will run to create final versions of npm packages and docker images. - -Following the example above: - -- `develop` -> `master` = `v0.9.160` - -The job will run when changes are pushed to `master` - -#### Release Self Host Job - -To release the self hosted version of docker images, an additional job (can be found at `.github/workflows/release-selfhost.yml`) must be ran manually. This will releaae docker images to docker hub under the tag `latest` to be picked up by self hosted installations. - + An overview of the CI pipelines can be found [here](./workflows/README.md) ### Troubleshooting Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again follow **Step 6. Cleanup**, then proceed from **Step 3. Install and Build** in the setup guide above. You should have a fresh Budibase installation. diff --git a/.github/stale.yml b/.github/stale.yml index 3112dfa8e7..5875ed1282 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,7 +1,9 @@ -# Number of days of inactivity before an issue becomes stale +# Configuration for probot-stale - https://github.com/probot/stale +# Number of days of inactivity before an Issue or Pull Request becomes stale daysUntilStale: 60 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. +daysUntilClose: false # Issues with these labels will never be considered stale exemptLabels: - pinned diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000000..d2fcd16bb0 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,93 @@ + +# Budibase CI Pipelines + +Welcome to the budibase CI pipelines directory. This document details what each of the CI pipelines are for, and come common combinations. + +## All CI Pipelines + +### Note +- When running workflow dispatch jobs, ensure you always run them off the `master` branch. It defaults to `develop`, so double check before running any jobs. + +### Standard CI Build Job (budibase_ci.yml) +Triggers: +- PR or push to develop +- PR or push to master + +The standard CI Build job is what runs when you raise a PR to develop or master. +- Installs all dependencies, +- builds the project +- run the unit tests +- Generate test coverage metrics with codecov +- Run the cypress tests + +### Release Develop Job (release-develop.yml) +Triggers: +- Push to develop + +The job responsible for building, tagging and pushing docker images out to the test and staging environments. +- Installs all dependencies +- builds the project +- run the unit tests +- publish the budibase JS packages under a prerelease tag to NPM +- build, tag and push docker images under the `develop` tag to docker hub + +These images will then be pulled by the test and staging environments, updating the latest automatically. Discord notifications are sent to the #infra channel when this occurs. + +### Release Job (release.yml) +Triggers: +- Push to master + +This job is responsible for building and pushing the latest code to NPM and docker hub, so that it can be deployed. +- Installs all dependencies +- builds the project +- run the unit tests +- publish the budibase JS packages under a release tag to NPM (always incremented by patch versions) +- build, tag and push docker images under the `v.x.x.x` (the tag of the NPM release) tag to docker hub + +### Release Selfhost Job (release-selfhost.yml) +Triggers: +- Manual Workflow Dispatch Trigger + +This job is responsible for delivering the latest version of budibase to those that are self-hosting. + +This job relies on the release job to have run first, so the latest image is pushed to dockerhub. This job then will pull the latest version from `lerna.json` and try to find an image in dockerhub corresponding to that version. For example, if the version in `lerna.json` is `1.0.0`: +- Pull the images for all budibase services tagged `v1.0.0` from dockerhub +- Tag these images as `latest` +- Push them back to dockerhub. This now means anyone who pulls `latest` (self hosters using docker-compose) will get the latest version. +- Build and release the budibase helm chart for kubernetes users +- Perform a github release with the latest version. You can see previous releases here (https://github.com/Budibase/budibase/releases) + + +### Cloud Deploy (deploy-cloud.yml) +Triggers: +- Manual Workflow Dispatch Trigger + +This job is responsible for deploying to our production, cloud kubernetes environment. You must run the release job first, to ensure that the latest images have been built and pushed to docker hub. You can also manually enter a version number for this job, so you can perform rollbacks or upgrade to a specific version. After kicking off this job, the following will occur: + +- Checks out the master branch +- Pulls the latest `values.yaml` from budibase infra, a private repo containing budibases infrastructure configuration +- Gets the latest budibase version from `lerna.json`, if it hasn't been specified in the workflow when you kicked it off +- Configures AWS Credentials +- Deploys the helm chart in the budibase repo to our production EKS cluster, injecting the `values.yaml` we pulled from budibase-infra +- Fires off a discord webhook in the #infra channel to show that the deployment completely successfully. + +## Common Workflows + +### Deploy Changes to Production (Release) +- Merge `develop` into `master` +- Wait for budibase CI job and release job to run +- Run cloud deploy job +- Run release selfhost job + +### Deploy Changes to Production (Hotfix) +- Branch off `master` +- Perform your hotfix +- Merge back into `master` +- Wait for budibase CI job and release job to run +- Run cloud deploy job +- Run release selfhost job + +### Rollback A Bad Cloud Deployment +- Kick off cloud deploy job +- Ensure you are running off master +- Enter the version number of the last known good version of budibase. For example `1.0.0` \ No newline at end of file diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 5c4a111e23..cb27b30f3f 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -41,4 +41,10 @@ jobs: files: ./packages/server/coverage/clover.xml name: codecov-umbrella verbose: true - - run: yarn test:e2e:ci + + # TODO: parallelise this + - name: Cypress run + uses: cypress-io/github-action@v2 + with: + install: false + command: yarn test:e2e:ci diff --git a/.github/workflows/deploy-cloud.yaml b/.github/workflows/deploy-cloud.yaml new file mode 100644 index 0000000000..d54e6c9c68 --- /dev/null +++ b/.github/workflows/deploy-cloud.yaml @@ -0,0 +1,67 @@ +name: Budibase Cloud Deploy + +on: + workflow_dispatch: + inputs: + version: + description: Budibase release version. For example - 1.0.0 + required: false + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Fail if branch is not master + if: github.ref != 'refs/heads/master' + run: | + echo "Ref is not master, you must run this job from master." + exit 1 + + - uses: actions/checkout@v2 + + - name: Pull values.yaml from budibase-infra + run: | + curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \ + -H 'Accept: application/vnd.github.v3.raw' \ + -o values.production.yaml \ + -L https://api.github.com/repos/budibase/budibase-infra/contents/kubernetes/values.yaml + wc -l values.production.yaml + + - name: Get the latest budibase release version + id: version + run: | + if [ -z "${{ github.event.inputs.version }}" ]; then + release_version=$(cat lerna.json | jq -r '.version') + else + release_version=${{ github.event.inputs.version }} + fi + echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-1 + + - name: Deploy to EKS + uses: craftech-io/eks-helm-deploy-action@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS__KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-1 + cluster-name: budibase-eks-production + config-files: values.production.yaml + chart-path: charts/budibase + namespace: budibase + values: globals.appVersion=v${{ env.RELEASE_VERSION }} + name: budibase-prod + + - name: Discord Webhook Action + uses: tsickert/discord-webhook@v4.0.0 + with: + webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }} + content: "Production Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Cloud." + embed-title: ${{ env.RELEASE_VERSION }} + diff --git a/.github/workflows/deploy-preprod.yml b/.github/workflows/deploy-preprod.yml new file mode 100644 index 0000000000..10fadc36c5 --- /dev/null +++ b/.github/workflows/deploy-preprod.yml @@ -0,0 +1,66 @@ +name: Budibase Release Preprod + +on: + workflow_dispatch: + +env: + POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }} + INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }} + POSTHOG_URL: ${{ secrets.POSTHOG_URL }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-1 + + - name: Get the latest budibase release version + id: version + run: | + release_version=$(cat lerna.json | jq -r '.version') + echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV + + - name: Pull values.yaml from budibase-infra + run: | + curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \ + -H 'Accept: application/vnd.github.v3.raw' \ + -o values.preprod.yaml \ + -L https://api.github.com/repos/budibase/budibase-infra/contents/kubernetes/budibase-preprod/values.yaml + wc -l values.preprod.yaml + + - name: Deploy to Preprod Environment + uses: glopezep/helm@v1.7.1 + with: + release: budibase-preprod + namespace: budibase + chart: charts/budibase + token: ${{ github.token }} + helm: helm3 + values: | + globals: + appVersion: v${{ env.RELEASE_VERSION }} + ingress: + enabled: true + nginx: true + value-files: >- + [ + "values.preprod.yaml" + ] + env: + KUBECONFIG_FILE: '${{ secrets.PREPROD_KUBECONFIG }}' + + - name: Discord Webhook Action + uses: tsickert/discord-webhook@v4.0.0 + with: + webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }} + content: "Preprod Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Pre-prod." + embed-title: ${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml index b0f052f68f..4467cd6c81 100644 --- a/.github/workflows/release-develop.yml +++ b/.github/workflows/release-develop.yml @@ -4,6 +4,16 @@ on: push: branches: - develop + paths: + - '.aws/**' + - '.github/**' + - 'charts/**' + - 'packages/**' + - 'scripts/**' + - 'package.json' + - 'yarn.lock' + - 'package.json' + - 'yarn.lock' env: POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }} diff --git a/.github/workflows/release-selfhost.yml b/.github/workflows/release-selfhost.yml index 42c7027b24..f51648074c 100644 --- a/.github/workflows/release-selfhost.yml +++ b/.github/workflows/release-selfhost.yml @@ -3,53 +3,77 @@ name: Budibase Release Selfhost on: workflow_dispatch: -env: - POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }} - INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }} - POSTHOG_URL: ${{ secrets.POSTHOG_URL }} - jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: + with: node-version: 14.x - - run: yarn - - run: yarn bootstrap + fetch_depth: 0 - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: eu-west-1 - - - name: 'Get Previous tag' - id: previoustag - uses: "WyriHaximus/github-action-get-previous-tag@v1" - - - name: Build/release Docker images (Self Host) + - name: Tag and release Docker images (Self Host) run: | docker login -u $DOCKER_USER -p $DOCKER_PASSWORD - yarn build - yarn build:docker:selfhost + + # Get latest release version + release_version=$(cat lerna.json | jq -r '.version') + echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV + release_tag=v$release_version + + # Pull apps and worker images + docker pull budibase/apps:$release_tag + docker pull budibase/worker:$release_tag + docker pull budibase/proxy:$release_tag + + # Tag apps and worker images + docker tag budibase/apps:$release_tag budibase/apps:$SELFHOST_TAG + docker tag budibase/worker:$release_tag budibase/worker:$SELFHOST_TAG + docker tag budibase/proxy:$release_tag budibase/proxy:$SELFHOST_TAG + + # Push images + docker push budibase/apps:$SELFHOST_TAG + docker push budibase/worker:$SELFHOST_TAG + docker push budibase/proxy:$SELFHOST_TAG env: DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} - BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }} + SELFHOST_TAG: latest - - uses: azure/setup-helm@v1 - id: install + - name: Build CLI executables + run: | + pushd packages/cli + yarn + yarn build + popd + + - name: Setup Helm + uses: azure/setup-helm@v1 + id: helm-install - # So, we need to inject the values into this - - run: yarn release:helm - - - name: Run chart-releaser - uses: helm/chart-releaser-action@v1.1.0 - with: - charts_dir: docs + - name: Build and release helm chart + run: | + git config user.name "Budibase Helm Bot" + git config user.email "<>" + git pull + helm package charts/budibase + git checkout gh-pages + mv *.tgz docs + helm repo index docs + git add -A + git commit -m "Helm Release: ${{ env.RELEASE_VERSION }}" + git push env: - CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Perform Github Release + uses: softprops/action-gh-release@v1 + with: + name: v${{ env.RELEASE_VERSION }} + tag_name: v${{ env.RELEASE_VERSION }} + generate_release_notes: true + files: | + packages/cli/build/cli-win.exe + packages/cli/build/cli-linux + packages/cli/build/cli-macos diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 71432c2403..82848c78e4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,16 @@ on: push: branches: - master + paths: + - '.aws/**' + - '.github/**' + - 'charts/**' + - 'packages/**' + - 'scripts/**' + - 'package.json' + - 'yarn.lock' + - 'package.json' + - 'yarn.lock' env: POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }} diff --git a/.github/workflows/smoke_test.yaml b/.github/workflows/smoke_test.yaml new file mode 100644 index 0000000000..745fed1306 --- /dev/null +++ b/.github/workflows/smoke_test.yaml @@ -0,0 +1,46 @@ +name: Budibase Smoke Test + +on: + workflow_dispatch: + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js 14.x + uses: actions/setup-node@v1 + with: + node-version: 14.x + - run: yarn + - run: yarn bootstrap + - run: yarn build + - name: Pull cypress.env.yaml from budibase-infra + run: | + curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \ + -H 'Accept: application/vnd.github.v3.raw' \ + -o packages/builder/cypress.env.json \ + -L https://api.github.com/repos/budibase/budibase-infra/contents/test/cypress.env.json + wc -l packages/builder/cypress.env.json + - run: yarn test:e2e:ci + env: + CI: true + name: Budibase CI + + # TODO: upload recordings to s3 + # - name: Configure AWS Credentials + # uses: aws-actions/configure-aws-credentials@v1 + # with: + # aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + # aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + # aws-region: eu-west-1 + + # TODO look at cypress reporters + # - name: Discord Webhook Action + # uses: tsickert/discord-webhook@v4.0.0 + # with: + # webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }} + # content: "Production Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Cloud." + # embed-title: ${{ env.RELEASE_VERSION }} + diff --git a/.gitignore b/.gitignore index 086813c97e..da62ecb153 100644 --- a/.gitignore +++ b/.gitignore @@ -64,7 +64,8 @@ typings/ # dotenv environment variables file .env !hosting/.env -hosting/.generated-envoy.dev.yaml +hosting/.generated-nginx.dev.conf +hosting/proxy/.generated-nginx.prod.conf # parcel-bundler cache (https://parceljs.org/) .cache @@ -93,3 +94,6 @@ hosting/.generated-envoy.dev.yaml # Sublime text *.sublime-project *.sublime-workspace + +bin/ +packages/builder/cypress.env.json diff --git a/.prettierignore b/.prettierignore index 522d218640..4bdb64f60c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,4 +6,6 @@ packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte packages/server/builder packages/server/coverage packages/server/client -packages/builder/.routify \ No newline at end of file +packages/builder/.routify +packages/builder/cypress/support/queryLevelTransformerFunction.js +packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js \ No newline at end of file diff --git a/README.md b/README.md index 0f4cfe31c2..aa368d29fd 100644 --- a/README.md +++ b/README.md @@ -104,12 +104,14 @@ Budibase is made to scale. With Budibase, you can self-host on your own infrastr ## 🏁 Get started - + -Deploy Budibase self-Hosted in your existing infrastructure, using Docker, Kubernetes, and Digital Ocean. +Deploy Budibase self-hosted in your existing infrastructure, using Docker, Kubernetes, and Digital Ocean. Or use Budibase Cloud if you don't need to self-host, and would like to get started quickly. -### [Get started with Budibase](https://budibase.com) +### [Get started with self-hosting Budibase](https://docs.budibase.com/self-hosting/self-host) + +### [Get started with Budibase Cloud](https://budibase.com)

@@ -201,9 +203,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
seoulaja

🌍
Maurits Lourens

⚠️ 💻 - -
Rory Powell

🚇 ⚠️ 💻 - diff --git a/hosting/kubernetes/budibase/.helmignore b/charts/budibase/.helmignore similarity index 100% rename from hosting/kubernetes/budibase/.helmignore rename to charts/budibase/.helmignore diff --git a/charts/budibase/Chart.lock b/charts/budibase/Chart.lock new file mode 100644 index 0000000000..75b9de07b5 --- /dev/null +++ b/charts/budibase/Chart.lock @@ -0,0 +1,9 @@ +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" diff --git a/charts/budibase/Chart.yaml b/charts/budibase/Chart.yaml new file mode 100644 index 0000000000..daf8dad635 --- /dev/null +++ b/charts/budibase/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: budibase +description: >- + Budibase is an open source low-code platform, helping thousands of teams build + apps for their workplace in minutes. +keywords: + - low-code + - database + - cluster +sources: + - https://github.com/Budibase/budibase + - https://budibase.com +type: application +version: 0.2.8 +appVersion: 1.0.48 +dependencies: + - name: couchdb + version: 3.3.4 + 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/hosting/kubernetes/budibase/README.md b/charts/budibase/README.md similarity index 100% rename from hosting/kubernetes/budibase/README.md rename to charts/budibase/README.md diff --git a/charts/budibase/charts/couchdb-3.3.4.tgz b/charts/budibase/charts/couchdb-3.3.4.tgz new file mode 100644 index 0000000000..f7ebfd3e96 Binary files /dev/null and b/charts/budibase/charts/couchdb-3.3.4.tgz differ diff --git a/charts/budibase/charts/ingress-nginx-4.0.13.tgz b/charts/budibase/charts/ingress-nginx-4.0.13.tgz new file mode 100644 index 0000000000..1e34215c5f Binary files /dev/null and b/charts/budibase/charts/ingress-nginx-4.0.13.tgz differ diff --git a/hosting/kubernetes/budibase/templates/NOTES.txt b/charts/budibase/templates/NOTES.txt similarity index 100% rename from hosting/kubernetes/budibase/templates/NOTES.txt rename to charts/budibase/templates/NOTES.txt diff --git a/hosting/kubernetes/budibase/templates/_helpers.tpl b/charts/budibase/templates/_helpers.tpl similarity index 100% rename from hosting/kubernetes/budibase/templates/_helpers.tpl rename to charts/budibase/templates/_helpers.tpl diff --git a/hosting/kubernetes/budibase/templates/alb-ingress.yaml b/charts/budibase/templates/alb-ingress.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/alb-ingress.yaml rename to charts/budibase/templates/alb-ingress.yaml diff --git a/hosting/kubernetes/budibase/templates/app-service-deployment.yaml b/charts/budibase/templates/app-service-deployment.yaml similarity index 90% rename from hosting/kubernetes/budibase/templates/app-service-deployment.yaml rename to charts/budibase/templates/app-service-deployment.yaml index 7c62ada63f..d9def8c641 100644 --- a/hosting/kubernetes/budibase/templates/app-service-deployment.yaml +++ b/charts/budibase/templates/app-service-deployment.yaml @@ -73,17 +73,13 @@ spec: name: {{ template "budibase.fullname" . }} key: objectStoreSecret - name: MINIO_URL - {{ if .Values.services.objectStore.url }} value: {{ .Values.services.objectStore.url }} - {{ else }} - value: http://minio-service:{{ .Values.services.objectStore.port }} - {{ end }} - name: PORT value: {{ .Values.services.apps.port | quote }} - name: MULTI_TENANCY value: {{ .Values.globals.multiTenancy | quote }} - name: LOG_LEVEL - value: {{ .Values.services.apps.logLevel | quote }} + value: {{ default "info" .Values.services.apps.logLevel | quote }} - name: REDIS_PASSWORD value: {{ .Values.services.redis.password }} - name: REDIS_URL @@ -103,14 +99,18 @@ spec: - name: PLATFORM_URL value: {{ .Values.globals.platformUrl | quote }} - name: USE_QUOTAS - value: "1" + value: {{ .Values.globals.useQuotas | quote }} + - name: EXCLUDE_QUOTAS_TENANTS + value: {{ .Values.globals.excludeQuotasTenants | quote }} - name: ACCOUNT_PORTAL_URL value: {{ .Values.globals.accountPortalUrl | quote }} - name: ACCOUNT_PORTAL_API_KEY value: {{ .Values.globals.accountPortalApiKey | quote }} - name: COOKIE_DOMAIN value: {{ .Values.globals.cookieDomain | quote }} - image: budibase/apps + - name: HTTP_MIGRATIONS + value: {{ .Values.globals.httpMigrations | quote }} + image: budibase/apps:{{ .Values.globals.appVersion }} imagePullPolicy: Always name: bbapps ports: diff --git a/hosting/kubernetes/budibase/templates/app-service-service.yaml b/charts/budibase/templates/app-service-service.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/app-service-service.yaml rename to charts/budibase/templates/app-service-service.yaml diff --git a/charts/budibase/templates/couchdb-backup.yaml b/charts/budibase/templates/couchdb-backup.yaml new file mode 100644 index 0000000000..1072046c8c --- /dev/null +++ b/charts/budibase/templates/couchdb-backup.yaml @@ -0,0 +1,43 @@ +{{- if .Values.services.couchdb.backup.enabled }} +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 +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: couchdb-backup + strategy: + 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: + containers: + - env: + - name: SOURCE + value: {{ .Values.services.couchdb.url }} + - name: TARGET + value: {{ .Values.services.couchdb.backup.target | quote }} + - name: RUN_EVERY_SECS + value: {{ .Values.services.couchdb.backup.interval | quote }} + - name: VERBOSE + value: "true" + image: redgeoff/replicate-couchdb-cluster + imagePullPolicy: Always + name: couchdb-backup + resources: {} +status: {} +{{- end }} diff --git a/hosting/kubernetes/budibase/templates/hpa.yaml b/charts/budibase/templates/hpa.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/hpa.yaml rename to charts/budibase/templates/hpa.yaml diff --git a/hosting/kubernetes/budibase/templates/ingress.yaml b/charts/budibase/templates/ingress.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/ingress.yaml rename to charts/budibase/templates/ingress.yaml diff --git a/hosting/kubernetes/budibase/templates/minio-data-persistentvolumeclaim.yaml b/charts/budibase/templates/minio-data-persistentvolumeclaim.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/minio-data-persistentvolumeclaim.yaml rename to charts/budibase/templates/minio-data-persistentvolumeclaim.yaml diff --git a/hosting/kubernetes/budibase/templates/minio-service-deployment.yaml b/charts/budibase/templates/minio-service-deployment.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/minio-service-deployment.yaml rename to charts/budibase/templates/minio-service-deployment.yaml diff --git a/hosting/kubernetes/budibase/templates/minio-service-service.yaml b/charts/budibase/templates/minio-service-service.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/minio-service-service.yaml rename to charts/budibase/templates/minio-service-service.yaml diff --git a/hosting/kubernetes/budibase/templates/proxy-service-deployment.yaml b/charts/budibase/templates/proxy-service-deployment.yaml similarity index 92% rename from hosting/kubernetes/budibase/templates/proxy-service-deployment.yaml rename to charts/budibase/templates/proxy-service-deployment.yaml index 2e453d1c5b..05bf4a7f1a 100644 --- a/hosting/kubernetes/budibase/templates/proxy-service-deployment.yaml +++ b/charts/budibase/templates/proxy-service-deployment.yaml @@ -25,7 +25,7 @@ spec: app.kubernetes.io/name: budibase-proxy spec: containers: - - image: budibase/proxy + - image: budibase/proxy:{{ .Values.services.proxy.tag | default "k8s" }} imagePullPolicy: Always name: proxy-service ports: diff --git a/hosting/kubernetes/budibase/templates/proxy-service-service.yaml b/charts/budibase/templates/proxy-service-service.yaml similarity index 75% rename from hosting/kubernetes/budibase/templates/proxy-service-service.yaml rename to charts/budibase/templates/proxy-service-service.yaml index 8f14d97862..bf2b199ee5 100644 --- a/hosting/kubernetes/budibase/templates/proxy-service-service.yaml +++ b/charts/budibase/templates/proxy-service-service.yaml @@ -9,12 +9,11 @@ metadata: app.kubernetes.io/name: budibase-proxy name: proxy-service spec: - type: NodePort ports: - - port: {{ .Values.services.proxy.port }} + - name: {{ .Values.services.proxy.port | quote }} + port: {{ .Values.services.proxy.port }} targetPort: {{ .Values.services.proxy.port }} - protocol: TCP selector: app.kubernetes.io/name: budibase-proxy status: - loadBalancer: {} + loadBalancer: {} \ No newline at end of file diff --git a/hosting/kubernetes/budibase/templates/redis-data-persistentvolumeclaim.yaml b/charts/budibase/templates/redis-data-persistentvolumeclaim.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/redis-data-persistentvolumeclaim.yaml rename to charts/budibase/templates/redis-data-persistentvolumeclaim.yaml diff --git a/hosting/kubernetes/budibase/templates/redis-service-deployment.yaml b/charts/budibase/templates/redis-service-deployment.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/redis-service-deployment.yaml rename to charts/budibase/templates/redis-service-deployment.yaml diff --git a/hosting/kubernetes/budibase/templates/redis-service-service.yaml b/charts/budibase/templates/redis-service-service.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/redis-service-service.yaml rename to charts/budibase/templates/redis-service-service.yaml diff --git a/hosting/kubernetes/budibase/templates/secrets.yaml b/charts/budibase/templates/secrets.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/secrets.yaml rename to charts/budibase/templates/secrets.yaml diff --git a/hosting/kubernetes/budibase/templates/service.yaml b/charts/budibase/templates/service.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/service.yaml rename to charts/budibase/templates/service.yaml diff --git a/hosting/kubernetes/budibase/templates/serviceaccount.yaml b/charts/budibase/templates/serviceaccount.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/serviceaccount.yaml rename to charts/budibase/templates/serviceaccount.yaml diff --git a/hosting/kubernetes/budibase/templates/tests/test-connection.yaml b/charts/budibase/templates/tests/test-connection.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/tests/test-connection.yaml rename to charts/budibase/templates/tests/test-connection.yaml diff --git a/hosting/kubernetes/budibase/templates/worker-service-deployment.yaml b/charts/budibase/templates/worker-service-deployment.yaml similarity index 92% rename from hosting/kubernetes/budibase/templates/worker-service-deployment.yaml rename to charts/budibase/templates/worker-service-deployment.yaml index 6cded8545f..b6c757cb9f 100644 --- a/hosting/kubernetes/budibase/templates/worker-service-deployment.yaml +++ b/charts/budibase/templates/worker-service-deployment.yaml @@ -70,17 +70,13 @@ spec: name: {{ template "budibase.fullname" . }} key: objectStoreSecret - name: MINIO_URL - {{ if .Values.services.objectStore.url }} value: {{ .Values.services.objectStore.url }} - {{ else }} - value: http://minio-service:{{ .Values.services.objectStore.port }} - {{ end }} - name: PORT value: {{ .Values.services.worker.port | quote }} - name: MULTI_TENANCY value: {{ .Values.globals.multiTenancy | quote }} - name: LOG_LEVEL - value: {{ .Values.services.worker.logLevel | quote }} + value: {{ default "info" .Values.services.worker.logLevel | quote }} - name: REDIS_PASSWORD value: {{ .Values.services.redis.password | quote }} - name: REDIS_URL @@ -115,7 +111,11 @@ spec: value: {{ .Values.globals.smtp.from | quote }} - name: APPS_URL value: http://app-service:{{ .Values.services.apps.port }} - image: budibase/worker + - name: GOOGLE_CLIENT_ID + value: {{ .Values.globals.google.clientId | quote }} + - name: GOOGLE_CLIENT_SECRET + value: {{ .Values.globals.google.secret | quote }} + image: budibase/worker:{{ .Values.globals.appVersion }} imagePullPolicy: Always name: bbworker ports: diff --git a/hosting/kubernetes/budibase/templates/worker-service-service.yaml b/charts/budibase/templates/worker-service-service.yaml similarity index 100% rename from hosting/kubernetes/budibase/templates/worker-service-service.yaml rename to charts/budibase/templates/worker-service-service.yaml diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml new file mode 100644 index 0000000000..648b1d2dee --- /dev/null +++ b/charts/budibase/values.yaml @@ -0,0 +1,312 @@ +# Default values for budibase. +# 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: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # 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: + type: ClusterIP + port: 10000 + +ingress: + enabled: true + aws: false + nginx: true + certificateArn: "" + className: "" + annotations: + kubernetes.io/ingress.class: nginx + hosts: + - host: # change if using custom domain + paths: + - path: / + pathType: Prefix + backend: + service: + name: proxy-service + port: + number: 10000 + +resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +globals: + appVersion: "latest" + budibaseEnv: PRODUCTION + enableAnalytics: true + sentryDSN: "" + posthogToken: "" + logLevel: info + 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 + useQuotas: "0" + excludeQuotasTenants: "" # comma seperated list of tenants to exclude from quotas + accountPortalUrl: "" + accountPortalApiKey: "" + cookieDomain: "" + platformUrl: "" + httpMigrations: "0" + google: + clientId: "" + secret: "" + + createSecrets: true # creates an internal API key, JWT secrets and redis password for you + + # if createSecrets is set to false, you can hard-code your secrets here + internalApiKey: "" + jwtSecret: "" + + smtp: + enabled: false + +services: + budibaseVersion: latest + dns: cluster.local + + proxy: + port: 10000 + replicaCount: 1 + + apps: + port: 4002 + replicaCount: 1 + logLevel: info + + worker: + port: 4003 + replicaCount: 1 + + couchdb: + 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: + enabled: false + # target couchDB instance to back up to + target: "" + # backup interval in seconds + interval: "" + + redis: + enabled: true # disable if using external redis + port: 6379 + replicaCount: 1 + url: "" # only change if pointing to existing redis cluster and enabled: false + password: "budibase" # recommended to override if using built-in redis + storage: 100Mi + + objectStore: + minio: true + browser: true + port: 9000 + 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 + storage: 100Mi + +# Override values in couchDB subchart +couchdb: + ## clusterSize is the initial size of the CouchDB cluster. + clusterSize: 3 + allowAdminParty: false + + # Secret Management + createAdminSecret: true + + # 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 + ## communicate with each other for clustering and ingress on port 5984 + networkPolicy: + enabled: true + + # 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 + accessModes: + - ReadWriteOnce + size: 10Gi + storageClass: "" + + ## The CouchDB image + image: + repository: couchdb + tag: 3.1.0 + pullPolicy: IfNotPresent + + ## Experimental integration with Lucene-powered fulltext search + enableSearch: true + searchImage: + repository: kocolosk/couchdb-search + tag: 0.2.0 + pullPolicy: IfNotPresent + + 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 + + ## Optional pod annotations + annotations: {} + + ## Optional tolerations + tolerations: [] + + service: + # annotations: + enabled: true + type: ClusterIP + externalPort: 5984 + + ## 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 + 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 + + ## 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 + erlangFlags: + name: couchdb + setcookie: monster + + ## 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 + # 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 + + # 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 diff --git a/docs/budibase-0.1.0.tgz b/docs/budibase-0.1.0.tgz deleted file mode 100644 index 7873874ab0..0000000000 Binary files a/docs/budibase-0.1.0.tgz and /dev/null differ diff --git a/docs/budibase-0.1.1.tgz b/docs/budibase-0.1.1.tgz deleted file mode 100644 index b38527c4a4..0000000000 Binary files a/docs/budibase-0.1.1.tgz and /dev/null differ diff --git a/docs/budibase-0.2.0.tgz b/docs/budibase-0.2.0.tgz deleted file mode 100644 index 379b92cbb7..0000000000 Binary files a/docs/budibase-0.2.0.tgz and /dev/null differ diff --git a/docs/budibase-0.2.1.tgz b/docs/budibase-0.2.1.tgz deleted file mode 100644 index f3423763a5..0000000000 Binary files a/docs/budibase-0.2.1.tgz and /dev/null differ diff --git a/docs/budibase-0.2.2.tgz b/docs/budibase-0.2.2.tgz deleted file mode 100644 index c70754eb99..0000000000 Binary files a/docs/budibase-0.2.2.tgz and /dev/null differ diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 0fa6060f8f..0000000000 --- a/docs/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - Budibase Helm Chart Repo - - -

Budibase Charts Repo

-

Point Helm at this repo to see charts.

- - \ No newline at end of file diff --git a/docs/index.yaml b/docs/index.yaml deleted file mode 100644 index 04543c147e..0000000000 --- a/docs/index.yaml +++ /dev/null @@ -1,132 +0,0 @@ -apiVersion: v1 -entries: - budibase: - - apiVersion: v2 - appVersion: 0.9.169 - created: "2021-10-20T14:27:23.521358+01:00" - dependencies: - - condition: services.couchdb.enabled - name: couchdb - repository: https://apache.github.io/couchdb-helm - version: 3.3.4 - - condition: ingress.nginx - name: ingress-nginx - repository: https://github.com/kubernetes/ingress-nginx - version: 3.35.0 - description: Budibase is an open source low-code platform, helping thousands of teams build apps for their workplace in minutes. - digest: 57f365d799fcaace4658883cb8ec961a7905383a68acf065af4f6e57f9878ff8 - keywords: - - low-code - - database - - cluster - name: budibase - sources: - - https://github.com/Budibase/budibase - - https://budibase.com - type: application - urls: - - https://budibase.github.io/budibase/budibase-0.2.2.tgz - version: 0.2.2 - - apiVersion: v2 - appVersion: 0.9.163 - created: "2021-10-20T14:27:23.5153+01:00" - dependencies: - - condition: services.couchdb.enabled - name: couchdb - repository: https://apache.github.io/couchdb-helm - version: 3.3.4 - - condition: ingress.nginx - name: ingress-nginx - repository: https://github.com/kubernetes/ingress-nginx - version: 3.35.0 - description: Budibase is an open source low-code platform, helping thousands of teams build apps for their workplace in minutes. - digest: ebac6d8631cc38b266c3689508b5123f5afc395f23bdb02738be26c7cae0b0b5 - keywords: - - low-code - - database - - cluster - name: budibase - sources: - - https://github.com/Budibase/budibase - - https://budibase.com - type: application - urls: - - https://budibase.github.io/budibase/budibase-0.2.1.tgz - version: 0.2.1 - - apiVersion: v2 - appVersion: 0.9.163 - created: "2021-10-20T14:27:23.510041+01:00" - dependencies: - - condition: services.couchdb.enabled - name: couchdb - repository: https://apache.github.io/couchdb-helm - version: 3.3.4 - - condition: ingress.nginx - name: ingress-nginx - repository: https://github.com/kubernetes/ingress-nginx - version: 3.35.0 - description: Budibase is an open source low-code platform, helping thousands of teams build apps for their workplace in minutes. - digest: f369536c0eac1f6959d51e8ce6d74a87a7a9df29ae84fb9cbed0a273ab77429b - keywords: - - low-code - - database - - cluster - name: budibase - sources: - - https://github.com/Budibase/budibase - - https://budibase.com - type: application - urls: - - https://budibase.github.io/budibase/budibase-0.2.0.tgz - version: 0.2.0 - - apiVersion: v2 - appVersion: 0.9.56 - created: "2021-10-20T14:27:23.504543+01:00" - dependencies: - - condition: services.couchdb.enabled - name: couchdb - repository: https://apache.github.io/couchdb-helm - version: 3.3.4 - - name: ingress-nginx - repository: https://github.com/kubernetes/ingress-nginx - version: 3.35.0 - description: Budibase is an open source low-code platform, helping thousands of teams build apps for their workplace in minutes. - digest: 8dc4f2ed4d98cad5adf25936aefea680042d3e4e17832f846b961fd8708ad192 - keywords: - - low-code - - database - - cluster - name: budibase - sources: - - https://github.com/Budibase/budibase - - https://budibase.com - type: application - urls: - - https://budibase.github.io/budibase/budibase-0.1.1.tgz - version: 0.1.1 - - apiVersion: v2 - appVersion: 0.9.56 - created: "2021-10-20T14:27:23.496847+01:00" - dependencies: - - condition: services.couchdb.enabled - name: couchdb - repository: https://apache.github.io/couchdb-helm - version: 3.3.4 - - name: ingress-nginx - repository: https://github.com/kubernetes/ingress-nginx - version: 3.35.0 - description: Budibase is an open source low-code platform, helping thousands of teams build apps for their workplace in minutes. - digest: 08031b0803cce0eff64472e569d454d9176119c8207aa9873a9c95ee66cc7d3f - keywords: - - low-code - - database - - cluster - name: budibase - sources: - - https://github.com/Budibase/budibase - - https://budibase.com - type: application - urls: - - https://budibase.github.io/budibase/budibase-0.1.0.tgz - version: 0.1.0 -generated: "2021-10-20T14:27:23.491132+01:00" diff --git a/hosting/.env b/hosting/.env deleted file mode 120000 index bb1b54ad77..0000000000 --- a/hosting/.env +++ /dev/null @@ -1 +0,0 @@ -hosting.properties \ No newline at end of file diff --git a/hosting/.env b/hosting/.env new file mode 100644 index 0000000000..39df76d01e --- /dev/null +++ b/hosting/.env @@ -0,0 +1,21 @@ +# Use the main port in the builder for your self hosting URL, e.g. localhost:10000 +MAIN_PORT=10000 + +# This section contains all secrets pertaining to the system +# These should be updated +JWT_SECRET=testsecret +MINIO_ACCESS_KEY=budibase +MINIO_SECRET_KEY=budibase +COUCH_DB_PASSWORD=budibase +COUCH_DB_USER=budibase +REDIS_PASSWORD=budibase +INTERNAL_API_KEY=budibase + +# This section contains variables that do not need to be altered under normal circumstances +APP_PORT=4002 +WORKER_PORT=4003 +MINIO_PORT=4004 +COUCH_DB_PORT=4005 +REDIS_PORT=6379 +WATCHTOWER_PORT=6161 +BUDIBASE_ENVIRONMENT=PRODUCTION \ No newline at end of file diff --git a/hosting/digitalocean/README.md b/hosting/digitalocean/README.md new file mode 100644 index 0000000000..72c1950d17 --- /dev/null +++ b/hosting/digitalocean/README.md @@ -0,0 +1,19 @@ +# Budibase DigitalOcean One Click +You will find in this directory configuration for packaging and creating a snapshot for the Budibase 1 click Digitalocean build. We use this configuration to have an immutable and reproducible build package for Digitalocean, that rarely needs updated. + +## Prerequisites +You must install Hashicorps `packer` to build the snapshot for digitalocean. Follow the instructions to install packer [here](https://learn.hashicorp.com/tutorials/packer/get-started-install-cli) + +You must have the `DIGITALOCEAN_TOKEN` environment variable set, so that packer can reach out to the digitalocean API for build information. + +## Building +Just run the following command: +``` +yarn build:digitalocean +``` + +## Uploading to Marketplace +You can upload the snapshot to the Digitalocean vendor portal at the following link (Requires vendor account): + +https://marketplace.digitalocean.com/vendorportal + diff --git a/hosting/digitalocean/build.sh b/hosting/digitalocean/build.sh new file mode 100755 index 0000000000..743629ca12 --- /dev/null +++ b/hosting/digitalocean/build.sh @@ -0,0 +1,2 @@ +#!/bin/bash +packer build template.json diff --git a/hosting/digitalocean/files/etc/update-motd.d/99-one-click b/hosting/digitalocean/files/etc/update-motd.d/99-one-click new file mode 100644 index 0000000000..0f087a26ee --- /dev/null +++ b/hosting/digitalocean/files/etc/update-motd.d/99-one-click @@ -0,0 +1,19 @@ +#!/bin/sh +# +# Configured as part of the DigitalOcean 1-Click Image build process + +myip=$(hostname -I | awk '{print$1}') +cat <> .env +done /root/.bash_history +unset HISTFILE +find /var/log -mtime -1 -type f -exec truncate -s 0 {} \; +rm -rf /var/log/*.gz /var/log/*.[0-9] /var/log/*-???????? +rm -rf /var/lib/cloud/instances/* +rm -f /root/.ssh/authorized_keys /etc/ssh/*key* +touch /etc/ssh/revoked_keys +chmod 600 /etc/ssh/revoked_keys + +# Securely erase the unused portion of the filesystem +GREEN='\033[0;32m' +NC='\033[0m' +printf "\n${GREEN}Writing zeros to the remaining disk space to securely +erase the unused portion of the file system. +Depending on your disk size this may take several minutes. +The secure erase will complete successfully when you see:${NC} + dd: writing to '/zerofile': No space left on device\n +Beginning secure erase now\n" + +dd if=/dev/zero of=/zerofile bs=4096 || rm /zerofile diff --git a/hosting/digitalocean/scripts/99-img_check.sh b/hosting/digitalocean/scripts/99-img_check.sh new file mode 100644 index 0000000000..32a9e77eac --- /dev/null +++ b/hosting/digitalocean/scripts/99-img_check.sh @@ -0,0 +1,617 @@ +#!/bin/bash + +# DigitalOcean Marketplace Image Validation Tool +# © 2021 DigitalOcean LLC. +# This code is licensed under Apache 2.0 license (see LICENSE.md for details) + +VERSION="v. 1.6" +RUNDATE=$( date ) + +# Script should be run with SUDO +if [ "$EUID" -ne 0 ] + then echo "[Error] - This script must be run with sudo or as the root user." + exit 1 +fi + +STATUS=0 +PASS=0 +WARN=0 +FAIL=0 + +# $1 == command to check for +# returns: 0 == true, 1 == false +cmdExists() { + if command -v "$1" > /dev/null 2>&1; then + return 0 + else + return 1 + fi +} + +function getDistro { + if [ -f /etc/os-release ]; then + # freedesktop.org and systemd + . /etc/os-release + OS=$NAME + VER=$VERSION_ID +elif type lsb_release >/dev/null 2>&1; then + # linuxbase.org + OS=$(lsb_release -si) + VER=$(lsb_release -sr) +elif [ -f /etc/lsb-release ]; then + # For some versions of Debian/Ubuntu without lsb_release command + . /etc/lsb-release + OS=$DISTRIB_ID + VER=$DISTRIB_RELEASE +elif [ -f /etc/debian_version ]; then + # Older Debian/Ubuntu/etc. + OS=Debian + VER=$(cat /etc/debian_version) +elif [ -f /etc/SuSe-release ]; then + # Older SuSE/etc. + : +elif [ -f /etc/redhat-release ]; then + # Older Red Hat, CentOS, etc. + VER=$( cat /etc/redhat-release | cut -d" " -f3 | cut -d "." -f1) + d=$( cat /etc/redhat-release | cut -d" " -f1 | cut -d "." -f1) + if [[ $d == "CentOS" ]]; then + OS="CentOS Linux" + fi +else + # Fall back to uname, e.g. "Linux ", also works for BSD, etc. + OS=$(uname -s) + VER=$(uname -r) +fi +} +function loadPasswords { +SHADOW=$(cat /etc/shadow) +} + +function checkAgent { + # Check for the presence of the do-agent in the filesystem + if [ -d /var/opt/digitalocean/do-agent ];then + echo -en "\e[41m[FAIL]\e[0m DigitalOcean Monitoring Agent detected.\n" + ((FAIL++)) + STATUS=2 + if [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]]; then + echo "The agent can be removed with 'sudo yum remove do-agent' " + elif [[ $OS == "Ubuntu" ]]; then + echo "The agent can be removed with 'sudo apt-get purge do-agent' " + fi + else + echo -en "\e[32m[PASS]\e[0m DigitalOcean Monitoring agent was not found\n" + ((PASS++)) + fi +} + +function checkLogs { + cp_ignore="/var/log/cpanel-install.log" + echo -en "\nChecking for log files in /var/log\n\n" + # Check if there are log archives or log files that have not been recently cleared. + for f in /var/log/*-????????; do + [[ -e $f ]] || break + if [ $f != $cp_ignore ]; then + echo -en "\e[93m[WARN]\e[0m Log archive ${f} found\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + fi + done + for f in /var/log/*.[0-9];do + [[ -e $f ]] || break + echo -en "\e[93m[WARN]\e[0m Log archive ${f} found\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + done + for f in /var/log/*.log; do + [[ -e $f ]] || break + if [[ "${f}" = '/var/log/lfd.log' && "$( cat "${f}" | egrep -v '/var/log/messages has been reset| Watching /var/log/messages' | wc -c)" -gt 50 ]]; then + if [ $f != $cp_ignore ]; then + echo -en "\e[93m[WARN]\e[0m un-cleared log file, ${f} found\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + fi + elif [[ "${f}" != '/var/log/lfd.log' && "$( cat "${f}" | wc -c)" -gt 50 ]]; then + if [ $f != $cp_ignore ]; then + echo -en "\e[93m[WARN]\e[0m un-cleared log file, ${f} found\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + fi + fi + done +} +function checkTMP { + # Check the /tmp directory to ensure it is empty. Warn on any files found. + return 1 +} +function checkRoot { + user="root" + uhome="/root" + for usr in $SHADOW + do + IFS=':' read -r -a u <<< "$usr" + if [[ "${u[0]}" == "${user}" ]]; then + if [[ ${u[1]} == "!" ]] || [[ ${u[1]} == "!!" ]] || [[ ${u[1]} == "*" ]]; then + echo -en "\e[32m[PASS]\e[0m User ${user} has no password set.\n" + ((PASS++)) + else + echo -en "\e[41m[FAIL]\e[0m User ${user} has a password set on their account.\n" + ((FAIL++)) + STATUS=2 + fi + fi + done + if [ -d ${uhome}/ ]; then + if [ -d ${uhome}/.ssh/ ]; then + if ls ${uhome}/.ssh/*> /dev/null 2>&1; then + for key in ${uhome}/.ssh/* + do + if [ "${key}" == "${uhome}/.ssh/authorized_keys" ]; then + + if [ "$( cat "${key}" | wc -c)" -gt 50 ]; then + echo -en "\e[41m[FAIL]\e[0m User \e[1m${user}\e[0m has a populated authorized_keys file in \e[93m${key}\e[0m\n" + akey=$(cat ${key}) + echo "File Contents:" + echo $akey + echo "--------------" + ((FAIL++)) + STATUS=2 + fi + elif [ "${key}" == "${uhome}/.ssh/id_rsa" ]; then + if [ "$( cat "${key}" | wc -c)" -gt 0 ]; then + echo -en "\e[41m[FAIL]\e[0m User \e[1m${user}\e[0m has a private key file in \e[93m${key}\e[0m\n" + akey=$(cat ${key}) + echo "File Contents:" + echo $akey + echo "--------------" + ((FAIL++)) + STATUS=2 + else + echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has empty private key file in \e[93m${key}\e[0m\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + fi + elif [ "${key}" != "${uhome}/.ssh/known_hosts" ]; then + echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has a file in their .ssh directory at \e[93m${key}\e[0m\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + else + if [ "$( cat "${key}" | wc -c)" -gt 50 ]; then + echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has a populated known_hosts file in \e[93m${key}\e[0m\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + fi + fi + done + else + echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m has no SSH keys present\n" + fi + else + echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m does not have an .ssh directory\n" + fi + if [ -f /root/.bash_history ];then + + BH_S=$( cat /root/.bash_history | wc -c) + + if [[ $BH_S -lt 200 ]]; then + echo -en "\e[32m[PASS]\e[0m ${user}'s Bash History appears to have been cleared\n" + ((PASS++)) + else + echo -en "\e[41m[FAIL]\e[0m ${user}'s Bash History should be cleared to prevent sensitive information from leaking\n" + ((FAIL++)) + STATUS=2 + fi + + return 1; + else + echo -en "\e[32m[PASS]\e[0m The Root User's Bash History is not present\n" + ((PASS++)) + fi + else + echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m does not have a directory in /home\n" + fi + echo -en "\n\n" + return 1 +} + +function checkUsers { + # Check each user-created account + for user in $(awk -F: '$3 >= 1000 && $1 != "nobody" {print $1}' /etc/passwd;) + do + # Skip some other non-user system accounts + if [[ $user == "centos" ]]; then + : + elif [[ $user == "nfsnobody" ]]; then + : + else + echo -en "\nChecking user: ${user}...\n" + for usr in $SHADOW + do + IFS=':' read -r -a u <<< "$usr" + if [[ "${u[0]}" == "${user}" ]]; then + if [[ ${u[1]} == "!" ]] || [[ ${u[1]} == "!!" ]] || [[ ${u[1]} == "*" ]]; then + echo -en "\e[32m[PASS]\e[0m User ${user} has no password set.\n" + ((PASS++)) + else + echo -en "\e[41m[FAIL]\e[0m User ${user} has a password set on their account. Only system users are allowed on the image.\n" + ((FAIL++)) + STATUS=2 + fi + fi + done + #echo "User Found: ${user}" + uhome="/home/${user}" + if [ -d "${uhome}/" ]; then + if [ -d "${uhome}/.ssh/" ]; then + if ls "${uhome}/.ssh/*"> /dev/null 2>&1; then + for key in ${uhome}/.ssh/* + do + if [ "${key}" == "${uhome}/.ssh/authorized_keys" ]; then + if [ "$( cat "${key}" | wc -c)" -gt 50 ]; then + echo -en "\e[41m[FAIL]\e[0m User \e[1m${user}\e[0m has a populated authorized_keys file in \e[93m${key}\e[0m\n" + akey=$(cat ${key}) + echo "File Contents:" + echo $akey + echo "--------------" + ((FAIL++)) + STATUS=2 + fi + elif [ "${key}" == "${uhome}/.ssh/id_rsa" ]; then + if [ "$( cat "${key}" | wc -c)" -gt 0 ]; then + echo -en "\e[41m[FAIL]\e[0m User \e[1m${user}\e[0m has a private key file in \e[93m${key}\e[0m\n" + akey=$(cat ${key}) + echo "File Contents:" + echo $akey + echo "--------------" + ((FAIL++)) + STATUS=2 + else + echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has empty private key file in \e[93m${key}\e[0m\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + fi + elif [ "${key}" != "${uhome}/.ssh/known_hosts" ]; then + + echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has a file in their .ssh directory named \e[93m${key}\e[0m\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + + else + if [ "$( cat "${key}" | wc -c)" -gt 50 ]; then + echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has a known_hosts file in \e[93m${key}\e[0m\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + fi + fi + + + done + else + echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m has no SSH keys present\n" + fi + else + echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m does not have an .ssh directory\n" + fi + else + echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m does not have a directory in /home\n" + fi + + # Check for an uncleared .bash_history for this user + if [ -f "${uhome}/.bash_history" ]; then + BH_S=$( cat "${uhome}/.bash_history" | wc -c ) + + if [[ $BH_S -lt 200 ]]; then + echo -en "\e[32m[PASS]\e[0m ${user}'s Bash History appears to have been cleared\n" + ((PASS++)) + else + echo -en "\e[41m[FAIL]\e[0m ${user}'s Bash History should be cleared to prevent sensitive information from leaking\n" + ((FAIL++)) + STATUS=2 + + fi + echo -en "\n\n" + fi + fi + done +} +function checkFirewall { + + if [[ $OS == "Ubuntu" ]]; then + fw="ufw" + ufwa=$(ufw status |head -1| sed -e "s/^Status:\ //") + if [[ $ufwa == "active" ]]; then + FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" + ((PASS++)) + else + FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" + ((WARN++)) + fi + elif [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]]; then + if [ -f /usr/lib/systemd/system/csf.service ]; then + fw="csf" + if [[ $(systemctl status $fw >/dev/null 2>&1) ]]; then + + FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" + ((PASS++)) + elif cmdExists "firewall-cmd"; then + if [[ $(systemctl is-active firewalld >/dev/null 2>&1 && echo 1 || echo 0) ]]; then + FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" + ((PASS++)) + else + FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" + ((WARN++)) + fi + else + FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" + ((WARN++)) + fi + else + fw="firewalld" + if [[ $(systemctl is-active firewalld >/dev/null 2>&1 && echo 1 || echo 0) ]]; then + FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" + ((PASS++)) + else + FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" + ((WARN++)) + fi + fi + elif [[ "$OS" =~ Debian.* ]]; then + # user could be using a number of different services for managing their firewall + # we will check some of the most common + if cmdExists 'ufw'; then + fw="ufw" + ufwa=$(ufw status |head -1| sed -e "s/^Status:\ //") + if [[ $ufwa == "active" ]]; then + FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" + ((PASS++)) + else + FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" + ((WARN++)) + fi + elif cmdExists "firewall-cmd"; then + fw="firewalld" + if [[ $(systemctl is-active --quiet $fw) ]]; then + FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" + ((PASS++)) + else + FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" + ((WARN++)) + fi + else + # user could be using vanilla iptables, check if kernel module is loaded + fw="iptables" + if [[ $(lsmod | grep -q '^ip_tables' 2>/dev/null) ]]; then + FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" + ((PASS++)) + else + FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" + ((WARN++)) + fi + fi + fi + +} +function checkUpdates { + if [[ $OS == "Ubuntu" ]] || [[ "$OS" =~ Debian.* ]]; then + # Ensure /tmp exists and has the proper permissions before + # checking for security updates + # https://github.com/digitalocean/marketplace-partners/issues/94 + if [[ ! -d /tmp ]]; then + mkdir /tmp + fi + chmod 1777 /tmp + + echo -en "\nUpdating apt package database to check for security updates, this may take a minute...\n\n" + apt-get -y update > /dev/null + + uc=$(apt-get --just-print upgrade | grep -i "security" | wc -l) + if [[ $uc -gt 0 ]]; then + update_count=$(( ${uc} / 2 )) + else + update_count=0 + fi + + if [[ $update_count -gt 0 ]]; then + echo -en "\e[41m[FAIL]\e[0m There are ${update_count} security updates available for this image that have not been installed.\n" + echo -en + echo -en "Here is a list of the security updates that are not installed:\n" + sleep 2 + apt-get --just-print upgrade | grep -i security | awk '{print $2}' | awk '!seen[$0]++' + echo -en + ((FAIL++)) + STATUS=2 + else + echo -en "\e[32m[PASS]\e[0m There are no pending security updates for this image.\n\n" + fi + elif [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]]; then + echo -en "\nChecking for available security updates, this may take a minute...\n\n" + + update_count=$(yum check-update --security --quiet | wc -l) + if [[ $update_count -gt 0 ]]; then + echo -en "\e[41m[FAIL]\e[0m There are ${update_count} security updates available for this image that have not been installed.\n" + ((FAIL++)) + STATUS=2 + else + echo -en "\e[32m[PASS]\e[0m There are no pending security updates for this image.\n" + ((PASS++)) + fi + else + echo "Error encountered" + exit 1 + fi + + return 1; +} +function checkCloudInit { + + if hash cloud-init 2>/dev/null; then + CI="\e[32m[PASS]\e[0m Cloud-init is installed.\n" + ((PASS++)) + else + CI="\e[41m[FAIL]\e[0m No valid verison of cloud-init was found.\n" + ((FAIL++)) + STATUS=2 + fi + return 1 +} + +function version_gt() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; } + + +clear +echo "DigitalOcean Marketplace Image Validation Tool ${VERSION}" +echo "Executed on: ${RUNDATE}" +echo "Checking local system for Marketplace compatibility..." + +getDistro + +echo -en "\n\e[1mDistribution:\e[0m ${OS}\n" +echo -en "\e[1mVersion:\e[0m ${VER}\n\n" + +ost=0 +osv=0 + +if [[ $OS == "Ubuntu" ]]; then + ost=1 + if [[ $VER == "20.04" ]]; then + osv=1 + elif [[ $VER == "18.04" ]]; then + osv=1 + elif [[ $VER == "16.04" ]]; then + osv=1 + else + osv=0 + fi + +elif [[ "$OS" =~ Debian.* ]]; then + ost=1 + case "$VER" in + 9) + osv=1 + ;; + 10) + osv=1 + ;; + *) + osv=2 + ;; + esac + +elif [[ $OS == "CentOS Linux" ]]; then + ost=1 + if [[ $VER == "8" ]]; then + osv=1 + elif [[ $VER == "7" ]]; then + osv=1 + elif [[ $VER == "6" ]]; then + osv=1 + else + osv=2 + fi +elif [[ $OS == "CentOS Stream" ]]; then + ost=1 + if [[ $VER == "8" ]]; then + osv=1 + else + osv=2 + fi +elif [[ $OS == "Rocky Linux" ]]; then + ost=1 + if [[ $VER =~ "8." ]]; then + osv=1 + else + osv=2 + fi +else + ost=0 +fi + +if [[ $ost == 1 ]]; then + echo -en "\e[32m[PASS]\e[0m Supported Operating System Detected: ${OS}\n" + ((PASS++)) +else + echo -en "\e[41m[FAIL]\e[0m ${OS} is not a supported Operating System\n" + ((FAIL++)) + STATUS=2 +fi + +if [[ $osv == 1 ]]; then + echo -en "\e[32m[PASS]\e[0m Supported Release Detected: ${VER}\n" + ((PASS++)) +elif [[ $ost == 1 ]]; then + echo -en "\e[41m[FAIL]\e[0m ${OS} ${VER} is not a supported Operating System Version\n" + ((FAIL++)) + STATUS=2 +else + echo "Exiting..." + exit 1 +fi + +checkCloudInit + +echo -en "${CI}" + +checkFirewall + +echo -en "${FW_VER}" + +checkUpdates + +loadPasswords + +checkLogs + +echo -en "\n\nChecking all user-created accounts...\n" +checkUsers + +echo -en "\n\nChecking the root account...\n" +checkRoot + +checkAgent + + +# Summary +echo -en "\n\n---------------------------------------------------------------------------------------------------\n" + +if [[ $STATUS == 0 ]]; then + echo -en "Scan Complete.\n\e[32mAll Tests Passed!\e[0m\n" +elif [[ $STATUS == 1 ]]; then + echo -en "Scan Complete. \n\e[93mSome non-critical tests failed. Please review these items.\e[0m\e[0m\n" +else + echo -en "Scan Complete. \n\e[41mOne or more tests failed. Please review these items and re-test.\e[0m\n" +fi +echo "---------------------------------------------------------------------------------------------------" +echo -en "\e[1m${PASS} Tests PASSED\e[0m\n" +echo -en "\e[1m${WARN} WARNINGS\e[0m\n" +echo -en "\e[1m${FAIL} Tests FAILED\e[0m\n" +echo -en "---------------------------------------------------------------------------------------------------\n" + +if [[ $STATUS == 0 ]]; then + echo -en "We did not detect any issues with this image. Please be sure to manually ensure that all software installed on the base system is functional, secure and properly configured (or facilities for configuration on first-boot have been created).\n\n" + exit 0 +elif [[ $STATUS == 1 ]]; then + echo -en "Please review all [WARN] items above and ensure they are intended or resolved. If you do not have a specific requirement, we recommend resolving these items before image submission\n\n" + exit 0 +else + echo -en "Some critical tests failed. These items must be resolved and this scan re-run before you submit your image to the DigitalOcean Marketplace.\n\n" + exit 1 +fi diff --git a/hosting/digitalocean/template.json b/hosting/digitalocean/template.json new file mode 100644 index 0000000000..bc3679abdc --- /dev/null +++ b/hosting/digitalocean/template.json @@ -0,0 +1,65 @@ +{ + "variables": { + "token": "{{env `DIGITALOCEAN_TOKEN`}}", + "image_name": "budibase-marketplace-snapshot-{{timestamp}}", + "apt_packages": "jq" + }, + "builders": [ + { + "type": "digitalocean", + "api_token": "{{user `token`}}", + "image": "docker-20-04", + "region": "lon1", + "size": "s-1vcpu-1gb", + "ssh_username": "root", + "snapshot_name": "{{user `image_name`}}" + } + ], + "provisioners": [ + { + "type": "shell", + "inline": [ + "cloud-init status --wait" + ] + }, + { + "type": "file", + "source": "files/etc/", + "destination": "/etc/" + }, + { + "type": "file", + "source": "files/var/", + "destination": "/var/" + }, + { + "type": "shell", + "environment_vars": [ + "DEBIAN_FRONTEND=noninteractive", + "LC_ALL=C", + "LANG=en_US.UTF-8", + "LC_CTYPE=en_US.UTF-8" + ], + "inline": [ + "apt -qqy update", + "apt -qqy -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' full-upgrade", + "apt -qqy -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' install {{user `apt_packages`}}" + ] + }, + { + "type": "shell", + "environment_vars": [ + "application_name={{user `application_name`}}", + "application_version={{user `application_version`}}", + "DEBIAN_FRONTEND=noninteractive", + "LC_ALL=C", + "LANG=en_US.UTF-8", + "LC_CTYPE=en_US.UTF-8" + ], + "scripts": [ + "scripts/90-cleanup.sh", + "scripts/99-img_check.sh" + ] + } + ] +} diff --git a/hosting/docker-compose.dev.yaml b/hosting/docker-compose.dev.yaml index eaced64e06..df403c0a22 100644 --- a/hosting/docker-compose.dev.yaml +++ b/hosting/docker-compose.dev.yaml @@ -22,18 +22,21 @@ services: retries: 3 proxy-service: - container_name: budi-envoy-dev + container_name: budi-nginx-dev restart: always - image: envoyproxy/envoy:v1.16-latest + image: nginx:latest volumes: - - ./.generated-envoy.dev.yaml:/etc/envoy/envoy.yaml + - ./.generated-nginx.dev.conf:/etc/nginx/nginx.conf ports: - "${MAIN_PORT}:10000" depends_on: - minio-service - couchdb-service + extra_hosts: + - "host.docker.internal:host-gateway" couchdb-service: + # platform: linux/amd64 container_name: budi-couchdb-dev restart: always image: ibmcom/couchdb3 diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index c94d1520a1..8143be54b7 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -7,8 +7,6 @@ services: restart: always image: budibase.docker.scarf.sh/budibase/apps container_name: bbapps - ports: - - "${APP_PORT}:4002" environment: SELF_HOSTED: 1 COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984 @@ -25,8 +23,6 @@ services: ENABLE_ANALYTICS: "true" REDIS_URL: redis-service:6379 REDIS_PASSWORD: ${REDIS_PASSWORD} - volumes: - - ./logs:/logs depends_on: - worker-service - redis-service @@ -35,8 +31,6 @@ services: restart: always image: budibase.docker.scarf.sh/budibase/worker container_name: bbworker - ports: - - "${WORKER_PORT}:4003" environment: SELF_HOSTED: 1 PORT: 4003 @@ -53,8 +47,6 @@ services: INTERNAL_API_KEY: ${INTERNAL_API_KEY} REDIS_URL: redis-service:6379 REDIS_PASSWORD: ${REDIS_PASSWORD} - volumes: - - ./logs:/logs depends_on: - redis-service - minio-service @@ -65,8 +57,6 @@ services: image: minio/minio volumes: - minio_data:/data - ports: - - "${MINIO_PORT}:9000" environment: MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} @@ -80,11 +70,10 @@ services: proxy-service: restart: always - image: envoyproxy/envoy:v1.16-latest - volumes: - - ./envoy.yaml:/etc/envoy/envoy.yaml ports: - "${MAIN_PORT}:10000" + container_name: bbproxy + image: budibase/proxy depends_on: - minio-service - worker-service @@ -97,8 +86,6 @@ services: environment: - COUCHDB_PASSWORD=${COUCH_DB_PASSWORD} - COUCHDB_USER=${COUCH_DB_USER} - ports: - - "${COUCH_DB_PORT}:5984" volumes: - couchdb3_data:/opt/couchdb/data @@ -114,18 +101,14 @@ services: restart: always image: redis command: redis-server --requirepass ${REDIS_PASSWORD} - ports: - - "${REDIS_PORT}:6379" volumes: - redis_data:/data watchtower-service: image: containrrr/watchtower - ports: - - "${WATCHTOWER_PORT}:8080" volumes: - /var/run/docker.sock:/var/run/docker.sock - command: --debug --http-api-update bbapps bbworker + command: --debug --http-api-update bbapps bbworker bbproxy environment: - WATCHTOWER_HTTP_API=true - WATCHTOWER_HTTP_API_TOKEN=budibase diff --git a/hosting/envoy.dev.yaml.hbs b/hosting/envoy.dev.yaml.hbs deleted file mode 100644 index 59363fab5e..0000000000 --- a/hosting/envoy.dev.yaml.hbs +++ /dev/null @@ -1,149 +0,0 @@ -static_resources: - listeners: - - name: main_listener - address: - socket_address: { address: 0.0.0.0, port_value: 10000 } - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - stat_prefix: ingress - codec_type: auto - route_config: - name: local_route - virtual_hosts: - - name: local_services - domains: ["*"] - routes: - # special case to redirect specifically the route path - # to the builder, if this were a prefix then it would break minio - - match: { path: "/" } - redirect: { path_redirect: "/builder/" } - - - match: { prefix: "/db/" } - route: - cluster: couchdb-service - prefix_rewrite: "/" - - - match: { prefix: "/api/system/" } - route: - cluster: worker-dev - - - match: { prefix: "/api/admin/" } - route: - cluster: worker-dev - - - match: { prefix: "/api/global/" } - route: - cluster: worker-dev - - - match: { prefix: "/api/" } - route: - cluster: server-dev - timeout: 120s - - - match: { prefix: "/app_" } - route: - cluster: server-dev - - - match: { prefix: "/app/" } - route: - cluster: server-dev - prefix_rewrite: "/" - - # the below three cases are needed to make sure - # all traffic prefixed for the builder is passed through - # correctly. - - match: { path: "/" } - route: - cluster: builder-dev - - - match: { prefix: "/builder/" } - route: - cluster: builder-dev - - - match: { prefix: "/builder" } - route: - cluster: builder-dev - prefix_rewrite: "/builder/" - - # minio is on the default route because this works - # best, minio + AWS SDK doesn't handle path proxy - - match: { prefix: "/" } - route: - cluster: minio-service - - http_filters: - - name: envoy.filters.http.router - - clusters: - - name: minio-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: minio-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: minio-service - port_value: 9000 - - - name: couchdb-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: couchdb-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: couchdb-service - port_value: 5984 - - - name: server-dev - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: server-dev - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: {{ address }} - port_value: 4001 - - - name: builder-dev - connect_timeout: 15s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: builder-dev - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: {{ address }} - port_value: 3000 - - - name: worker-dev - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: worker-dev - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: {{ address }} - port_value: 4002 diff --git a/hosting/kubernetes/budibase/Chart.yaml b/hosting/kubernetes/budibase/Chart.yaml deleted file mode 100644 index c5233842ad..0000000000 --- a/hosting/kubernetes/budibase/Chart.yaml +++ /dev/null @@ -1,41 +0,0 @@ -apiVersion: v2 -name: budibase -description: Budibase is an open source low-code platform, helping thousands of teams build apps for their workplace in minutes. -keywords: -- low-code -- database -- cluster -sources: -- https://github.com/Budibase/budibase -- https://budibase.com - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.2.2 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "0.9.169" - -dependencies: - - name: couchdb - version: 3.3.4 - repository: https://apache.github.io/couchdb-helm - condition: services.couchdb.enabled - - name: ingress-nginx - version: 3.35.0 - repository: https://github.com/kubernetes/ingress-nginx - condition: ingress.nginx diff --git a/hosting/kubernetes/budibase/charts/couchdb/Chart.yaml b/hosting/kubernetes/budibase/charts/couchdb/Chart.yaml deleted file mode 100755 index 74ae734a17..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/Chart.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -appVersion: 3.1.0 -description: A database featuring seamless multi-master sync, that scales from big - data to mobile, with an intuitive HTTP/JSON API and designed for reliability. -home: https://couchdb.apache.org/ -icon: http://couchdb.apache.org/CouchDB-visual-identity/logo/CouchDB-couch-symbol.svg -keywords: -- couchdb -- database -- nosql -maintainers: -- email: kocolosk@apache.org - name: kocolosk -- email: willholley@apache.org - name: willholley -name: couchdb -sources: -- https://github.com/apache/couchdb-docker -version: 3.3.4 diff --git a/hosting/kubernetes/budibase/charts/couchdb/README.md b/hosting/kubernetes/budibase/charts/couchdb/README.md deleted file mode 100755 index 3227123d06..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/README.md +++ /dev/null @@ -1,244 +0,0 @@ -# CouchDB - -Apache CouchDB is a database featuring seamless multi-master sync, that scales -from big data to mobile, with an intuitive HTTP/JSON API and designed for -reliability. - -This chart deploys a CouchDB cluster as a StatefulSet. It creates a ClusterIP -Service in front of the Deployment for load balancing by default, but can also -be configured to deploy other Service types or an Ingress Controller. The -default persistence mechanism is simply the ephemeral local filesystem, but -production deployments should set `persistentVolume.enabled` to `true` to attach -storage volumes to each Pod in the Deployment. - -## TL;DR - -```bash -$ helm repo add couchdb https://apache.github.io/couchdb-helm -$ helm install couchdb/couchdb \ - --set allowAdminParty=true \ - --set couchdbConfig.couchdb.uuid=$(curl https://www.uuidgenerator.net/api/version4 2>/dev/null | tr -d -) -``` - -## Prerequisites - -- Kubernetes 1.9+ with Beta APIs enabled -- Ingress requires Kubernetes 1.14+ - -## Installing the Chart - -To install the chart with the release name `my-release`: - -Add the CouchDB Helm repository: - -```bash -$ helm repo add couchdb https://apache.github.io/couchdb-helm -``` - -Afterwards install the chart replacing the UUID -`decafbaddecafbaddecafbaddecafbad` with a custom one: - -```bash -$ helm install \ - --name my-release \ - --set couchdbConfig.couchdb.uuid=decafbaddecafbaddecafbaddecafbad \ - couchdb/couchdb -``` - -This will create a Secret containing the admin credentials for the cluster. -Those credentials can be retrieved as follows: - -```bash -$ kubectl get secret my-release-couchdb -o go-template='{{ .data.adminPassword }}' | base64 --decode -``` - -If you prefer to configure the admin credentials directly you can create a -Secret containing `adminUsername`, `adminPassword` and `cookieAuthSecret` keys: - -```bash -$ kubectl create secret generic my-release-couchdb --from-literal=adminUsername=foo --from-literal=adminPassword=bar --from-literal=cookieAuthSecret=baz -``` - -If you want to set the `adminHash` directly to achieve consistent salts between -different nodes you need to addionally add the key `password.ini` to the secret: - -```bash -$ kubectl create secret generic my-release-couchdb \ - --from-literal=adminUsername=foo \ - --from-literal=cookieAuthSecret=baz \ - --from-file=./my-password.ini -``` - -With the following contents in `my-password.ini`: - -``` -[admins] -foo = -``` - -and then install the chart while overriding the `createAdminSecret` setting: - -```bash -$ helm install \ - --name my-release \ - --set createAdminSecret=false \ - --set couchdbConfig.couchdb.uuid=decafbaddecafbaddecafbaddecafbad \ - couchdb/couchdb -``` - -This Helm chart deploys CouchDB on the Kubernetes cluster in a default -configuration. The [configuration](#configuration) section lists -the parameters that can be configured during installation. - -> **Tip**: List all releases using `helm list` - -## Uninstalling the Chart - -To uninstall/delete the `my-release` Deployment: - -```bash -$ helm delete my-release -``` - -The command removes all the Kubernetes components associated with the chart and -deletes the release. - -## Upgrading an existing Release to a new major version - -A major chart version change (like v0.2.3 -> v1.0.0) indicates that there is an -incompatible breaking change needing manual actions. - -### Upgrade to 3.0.0 - -Since version 3.0.0 setting the CouchDB server instance UUID is mandatory. -Therefore you need to generate a UUID and supply it as a value during the -upgrade as follows: - -```bash -$ helm upgrade \ - --reuse-values \ - --set couchdbConfig.couchdb.uuid= \ - couchdb/couchdb -``` - -## Migrating from stable/couchdb - -This chart replaces the `stable/couchdb` chart previously hosted by Helm and continues the -version semantics. You can upgrade directly from `stable/couchdb` to this chart using: - -```bash -$ helm repo add couchdb https://apache.github.io/couchdb-helm -$ helm upgrade my-release couchdb/couchdb -``` - -## Configuration - -The following table lists the most commonly configured parameters of the -CouchDB chart and their default values: - -| Parameter | Description | Default | -|---------------------------------|-------------------------------------------------------|----------------------------------------| -| `clusterSize` | The initial number of nodes in the CouchDB cluster | 3 | -| `couchdbConfig` | Map allowing override elements of server .ini config | *See below* | -| `allowAdminParty` | If enabled, start cluster without admin account | false (requires creating a Secret) | -| `createAdminSecret` | If enabled, create an admin account and cookie secret | true | -| `schedulerName` | Name of the k8s scheduler (other than default) | `nil` | -| `erlangFlags` | Map of flags supplied to the underlying Erlang VM | name: couchdb, setcookie: monster -| `persistentVolume.enabled` | Boolean determining whether to attach a PV to each node | false -| `persistentVolume.size` | If enabled, the size of the persistent volume to attach | 10Gi -| `enableSearch` | Adds a sidecar for Lucene-powered text search | false | - -You can set the values of the `couchdbConfig` map according to the -[official configuration][4]. The following shows the map's default values and -required options to set: - -| Parameter | Description | Default | -|---------------------------------|--------------------------------------------------------------------|----------------------------------------| -| `couchdb.uuid` | UUID for this CouchDB server instance ([Required in a cluster][5]) | | -| `chttpd.bind_address` | listens on all interfaces when set to any | any | -| `chttpd.require_valid_user` | disables all the anonymous requests to the port 5984 when true | false | - -A variety of other parameters are also configurable. See the comments in the -`values.yaml` file for further details: - -| Parameter | Default | -|--------------------------------------|----------------------------------------| -| `adminUsername` | admin | -| `adminPassword` | auto-generated | -| `adminHash` | | -| `cookieAuthSecret` | auto-generated | -| `image.repository` | couchdb | -| `image.tag` | 3.1.0 | -| `image.pullPolicy` | IfNotPresent | -| `searchImage.repository` | kocolosk/couchdb-search | -| `searchImage.tag` | 0.1.0 | -| `searchImage.pullPolicy` | IfNotPresent | -| `initImage.repository` | busybox | -| `initImage.tag` | latest | -| `initImage.pullPolicy` | Always | -| `ingress.enabled` | false | -| `ingress.hosts` | chart-example.local | -| `ingress.annotations` | | -| `ingress.path` | / | -| `ingress.tls` | | -| `persistentVolume.accessModes` | ReadWriteOnce | -| `persistentVolume.storageClass` | Default for the Kube cluster | -| `podManagementPolicy` | Parallel | -| `affinity` | | -| `annotations` | | -| `tolerations` | | -| `resources` | | -| `service.annotations` | | -| `service.enabled` | true | -| `service.type` | ClusterIP | -| `service.externalPort` | 5984 | -| `dns.clusterDomainSuffix` | cluster.local | -| `networkPolicy.enabled` | true | -| `serviceAccount.enabled` | true | -| `serviceAccount.create` | true | -| `serviceAccount.imagePullSecrets` | | -| `sidecars` | {} | -| `livenessProbe.enabled` | true | -| `livenessProbe.failureThreshold` | 3 | -| `livenessProbe.initialDelaySeconds` | 0 | -| `livenessProbe.periodSeconds` | 10 | -| `livenessProbe.successThreshold` | 1 | -| `livenessProbe.timeoutSeconds` | 1 | -| `readinessProbe.enabled` | true | -| `readinessProbe.failureThreshold` | 3 | -| `readinessProbe.initialDelaySeconds` | 0 | -| `readinessProbe.periodSeconds` | 10 | -| `readinessProbe.successThreshold` | 1 | -| `readinessProbe.timeoutSeconds` | 1 | - -## Feedback, Issues, Contributing - -General feedback is welcome at our [user][1] or [developer][2] mailing lists. - -Apache CouchDB has a [CONTRIBUTING][3] file with details on how to get started -with issue reporting or contributing to the upkeep of this project. In short, -use GitHub Issues, do not report anything on Docker's website. - -## Non-Apache CouchDB Development Team Contributors - -- [@natarajaya](https://github.com/natarajaya) -- [@satchpx](https://github.com/satchpx) -- [@spanato](https://github.com/spanato) -- [@jpds](https://github.com/jpds) -- [@sebastien-prudhomme](https://github.com/sebastien-prudhomme) -- [@stepanstipl](https://github.com/sebastien-stepanstipl) -- [@amatas](https://github.com/amatas) -- [@Chimney42](https://github.com/Chimney42) -- [@mattjmcnaughton](https://github.com/mattjmcnaughton) -- [@mainephd](https://github.com/mainephd) -- [@AdamDang](https://github.com/AdamDang) -- [@mrtyler](https://github.com/mrtyler) -- [@kevinwlau](https://github.com/kevinwlau) -- [@jeyenzo](https://github.com/jeyenzo) -- [@Pinpin31.](https://github.com/Pinpin31) - -[1]: http://mail-archives.apache.org/mod_mbox/couchdb-user/ -[2]: http://mail-archives.apache.org/mod_mbox/couchdb-dev/ -[3]: https://github.com/apache/couchdb/blob/master/CONTRIBUTING.md -[4]: https://docs.couchdb.org/en/stable/config/index.html -[5]: https://docs.couchdb.org/en/latest/setup/cluster.html#preparing-couchdb-nodes-to-be-joined-into-a-cluster diff --git a/hosting/kubernetes/budibase/charts/couchdb/ci/required-values.yaml b/hosting/kubernetes/budibase/charts/couchdb/ci/required-values.yaml deleted file mode 100755 index 79589d2e04..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/ci/required-values.yaml +++ /dev/null @@ -1,3 +0,0 @@ -couchdbConfig: - couchdb: - uuid: "decafbaddecafbaddecafbaddecafbad" diff --git a/hosting/kubernetes/budibase/charts/couchdb/ci/sidecar.yaml b/hosting/kubernetes/budibase/charts/couchdb/ci/sidecar.yaml deleted file mode 100755 index aa570bdf74..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/ci/sidecar.yaml +++ /dev/null @@ -1,9 +0,0 @@ -sidecars: - - name: foo - image: "busybox" - imagePullPolicy: IfNotPresent - resources: - requests: - cpu: "0.1" - memory: 10Mi - command: ['while true; do echo "foo"; sleep 5; done;'] diff --git a/hosting/kubernetes/budibase/charts/couchdb/password.ini b/hosting/kubernetes/budibase/charts/couchdb/password.ini deleted file mode 100755 index 4ce8445aae..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/password.ini +++ /dev/null @@ -1,2 +0,0 @@ -[admins] -{{ .Values.adminUsername }} = {{ .Values.adminHash }} diff --git a/hosting/kubernetes/budibase/charts/couchdb/templates/NOTES.txt b/hosting/kubernetes/budibase/charts/couchdb/templates/NOTES.txt deleted file mode 100755 index a3658bd37f..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/templates/NOTES.txt +++ /dev/null @@ -1,20 +0,0 @@ -Apache CouchDB is starting. Check the status of the Pods using: - - kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "couchdb.name" . }},release={{ .Release.Name }}" - -Once all of the Pods are fully Ready, execute the following command to create -some required system databases: - - kubectl exec --namespace {{ .Release.Namespace }} {{ if not .Values.allowAdminParty }}-it {{ end }}{{ template "couchdb.fullname" . }}-0 -c couchdb -- \ - curl -s \ - http://127.0.0.1:5984/_cluster_setup \ - -X POST \ - -H "Content-Type: application/json" \ -{{- if .Values.allowAdminParty }} - -d '{"action": "finish_cluster"}' -{{- else }} - -d '{"action": "finish_cluster"}' \ - -u -{{- end }} - -Then it's time to relax. diff --git a/hosting/kubernetes/budibase/charts/couchdb/templates/_helpers.tpl b/hosting/kubernetes/budibase/charts/couchdb/templates/_helpers.tpl deleted file mode 100755 index f9d013e487..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/templates/_helpers.tpl +++ /dev/null @@ -1,81 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "couchdb.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "couchdb.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- printf "%s-%s" .Values.fullnameOverride .Chart.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} - -{{/* -In the event that we create both a headless service and a traditional one, -ensure that the latter gets a unique name. -*/}} -{{- define "couchdb.svcname" -}} -{{- if .Values.fullnameOverride -}} -{{- printf "%s-svc-%s" .Values.fullnameOverride .Chart.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-svc-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} - -{{/* -Create a random string if the supplied key does not exist -*/}} -{{- define "couchdb.defaultsecret" -}} -{{- if . -}} -{{- . | b64enc | quote -}} -{{- else -}} -{{- randAlphaNum 20 | b64enc | quote -}} -{{- end -}} -{{- end -}} - -{{/* -Labels used to define Pods in the CouchDB statefulset -*/}} -{{- define "couchdb.ss.selector" -}} -app: {{ template "couchdb.name" . }} -release: {{ .Release.Name }} -{{- end -}} - -{{/* -Generates a comma delimited list of nodes in the cluster -*/}} -{{- define "couchdb.seedlist" -}} -{{- $nodeCount := min 5 .Values.clusterSize | int }} - {{- range $index0 := until $nodeCount -}} - {{- $index1 := $index0 | add1 -}} - {{ $.Values.erlangFlags.name }}@{{ template "couchdb.fullname" $ }}-{{ $index0 }}.{{ template "couchdb.fullname" $ }}.{{ $.Release.Namespace }}.svc.{{ $.Values.dns.clusterDomainSuffix }}{{ if ne $index1 $nodeCount }},{{ end }} - {{- end -}} -{{- end -}} - -{{/* -If serviceAccount.name is specified, use that, else use the couchdb instance name -*/}} -{{- define "couchdb.serviceAccount" -}} -{{- if .Values.serviceAccount.name -}} -{{- .Values.serviceAccount.name }} -{{- else -}} -{{- template "couchdb.fullname" . -}} -{{- end -}} -{{- end -}} - -{{/* -Fail if couchdbConfig.couchdb.uuid is undefined -*/}} -{{- define "couchdb.uuid" -}} -{{- required "A value for couchdbConfig.couchdb.uuid must be set" (.Values.couchdbConfig.couchdb | default dict).uuid -}} -{{- end -}} \ No newline at end of file diff --git a/hosting/kubernetes/budibase/charts/couchdb/templates/configmap.yaml b/hosting/kubernetes/budibase/charts/couchdb/templates/configmap.yaml deleted file mode 100755 index a6a20e0574..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/templates/configmap.yaml +++ /dev/null @@ -1,23 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "couchdb.fullname" . }} - labels: - app: {{ template "couchdb.name" . }} - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - heritage: {{ .Release.Service | quote }} - release: {{ .Release.Name | quote }} -data: - inifile: | - {{ $couchdbConfig := dict "couchdb" (dict "uuid" (include "couchdb.uuid" .)) -}} - {{- $couchdbConfig := merge $couchdbConfig .Values.couchdbConfig -}} - {{- range $section, $settings := $couchdbConfig -}} - {{ printf "[%s]" $section }} - {{ range $key, $value := $settings -}} - {{ printf "%s = %s" $key ($value | toString) }} - {{ end }} - {{ end }} - - seedlistinifile: | - [cluster] - seedlist = {{ template "couchdb.seedlist" . }} diff --git a/hosting/kubernetes/budibase/charts/couchdb/templates/headless.yaml b/hosting/kubernetes/budibase/charts/couchdb/templates/headless.yaml deleted file mode 100755 index 0ce3ef0f35..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/templates/headless.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ template "couchdb.fullname" . }} - labels: - app: {{ template "couchdb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - clusterIP: None - publishNotReadyAddresses: true - ports: - - name: couchdb - port: 5984 - selector: -{{ include "couchdb.ss.selector" . | indent 4 }} diff --git a/hosting/kubernetes/budibase/charts/couchdb/templates/ingress.yaml b/hosting/kubernetes/budibase/charts/couchdb/templates/ingress.yaml deleted file mode 100755 index c547847ce5..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/templates/ingress.yaml +++ /dev/null @@ -1,33 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $serviceName := include "couchdb.fullname" . -}} -{{- $servicePort := .Values.service.externalPort -}} -{{- $path := .Values.ingress.path | quote -}} -apiVersion: networking.k8s.io/v1beta1 -kind: Ingress -metadata: - name: {{ template "couchdb.fullname" . }} - labels: - app: {{ template "couchdb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - annotations: - {{- range $key, $value := .Values.ingress.annotations }} - {{ $key }}: {{ $value | quote }} - {{- end }} -spec: - rules: - {{- range $host := .Values.ingress.hosts }} - - host: {{ $host }} - http: - paths: - - path: {{ $path }} - backend: - serviceName: {{ $serviceName }} - servicePort: {{ $servicePort }} - {{- end -}} - {{- if .Values.ingress.tls }} - tls: -{{ toYaml .Values.ingress.tls | indent 4 }} - {{- end -}} -{{- end -}} diff --git a/hosting/kubernetes/budibase/charts/couchdb/templates/networkpolicy.yaml b/hosting/kubernetes/budibase/charts/couchdb/templates/networkpolicy.yaml deleted file mode 100755 index 2830708bef..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/templates/networkpolicy.yaml +++ /dev/null @@ -1,31 +0,0 @@ - -{{- if .Values.networkPolicy.enabled }} -kind: NetworkPolicy -apiVersion: networking.k8s.io/v1 -metadata: - name: {{ template "couchdb.fullname" . }} - labels: - app: {{ template "couchdb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - podSelector: - matchLabels: -{{ include "couchdb.ss.selector" . | indent 6 }} - ingress: - - ports: - - protocol: TCP - port: 5984 - - ports: - - protocol: TCP - port: 9100 - - protocol: TCP - port: 4369 - from: - - podSelector: - matchLabels: -{{ include "couchdb.ss.selector" . | indent 14 }} - policyTypes: - - Ingress -{{- end }} diff --git a/hosting/kubernetes/budibase/charts/couchdb/templates/secrets.yaml b/hosting/kubernetes/budibase/charts/couchdb/templates/secrets.yaml deleted file mode 100755 index 92f55c6d6b..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/templates/secrets.yaml +++ /dev/null @@ -1,19 +0,0 @@ -{{- if .Values.createAdminSecret -}} -apiVersion: v1 -kind: Secret -metadata: - name: {{ template "couchdb.fullname" . }} - labels: - app: {{ template "couchdb.fullname" . }} - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" -type: Opaque -data: - adminUsername: {{ template "couchdb.defaultsecret" .Values.adminUsername }} - adminPassword: {{ template "couchdb.defaultsecret" .Values.adminPassword }} - cookieAuthSecret: {{ template "couchdb.defaultsecret" .Values.cookieAuthSecret }} -{{- if .Values.adminHash }} - password.ini: {{ tpl (.Files.Get "password.ini") . | b64enc }} -{{- end -}} -{{- end -}} diff --git a/hosting/kubernetes/budibase/charts/couchdb/templates/service.yaml b/hosting/kubernetes/budibase/charts/couchdb/templates/service.yaml deleted file mode 100755 index 6d0382477d..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/templates/service.yaml +++ /dev/null @@ -1,23 +0,0 @@ -{{- if .Values.service.enabled -}} -apiVersion: v1 -kind: Service -metadata: - name: {{ template "couchdb.svcname" . }} - labels: - app: {{ template "couchdb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -{{- if .Values.service.annotations }} - annotations: -{{ toYaml .Values.service.annotations | indent 4 }} -{{- end }} -spec: - ports: - - port: {{ .Values.service.externalPort }} - protocol: TCP - targetPort: 5984 - type: {{ .Values.service.type }} - selector: -{{ include "couchdb.ss.selector" . | indent 4 }} -{{- end -}} diff --git a/hosting/kubernetes/budibase/charts/couchdb/templates/serviceaccount.yaml b/hosting/kubernetes/budibase/charts/couchdb/templates/serviceaccount.yaml deleted file mode 100755 index bb82799a49..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/templates/serviceaccount.yaml +++ /dev/null @@ -1,15 +0,0 @@ -{{- if .Values.serviceAccount.create }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ template "couchdb.serviceAccount" . }} - labels: - app: {{ template "couchdb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -{{- if .Values.serviceAccount.imagePullSecrets }} -imagePullSecrets: -{{ toYaml .Values.serviceAccount.imagePullSecrets }} -{{- end }} -{{- end }} diff --git a/hosting/kubernetes/budibase/charts/couchdb/templates/statefulset.yaml b/hosting/kubernetes/budibase/charts/couchdb/templates/statefulset.yaml deleted file mode 100755 index 6225fbe98c..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/templates/statefulset.yaml +++ /dev/null @@ -1,202 +0,0 @@ -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ template "couchdb.fullname" . }} - labels: - app: {{ template "couchdb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - replicas: {{ .Values.clusterSize }} - serviceName: {{ template "couchdb.fullname" . }} - podManagementPolicy: {{ .Values.podManagementPolicy }} - selector: - matchLabels: -{{ include "couchdb.ss.selector" . | indent 6 }} - template: - metadata: - labels: -{{ include "couchdb.ss.selector" . | indent 8 }} -{{- with .Values.annotations }} - annotations: - checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} -{{ toYaml . | indent 8 }} -{{- end }} - spec: - {{- if .Values.schedulerName }} - schedulerName: "{{ .Values.schedulerName }}" - {{- end }} - {{- if .Values.serviceAccount.enabled }} - serviceAccountName: {{ template "couchdb.serviceAccount" . }} - {{- end }} - initContainers: - - name: init-copy - image: "{{ .Values.initImage.repository }}:{{ .Values.initImage.tag }}" - imagePullPolicy: {{ .Values.initImage.pullPolicy }} - command: ['sh','-c','cp /tmp/chart.ini /default.d; cp /tmp/seedlist.ini /default.d; ls -lrt /default.d;'] - volumeMounts: - - name: config - mountPath: /tmp/ - - name: config-storage - mountPath: /default.d -{{- if .Values.adminHash }} - - name: admin-hash-copy - image: "{{ .Values.initImage.repository }}:{{ .Values.initImage.tag }}" - imagePullPolicy: {{ .Values.initImage.pullPolicy }} - command: ['sh','-c','cp /tmp/password.ini /local.d/ ;'] - volumeMounts: - - name: admin-password - mountPath: /tmp/password.ini - subPath: "password.ini" - - name: local-config-storage - mountPath: /local.d -{{- end }} - containers: - - name: couchdb - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - name: couchdb - containerPort: 5984 - - name: epmd - containerPort: 4369 - - containerPort: 9100 - env: -{{- if not .Values.allowAdminParty }} - - name: COUCHDB_USER - valueFrom: - secretKeyRef: - name: {{ template "couchdb.fullname" . }} - key: adminUsername - - name: COUCHDB_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "couchdb.fullname" . }} - key: adminPassword - - name: COUCHDB_SECRET - valueFrom: - secretKeyRef: - name: {{ template "couchdb.fullname" . }} - key: cookieAuthSecret -{{- end }} - - name: ERL_FLAGS - value: "{{ range $k, $v := .Values.erlangFlags }} -{{ $k }} {{ $v }} {{ end }}" -{{- if .Values.livenessProbe.enabled }} - livenessProbe: -{{- if .Values.couchdbConfig.chttpd.require_valid_user }} - exec: - command: - - sh - - -c - - curl -G --silent --fail -u ${COUCHDB_USER}:${COUCHDB_PASSWORD} http://localhost:5984/_up -{{- else }} - httpGet: - path: /_up - port: 5984 -{{- end }} - failureThreshold: {{ .Values.livenessProbe.failureThreshold }} - initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.livenessProbe.periodSeconds }} - successThreshold: {{ .Values.livenessProbe.successThreshold }} - timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} -{{- end }} -{{- if .Values.readinessProbe.enabled }} - readinessProbe: -{{- if .Values.couchdbConfig.chttpd.require_valid_user }} - exec: - command: - - sh - - -c - - curl -G --silent --fail -u ${COUCHDB_USER}:${COUCHDB_PASSWORD} http://localhost:5984/_up -{{- else }} - httpGet: - path: /_up - port: 5984 -{{- end }} - failureThreshold: {{ .Values.readinessProbe.failureThreshold }} - initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.readinessProbe.periodSeconds }} - successThreshold: {{ .Values.readinessProbe.successThreshold }} - timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} -{{- end }} - resources: -{{ toYaml .Values.resources | indent 12 }} - volumeMounts: - - name: config-storage - mountPath: /opt/couchdb/etc/default.d -{{- if .Values.adminHash }} - - name: local-config-storage - mountPath: /opt/couchdb/etc/local.d -{{- end }} - - name: database-storage - mountPath: /opt/couchdb/data -{{- if .Values.enableSearch }} - - name: clouseau - image: "{{ .Values.searchImage.repository }}:{{ .Values.searchImage.tag }}" - imagePullPolicy: {{ .Values.searchImage.pullPolicy }} - volumeMounts: - - name: database-storage - mountPath: /opt/couchdb-search/data -{{- end }} -{{- if .Values.sidecars }} -{{ toYaml .Values.sidecars | indent 8}} -{{- end }} -{{- if .Values.nodeSelector }} - nodeSelector: -{{ toYaml .Values.nodeSelector | indent 8 }} -{{- end }} -{{- with .Values.tolerations }} - tolerations: -{{ toYaml . | indent 8 }} -{{- end }} -{{- with .Values.affinity }} - affinity: -{{ toYaml . | indent 8 }} -{{- end }} - volumes: - - name: config-storage - emptyDir: {} - - name: config - configMap: - name: {{ template "couchdb.fullname" . }} - items: - - key: inifile - path: chart.ini - - key: seedlistinifile - path: seedlist.ini - -{{- if .Values.adminHash }} - - name: local-config-storage - emptyDir: {} - - name: admin-password - secret: - secretName: {{ template "couchdb.fullname" . }} -{{- end -}} - -{{- if not .Values.persistentVolume.enabled }} - - name: database-storage - emptyDir: {} -{{- else }} - volumeClaimTemplates: - - metadata: - name: database-storage - labels: - app: {{ template "couchdb.name" . }} - release: {{ .Release.Name }} - spec: - accessModes: - {{- range .Values.persistentVolume.accessModes }} - - {{ . | quote }} - {{- end }} - resources: - requests: - storage: {{ .Values.persistentVolume.size | quote }} - {{- if .Values.persistentVolume.storageClass }} - {{- if (eq "-" .Values.persistentVolume.storageClass) }} - storageClassName: "" - {{- else }} - storageClassName: "{{ .Values.persistentVolume.storageClass }}" - {{- end }} - {{- end }} -{{- end }} diff --git a/hosting/kubernetes/budibase/charts/couchdb/values.yaml b/hosting/kubernetes/budibase/charts/couchdb/values.yaml deleted file mode 100755 index 5a5025f816..0000000000 --- a/hosting/kubernetes/budibase/charts/couchdb/values.yaml +++ /dev/null @@ -1,201 +0,0 @@ -## clusterSize is the initial size of 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 - -## 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 -# adminHash: -pbkdf2-this_is_not_necessarily_secure_either -# cookieAuthSecret: admin - -## 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 - accessModes: - - ReadWriteOnce - size: 10Gi - storageClass: "" - -## The CouchDB image -image: - repository: couchdb - tag: 3.1.0 - pullPolicy: IfNotPresent - -## Experimental integration with Lucene-powered fulltext search -searchImage: - repository: kocolosk/couchdb-search - tag: 0.2.0 - pullPolicy: IfNotPresent - -## Flip this to flag to include the Search container in each Pod -enableSearch: true - -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" - -## 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 - -## 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 - 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 - -## 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 -erlangFlags: - name: couchdb - setcookie: monster - -## 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 - # 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 - -# 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 - -# 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/ diff --git a/hosting/kubernetes/budibase/charts/ingress-nginx-3.35.0.tgz b/hosting/kubernetes/budibase/charts/ingress-nginx-3.35.0.tgz deleted file mode 100644 index ee5214c497..0000000000 Binary files a/hosting/kubernetes/budibase/charts/ingress-nginx-3.35.0.tgz and /dev/null differ diff --git a/hosting/kubernetes/budibase/values.yaml b/hosting/kubernetes/budibase/values.yaml deleted file mode 100644 index bd9f6543b3..0000000000 --- a/hosting/kubernetes/budibase/values.yaml +++ /dev/null @@ -1,153 +0,0 @@ -# Default values for budibase. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -replicaCount: 1 - -image: - pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. - tag: "" - -imagePullSecrets: [] -nameOverride: "" -# fullnameOverride: "" - -serviceAccount: - # Specifies whether a service account should be created - create: true - # Annotations to add to the service account - annotations: {} - # 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: - type: ClusterIP - port: 10000 - -ingress: - enabled: false - aws: false - nginx: true - certificateArn: "" - className: "" - annotations: - kubernetes.io/ingress.class: nginx - hosts: - - host: # change if using custom domain - paths: - - path: / - pathType: Prefix - backend: - service: - name: proxy-service - port: - number: 10000 - -resources: - {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 100 - targetCPUUtilizationPercentage: 80 - # targetMemoryUtilizationPercentage: 80 - -nodeSelector: {} - -tolerations: [] - -affinity: {} - -globals: - budibaseEnv: PRODUCTION - enableAnalytics: true - sentryDSN: "" - posthogToken: "" - logLevel: info - 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 - accountPortalUrl: "" - accountPortalApiKey: "" - cookieDomain: "" - platformUrl: "" - - createSecrets: true # creates an internal API key, JWT secrets and redis password for you - - # if createSecrets is set to false, you can hard-code your secrets here - internalApiKey: "" - jwtSecret: "" - - smtp: - enabled: false - -services: - dns: cluster.local - - proxy: - port: 10000 - replicaCount: 1 - - apps: - port: 4002 - replicaCount: 1 - logLevel: info - - worker: - port: 4001 - replicaCount: 1 - - couchdb: - enabled: true - replicaCount: 3 - # 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 - storage: 100Mi - - redis: - enabled: true # disable if using external redis - port: 6379 - replicaCount: 1 - url: "" # only change if pointing to existing redis cluster and enabled: false - password: "budibase" # recommended to override if using built-in redis - storage: 100Mi - - objectStore: - minio: true - browser: true - port: 9000 - 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: "" # only change if pointing to existing minio cluster and minio: false - storage: 100Mi diff --git a/hosting/kubernetes/envoy/Dockerfile b/hosting/kubernetes/envoy/Dockerfile deleted file mode 100644 index 96334fa723..0000000000 --- a/hosting/kubernetes/envoy/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM envoyproxy/envoy:v1.16-latest -COPY envoy.yaml /etc/envoy/envoy.yaml -RUN chmod go+r /etc/envoy/envoy.yaml - diff --git a/hosting/kubernetes/envoy/envoy.yaml b/hosting/kubernetes/envoy/envoy.yaml deleted file mode 100644 index 25a774dc7e..0000000000 --- a/hosting/kubernetes/envoy/envoy.yaml +++ /dev/null @@ -1,138 +0,0 @@ -static_resources: - listeners: - - name: main_listener - address: - socket_address: { address: 0.0.0.0, port_value: 10000 } - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - stat_prefix: ingress - codec_type: auto - route_config: - name: local_route - virtual_hosts: - - name: local_services - domains: ["*"] - routes: - - match: { prefix: "/app/" } - route: - cluster: app-service - prefix_rewrite: "/" - - - match: { prefix: "/builder/" } - route: - cluster: app-service - - - match: { prefix: "/builder" } - route: - cluster: app-service - - - match: { prefix: "/app_" } - route: - cluster: app-service - - # special cases for worker admin (deprecated), global and system API - - match: { prefix: "/api/global/" } - route: - cluster: worker-service - - - match: { prefix: "/api/admin/" } - route: - cluster: worker-service - - - match: { prefix: "/api/system/" } - route: - cluster: worker-service - - - match: { path: "/" } - route: - cluster: app-service - - - match: { path: "/api/deploy" } - route: - timeout: 60s - cluster: app-service - - # special case for when API requests are made, can just forward, not to minio - - match: { prefix: "/api/" } - route: - cluster: app-service - - - match: { prefix: "/worker/" } - route: - cluster: worker-service - prefix_rewrite: "/" - - - match: { prefix: "/db/" } - route: - cluster: couchdb-service - prefix_rewrite: "/" - - # minio is on the default route because this works - # best, minio + AWS SDK doesn't handle path proxy - - match: { prefix: "/" } - route: - cluster: minio-service - - http_filters: - - name: envoy.filters.http.router - - clusters: - - name: app-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: app-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: app-service.budibase.svc.cluster.local - port_value: 4002 - - - name: minio-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: minio-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: minio-service.budibase.svc.cluster.local - port_value: 9000 - - - name: worker-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: worker-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: worker-service.budibase.svc.cluster.local - port_value: 4001 - - - name: couchdb-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: couchdb-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: budibase-prod-svc-couchdb - port_value: 5984 - diff --git a/hosting/nginx.dev.conf.hbs b/hosting/nginx.dev.conf.hbs new file mode 100644 index 0000000000..624b4c2653 --- /dev/null +++ b/hosting/nginx.dev.conf.hbs @@ -0,0 +1,94 @@ +user nginx; +error_log /var/log/nginx/error.log debug; +pid /var/run/nginx.pid; +worker_processes auto; +worker_rlimit_nofile 33282; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + proxy_set_header Host $host; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + map $http_upgrade $connection_upgrade { + default "upgrade"; + } + + server { + listen 10000 default_server; + server_name _; + client_max_body_size 1000m; + ignore_invalid_headers off; + proxy_buffering off; + + location /db/ { + proxy_pass http://couchdb-service:5984; + rewrite ^/db/(.*)$ /$1 break; + } + + location ~ ^/api/(system|admin|global)/ { + proxy_pass http://{{ address }}:4002; + } + + location /api/ { + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + proxy_pass http://{{ address }}:4001; + } + + location = / { + proxy_pass http://{{ address }}:4001; + } + + location /app_ { + proxy_pass http://{{ address }}:4001; + } + + location /app/ { + proxy_pass http://{{ address }}:4001; + rewrite ^/app/(.*)$ /$1 break; + } + + location /builder { + proxy_pass http://{{ address }}:3000; + rewrite ^/builder(.*)$ /builder/$1 break; + } + + location /builder/ { + proxy_pass http://{{ address }}:3000; + + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_connect_timeout 300; + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding off; + + proxy_pass http://minio-service:9000; + } + + client_header_timeout 60; + client_body_timeout 60; + keepalive_timeout 60; + gzip off; + gzip_comp_level 4; + } +} \ No newline at end of file diff --git a/hosting/nginx.prod.conf.hbs b/hosting/nginx.prod.conf.hbs new file mode 100644 index 0000000000..f446c928fb --- /dev/null +++ b/hosting/nginx.prod.conf.hbs @@ -0,0 +1,147 @@ +user nginx; +error_log /var/log/nginx/error.log debug; +pid /var/run/nginx.pid; +worker_processes auto; +worker_rlimit_nofile 33282; + +events { + worker_connections 1024; +} + +http { + limit_req_zone $binary_remote_addr zone=ratelimit:10m rate=20r/s; + include /etc/nginx/mime.types; + default_type application/octet-stream; + proxy_set_header Host $host; + charset utf-8; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + server_tokens off; + types_hash_max_size 2048; + {{#if compose}} + resolver 127.0.0.11 ipv6=off; + {{/if}} + {{#if k8s}} + resolver kube-dns.kube-system.svc.cluster.local valid=10s; + {{/if}} + + + # buffering + client_body_buffer_size 1K; + client_header_buffer_size 1k; + client_max_body_size 10M; + ignore_invalid_headers off; + proxy_buffering off; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + map $http_upgrade $connection_upgrade { + default "upgrade"; + } + + server { + listen 10000 default_server; + listen [::]:10000 default_server; + server_name _; + client_max_body_size 1000m; + ignore_invalid_headers off; + proxy_buffering off; + # port_in_redirect off; + + # Security Headers + add_header X-Frame-Options SAMEORIGIN always; + add_header X-Content-Type-Options nosniff always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com; object-src 'none'; base-uri 'self'; connect-src 'self' https://api-iam.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io; font-src 'self' data https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com; frame-src 'self'; img-src http: https: data; manifest-src 'self'; media-src 'self'; worker-src 'none';" always; + + # upstreams + set $apps {{ apps }}; + set $worker {{ worker }}; + set $minio {{ minio }}; + set $couchdb {{ couchdb }}; + {{#if watchtower}} + set $watchtower {{ watchtower }}; + {{/if}} + + location /app { + proxy_pass http://$apps:4002; + rewrite ^/app/(.*)$ /$1 break; + } + + location = / { + proxy_pass http://$apps:4002; + } + + {{#if watchtower}} + location = /v1/update { + proxy_pass http://$watchtower:8080; + } + {{/if}} + location ~ ^/(builder|app_) { + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://$apps:4002; + } + + location ~ ^/api/(system|admin|global)/ { + proxy_pass http://$worker:4003; + } + + location /worker/ { + proxy_pass http://$worker:4003; + rewrite ^/worker/(.*)$ /$1 break; + } + + location /api/ { + # calls to the API are rate limited with bursting + limit_req zone=ratelimit burst=20 nodelay; + + # 120s timeout on API requests + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass http://$apps:4002; + } + + location /db/ { + proxy_pass http://$couchdb:5984; + rewrite ^/db/(.*)$ /$1 break; + } + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_connect_timeout 300; + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding off; + proxy_pass http://$minio:9000; + } + + client_header_timeout 60; + client_body_timeout 60; + keepalive_timeout 60; + + # gzip + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + } +} \ No newline at end of file diff --git a/hosting/proxy/Dockerfile b/hosting/proxy/Dockerfile new file mode 100644 index 0000000000..b577e3e40f --- /dev/null +++ b/hosting/proxy/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx:latest +COPY .generated-nginx.prod.conf /etc/nginx/nginx.conf \ No newline at end of file diff --git a/hosting/proxy/nginx.conf b/hosting/proxy/nginx.conf new file mode 100644 index 0000000000..95b06d4fff --- /dev/null +++ b/hosting/proxy/nginx.conf @@ -0,0 +1,145 @@ +user nginx; +error_log /var/log/nginx/error.log debug; +pid /var/run/nginx.pid; +worker_processes auto; +worker_rlimit_nofile 33282; + +events { + worker_connections 1024; +} + +http { + limit_req_zone $binary_remote_addr zone=ratelimit:10m rate=20r/s; + include /etc/nginx/mime.types; + default_type application/octet-stream; + charset utf-8; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + server_tokens off; + types_hash_max_size 2048; + + # buffering + client_body_buffer_size 1K; + client_header_buffer_size 1k; + client_max_body_size 1k; + ignore_invalid_headers off; + + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + map $http_upgrade $connection_upgrade { + default "upgrade"; + } + + server { + listen 10000 default_server; + listen [::]:10000 default_server; + server_name _; + client_max_body_size 1000m; + ignore_invalid_headers off; + proxy_buffering off; + port_in_redirect off; + + # Security Headers + add_header X-Frame-Options SAMEORIGIN always; + add_header X-Content-Type-Options nosniff always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me; object-src 'none'; base-uri 'self'; connect-src 'self' https://api-iam.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io; font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me; frame-src 'self'; img-src http: https: data:; manifest-src 'self'; media-src 'self'; worker-src 'none';" always; + + location /app { + proxy_pass http://app-service:4002; + rewrite ^/app/(.*)$ /$1 break; + } + + location = / { + port_in_redirect off; + proxy_pass http://app-service:4002; + } + + location = /v1/update { + proxy_pass http://watchtower-service:8080; + } + + location /builder/ { + port_in_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://app-service:4002; + } + + location ~ ^/(builder|app_) { + port_in_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://app-service:4002; + } + + location ~ ^/api/(system|admin|global)/ { + proxy_pass http://worker-service:4003; + } + + location /worker/ { + proxy_pass http://worker-service:4003; + rewrite ^/worker/(.*)$ /$1 break; + } + + location /api/ { + # calls to the API are rate limited with bursting + limit_req zone=ratelimit burst=20 nodelay; + + # 120s timeout on API requests + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass http://app-service:4002; + } + + location /db/ { + proxy_pass http://couchdb-service:5984; + rewrite ^/db/(.*)$ /$1 break; + } + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + + proxy_connect_timeout 300; + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding off; + proxy_pass http://minio-service:9000; + } + + client_header_timeout 60; + client_body_timeout 60; + keepalive_timeout 60; + + # gzip + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + } +} \ No newline at end of file diff --git a/hosting/scripts/airgapped/airgappedDockerBuild.js b/hosting/scripts/airgapped/airgappedDockerBuild.js index 5be19fdeb8..cc0ea48eb3 100755 --- a/hosting/scripts/airgapped/airgappedDockerBuild.js +++ b/hosting/scripts/airgapped/airgappedDockerBuild.js @@ -5,7 +5,7 @@ const path = require("path") const IMAGES = { worker: "budibase/worker", apps: "budibase/apps", - proxy: "envoyproxy/envoy:v1.16-latest", + proxy: "budibase/proxy", minio: "minio/minio", couch: "ibmcom/couchdb3", curl: "curlimages/curl", @@ -15,8 +15,7 @@ const IMAGES = { const FILES = { COMPOSE: "docker-compose.yaml", - ENVOY: "envoy.yaml", - PROPERTIES: "hosting.properties" + ENV: ".env" } const OUTPUT_DIR = path.join(__dirname, "../", "bb-airgapped") @@ -41,8 +40,7 @@ for (let image in IMAGES) { // copy config files copyFile(FILES.COMPOSE) -copyFile(FILES.ENVOY) -copyFile(FILES.PROPERTIES) +copyFile(FILES.ENV) // compress execSync(`tar -czf bb-airgapped.tar.gz hosting/scripts/bb-airgapped`) diff --git a/hosting/scripts/linux/release-to-docker-hub.sh b/hosting/scripts/linux/release-to-docker-hub.sh index 642a8682fb..599a10f914 100755 --- a/hosting/scripts/linux/release-to-docker-hub.sh +++ b/hosting/scripts/linux/release-to-docker-hub.sh @@ -9,8 +9,10 @@ fi echo "Tagging images with tag: $tag" +docker tag proxy-service budibase/proxy:$tag docker tag app-service budibase/apps:$tag docker tag worker-service budibase/worker:$tag docker push --all-tags budibase/apps docker push --all-tags budibase/worker +docker push --all-tags budibase/proxy diff --git a/i18n/README.fr.md b/i18n/README.fr.md new file mode 100644 index 0000000000..12abd4d073 --- /dev/null +++ b/i18n/README.fr.md @@ -0,0 +1,214 @@ +

+ + Budibase + +

+

+ Budibase +

+ +

+ La plateform low-code que vous aimerez utiliser +

+

+ Budibase est une plateforme low-code open source et c'est la façon la plus facile de créer des outils internes qui améliore la productivité. +

+ +

+ 🤖 🎨 🚀 +

+
+ +

+ Budibase design ui +

+ +

+ + GitHub toutes les releases + + + GitHub release (par ordre chronologique) + + + Suivre @budibase + + Code de conduite + + + +

+ +

+ Commencer + · + Documentation + · + Demandes d'amélioration + · + Signaler un bug + · + Support: Discussions +

+ +

+## ✨ Fontionnalités + +### Construire et déployer un vrai logiciel +Contrairement à d'autres plateformes, avec Budibase vous construisez et déployez des applications one-page. Les applications Budibase sont très perfomantes et peuvent être designées de manière responsive, offrant ainsi à vos utilisateurs une expérience exceptionnelle. +

+ +### Source libre et extensible +Budibase est un logiciel libre - sous licence GPL v3. Cela devrait vous rassurer sur le fait que Budibase sera toujours là. Vous pouvez également coder dans Budibase ou le forker et apporter des modifications à votre guise, ce qui en fera une expérience conviviale pour les développeurs. +

+ +### Importer les données ou partir de zéro +Budibase peut tirer ses données de plusieurs sources, dont MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB ou une API REST. Et contrairement à d'autres plateformes, avec Budibase, vous pouvez partir de zéro et créer des applications métier sans aucune source de données. [Demander une nouvelle source de données](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). + +

+ Budibase data +

+

+ +### Concevoir et créer des applications à l'aide de composants prédéfinis. + +Budibase est livré avec des composants joliment conçus et puissants que vous pouvez utiliser comme des blocs de construction pour bâtir votre interface utilisateur. Nous exposons également un grand nombre de vos options de style CSS préférées afin que vous puissiez faire preuve d'une créativité accrue. [Demander un nouveau composant](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). + +

+ Budibase design +

+

+ +### Automatiser les processus, intégrer d'autres outils et se connecter à des webhooks +Gagnez du temps en automatisant les processus manuels et les flux de travail. Qu'il s'agisse de se connecter à des webhooks ou d'automatiser des e-mails, il suffit de dire à Budibase ce qu'il doit faire et de le laisser travailler pour vous. Vous pouvez aisément [créer une nouvelle automatisation pour Budibase ici](https://github.com/Budibase/automations) ou [Demander une nouvelle automatisation](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). + +

+ Budibase automations +

+

+ +### Intégration avec vos outils préférés +Budibase s'intègre à un certain nombre d'outils populaires, ce qui vous permet de créer des applications qui s'adaptent parfaitement à votre pile technologique. + +

+ Budibase integrations +

+

+ +### Paradis des admins +Budibase est conçu pour évoluer. Avec Budibase, vous pouvez vous auto-héberger sur votre propre infrastructure et gérer globalement les utilisateurs, l'accueil, le SMTP, les applications, les groupes, l'apparence et plus encore. Vous pouvez également fournir aux utilisateurs/groupes un portail d'applications et confier la gestion des utilisateurs au responsable du groupe. + +- Regardez la vidéo de promotion: https://youtu.be/xoljVpty_Kw + +


+ +## 🏁 Commencer + + + +Déployez Budibase en auto-hébergement dans votre infrastructure existante, en utilisant Docker, Kubernetes et Digital Ocean. +Ou utilisez Budibase Cloud si vous n'avez pas besoin de vous auto-héberger, et que vous souhaitez démarrer rapidement. + +### [Commencer avec Budibase](https://budibase.com) + + +

+ +## 🎓 Apprendre Budibase + +La documentation Budibase [est ic](https://docs.budibase.com). +
+ + +

+ +## 💬 Communauté + +Si vous avez une question ou si vous souhaitez discuter avec d'autres utilisateurs de Budibase et rejoindre notre communauté, veuillez vous rendre à l'adresse suivante : [Discussions Github](https://github.com/Budibase/budibase/discussions) + +


+ + +## ❗ Code de conduite + +Budibase s'engage à offrir à chacun une expérience accueillante, diversifiée et exempte de harcèlement. Nous attendons de tous les membres de la communauté Budibase qu'ils se conforment aux principes de notre [**Code de conduite**](https://github.com/Budibase/budibase/blob/HEAD/.github/CODE_OF_CONDUCT.md). Merci de le lire. +
+ + +

+ + +## 🙌 Contribuer à Budibase + +Qu'il s'agisse d'ouvrir un rapport de bug ou de créer une Pull request, toute contribution est appréciée et bienvenue. Si vous envisagez de mettre en œuvre une nouvelle fonctionnalité ou de modifier l'API, veuillez d'abord créer un Issue. Nous pourrons ainsi nous assurer que votre travail n'est pas vain. + +### Vous ne savez pas par où commencer ? +Un bon endroit pour commencer à contribuer, c'est ici : [Projets en cours](https://github.com/Budibase/budibase/projects/22). + +### Comment le repo est-il organisé ? +Budibase est une monorepo gérée par lerna. Lerna gère la construction et la publication des paquets de Budibase. Voici, à un haut niveau, les paquets qui composent Budibase. + +- [packages/builder](https://github.com/Budibase/budibase/tree/HEAD/packages/builder) - contient le code pour l'application svelte côté client du budibase builder. + +- [packages/client](https://github.com/Budibase/budibase/tree/HEAD/packages/client) - Un module qui s'exécute dans le navigateur et qui est chargé de lire les définitions JSON et de créer des applications web vivantes à partir de celles-ci.. + +- [packages/server](https://github.com/Budibase/budibase/tree/HEAD/packages/server) - Le serveur budibase. Cette application Koa est responsable de servir le JS pour les applications builder et budibase, ainsi que de fournir l'API pour l'interaction avec la base de données et le système de fichiers. + +Pour plus d'informations, voir [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md) + +

+ + +## 📝 Licence + +Budibase est open source, sous licence de [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html). Les bibliothèques du client et des composants sont sous licence [MPL](https://directory.fsf.org/wiki/License:MPL-2.0) - afin que les applications que vous créez puissent être utilisées sous licence comme vous le souhaitez. + +

+ +## ⭐ Stargazers dans le temps + +[![Stargazers dans le temps](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase) + +Si vous rencontrez des problèmes entre les mises à jour du builder, veuillez utiliser le guide suivant [ici](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md#troubleshooting) pour nettoyer votre environnement. + +

+ +## Contributeurs ✨ + +Merci à ces personnes merveilleuses ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Martin McKeaveney

💻 📖 ⚠️ 🚇

Michael Drury

📖 💻 ⚠️ 🚇

Andrew Kingston

📖 💻 ⚠️ 🎨

Michael Shanks

📖 💻 ⚠️

Kevin Åberg Kultalahti

📖 💻 ⚠️

Joe

📖 💻 🖋 🎨

Rory Powell

💻 📖 ⚠️

Peter Clement

💻 📖 ⚠️

Conor_Mack

💻 ⚠️

pngwn

💻 ⚠️

HugoLd

💻

victoriasloan

💻

yashank09

💻

SOVLOOKUP

💻

seoulaja

🌍

Maurits Lourens

⚠️ 💻

Rory Powell

🚇 ⚠️ 💻
+ + + + + + +Ce projet suit la spécification [all-contributors](https://allcontributors.org/docs/fr/overview). Les contributions de toute nature sont les bienvenues ! diff --git a/i18n/README.jp.md b/i18n/README.jp.md new file mode 100644 index 0000000000..6fea497d53 --- /dev/null +++ b/i18n/README.jp.md @@ -0,0 +1,214 @@ +

+ + Budibase + +

+

+ Budibase +

+ +

+ 使って楽しいローコードプラットフォーム +

+

+ Budibaseはオープンソースのローコードプラットフォームで、生産性を向上させるツールを簡単に構築することができます。 +

+ +

+ 🤖 🎨 🚀 +

+
+ +

+ Budibase design ui +

+ +

+ + GitHub all releases + + + GitHub release (latest by date) + + + Follow @budibase + + Code of conduct + + + +

+ +

+ はじめに + · + ドキュメント + · + 機能リクエスト + · + バグ報告 + · + サポート: ディスカッション +

+ +

+## ✨ 特徴 + +### "本物"のソフトウェアを構築できます +ほかのプラットフォームとは違い、Budibaseだけでシングルページのアプリケーションを制作し完成させることができます。Budibaseで作られたアプリケーションは素晴らしいパフォーマンスを持っており、レスポンシブデザインにも対応しています。ユーザー達にいい印象を与えること間違いなしでしょう! +

+ +### 拡張性が高くオープンソース +Budibaseはオープンソースで、GPL v3ライセンスの下に公開されています。このことは、Budibaseが常にあなたのそばにいるという安心感を与えてくれることでしょう。そして、私たちは開発者に優しい環境を提供しているので、あなたは好きなだけにソースコードをフォークして改造、もしくは直接Budibaseにコントリビュートすることができます。 +

+ +### 既存のデータ、もしくは一から始める +Budibaseはいろんなツールから既存のデータを使用できます。たとえばMongoDB、CouchDB、 PostgreSQL、MySQL、Airtable、S3、DynamoDB、REST APIなど。ほかのプラットフォームにない特徴として、Budibaseはデータなしの状態でビジネスアプリケーションの構築を一から始めることができます。 [新しいデータリソースをリクエスト](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas)。 + +

+ Budibase data +

+

+ +### パワフルな内蔵コンポーネントでアプリケーションを設計し構築 + +Budibaseには、美しくデザインされた強力なコンポーネントが付属しており、それら使用しUIを簡単に構築することができます。また、CSSによるスタイリングオプションも豊富に用意されているので、よりクリエイティブな表現もも可能です。 + [Request new component](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas)。 + +

+ Budibase design +

+

+ +### プロセスを自動化し、ほかのツールと連携し、Webhookをでつながる! +定型化した作業を自動化して時間を節約しましょう。Webhookに接続、Eメールの自動送信など、すべてBudibaseに任せましょう。 こちらで簡単に [新しいオートメーションを作る](https://github.com/Budibase/automations)または[新しいオートメーションをリクエストすることができます](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas)。 + +

+ Budibase automations +

+

+ +### 使い親しんだツールとの統合 +Budibaseは多くの人気ツールと統合されており、あなたのニーズに合わせたパーフェクトなアプリケーションを構築することができます。 + +

+ Budibase integrations +

+

+ +### 管理者のパラダイス +Budibaseはどんな規模のプロジェクトにも柔軟に対応できます。Budibaseを使えば、個人または組織のサーバーでセルフホスティングし、ユーザー、オンボーディング、SMTP、アプリ、グループ、テーマなどをひとまとめに管理することが可能です。また、ユーザーやグループにアプリポータルを提供し、グループ管理者にユーザー管理を委ねることも可能です。 +- プロモーションビデオを視聴する: https://youtu.be/xoljVpty_Kw + +


+ +## 🏁 始めましょう + + + +Docker、KubernetesもしくはDegital Oceanを使用しセルフホスティングするか、セルフホスティングに困難がある、もしくは今すぐ開始したい場合はBudibase Cloudを使用しすぐに始めましょう。 + +### [Budibaseをセルフホスティングする](https://docs.budibase.com/self-hosting/self-host) + +### [Budibase Cloudを使用する](https://budibase.com) + + +

+ +## 🎓 Budibaseを学ぶ + +Budibaseのドキュメント[はここです](https://docs.budibase.com)。 +
+ + +

+ +## 💬 コミュニティ + +もし何か問題がある、もしくはBudibaseコミュニティのほかのユーザーと交流したいのであれば私たちの[Github discussions](https://github.com/Budibase/budibase/discussions)までお越しください。 + +


+ + +## ❗ 行動規範 + +Budibase は、すべての人を歓迎し、多様で、ハラスメントのない環境を提供することに尽力しています。Budibase コミュニティに参加するすべての人たちが私たちの[**行動規範**](https://github.com/Budibase/budibase/blob/HEAD/.github/CODE_OF_CONDUCT.md)を遵守していただくことお願いします。必ず読んでください。 +
+ + +

+ + +## 🙌 Budibaseにコントリビュート + + +バグレポートからプルリクエストの作成まで、すべての貢献は感謝、そして歓迎されております。新しい新機能の実装やAPIの変更を計画している場合は、まずIssueを作成してください。これであなたの貴重な考えは私たちにも伝わり、無駄とはなりません。 + +### どこから始めるか混乱していますか? +ここはコントリビュートをはじめるための最適な場所です! [First time issues project](https://github.com/Budibase/budibase/projects/22). + +### リポジトリの構成 +Budibaseは、lernaによってmonorepo方式で管理されています。budibase パッケージのビルドと公開はlernaによって管理されています。Budibaseを構成するパッケージは以下の通り: + +- [packages/builder](https://github.com/Budibase/budibase/tree/HEAD/packages/builder) - budibase builder クライアントサイドのsvelteアプリケーションのコードが含まれています。 + +- [packages/client](https://github.com/Budibase/budibase/tree/HEAD/packages/client) - ブラウザ上で動作するモジュールで、JSONの定義を読み取り、そこから"生きている"Webアプリケーションを作成します。 + +- [packages/server](https://github.com/Budibase/budibase/tree/HEAD/packages/server) - budibaseのサーバーです。この Koa アプリは、builder アプリと budibase アプリの JS を提供し、データベースとファイル システムと対話するための API を提供する役割を担っています。 + +詳しくは[CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md)をご覧ください。 + +

+ + +## 📝 ライセンス + +Budibase はオープンソースであり、[GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html)ライセンスの下に公開されています。クライアントとコンポーネントライブラリは [MPL](https://directory.fsf.org/wiki/License:MPL-2.0)で公開されています - ですから、あなたが制作したアプリケーションはどのようなライセンスでも公開することができます。 + +

+ +## ⭐ スター数の履歴 + +[![Stargazers over time](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase) + +ビルダーのアップデートの間に問題が発生する場合は[ここ](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md#troubleshooting)を参考に環境をクリアにしてください。 + +

+ +## Contributors ✨ + +すばらしい皆さまに感謝しかありません。([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + + + + + + + + + + + + + + + +

Martin McKeaveney

💻 📖 ⚠️ 🚇

Michael Drury

📖 💻 ⚠️ 🚇

Andrew Kingston

📖 💻 ⚠️ 🎨

Michael Shanks

📖 💻 ⚠️

Kevin Åberg Kultalahti

📖 💻 ⚠️

Joe

📖 💻 🖋 🎨

Rory Powell

💻 📖 ⚠️

Peter Clement

💻 📖 ⚠️

Conor_Mack

💻 ⚠️

pngwn

💻 ⚠️

HugoLd

💻

victoriasloan

💻

yashank09

💻

SOVLOOKUP

💻

seoulaja

🌍

Maurits Lourens

⚠️ 💻
+ + + + + + +このプロジェクトは、[all-contributors](https://github.com/all-contributors/all-contributors)仕様に準拠しています。どのような貢献でも歓迎します。 + diff --git a/index.yaml b/index.yaml new file mode 100644 index 0000000000..4b00ff2f03 --- /dev/null +++ b/index.yaml @@ -0,0 +1,3 @@ +apiVersion: v1 +entries: {} +generated: "2021-12-13T12:46:40.291206+01:00" diff --git a/lerna.json b/lerna.json index fa174a2714..f7afef9948 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.190-alpha.3", + "version": "1.0.76-alpha.3", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/package.json b/package.json index f863f3f440..5111ad204f 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "eslint-plugin-cypress": "^2.11.3", "eslint-plugin-svelte3": "^3.2.0", "husky": "^7.0.1", + "js-yaml": "^4.1.0", "kill-port": "^1.6.1", "lerna": "3.14.1", "prettier": "^2.3.1", @@ -22,8 +23,8 @@ "build": "lerna run build", "publishdev": "lerna run publishdev", "publishnpm": "yarn build && lerna publish --force-publish", - "release": "yarn build && lerna publish patch --yes --force-publish", - "release:develop": "yarn build && lerna publish prerelease --yes --force-publish --dist-tag develop", + "release": "lerna publish patch --yes --force-publish", + "release:develop": "lerna publish prerelease --yes --force-publish --dist-tag develop", "restore": "yarn run clean && yarn run bootstrap && yarn run build", "nuke": "yarn run nuke:packages && yarn run nuke:docker", "nuke:packages": "yarn run restore", @@ -35,19 +36,25 @@ "dev:server": "lerna run --parallel dev:builder --concurrency 1 --scope @budibase/worker --scope @budibase/server", "test": "lerna run test", "lint:eslint": "eslint packages", - "lint:prettier": "prettier --check \"packages/**/*.{js,svelte}\"", + "lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\"", "lint": "yarn run lint:eslint && yarn run lint:prettier", "lint:fix:eslint": "eslint --fix packages", - "lint:fix:prettier": "prettier --write \"packages/**/*.{js,svelte}\"", + "lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\"", "lint:fix:ts": "lerna run lint:fix", "lint:fix": "yarn run lint:fix:ts && yarn run lint:fix:prettier && yarn run lint:fix:eslint", "test:e2e": "lerna run cy:test", "test:e2e:ci": "lerna run cy:ci", - "build:docker": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -", + "build:docker": "lerna run build:docker && npm run build:docker:proxy:compose && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -", + "build:docker:proxy": "docker build hosting/proxy -t proxy-service", + "build:docker:proxy:compose": "lerna run generate:proxy:compose && npm run build:docker:proxy", + "build:docker:proxy:preprod": "lerna run generate:proxy:preprod && npm run build:docker:proxy", + "build:docker:proxy:prod": "lerna run generate:proxy:prod && npm run build:docker:proxy", "build:docker:selfhost": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh latest && cd -", - "build:docker:develop": "node scripts/pinVersions && lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -", + "build:docker:develop": "node scripts/pinVersions && lerna run build:docker && npm run build:docker:proxy:compose && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -", "build:docker:airgap": "node hosting/scripts/airgapped/airgappedDockerBuild", - "release:helm": "./scripts/release_helm_chart.sh", + "build:digitalocean": "cd hosting/digitalocean && ./build.sh && cd -", + "build:docs": "lerna run build:docs", + "release:helm": "node scripts/releaseHelmChart", "env:multi:enable": "lerna run env:multi:enable", "env:multi:disable": "lerna run env:multi:disable", "env:selfhost:enable": "lerna run env:selfhost:enable", diff --git a/packages/auth/deprovision.js b/packages/auth/deprovision.js deleted file mode 100644 index b4b8dc6110..0000000000 --- a/packages/auth/deprovision.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("./src/tenancy/deprovision") diff --git a/packages/auth/src/migrations/index.js b/packages/auth/src/migrations/index.js deleted file mode 100644 index 7492e94511..0000000000 --- a/packages/auth/src/migrations/index.js +++ /dev/null @@ -1,61 +0,0 @@ -const { DocumentTypes } = require("../db/constants") -const { getGlobalDB } = require("../tenancy") - -exports.MIGRATION_DBS = { - GLOBAL_DB: "GLOBAL_DB", -} - -exports.MIGRATIONS = { - USER_EMAIL_VIEW_CASING: "user_email_view_casing", -} - -const DB_LOOKUP = { - [exports.MIGRATION_DBS.GLOBAL_DB]: [ - exports.MIGRATIONS.USER_EMAIL_VIEW_CASING, - ], -} - -exports.getMigrationsDoc = async db => { - // get the migrations doc - try { - return await db.get(DocumentTypes.MIGRATIONS) - } catch (err) { - if (err.status && err.status === 404) { - return { _id: DocumentTypes.MIGRATIONS } - } - } -} - -exports.migrateIfRequired = async (migrationDb, migrationName, migrateFn) => { - try { - let db - if (migrationDb === exports.MIGRATION_DBS.GLOBAL_DB) { - db = getGlobalDB() - } else { - throw new Error(`Unrecognised migration db [${migrationDb}]`) - } - - if (!DB_LOOKUP[migrationDb].includes(migrationName)) { - throw new Error( - `Unrecognised migration name [${migrationName}] for db [${migrationDb}]` - ) - } - - const doc = await exports.getMigrationsDoc(db) - // exit if the migration has been performed - if (doc[migrationName]) { - return - } - - console.log(`Performing migration: ${migrationName}`) - await migrateFn() - console.log(`Migration complete: ${migrationName}`) - - // mark as complete - doc[migrationName] = Date.now() - await db.put(doc) - } catch (err) { - console.error(`Error performing migration: ${migrationName}: `, err) - throw err - } -} diff --git a/packages/auth/src/migrations/tests/index.spec.js b/packages/auth/src/migrations/tests/index.spec.js deleted file mode 100644 index 0ed16fc184..0000000000 --- a/packages/auth/src/migrations/tests/index.spec.js +++ /dev/null @@ -1,60 +0,0 @@ -require("../../tests/utilities/dbConfig") - -const { migrateIfRequired, MIGRATION_DBS, MIGRATIONS, getMigrationsDoc } = require("../index") -const database = require("../../db") -const { - StaticDatabases, -} = require("../../db/utils") - -Date.now = jest.fn(() => 1487076708000) -let db - -describe("migrations", () => { - - const migrationFunction = jest.fn() - - beforeEach(() => { - db = database.getDB(StaticDatabases.GLOBAL.name) - }) - - afterEach(async () => { - jest.clearAllMocks() - await db.destroy() - }) - - const validMigration = () => { - return migrateIfRequired(MIGRATION_DBS.GLOBAL_DB, MIGRATIONS.USER_EMAIL_VIEW_CASING, migrationFunction) - } - - it("should run a new migration", async () => { - await validMigration() - expect(migrationFunction).toHaveBeenCalled() - }) - - it("should match snapshot", async () => { - await validMigration() - const doc = await getMigrationsDoc(db) - expect(doc).toMatchSnapshot() - }) - - it("should skip a previously run migration", async () => { - await validMigration() - await validMigration() - expect(migrationFunction).toHaveBeenCalledTimes(1) - }) - - it("should reject an unknown migration name", async () => { - expect(async () => { - await migrateIfRequired(MIGRATION_DBS.GLOBAL_DB, "bogus_name", migrationFunction) - }).rejects.toThrow() - expect(migrationFunction).not.toHaveBeenCalled() - }) - - it("should reject an unknown database name", async () => { - expect(async () => { - await migrateIfRequired("bogus_db", MIGRATIONS.USER_EMAIL_VIEW_CASING, migrationFunction) - }).rejects.toThrow() - expect(migrationFunction).not.toHaveBeenCalled() - }) - -}) \ No newline at end of file diff --git a/packages/auth/src/tenancy/context.js b/packages/auth/src/tenancy/context.js deleted file mode 100644 index 01d1fdc604..0000000000 --- a/packages/auth/src/tenancy/context.js +++ /dev/null @@ -1,84 +0,0 @@ -const env = require("../environment") -const { Headers } = require("../../constants") -const cls = require("./FunctionContext") - -exports.DEFAULT_TENANT_ID = "default" - -exports.isDefaultTenant = () => { - return exports.getTenantId() === exports.DEFAULT_TENANT_ID -} - -exports.isMultiTenant = () => { - return env.MULTI_TENANCY -} - -const TENANT_ID = "tenantId" - -// used for automations, API endpoints should always be in context already -exports.doInTenant = (tenantId, task) => { - return cls.run(() => { - // set the tenant id - cls.setOnContext(TENANT_ID, tenantId) - - // invoke the task - return task() - }) -} - -exports.updateTenantId = tenantId => { - cls.setOnContext(TENANT_ID, tenantId) -} - -exports.setTenantId = ( - ctx, - opts = { allowQs: false, allowNoTenant: false } -) => { - let tenantId - // exit early if not multi-tenant - if (!exports.isMultiTenant()) { - cls.setOnContext(TENANT_ID, this.DEFAULT_TENANT_ID) - return - } - - const allowQs = opts && opts.allowQs - const allowNoTenant = opts && opts.allowNoTenant - const header = ctx.request.headers[Headers.TENANT_ID] - const user = ctx.user || {} - if (allowQs) { - const query = ctx.request.query || {} - tenantId = query.tenantId - } - // override query string (if allowed) by user, or header - // URL params cannot be used in a middleware, as they are - // processed later in the chain - tenantId = user.tenantId || header || tenantId - - // Set the tenantId from the subdomain - if (!tenantId) { - tenantId = ctx.subdomains && ctx.subdomains[0] - } - - if (!tenantId && !allowNoTenant) { - ctx.throw(403, "Tenant id not set") - } - // check tenant ID just incase no tenant was allowed - if (tenantId) { - cls.setOnContext(TENANT_ID, tenantId) - } -} - -exports.isTenantIdSet = () => { - const tenantId = cls.getFromContext(TENANT_ID) - return !!tenantId -} - -exports.getTenantId = () => { - if (!exports.isMultiTenant()) { - return exports.DEFAULT_TENANT_ID - } - const tenantId = cls.getFromContext(TENANT_ID) - if (!tenantId) { - throw Error("Tenant id not found") - } - return tenantId -} diff --git a/packages/auth/.gitignore b/packages/backend-core/.gitignore similarity index 100% rename from packages/auth/.gitignore rename to packages/backend-core/.gitignore diff --git a/packages/auth/LICENSE b/packages/backend-core/LICENSE similarity index 100% rename from packages/auth/LICENSE rename to packages/backend-core/LICENSE diff --git a/packages/auth/README.md b/packages/backend-core/README.md similarity index 78% rename from packages/auth/README.md rename to packages/backend-core/README.md index 4c6a474b5b..f36f80f84d 100644 --- a/packages/auth/README.md +++ b/packages/backend-core/README.md @@ -4,7 +4,7 @@ This library contains core functionality, like auth and security features which are shared between backend services. #### Note about top level JS files -For the purposes of being able to do say `require("@budibase/auth/permissions")` we need to +For the purposes of being able to do say `require("@budibase/backend-core/permissions")` we need to specify the exports at the top-level of the module. For these files they should be limited to a single `require` of the file that should diff --git a/packages/auth/accounts.js b/packages/backend-core/accounts.js similarity index 100% rename from packages/auth/accounts.js rename to packages/backend-core/accounts.js diff --git a/packages/backend-core/auth.js b/packages/backend-core/auth.js new file mode 100644 index 0000000000..bbfe3d41dd --- /dev/null +++ b/packages/backend-core/auth.js @@ -0,0 +1 @@ +module.exports = require("./src/auth") diff --git a/packages/auth/cache.js b/packages/backend-core/cache.js similarity index 100% rename from packages/auth/cache.js rename to packages/backend-core/cache.js diff --git a/packages/auth/constants.js b/packages/backend-core/constants.js similarity index 100% rename from packages/auth/constants.js rename to packages/backend-core/constants.js diff --git a/packages/backend-core/context.js b/packages/backend-core/context.js new file mode 100644 index 0000000000..4bc100687d --- /dev/null +++ b/packages/backend-core/context.js @@ -0,0 +1,17 @@ +const { + getAppDB, + getDevAppDB, + getProdAppDB, + getAppId, + updateAppId, + doInAppContext, +} = require("./src/context") + +module.exports = { + getAppDB, + getDevAppDB, + getProdAppDB, + getAppId, + updateAppId, + doInAppContext, +} diff --git a/packages/auth/db.js b/packages/backend-core/db.js similarity index 60% rename from packages/auth/db.js rename to packages/backend-core/db.js index a7b38821a7..d2adf6c092 100644 --- a/packages/auth/db.js +++ b/packages/backend-core/db.js @@ -1,4 +1,6 @@ module.exports = { ...require("./src/db/utils"), ...require("./src/db/constants"), + ...require("./src/db"), + ...require("./src/db/views"), } diff --git a/packages/backend-core/deprovision.js b/packages/backend-core/deprovision.js new file mode 100644 index 0000000000..672da214ff --- /dev/null +++ b/packages/backend-core/deprovision.js @@ -0,0 +1 @@ +module.exports = require("./src/context/deprovision") diff --git a/packages/backend-core/middleware.js b/packages/backend-core/middleware.js new file mode 100644 index 0000000000..30fec96239 --- /dev/null +++ b/packages/backend-core/middleware.js @@ -0,0 +1 @@ +module.exports = require("./src/middleware") diff --git a/packages/backend-core/migrations.js b/packages/backend-core/migrations.js new file mode 100644 index 0000000000..2de19ebf65 --- /dev/null +++ b/packages/backend-core/migrations.js @@ -0,0 +1 @@ +module.exports = require("./src/migrations") diff --git a/packages/backend-core/objectStore.js b/packages/backend-core/objectStore.js new file mode 100644 index 0000000000..3ee433f224 --- /dev/null +++ b/packages/backend-core/objectStore.js @@ -0,0 +1,4 @@ +module.exports = { + ...require("./src/objectStore"), + ...require("./src/objectStore/utils"), +} diff --git a/packages/auth/package.json b/packages/backend-core/package.json similarity index 87% rename from packages/auth/package.json rename to packages/backend-core/package.json index 6c75aa38ad..bcacc06ab8 100644 --- a/packages/auth/package.json +++ b/packages/backend-core/package.json @@ -1,7 +1,7 @@ { - "name": "@budibase/auth", - "version": "0.9.190-alpha.3", - "description": "Authentication middlewares for budibase builder and apps", + "name": "@budibase/backend-core", + "version": "1.0.76-alpha.3", + "description": "Budibase backend core libraries used in server and worker", "main": "src/index.js", "author": "Budibase", "license": "GPL-3.0", diff --git a/packages/auth/permissions.js b/packages/backend-core/permissions.js similarity index 100% rename from packages/auth/permissions.js rename to packages/backend-core/permissions.js diff --git a/packages/auth/redis.js b/packages/backend-core/redis.js similarity index 100% rename from packages/auth/redis.js rename to packages/backend-core/redis.js diff --git a/packages/auth/roles.js b/packages/backend-core/roles.js similarity index 100% rename from packages/auth/roles.js rename to packages/backend-core/roles.js diff --git a/packages/auth/scripts/jestSetup.js b/packages/backend-core/scripts/jestSetup.js similarity index 100% rename from packages/auth/scripts/jestSetup.js rename to packages/backend-core/scripts/jestSetup.js diff --git a/packages/auth/sessions.js b/packages/backend-core/sessions.js similarity index 100% rename from packages/auth/sessions.js rename to packages/backend-core/sessions.js diff --git a/packages/auth/src/index.js b/packages/backend-core/src/auth.js similarity index 52% rename from packages/auth/src/index.js rename to packages/backend-core/src/auth.js index 4aa2c8ab96..f6d53522d5 100644 --- a/packages/auth/src/index.js +++ b/packages/backend-core/src/auth.js @@ -1,7 +1,6 @@ const passport = require("koa-passport") const LocalStrategy = require("passport-local").Strategy const JwtStrategy = require("passport-jwt").Strategy -const { StaticDatabases } = require("./db/utils") const { getGlobalDB } = require("./tenancy") const { jwt, @@ -13,9 +12,9 @@ const { tenancy, appTenancy, authError, + csrf, + internalApi, } = require("./middleware") -const { setDB } = require("./db") -const userCache = require("./cache/user") // Strategies passport.use(new LocalStrategy(local.options, local.authenticate)) @@ -36,36 +35,15 @@ passport.deserializeUser(async (user, done) => { }) module.exports = { - init(pouch) { - setDB(pouch) - }, - db: require("./db/utils"), - redis: { - Client: require("./redis"), - utils: require("./redis/utils"), - }, - objectStore: { - ...require("./objectStore"), - ...require("./objectStore/utils"), - }, - utils: { - ...require("./utils"), - ...require("./hashing"), - }, - auth: { - buildAuthMiddleware: authenticated, - passport, - google, - oidc, - jwt: require("jsonwebtoken"), - buildTenancyMiddleware: tenancy, - buildAppTenancyMiddleware: appTenancy, - auditLog, - authError, - }, - cache: { - user: userCache, - }, - StaticDatabases, - constants: require("./constants"), + buildAuthMiddleware: authenticated, + passport, + google, + oidc, + jwt: require("jsonwebtoken"), + buildTenancyMiddleware: tenancy, + buildAppTenancyMiddleware: appTenancy, + auditLog, + authError, + buildCsrfMiddleware: csrf, + internalApi, } diff --git a/packages/auth/src/cache/appMetadata.js b/packages/backend-core/src/cache/appMetadata.js similarity index 100% rename from packages/auth/src/cache/appMetadata.js rename to packages/backend-core/src/cache/appMetadata.js diff --git a/packages/auth/src/cache/user.js b/packages/backend-core/src/cache/user.js similarity index 100% rename from packages/auth/src/cache/user.js rename to packages/backend-core/src/cache/user.js diff --git a/packages/auth/src/cloud/accounts.js b/packages/backend-core/src/cloud/accounts.js similarity index 87% rename from packages/auth/src/cloud/accounts.js rename to packages/backend-core/src/cloud/accounts.js index a02fe60926..b2e8817ad6 100644 --- a/packages/auth/src/cloud/accounts.js +++ b/packages/backend-core/src/cloud/accounts.js @@ -17,7 +17,7 @@ exports.getAccount = async email => { const json = await response.json() if (response.status !== 200) { - throw Error(`Error getting account by email ${email}`, json) + throw new Error(`Error getting account by email ${email}`, json) } return json[0] diff --git a/packages/auth/src/cloud/api.js b/packages/backend-core/src/cloud/api.js similarity index 100% rename from packages/auth/src/cloud/api.js rename to packages/backend-core/src/cloud/api.js diff --git a/packages/auth/src/constants.js b/packages/backend-core/src/constants.js similarity index 83% rename from packages/auth/src/constants.js rename to packages/backend-core/src/constants.js index c7a0c20a41..42450190e5 100644 --- a/packages/auth/src/constants.js +++ b/packages/backend-core/src/constants.js @@ -7,6 +7,7 @@ exports.Cookies = { CurrentApp: "budibase:currentapp", Auth: "budibase:auth", Init: "budibase:init", + DatasourceAuth: "budibase:datasourceauth", OIDC_CONFIG: "budibase:oidc:config", } @@ -17,6 +18,8 @@ exports.Headers = { TYPE: "x-budibase-type", PREVIEW_ROLE: "x-budibase-role", TENANT_ID: "x-budibase-tenant-id", + TOKEN: "x-budibase-token", + CSRF_TOKEN: "x-csrf-token", } exports.GlobalRoles = { @@ -35,4 +38,5 @@ exports.Configs = { OIDC_LOGOS: "logos_oidc", } +exports.MAX_VALID_DATE = new Date(2147483647000) exports.DEFAULT_TENANT_ID = "default" diff --git a/packages/auth/src/tenancy/FunctionContext.js b/packages/backend-core/src/context/FunctionContext.js similarity index 70% rename from packages/auth/src/tenancy/FunctionContext.js rename to packages/backend-core/src/context/FunctionContext.js index d97a3a30b4..1a3f65056e 100644 --- a/packages/auth/src/tenancy/FunctionContext.js +++ b/packages/backend-core/src/context/FunctionContext.js @@ -4,8 +4,8 @@ const { newid } = require("../hashing") const REQUEST_ID_KEY = "requestId" class FunctionContext { - static getMiddleware(updateCtxFn = null) { - const namespace = this.createNamespace() + static getMiddleware(updateCtxFn = null, contextName = "session") { + const namespace = this.createNamespace(contextName) return async function (ctx, next) { await new Promise( @@ -24,14 +24,14 @@ class FunctionContext { } } - static run(callback) { - const namespace = this.createNamespace() + static run(callback, contextName = "session") { + const namespace = this.createNamespace(contextName) return namespace.runAndReturn(callback) } - static setOnContext(key, value) { - const namespace = this.createNamespace() + static setOnContext(key, value, contextName = "session") { + const namespace = this.createNamespace(contextName) namespace.set(key, value) } @@ -55,16 +55,16 @@ class FunctionContext { } } - static destroyNamespace() { + static destroyNamespace(name = "session") { if (this._namespace) { - cls.destroyNamespace("session") + cls.destroyNamespace(name) this._namespace = null } } - static createNamespace() { + static createNamespace(name = "session") { if (!this._namespace) { - this._namespace = cls.createNamespace("session") + this._namespace = cls.createNamespace(name) } return this._namespace } diff --git a/packages/auth/src/tenancy/deprovision.js b/packages/backend-core/src/context/deprovision.js similarity index 94% rename from packages/auth/src/tenancy/deprovision.js rename to packages/backend-core/src/context/deprovision.js index 608ca1b84a..9f89dbbfa9 100644 --- a/packages/auth/src/tenancy/deprovision.js +++ b/packages/backend-core/src/context/deprovision.js @@ -1,6 +1,6 @@ const { getGlobalUserParams, getAllApps } = require("../db/utils") -const { getDB, getCouch } = require("../db") -const { getGlobalDB } = require("./tenancy") +const { getDB } = require("../db") +const { getGlobalDB } = require("../tenancy") const { StaticDatabases } = require("../db/constants") const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants @@ -79,7 +79,7 @@ const removeGlobalDB = async tenantId => { const removeTenantApps = async tenantId => { try { - const apps = await getAllApps(getCouch(), { all: true }) + const apps = await getAllApps({ all: true }) const destroyPromises = apps.map(app => getDB(app.appId).destroy()) await Promise.allSettled(destroyPromises) } catch (err) { diff --git a/packages/backend-core/src/context/index.js b/packages/backend-core/src/context/index.js new file mode 100644 index 0000000000..968ad4eefb --- /dev/null +++ b/packages/backend-core/src/context/index.js @@ -0,0 +1,195 @@ +const env = require("../environment") +const { Headers } = require("../../constants") +const cls = require("./FunctionContext") +const { getCouch } = require("../db") +const { getProdAppID, getDevelopmentAppID } = require("../db/conversions") +const { isEqual } = require("lodash") + +// some test cases call functions directly, need to +// store an app ID to pretend there is a context +let TEST_APP_ID = null + +const ContextKeys = { + TENANT_ID: "tenantId", + APP_ID: "appId", + // whatever the request app DB was + CURRENT_DB: "currentDb", + // get the prod app DB from the request + PROD_DB: "prodDb", + // get the dev app DB from the request + DEV_DB: "devDb", + DB_OPTS: "dbOpts", +} + +exports.DEFAULT_TENANT_ID = "default" + +exports.isDefaultTenant = () => { + return exports.getTenantId() === exports.DEFAULT_TENANT_ID +} + +exports.isMultiTenant = () => { + return env.MULTI_TENANCY +} + +// used for automations, API endpoints should always be in context already +exports.doInTenant = (tenantId, task) => { + return cls.run(() => { + // set the tenant id + cls.setOnContext(ContextKeys.TENANT_ID, tenantId) + + // invoke the task + return task() + }) +} + +exports.doInAppContext = (appId, task) => { + return cls.run(() => { + // set the app ID + cls.setOnContext(ContextKeys.APP_ID, appId) + + // invoke the task + return task() + }) +} + +exports.updateTenantId = tenantId => { + cls.setOnContext(ContextKeys.TENANT_ID, tenantId) +} + +exports.updateAppId = appId => { + try { + cls.setOnContext(ContextKeys.APP_ID, appId) + cls.setOnContext(ContextKeys.PROD_DB, null) + cls.setOnContext(ContextKeys.DEV_DB, null) + cls.setOnContext(ContextKeys.CURRENT_DB, null) + cls.setOnContext(ContextKeys.DB_OPTS, null) + } catch (err) { + if (env.isTest()) { + TEST_APP_ID = appId + } else { + throw err + } + } +} + +exports.setTenantId = ( + ctx, + opts = { allowQs: false, allowNoTenant: false } +) => { + let tenantId + // exit early if not multi-tenant + if (!exports.isMultiTenant()) { + cls.setOnContext(ContextKeys.TENANT_ID, this.DEFAULT_TENANT_ID) + return + } + + const allowQs = opts && opts.allowQs + const allowNoTenant = opts && opts.allowNoTenant + const header = ctx.request.headers[Headers.TENANT_ID] + const user = ctx.user || {} + if (allowQs) { + const query = ctx.request.query || {} + tenantId = query.tenantId + } + // override query string (if allowed) by user, or header + // URL params cannot be used in a middleware, as they are + // processed later in the chain + tenantId = user.tenantId || header || tenantId + + // Set the tenantId from the subdomain + if (!tenantId) { + tenantId = ctx.subdomains && ctx.subdomains[0] + } + + if (!tenantId && !allowNoTenant) { + ctx.throw(403, "Tenant id not set") + } + // check tenant ID just incase no tenant was allowed + if (tenantId) { + cls.setOnContext(ContextKeys.TENANT_ID, tenantId) + } +} + +exports.isTenantIdSet = () => { + const tenantId = cls.getFromContext(ContextKeys.TENANT_ID) + return !!tenantId +} + +exports.getTenantId = () => { + if (!exports.isMultiTenant()) { + return exports.DEFAULT_TENANT_ID + } + const tenantId = cls.getFromContext(ContextKeys.TENANT_ID) + if (!tenantId) { + throw new Error("Tenant id not found") + } + return tenantId +} + +exports.getAppId = () => { + const foundId = cls.getFromContext(ContextKeys.APP_ID) + if (!foundId && env.isTest() && TEST_APP_ID) { + return TEST_APP_ID + } else { + return foundId + } +} + +function getDB(key, opts) { + const dbOptsKey = `${key}${ContextKeys.DB_OPTS}` + let storedOpts = cls.getFromContext(dbOptsKey) + let db = cls.getFromContext(key) + if (db && isEqual(opts, storedOpts)) { + return db + } + const appId = exports.getAppId() + const CouchDB = getCouch() + let toUseAppId + switch (key) { + case ContextKeys.CURRENT_DB: + toUseAppId = appId + break + case ContextKeys.PROD_DB: + toUseAppId = getProdAppID(appId) + break + case ContextKeys.DEV_DB: + toUseAppId = getDevelopmentAppID(appId) + break + } + db = new CouchDB(toUseAppId, opts) + try { + cls.setOnContext(key, db) + if (opts) { + cls.setOnContext(dbOptsKey, opts) + } + } catch (err) { + if (!env.isTest()) { + throw err + } + } + return db +} + +/** + * Opens the app database based on whatever the request + * contained, dev or prod. + */ +exports.getAppDB = opts => { + return getDB(ContextKeys.CURRENT_DB, opts) +} + +/** + * This specifically gets the prod app ID, if the request + * contained a development app ID, this will open the prod one. + */ +exports.getProdAppDB = opts => { + return getDB(ContextKeys.PROD_DB, opts) +} + +/** + * This specifically gets the dev app ID, if the request + * contained a prod app ID, this will open the dev one. + */ +exports.getDevAppDB = opts => { + return getDB(ContextKeys.DEV_DB, opts) +} diff --git a/packages/auth/src/db/Replication.js b/packages/backend-core/src/db/Replication.js similarity index 100% rename from packages/auth/src/db/Replication.js rename to packages/backend-core/src/db/Replication.js diff --git a/packages/auth/src/db/constants.js b/packages/backend-core/src/db/constants.js similarity index 80% rename from packages/auth/src/db/constants.js rename to packages/backend-core/src/db/constants.js index ecdaae5bad..b41a9a9c08 100644 --- a/packages/auth/src/db/constants.js +++ b/packages/backend-core/src/db/constants.js @@ -21,6 +21,7 @@ exports.StaticDatabases = { name: "global-db", docs: { apiKeys: "apikeys", + usageQuota: "usage_quota", }, }, // contains information about tenancy and so on @@ -28,7 +29,10 @@ exports.StaticDatabases = { name: "global-info", docs: { tenants: "tenants", - usageQuota: "usage_quota", }, }, } + +exports.APP_PREFIX = exports.DocumentTypes.APP + exports.SEPARATOR +exports.APP_DEV = exports.APP_DEV_PREFIX = + exports.DocumentTypes.APP_DEV + exports.SEPARATOR diff --git a/packages/backend-core/src/db/conversions.js b/packages/backend-core/src/db/conversions.js new file mode 100644 index 0000000000..50d896322f --- /dev/null +++ b/packages/backend-core/src/db/conversions.js @@ -0,0 +1,46 @@ +const NO_APP_ERROR = "No app provided" +const { APP_DEV_PREFIX, APP_PREFIX } = require("./constants") + +exports.isDevAppID = appId => { + if (!appId) { + throw NO_APP_ERROR + } + return appId.startsWith(APP_DEV_PREFIX) +} + +exports.isProdAppID = appId => { + if (!appId) { + throw NO_APP_ERROR + } + return appId.startsWith(APP_PREFIX) && !exports.isDevAppID(appId) +} + +exports.isDevApp = app => { + if (!app) { + throw NO_APP_ERROR + } + return exports.isDevAppID(app.appId) +} + +/** + * Convert a development app ID to a deployed app ID. + */ +exports.getProdAppID = appId => { + // if dev, convert it + if (appId.startsWith(APP_DEV_PREFIX)) { + const id = appId.split(APP_DEV_PREFIX)[1] + return `${APP_PREFIX}${id}` + } + return appId +} + +/** + * Convert a deployed app ID to a development app ID. + */ +exports.getDevelopmentAppID = appId => { + if (!appId.startsWith(APP_DEV_PREFIX)) { + const id = appId.split(APP_PREFIX)[1] + return `${APP_DEV_PREFIX}${id}` + } + return appId +} diff --git a/packages/auth/src/db/index.js b/packages/backend-core/src/db/index.js similarity index 100% rename from packages/auth/src/db/index.js rename to packages/backend-core/src/db/index.js diff --git a/packages/auth/src/db/utils.js b/packages/backend-core/src/db/utils.js similarity index 79% rename from packages/auth/src/db/utils.js rename to packages/backend-core/src/db/utils.js index d23d407dab..c4dcb8248b 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/backend-core/src/db/utils.js @@ -2,13 +2,29 @@ const { newid } = require("../hashing") const Replication = require("./Replication") const { DEFAULT_TENANT_ID, Configs } = require("../constants") const env = require("../environment") -const { StaticDatabases, SEPARATOR, DocumentTypes } = require("./constants") -const { getTenantId, getTenantIDFromAppID } = require("../tenancy") +const { + StaticDatabases, + SEPARATOR, + DocumentTypes, + APP_PREFIX, + APP_DEV, +} = require("./constants") +const { + getTenantId, + getTenantIDFromAppID, + getGlobalDBName, +} = require("../tenancy") const fetch = require("node-fetch") const { getCouch } = require("./index") const { getAppMetadata } = require("../cache/appMetadata") - -const NO_APP_ERROR = "No app provided" +const { checkSlashesInUrl } = require("../helpers") +const { + isDevApp, + isProdAppID, + isDevAppID, + getDevelopmentAppID, + getProdAppID, +} = require("./conversions") const UNICODE_MAX = "\ufff0" @@ -19,10 +35,15 @@ exports.ViewNames = { exports.StaticDatabases = StaticDatabases exports.DocumentTypes = DocumentTypes -exports.APP_PREFIX = DocumentTypes.APP + SEPARATOR -exports.APP_DEV = exports.APP_DEV_PREFIX = DocumentTypes.APP_DEV + SEPARATOR +exports.APP_PREFIX = APP_PREFIX +exports.APP_DEV = exports.APP_DEV_PREFIX = APP_DEV exports.SEPARATOR = SEPARATOR exports.getTenantIDFromAppID = getTenantIDFromAppID +exports.isDevApp = isDevApp +exports.isProdAppID = isProdAppID +exports.isDevAppID = isDevAppID +exports.getDevelopmentAppID = getDevelopmentAppID +exports.getProdAppID = getProdAppID /** * If creating DB allDocs/query params with only a single top level ID this can be used, this @@ -47,27 +68,6 @@ function getDocParams(docType, docId = null, otherProps = {}) { } } -exports.isDevAppID = appId => { - if (!appId) { - throw NO_APP_ERROR - } - return appId.startsWith(exports.APP_DEV_PREFIX) -} - -exports.isProdAppID = appId => { - if (!appId) { - throw NO_APP_ERROR - } - return appId.startsWith(exports.APP_PREFIX) && !exports.isDevAppID(appId) -} - -function isDevApp(app) { - if (!app) { - throw NO_APP_ERROR - } - return exports.isDevAppID(app.appId) -} - /** * Generates a new workspace ID. * @returns {string} The new workspace ID which the workspace doc can be stored under. @@ -152,29 +152,6 @@ exports.getRoleParams = (roleId = null, otherProps = {}) => { return getDocParams(DocumentTypes.ROLE, roleId, otherProps) } -/** - * Convert a development app ID to a deployed app ID. - */ -exports.getDeployedAppID = appId => { - // if dev, convert it - if (appId.startsWith(exports.APP_DEV_PREFIX)) { - const id = appId.split(exports.APP_DEV_PREFIX)[1] - return `${exports.APP_PREFIX}${id}` - } - return appId -} - -/** - * Convert a deployed app ID to a development app ID. - */ -exports.getDevelopmentAppID = appId => { - if (!appId.startsWith(exports.APP_DEV_PREFIX)) { - const id = appId.split(exports.APP_PREFIX)[1] - return `${exports.APP_DEV_PREFIX}${id}` - } - return appId -} - exports.getCouchUrl = () => { if (!env.COUCH_DB_URL) return @@ -194,36 +171,70 @@ exports.getCouchUrl = () => { return `${protocol}://${env.COUCH_DB_USERNAME}:${env.COUCH_DB_PASSWORD}@${rest}` } +exports.getStartEndKeyURL = (base, baseKey, tenantId = null) => { + const tenancy = tenantId ? `${SEPARATOR}${tenantId}` : "" + return `${base}?startkey="${baseKey}${tenancy}"&endkey="${baseKey}${tenancy}${UNICODE_MAX}"` +} + /** * if in production this will use the CouchDB _all_dbs call to retrieve a list of databases. If testing * when using Pouch it will use the pouchdb-all-dbs package. + * opts.efficient can be provided to make sure this call is always quick in a multi-tenant environment, + * but it may not be 100% accurate in full efficiency mode (some tenantless apps may be missed). */ -exports.getAllDbs = async () => { +exports.getAllDbs = async (opts = { efficient: false }) => { + const efficient = opts && opts.efficient // specifically for testing we use the pouch package for this if (env.isTest()) { return getCouch().allDbs() } - const response = await fetch(`${exports.getCouchUrl()}/_all_dbs`) - if (response.status === 200) { - return response.json() - } else { - throw "Cannot connect to CouchDB instance" + let dbs = [] + async function addDbs(url) { + const response = await fetch(checkSlashesInUrl(encodeURI(url))) + if (response.status === 200) { + let json = await response.json() + dbs = dbs.concat(json) + } else { + throw "Cannot connect to CouchDB instance" + } } + let couchUrl = `${exports.getCouchUrl()}/_all_dbs` + let tenantId = getTenantId() + if (!env.MULTI_TENANCY || (!efficient && tenantId === DEFAULT_TENANT_ID)) { + // just get all DBs when: + // - single tenancy + // - default tenant + // - apps dbs don't contain tenant id + // - non-default tenant dbs are filtered out application side in getAllApps + await addDbs(couchUrl) + } else { + // get prod apps + await addDbs( + exports.getStartEndKeyURL(couchUrl, DocumentTypes.APP, tenantId) + ) + // get dev apps + await addDbs( + exports.getStartEndKeyURL(couchUrl, DocumentTypes.APP_DEV, tenantId) + ) + // add global db name + dbs.push(getGlobalDBName(tenantId)) + } + return dbs } /** * Lots of different points in the system need to find the full list of apps, this will * enumerate the entire CouchDB cluster and get the list of databases (every app). - * NOTE: this operation is fine in self hosting, but cannot be used when hosting many - * different users/companies apps as there is no security around it - all apps are returned. + * * @return {Promise} returns the app information document stored in each app database. */ -exports.getAllApps = async (CouchDB, { dev, all, idsOnly } = {}) => { +exports.getAllApps = async ({ dev, all, idsOnly, efficient } = {}) => { + const CouchDB = getCouch() let tenantId = getTenantId() if (!env.MULTI_TENANCY && !tenantId) { tenantId = DEFAULT_TENANT_ID } - let dbs = await exports.getAllDbs() + let dbs = await exports.getAllDbs({ efficient }) const appDbNames = dbs.filter(dbName => { const split = dbName.split(SEPARATOR) // it is an app, check the tenantId @@ -274,8 +285,8 @@ exports.getAllApps = async (CouchDB, { dev, all, idsOnly } = {}) => { /** * Utility function for getAllApps but filters to production apps only. */ -exports.getDeployedAppIDs = async CouchDB => { - return (await exports.getAllApps(CouchDB, { idsOnly: true })).filter( +exports.getProdAppIDs = async () => { + return (await exports.getAllApps({ idsOnly: true })).filter( id => !exports.isDevAppID(id) ) } @@ -283,13 +294,14 @@ exports.getDeployedAppIDs = async CouchDB => { /** * Utility function for the inverse of above. */ -exports.getDevAppIDs = async CouchDB => { - return (await exports.getAllApps(CouchDB, { idsOnly: true })).filter(id => +exports.getDevAppIDs = async () => { + return (await exports.getAllApps({ idsOnly: true })).filter(id => exports.isDevAppID(id) ) } -exports.dbExists = async (CouchDB, dbName) => { +exports.dbExists = async dbName => { + const CouchDB = getCouch() let exists = false try { const db = CouchDB(dbName, { skip_setup: true }) @@ -389,7 +401,7 @@ const getScopedFullConfig = async function (db, { type, user, workspace }) { } const getPlatformUrl = async settings => { - let platformUrl = env.PLATFORM_URL + let platformUrl = env.PLATFORM_URL || "http://localhost:10000" if (!env.SELF_HOSTED && env.MULTI_TENANCY) { // cloud and multi tenant - add the tenant to the default platform url @@ -404,7 +416,7 @@ const getPlatformUrl = async settings => { } } - return platformUrl ? platformUrl : "http://localhost:10000" + return platformUrl } async function getScopedConfig(db, params) { @@ -414,7 +426,7 @@ async function getScopedConfig(db, params) { function generateNewUsageQuotaDoc() { return { - _id: StaticDatabases.PLATFORM_INFO.docs.usageQuota, + _id: StaticDatabases.GLOBAL.docs.usageQuota, quotaReset: Date.now() + 2592000000, usageQuota: { automationRuns: 0, diff --git a/packages/auth/src/db/views.js b/packages/backend-core/src/db/views.js similarity index 100% rename from packages/auth/src/db/views.js rename to packages/backend-core/src/db/views.js diff --git a/packages/auth/src/environment.js b/packages/backend-core/src/environment.js similarity index 91% rename from packages/auth/src/environment.js rename to packages/backend-core/src/environment.js index c26ad1c199..d112ad8599 100644 --- a/packages/auth/src/environment.js +++ b/packages/backend-core/src/environment.js @@ -11,6 +11,8 @@ module.exports = { COUCH_DB_URL: process.env.COUCH_DB_URL, COUCH_DB_USERNAME: process.env.COUCH_DB_USER, COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD, + GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, + GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET, SALT_ROUNDS: process.env.SALT_ROUNDS, REDIS_URL: process.env.REDIS_URL, REDIS_PASSWORD: process.env.REDIS_PASSWORD, diff --git a/packages/auth/src/hashing.js b/packages/backend-core/src/hashing.js similarity index 100% rename from packages/auth/src/hashing.js rename to packages/backend-core/src/hashing.js diff --git a/packages/backend-core/src/helpers.js b/packages/backend-core/src/helpers.js new file mode 100644 index 0000000000..b402a82cf3 --- /dev/null +++ b/packages/backend-core/src/helpers.js @@ -0,0 +1,9 @@ +/** + * Makes sure that a URL has the correct number of slashes, while maintaining the + * http(s):// double slashes. + * @param {string} url The URL to test and remove any extra double slashes. + * @return {string} The updated url. + */ +exports.checkSlashesInUrl = url => { + return url.replace(/(https?:\/\/)|(\/)+/g, "$1$2") +} diff --git a/packages/backend-core/src/index.js b/packages/backend-core/src/index.js new file mode 100644 index 0000000000..b0bc524d9b --- /dev/null +++ b/packages/backend-core/src/index.js @@ -0,0 +1,18 @@ +const { setDB } = require("./db") + +module.exports = { + init(pouch) { + setDB(pouch) + }, + // some default exports from the library, however these ideally shouldn't + // be used, instead the syntax require("@budibase/backend-core/db") should be used + StaticDatabases: require("./db/utils").StaticDatabases, + db: require("../db"), + redis: require("../redis"), + objectStore: require("../objectStore"), + utils: require("../utils"), + cache: require("../cache"), + auth: require("../auth"), + constants: require("../constants"), + migrations: require("../migrations"), +} diff --git a/packages/auth/src/middleware/appTenancy.js b/packages/backend-core/src/middleware/appTenancy.js similarity index 87% rename from packages/auth/src/middleware/appTenancy.js rename to packages/backend-core/src/middleware/appTenancy.js index 30fc4f7453..b0430a0051 100644 --- a/packages/auth/src/middleware/appTenancy.js +++ b/packages/backend-core/src/middleware/appTenancy.js @@ -3,8 +3,9 @@ const { updateTenantId, isTenantIdSet, DEFAULT_TENANT_ID, + updateAppId, } = require("../tenancy") -const ContextFactory = require("../tenancy/FunctionContext") +const ContextFactory = require("../context/FunctionContext") const { getTenantIDFromAppID } = require("../db/utils") module.exports = () => { @@ -21,5 +22,6 @@ module.exports = () => { const appId = ctx.appId ? ctx.appId : ctx.user ? ctx.user.appId : null const tenantId = getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID updateTenantId(tenantId) + updateAppId(appId) }) } diff --git a/packages/auth/src/middleware/auditLog.js b/packages/backend-core/src/middleware/auditLog.js similarity index 100% rename from packages/auth/src/middleware/auditLog.js rename to packages/backend-core/src/middleware/auditLog.js diff --git a/packages/auth/src/middleware/authenticated.js b/packages/backend-core/src/middleware/authenticated.js similarity index 91% rename from packages/auth/src/middleware/authenticated.js rename to packages/backend-core/src/middleware/authenticated.js index f0fb6e21c5..4978f7b9dc 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/backend-core/src/middleware/authenticated.js @@ -1,5 +1,5 @@ const { Cookies, Headers } = require("../constants") -const { getCookie, clearCookie } = require("../utils") +const { getCookie, clearCookie, openJwt } = require("../utils") const { getUser } = require("../cache/user") const { getSession, updateSessionTTL } = require("../security/sessions") const { buildMatcherRegex, matches } = require("./matchers") @@ -35,8 +35,9 @@ module.exports = ( publicEndpoint = true } try { - // check the actual user is authenticated first - const authCookie = getCookie(ctx, Cookies.Auth) + // check the actual user is authenticated first, try header or cookie + const headerToken = ctx.request.headers[Headers.TOKEN] + const authCookie = getCookie(ctx, Cookies.Auth) || openJwt(headerToken) let authenticated = false, user = null, internal = false @@ -59,6 +60,7 @@ module.exports = ( } else { user = await getUser(userId, session.tenantId) } + user.csrfToken = session.csrfToken delete user.password authenticated = true } catch (err) { diff --git a/packages/backend-core/src/middleware/csrf.js b/packages/backend-core/src/middleware/csrf.js new file mode 100644 index 0000000000..12bd9473e6 --- /dev/null +++ b/packages/backend-core/src/middleware/csrf.js @@ -0,0 +1,78 @@ +const { Headers } = require("../constants") +const { buildMatcherRegex, matches } = require("./matchers") + +/** + * GET, HEAD and OPTIONS methods are considered safe operations + * + * POST, PUT, PATCH, and DELETE methods, being state changing verbs, + * should have a CSRF token attached to the request + */ +const EXCLUDED_METHODS = ["GET", "HEAD", "OPTIONS"] + +/** + * There are only three content type values that can be used in cross domain requests. + * If any other value is used, e.g. application/json, the browser will first make a OPTIONS + * request which will be protected by CORS. + */ +const INCLUDED_CONTENT_TYPES = [ + "application/x-www-form-urlencoded", + "multipart/form-data", + "text/plain", +] + +/** + * Validate the CSRF token generated aganst the user session. + * Compare the token with the x-csrf-token header. + * + * If the token is not found within the request or the value provided + * does not match the value within the user session, the request is rejected. + * + * CSRF protection provided using the 'Synchronizer Token Pattern' + * https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern + * + */ +module.exports = (opts = { noCsrfPatterns: [] }) => { + const noCsrfOptions = buildMatcherRegex(opts.noCsrfPatterns) + return async (ctx, next) => { + // don't apply for excluded paths + const found = matches(ctx, noCsrfOptions) + if (found) { + return next() + } + + // don't apply for the excluded http methods + if (EXCLUDED_METHODS.indexOf(ctx.method) !== -1) { + return next() + } + + // don't apply when the content type isn't supported + let contentType = ctx.get("content-type") + ? ctx.get("content-type").toLowerCase() + : "" + if ( + !INCLUDED_CONTENT_TYPES.filter(type => contentType.includes(type)).length + ) { + return next() + } + + // don't apply csrf when the internal api key has been used + if (ctx.internal) { + return next() + } + + // apply csrf when there is a token in the session (new logins) + // in future there should be a hard requirement that the token is present + const userToken = ctx.user.csrfToken + if (!userToken) { + return next() + } + + // reject if no token in request or mismatch + const requestToken = ctx.get(Headers.CSRF_TOKEN) + if (!requestToken || requestToken !== userToken) { + ctx.throw(403, "Invalid CSRF token") + } + + return next() + } +} diff --git a/packages/auth/src/middleware/index.js b/packages/backend-core/src/middleware/index.js similarity index 70% rename from packages/auth/src/middleware/index.js rename to packages/backend-core/src/middleware/index.js index cf8676a2bc..5878479152 100644 --- a/packages/auth/src/middleware/index.js +++ b/packages/backend-core/src/middleware/index.js @@ -7,6 +7,9 @@ const authenticated = require("./authenticated") const auditLog = require("./auditLog") const tenancy = require("./tenancy") const appTenancy = require("./appTenancy") +const internalApi = require("./internalApi") +const datasourceGoogle = require("./passport/datasource/google") +const csrf = require("./csrf") module.exports = { google, @@ -18,4 +21,9 @@ module.exports = { tenancy, appTenancy, authError, + internalApi, + datasource: { + google: datasourceGoogle, + }, + csrf, } diff --git a/packages/backend-core/src/middleware/internalApi.js b/packages/backend-core/src/middleware/internalApi.js new file mode 100644 index 0000000000..275d559a9e --- /dev/null +++ b/packages/backend-core/src/middleware/internalApi.js @@ -0,0 +1,14 @@ +const env = require("../environment") +const { Headers } = require("../constants") + +/** + * API Key only endpoint. + */ +module.exports = async (ctx, next) => { + const apiKey = ctx.request.headers[Headers.API_KEY] + if (apiKey !== env.INTERNAL_API_KEY) { + ctx.throw(403, "Unauthorized") + } + + return next() +} diff --git a/packages/auth/src/middleware/matchers.js b/packages/backend-core/src/middleware/matchers.js similarity index 100% rename from packages/auth/src/middleware/matchers.js rename to packages/backend-core/src/middleware/matchers.js diff --git a/packages/backend-core/src/middleware/passport/datasource/google.js b/packages/backend-core/src/middleware/passport/datasource/google.js new file mode 100644 index 0000000000..c7553cee50 --- /dev/null +++ b/packages/backend-core/src/middleware/passport/datasource/google.js @@ -0,0 +1,66 @@ +const google = require("../google") +const { Cookies } = require("../../../constants") +const { clearCookie, getCookie } = require("../../../utils") +const { getDB } = require("../../../db") +const environment = require("../../../environment") + +async function preAuth(passport, ctx, next) { + // get the relevant config + const googleConfig = { + clientID: environment.GOOGLE_CLIENT_ID, + clientSecret: environment.GOOGLE_CLIENT_SECRET, + } + let callbackUrl = `${environment.PLATFORM_URL}/api/global/auth/datasource/google/callback` + const strategy = await google.strategyFactory(googleConfig, callbackUrl) + + if (!ctx.query.appId || !ctx.query.datasourceId) { + ctx.throw(400, "appId and datasourceId query params not present.") + } + + return passport.authenticate(strategy, { + scope: ["profile", "email", "https://www.googleapis.com/auth/spreadsheets"], + accessType: "offline", + prompt: "consent", + })(ctx, next) +} + +async function postAuth(passport, ctx, next) { + // get the relevant config + const config = { + clientID: environment.GOOGLE_CLIENT_ID, + clientSecret: environment.GOOGLE_CLIENT_SECRET, + } + + let callbackUrl = `${environment.PLATFORM_URL}/api/global/auth/datasource/google/callback` + const strategy = await google.strategyFactory( + config, + callbackUrl, + (accessToken, refreshToken, profile, done) => { + clearCookie(ctx, Cookies.DatasourceAuth) + done(null, { accessToken, refreshToken }) + } + ) + + const authStateCookie = getCookie(ctx, Cookies.DatasourceAuth) + + return passport.authenticate( + strategy, + { successRedirect: "/", failureRedirect: "/error" }, + async (err, tokens) => { + // update the DB for the datasource with all the user info + const db = getDB(authStateCookie.appId) + const datasource = await db.get(authStateCookie.datasourceId) + if (!datasource.config) { + datasource.config = {} + } + datasource.config.auth = { type: "google", ...tokens } + await db.put(datasource) + ctx.redirect( + `/builder/app/${authStateCookie.appId}/data/datasource/${authStateCookie.datasourceId}` + ) + } + )(ctx, next) +} + +exports.preAuth = preAuth +exports.postAuth = postAuth diff --git a/packages/auth/src/middleware/passport/google.js b/packages/backend-core/src/middleware/passport/google.js similarity index 100% rename from packages/auth/src/middleware/passport/google.js rename to packages/backend-core/src/middleware/passport/google.js diff --git a/packages/auth/src/middleware/passport/jwt.js b/packages/backend-core/src/middleware/passport/jwt.js similarity index 100% rename from packages/auth/src/middleware/passport/jwt.js rename to packages/backend-core/src/middleware/passport/jwt.js diff --git a/packages/auth/src/middleware/passport/local.js b/packages/backend-core/src/middleware/passport/local.js similarity index 98% rename from packages/auth/src/middleware/passport/local.js rename to packages/backend-core/src/middleware/passport/local.js index f95c3a173e..2149bd3e18 100644 --- a/packages/auth/src/middleware/passport/local.js +++ b/packages/backend-core/src/middleware/passport/local.js @@ -8,7 +8,7 @@ const { newid } = require("../../hashing") const { createASession } = require("../../security/sessions") const { getTenantId } = require("../../tenancy") -const INVALID_ERR = "Invalid Credentials" +const INVALID_ERR = "Invalid credentials" const SSO_NO_PASSWORD = "SSO user does not have a password set" const EXPIRED = "This account has expired. Please reset your password" diff --git a/packages/auth/src/middleware/passport/oidc.js b/packages/backend-core/src/middleware/passport/oidc.js similarity index 100% rename from packages/auth/src/middleware/passport/oidc.js rename to packages/backend-core/src/middleware/passport/oidc.js diff --git a/packages/auth/src/middleware/passport/tests/google.spec.js b/packages/backend-core/src/middleware/passport/tests/google.spec.js similarity index 100% rename from packages/auth/src/middleware/passport/tests/google.spec.js rename to packages/backend-core/src/middleware/passport/tests/google.spec.js diff --git a/packages/auth/src/middleware/passport/tests/oidc.spec.js b/packages/backend-core/src/middleware/passport/tests/oidc.spec.js similarity index 100% rename from packages/auth/src/middleware/passport/tests/oidc.spec.js rename to packages/backend-core/src/middleware/passport/tests/oidc.spec.js diff --git a/packages/auth/src/middleware/passport/tests/third-party-common.spec.js b/packages/backend-core/src/middleware/passport/tests/third-party-common.spec.js similarity index 100% rename from packages/auth/src/middleware/passport/tests/third-party-common.spec.js rename to packages/backend-core/src/middleware/passport/tests/third-party-common.spec.js diff --git a/packages/auth/src/middleware/passport/tests/utilities/mock-data.js b/packages/backend-core/src/middleware/passport/tests/utilities/mock-data.js similarity index 100% rename from packages/auth/src/middleware/passport/tests/utilities/mock-data.js rename to packages/backend-core/src/middleware/passport/tests/utilities/mock-data.js diff --git a/packages/auth/src/middleware/passport/third-party-common.js b/packages/backend-core/src/middleware/passport/third-party-common.js similarity index 100% rename from packages/auth/src/middleware/passport/third-party-common.js rename to packages/backend-core/src/middleware/passport/third-party-common.js diff --git a/packages/auth/src/middleware/passport/utils.js b/packages/backend-core/src/middleware/passport/utils.js similarity index 100% rename from packages/auth/src/middleware/passport/utils.js rename to packages/backend-core/src/middleware/passport/utils.js diff --git a/packages/auth/src/middleware/tenancy.js b/packages/backend-core/src/middleware/tenancy.js similarity index 90% rename from packages/auth/src/middleware/tenancy.js rename to packages/backend-core/src/middleware/tenancy.js index adfd36a503..5bb81f8824 100644 --- a/packages/auth/src/middleware/tenancy.js +++ b/packages/backend-core/src/middleware/tenancy.js @@ -1,5 +1,5 @@ const { setTenantId } = require("../tenancy") -const ContextFactory = require("../tenancy/FunctionContext") +const ContextFactory = require("../context/FunctionContext") const { buildMatcherRegex, matches } = require("./matchers") module.exports = ( diff --git a/packages/backend-core/src/migrations/index.js b/packages/backend-core/src/migrations/index.js new file mode 100644 index 0000000000..db0fe6b8ce --- /dev/null +++ b/packages/backend-core/src/migrations/index.js @@ -0,0 +1,117 @@ +const { DEFAULT_TENANT_ID } = require("../constants") +const { DocumentTypes } = require("../db/constants") +const { getAllApps } = require("../db/utils") +const environment = require("../environment") +const { + doInTenant, + getTenantIds, + getGlobalDBName, + getTenantId, +} = require("../tenancy") + +exports.MIGRATION_TYPES = { + GLOBAL: "global", // run once, recorded in global db, global db is provided as an argument + APP: "app", // run per app, recorded in each app db, app db is provided as an argument +} + +exports.getMigrationsDoc = async db => { + // get the migrations doc + try { + return await db.get(DocumentTypes.MIGRATIONS) + } catch (err) { + if (err.status && err.status === 404) { + return { _id: DocumentTypes.MIGRATIONS } + } + console.error(err) + } +} + +const runMigration = async (CouchDB, migration, options = {}) => { + const tenantId = getTenantId() + const migrationType = migration.type + const migrationName = migration.name + + // get the db to store the migration in + let dbNames + if (migrationType === exports.MIGRATION_TYPES.GLOBAL) { + dbNames = [getGlobalDBName()] + } else if (migrationType === exports.MIGRATION_TYPES.APP) { + const apps = await getAllApps(migration.opts) + dbNames = apps.map(app => app.appId) + } else { + throw new Error( + `[Tenant: ${tenantId}] Unrecognised migration type [${migrationType}]` + ) + } + + // run the migration against each db + for (const dbName of dbNames) { + const db = new CouchDB(dbName) + try { + const doc = await exports.getMigrationsDoc(db) + + // exit if the migration has been performed already + if (doc[migrationName]) { + if ( + options.force && + options.force[migrationType] && + options.force[migrationType].includes(migrationName) + ) { + console.log( + `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Forcing` + ) + } else { + // the migration has already been performed + continue + } + } + + console.log( + `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Running` + ) + // run the migration with tenant context + await migration.fn(db) + console.log( + `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Complete` + ) + + // mark as complete + doc[migrationName] = Date.now() + await db.put(doc) + } catch (err) { + console.error( + `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Error: `, + err + ) + throw err + } + } +} + +exports.runMigrations = async (CouchDB, migrations, options = {}) => { + console.log("Running migrations") + let tenantIds + if (environment.MULTI_TENANCY) { + if (!options.tenantIds || !options.tenantIds.length) { + // run for all tenants + tenantIds = await getTenantIds() + } else { + tenantIds = options.tenantIds + } + } else { + // single tenancy + tenantIds = [DEFAULT_TENANT_ID] + } + + // for all tenants + for (const tenantId of tenantIds) { + // for all migrations + for (const migration of migrations) { + // run the migration + await doInTenant(tenantId, () => + runMigration(CouchDB, migration, options) + ) + } + } + console.log("Migrations complete") +} diff --git a/packages/auth/src/migrations/tests/__snapshots__/index.spec.js.snap b/packages/backend-core/src/migrations/tests/__snapshots__/index.spec.js.snap similarity index 59% rename from packages/auth/src/migrations/tests/__snapshots__/index.spec.js.snap rename to packages/backend-core/src/migrations/tests/__snapshots__/index.spec.js.snap index e9a18eadde..222c3b1228 100644 --- a/packages/auth/src/migrations/tests/__snapshots__/index.spec.js.snap +++ b/packages/backend-core/src/migrations/tests/__snapshots__/index.spec.js.snap @@ -3,7 +3,7 @@ exports[`migrations should match snapshot 1`] = ` Object { "_id": "migrations", - "_rev": "1-af6c272fe081efafecd2ea49a8fcbb40", - "user_email_view_casing": 1487076708000, + "_rev": "1-6277abc4e3db950221768e5a2618a059", + "test": 1487076708000, } `; diff --git a/packages/backend-core/src/migrations/tests/index.spec.js b/packages/backend-core/src/migrations/tests/index.spec.js new file mode 100644 index 0000000000..12a2e54cb3 --- /dev/null +++ b/packages/backend-core/src/migrations/tests/index.spec.js @@ -0,0 +1,56 @@ +require("../../tests/utilities/dbConfig") + +const { runMigrations, getMigrationsDoc } = require("../index") +const CouchDB = require("../../db").getCouch() +const { + StaticDatabases, +} = require("../../db/utils") + +Date.now = jest.fn(() => 1487076708000) +let db + +describe("migrations", () => { + + const migrationFunction = jest.fn() + + const MIGRATIONS = [{ + type: "global", + name: "test", + fn: migrationFunction + }] + + beforeEach(() => { + db = new CouchDB(StaticDatabases.GLOBAL.name) + }) + + afterEach(async () => { + jest.clearAllMocks() + await db.destroy() + }) + + const migrate = () => { + return runMigrations(CouchDB, MIGRATIONS) + } + + it("should run a new migration", async () => { + await migrate() + expect(migrationFunction).toHaveBeenCalled() + const doc = await getMigrationsDoc(db) + expect(doc.test).toBeDefined() + }) + + it("should match snapshot", async () => { + await migrate() + const doc = await getMigrationsDoc(db) + expect(doc).toMatchSnapshot() + }) + + it("should skip a previously run migration", async () => { + await migrate() + const previousMigrationTime = await getMigrationsDoc(db).test + await migrate() + const currentMigrationTime = await getMigrationsDoc(db).test + expect(migrationFunction).toHaveBeenCalledTimes(1) + expect(currentMigrationTime).toBe(previousMigrationTime) + }) +}) \ No newline at end of file diff --git a/packages/auth/src/objectStore/index.js b/packages/backend-core/src/objectStore/index.js similarity index 91% rename from packages/auth/src/objectStore/index.js rename to packages/backend-core/src/objectStore/index.js index 87b67d464e..b5d8475cee 100644 --- a/packages/auth/src/objectStore/index.js +++ b/packages/backend-core/src/objectStore/index.js @@ -206,6 +206,34 @@ exports.retrieveToTmp = async (bucketName, filepath) => { return outputPath } +/** + * Delete a single file. + */ +exports.deleteFile = async (bucketName, filepath) => { + const objectStore = exports.ObjectStore(bucketName) + await exports.makeSureBucketExists(objectStore, bucketName) + const params = { + Bucket: bucketName, + Key: filepath, + } + return objectStore.deleteObject(params) +} + +exports.deleteFiles = async (bucketName, filepaths) => { + const objectStore = exports.ObjectStore(bucketName) + await exports.makeSureBucketExists(objectStore, bucketName) + const params = { + Bucket: bucketName, + Delete: { + Objects: filepaths.map(path => ({ Key: path })), + }, + } + return objectStore.deleteObjects(params).promise() +} + +/** + * Delete a path, including everything within. + */ exports.deleteFolder = async (bucketName, folder) => { bucketName = sanitizeBucket(bucketName) folder = sanitizeKey(folder) diff --git a/packages/auth/src/objectStore/utils.js b/packages/backend-core/src/objectStore/utils.js similarity index 100% rename from packages/auth/src/objectStore/utils.js rename to packages/backend-core/src/objectStore/utils.js diff --git a/packages/auth/src/redis/authRedis.js b/packages/backend-core/src/redis/authRedis.js similarity index 100% rename from packages/auth/src/redis/authRedis.js rename to packages/backend-core/src/redis/authRedis.js diff --git a/packages/auth/src/redis/index.js b/packages/backend-core/src/redis/index.js similarity index 100% rename from packages/auth/src/redis/index.js rename to packages/backend-core/src/redis/index.js diff --git a/packages/auth/src/redis/utils.js b/packages/backend-core/src/redis/utils.js similarity index 98% rename from packages/auth/src/redis/utils.js rename to packages/backend-core/src/redis/utils.js index 466b117e96..3461d2a511 100644 --- a/packages/auth/src/redis/utils.js +++ b/packages/backend-core/src/redis/utils.js @@ -16,6 +16,7 @@ exports.Databases = { USER_CACHE: "users", FLAGS: "flags", APP_METADATA: "appMetadata", + QUERY_VARS: "queryVars", } exports.SEPARATOR = SEPARATOR diff --git a/packages/auth/src/security/permissions.js b/packages/backend-core/src/security/permissions.js similarity index 100% rename from packages/auth/src/security/permissions.js rename to packages/backend-core/src/security/permissions.js diff --git a/packages/auth/src/security/roles.js b/packages/backend-core/src/security/roles.js similarity index 90% rename from packages/auth/src/security/roles.js rename to packages/backend-core/src/security/roles.js index 8529dde6f4..11abc70bdd 100644 --- a/packages/auth/src/security/roles.js +++ b/packages/backend-core/src/security/roles.js @@ -1,4 +1,3 @@ -const { getDB } = require("../db") const { cloneDeep } = require("lodash/fp") const { BUILTIN_PERMISSION_IDS } = require("./permissions") const { @@ -7,6 +6,8 @@ const { DocumentTypes, SEPARATOR, } = require("../db/utils") +const { getAppDB } = require("../context") +const { getDB } = require("../db") const BUILTIN_IDS = { ADMIN: "ADMIN", @@ -111,11 +112,10 @@ exports.lowerBuiltinRoleID = (roleId1, roleId2) => { /** * Gets the role object, this is mainly useful for two purposes, to check if the level exists and * to check if the role inherits any others. - * @param {string} appId The app in which to look for the role. * @param {string|null} roleId The level ID to lookup. * @returns {Promise} The role object, which may contain an "inherits" property. */ -exports.getRole = async (appId, roleId) => { +exports.getRole = async roleId => { if (!roleId) { return null } @@ -128,7 +128,7 @@ exports.getRole = async (appId, roleId) => { ) } try { - const db = getDB(appId) + const db = getAppDB() const dbRole = await db.get(exports.getDBRoleID(roleId)) role = Object.assign(role, dbRole) // finalise the ID @@ -145,11 +145,12 @@ exports.getRole = async (appId, roleId) => { /** * Simple function to get all the roles based on the top level user role ID. */ -async function getAllUserRoles(appId, userRoleId) { - if (!userRoleId) { - return [BUILTIN_IDS.BASIC] +async function getAllUserRoles(userRoleId) { + // admins have access to all roles + if (userRoleId === BUILTIN_IDS.ADMIN) { + return exports.getAllRoles() } - let currentRole = await exports.getRole(appId, userRoleId) + let currentRole = await exports.getRole(userRoleId) let roles = currentRole ? [currentRole] : [] let roleIds = [userRoleId] // get all the inherited roles @@ -159,7 +160,7 @@ async function getAllUserRoles(appId, userRoleId) { roleIds.indexOf(currentRole.inherits) === -1 ) { roleIds.push(currentRole.inherits) - currentRole = await exports.getRole(appId, currentRole.inherits) + currentRole = await exports.getRole(currentRole.inherits) roles.push(currentRole) } return roles @@ -168,29 +169,23 @@ async function getAllUserRoles(appId, userRoleId) { /** * Returns an ordered array of the user's inherited role IDs, this can be used * to determine if a user can access something that requires a specific role. - * @param {string} appId The ID of the application from which roles should be obtained. * @param {string} userRoleId The user's role ID, this can be found in their access token. * @param {object} opts Various options, such as whether to only retrieve the IDs (default true). * @returns {Promise} returns an ordered array of the roles, with the first being their * highest level of access and the last being the lowest level. */ -exports.getUserRoleHierarchy = async ( - appId, - userRoleId, - opts = { idOnly: true } -) => { +exports.getUserRoleHierarchy = async (userRoleId, opts = { idOnly: true }) => { // special case, if they don't have a role then they are a public user - const roles = await getAllUserRoles(appId, userRoleId) + const roles = await getAllUserRoles(userRoleId) return opts.idOnly ? roles.map(role => role._id) : roles } /** * Given an app ID this will retrieve all of the roles that are currently within that app. - * @param {string} appId The ID of the app to retrieve the roles from. * @return {Promise} An array of the role objects that were found. */ exports.getAllRoles = async appId => { - const db = getDB(appId) + const db = appId ? getDB(appId) : getAppDB() const body = await db.allDocs( getRoleParams(null, { include_docs: true, @@ -218,19 +213,17 @@ exports.getAllRoles = async appId => { } /** - * This retrieves the required role/ - * @param appId + * This retrieves the required role * @param permLevel * @param resourceId * @param subResourceId * @return {Promise<{permissions}|Object>} */ exports.getRequiredResourceRole = async ( - appId, permLevel, { resourceId, subResourceId } ) => { - const roles = await exports.getAllRoles(appId) + const roles = await exports.getAllRoles() let main = [], sub = [] for (let role of roles) { @@ -251,8 +244,7 @@ exports.getRequiredResourceRole = async ( } class AccessController { - constructor(appId) { - this.appId = appId + constructor() { this.userHierarchies = {} } @@ -270,7 +262,7 @@ class AccessController { } let roleIds = this.userHierarchies[userRoleId] if (!roleIds) { - roleIds = await exports.getUserRoleHierarchy(this.appId, userRoleId) + roleIds = await exports.getUserRoleHierarchy(userRoleId) this.userHierarchies[userRoleId] = roleIds } diff --git a/packages/auth/src/security/sessions.js b/packages/backend-core/src/security/sessions.js similarity index 93% rename from packages/auth/src/security/sessions.js rename to packages/backend-core/src/security/sessions.js index 93c2d0a9ca..bbe6be299d 100644 --- a/packages/auth/src/security/sessions.js +++ b/packages/backend-core/src/security/sessions.js @@ -1,6 +1,8 @@ const redis = require("../redis/authRedis") +const { v4: uuidv4 } = require("uuid") -const EXPIRY_SECONDS = 86400 +// a week in seconds +const EXPIRY_SECONDS = 86400 * 7 async function getSessionsForUser(userId) { const client = await redis.getSessionClient() @@ -15,6 +17,9 @@ function makeSessionID(userId, sessionId) { exports.createASession = async (userId, session) => { const client = await redis.getSessionClient() const sessionId = session.sessionId + if (!session.csrfToken) { + session.csrfToken = uuidv4() + } session = { createdAt: new Date().toISOString(), lastAccessedAt: new Date().toISOString(), diff --git a/packages/auth/src/tenancy/index.js b/packages/backend-core/src/tenancy/index.js similarity index 63% rename from packages/auth/src/tenancy/index.js rename to packages/backend-core/src/tenancy/index.js index 2fe257d885..c847033a12 100644 --- a/packages/auth/src/tenancy/index.js +++ b/packages/backend-core/src/tenancy/index.js @@ -1,4 +1,4 @@ module.exports = { - ...require("./context"), + ...require("../context"), ...require("./tenancy"), } diff --git a/packages/auth/src/tenancy/tenancy.js b/packages/backend-core/src/tenancy/tenancy.js similarity index 92% rename from packages/auth/src/tenancy/tenancy.js rename to packages/backend-core/src/tenancy/tenancy.js index 2cd05ea925..8360198b60 100644 --- a/packages/auth/src/tenancy/tenancy.js +++ b/packages/backend-core/src/tenancy/tenancy.js @@ -1,6 +1,6 @@ const { getDB } = require("../db") const { SEPARATOR, StaticDatabases, DocumentTypes } = require("../db/constants") -const { getTenantId, DEFAULT_TENANT_ID, isMultiTenant } = require("./context") +const { getTenantId, DEFAULT_TENANT_ID, isMultiTenant } = require("../context") const env = require("../environment") const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants @@ -148,3 +148,15 @@ exports.isUserInAppTenant = (appId, user = null) => { const tenantId = exports.getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID return tenantId === userTenantId } + +exports.getTenantIds = async () => { + const db = getDB(PLATFORM_INFO_DB) + let tenants + try { + tenants = await db.get(TENANT_DOC) + } catch (err) { + // if theres an error the doc doesn't exist, no tenants exist + return [] + } + return (tenants && tenants.tenantIds) || [] +} diff --git a/packages/auth/src/tests/utilities/db.js b/packages/backend-core/src/tests/utilities/db.js similarity index 100% rename from packages/auth/src/tests/utilities/db.js rename to packages/backend-core/src/tests/utilities/db.js diff --git a/packages/auth/src/tests/utilities/dbConfig.js b/packages/backend-core/src/tests/utilities/dbConfig.js similarity index 100% rename from packages/auth/src/tests/utilities/dbConfig.js rename to packages/backend-core/src/tests/utilities/dbConfig.js diff --git a/packages/auth/src/utils.js b/packages/backend-core/src/utils.js similarity index 90% rename from packages/auth/src/utils.js rename to packages/backend-core/src/utils.js index f7ab5d6990..45fb4acd55 100644 --- a/packages/auth/src/utils.js +++ b/packages/backend-core/src/utils.js @@ -7,7 +7,7 @@ const { const jwt = require("jsonwebtoken") const { options } = require("./middleware/passport/jwt") const { createUserEmailView } = require("./db/views") -const { Headers, UserStatus, Cookies } = require("./constants") +const { Headers, UserStatus, Cookies, MAX_VALID_DATE } = require("./constants") const { getGlobalDB, updateTenantId, @@ -20,9 +20,6 @@ const { hash } = require("./hashing") const userCache = require("./cache/user") const env = require("./environment") const { getUserSessions, invalidateSessions } = require("./security/sessions") -const { migrateIfRequired } = require("./migrations") -const { USER_EMAIL_VIEW_CASING } = require("./migrations").MIGRATIONS -const { GLOBAL_DB } = require("./migrations").MIGRATION_DBS const APP_PREFIX = DocumentTypes.APP + SEPARATOR @@ -63,6 +60,17 @@ exports.getAppId = ctx => { return appId } +/** + * opens the contents of the specified encrypted JWT. + * @return {object} the contents of the token. + */ +exports.openJwt = token => { + if (!token) { + return token + } + return jwt.verify(token, options.secretOrKey) +} + /** * Get a cookie from context, and decrypt if necessary. * @param {object} ctx The request which is to be manipulated. @@ -75,7 +83,7 @@ exports.getCookie = (ctx, name) => { return cookie } - return jwt.verify(cookie, options.secretOrKey) + return exports.openJwt(cookie) } /** @@ -83,14 +91,15 @@ exports.getCookie = (ctx, name) => { * @param {object} ctx The request which is to be manipulated. * @param {string} name The name of the cookie to set. * @param {string|object} value The value of cookie which will be set. + * @param {object} opts options like whether to sign. */ -exports.setCookie = (ctx, value, name = "builder") => { - if (value) { +exports.setCookie = (ctx, value, name = "builder", opts = { sign: true }) => { + if (value && opts && opts.sign) { value = jwt.sign(value, options.secretOrKey) } const config = { - maxAge: Number.MAX_SAFE_INTEGER, + expires: MAX_VALID_DATE, path: "/", httpOnly: false, overwrite: true, @@ -132,11 +141,6 @@ exports.getGlobalUserByEmail = async email => { } const db = getGlobalDB() - await migrateIfRequired(GLOBAL_DB, USER_EMAIL_VIEW_CASING, async () => { - // re-create the view with latest changes - await createUserEmailView(db) - }) - try { let users = ( await db.query(`database/${ViewNames.USER_BY_EMAIL}`, { @@ -252,7 +256,7 @@ exports.saveUser = async ( exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => { if (!ctx) throw new Error("Koa context must be supplied to logout.") - const currentSession = this.getCookie(ctx, Cookies.Auth) + const currentSession = exports.getCookie(ctx, Cookies.Auth) let sessions = await getUserSessions(userId) if (keepActiveSession) { @@ -261,8 +265,8 @@ exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => { ) } else { // clear cookies - this.clearCookie(ctx, Cookies.Auth) - this.clearCookie(ctx, Cookies.CurrentApp) + exports.clearCookie(ctx, Cookies.Auth) + exports.clearCookie(ctx, Cookies.CurrentApp) } await invalidateSessions( diff --git a/packages/auth/tenancy.js b/packages/backend-core/tenancy.js similarity index 100% rename from packages/auth/tenancy.js rename to packages/backend-core/tenancy.js diff --git a/packages/backend-core/utils.js b/packages/backend-core/utils.js new file mode 100644 index 0000000000..2ef920e103 --- /dev/null +++ b/packages/backend-core/utils.js @@ -0,0 +1,4 @@ +module.exports = { + ...require("./src/utils"), + ...require("./src/hashing"), +} diff --git a/packages/auth/yarn.lock b/packages/backend-core/yarn.lock similarity index 99% rename from packages/auth/yarn.lock rename to packages/backend-core/yarn.lock index f28f2f932f..fc70e3d6a1 100644 --- a/packages/auth/yarn.lock +++ b/packages/backend-core/yarn.lock @@ -3410,9 +3410,9 @@ node-fetch@2.6.0: integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== node-fetch@^2.6.1: - version "2.6.6" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" - integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== dependencies: whatwg-url "^5.0.0" diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 25e288497a..eb1c3da6f5 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.190-alpha.3", + "version": "1.0.76-alpha.3", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -38,6 +38,7 @@ ], "dependencies": { "@adobe/spectrum-css-workflow-icons": "^1.2.1", + "@budibase/string-templates": "^1.0.72-alpha.0", "@spectrum-css/actionbutton": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/avatar": "^3.0.2", @@ -79,6 +80,7 @@ "@spectrum-css/underlay": "^2.0.9", "@spectrum-css/vars": "^3.0.1", "dayjs": "^1.10.4", + "easymde": "^2.16.1", "svelte-flatpickr": "^3.2.3", "svelte-portal": "^1.0.0" }, diff --git a/packages/bbui/src/ActionButton/ActionButton.svelte b/packages/bbui/src/ActionButton/ActionButton.svelte index b518ac3d92..5ab7a7f047 100644 --- a/packages/bbui/src/ActionButton/ActionButton.svelte +++ b/packages/bbui/src/ActionButton/ActionButton.svelte @@ -80,4 +80,8 @@ .active svg { color: var(--spectrum-global-color-blue-600); } + + .spectrum-ActionButton-label { + padding-bottom: 2px; + } diff --git a/packages/bbui/src/Banner/Banner.svelte b/packages/bbui/src/Banner/Banner.svelte new file mode 100644 index 0000000000..f28ee09d9c --- /dev/null +++ b/packages/bbui/src/Banner/Banner.svelte @@ -0,0 +1,59 @@ + + +{#if show} +
+ +
+
+ +
+ {#if extraButtonText && extraButtonAction} + + {/if} +
+
+ +
+
+{/if} diff --git a/packages/bbui/src/Button/Button.svelte b/packages/bbui/src/Button/Button.svelte index da4d405f02..a3d2df069a 100644 --- a/packages/bbui/src/Button/Button.svelte +++ b/packages/bbui/src/Button/Button.svelte @@ -1,5 +1,6 @@ diff --git a/packages/bbui/src/ButtonGroup/ButtonGroup.svelte b/packages/bbui/src/ButtonGroup/ButtonGroup.svelte index b845a770e1..50d0073a25 100644 --- a/packages/bbui/src/ButtonGroup/ButtonGroup.svelte +++ b/packages/bbui/src/ButtonGroup/ButtonGroup.svelte @@ -1,6 +1,16 @@ - - {#if visible} -
+
{title} @@ -52,6 +61,11 @@ {/if} diff --git a/packages/bbui/src/Form/Core/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker.svelte index 8edb68a38e..c1c4cc866f 100644 --- a/packages/bbui/src/Form/Core/DatePicker.svelte +++ b/packages/bbui/src/Form/Core/DatePicker.svelte @@ -5,7 +5,7 @@ import "@spectrum-css/textfield/dist/index-vars.css" import "@spectrum-css/picker/dist/index-vars.css" import { createEventDispatcher } from "svelte" - import { generateID } from "../../utils/helpers" + import { uuid } from "../../helpers" export let id = null export let disabled = false @@ -14,16 +14,20 @@ export let value = null export let placeholder = null export let appendTo = undefined + export let timeOnly = false const dispatch = createEventDispatcher() - const flatpickrId = `${generateID()}-wrapper` + const flatpickrId = `${uuid()}-wrapper` let open = false - let flatpickr + let flatpickr, flatpickrOptions, isTimeOnly + + $: isTimeOnly = !timeOnly && value ? !isNaN(new Date(`0-${value}`)) : timeOnly $: flatpickrOptions = { element: `#${flatpickrId}`, - enableTime: enableTime || false, + enableTime: isTimeOnly || enableTime || false, + noCalendar: isTimeOnly || false, altInput: true, - altFormat: enableTime ? "F j Y, H:i" : "F j, Y", + altFormat: isTimeOnly ? "H:i" : enableTime ? "F j Y, H:i" : "F j, Y", wrap: true, appendTo, disableMobile: "true", @@ -35,6 +39,11 @@ if (newValue) { newValue = newValue.toISOString() } + // if time only set date component to today + if (timeOnly) { + const todayDate = new Date().toISOString().split("T")[0] + newValue = `${todayDate}T${newValue.split("T")[1]}` + } dispatch("change", newValue) } @@ -67,7 +76,11 @@ return null } let date - if (val instanceof Date) { + let time = new Date(`0-${val}`) + // it is a string like 00:00:00, just time + if (timeOnly || (typeof val === "string" && !isNaN(time))) { + date = time + } else if (val instanceof Date) { // Use real date obj if already parsed date = val } else if (isNaN(val)) { @@ -77,7 +90,7 @@ // Treat as numerical timestamp date = new Date(parseInt(val)) } - const time = date.getTime() + time = date.getTime() if (isNaN(time)) { return null } @@ -88,69 +101,71 @@ } - -
- {#if !!error} +
+ {#if !!error} + + {/if} + +
+
- -
-
+ +{/key} {#if open}
{/if} diff --git a/packages/bbui/src/Form/Core/Dropzone.svelte b/packages/bbui/src/Form/Core/Dropzone.svelte index a543dea1d4..d739e751c9 100644 --- a/packages/bbui/src/Form/Core/Dropzone.svelte +++ b/packages/bbui/src/Form/Core/Dropzone.svelte @@ -3,9 +3,11 @@ import "@spectrum-css/typography/dist/index-vars.css" import "@spectrum-css/illustratedmessage/dist/index-vars.css" import { createEventDispatcher } from "svelte" - import { generateID } from "../../utils/helpers" + import { uuid } from "../../helpers" import Icon from "../../Icon/Icon.svelte" import Link from "../../Link/Link.svelte" + import Tag from "../../Tags/Tag.svelte" + import Tags from "../../Tags/Tags.svelte" const BYTES_IN_KB = 1000 const BYTES_IN_MB = 1000000 @@ -18,6 +20,9 @@ export let handleFileTooLarge = null export let gallery = true export let error = null + export let fileTags = [] + export let maximum = null + export let extensions = "*" const dispatch = createEventDispatcher() const imageExtensions = [ @@ -32,7 +37,7 @@ "jfif", ] - const fieldId = id || generateID() + const fieldId = id || uuid() let selectedImageIdx = 0 let fileDragged = false let selectedUrl @@ -142,7 +147,9 @@ preview {:else}
-
{selectedImage.extension}
+
+ {selectedImage.name || "Unknown file"} +
Preview not supported
{/if} @@ -184,103 +191,119 @@ {/each} {/if} {/if} -
-
- - - - - - +
+ - - - - - - - - - - -

- Drag and drop your file -

- {#if !disabled} -

- -
- from your computer -

- {/if} + + + + + + + + + + + + + + +

+ Drag and drop your file +

+ {#if !disabled} +

+ +
+ from your computer +

+ {#if fileTags.length} + +
+ {#each fileTags as tag} +
+ + {tag} + +
+ {/each} +
+
+ {/if} + {/if} +
-
+ {/if}
diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte index 5f4da1cad9..143536a60a 100644 --- a/packages/bbui/src/Form/Core/Picker.svelte +++ b/packages/bbui/src/Form/Core/Picker.svelte @@ -63,9 +63,9 @@ const getFilteredOptions = (options, term, getLabel) => { if (autocomplete && term) { const lowerCaseTerm = term.toLowerCase() - return options.filter(option => - getLabel(option)?.toLowerCase().includes(lowerCaseTerm) - ) + return options.filter(option => { + return `${getLabel(option)}`.toLowerCase().includes(lowerCaseTerm) + }) } return options } diff --git a/packages/bbui/src/Form/Core/RichTextField.svelte b/packages/bbui/src/Form/Core/RichTextField.svelte new file mode 100644 index 0000000000..f964405f0d --- /dev/null +++ b/packages/bbui/src/Form/Core/RichTextField.svelte @@ -0,0 +1,42 @@ + + +
+ +
+ + diff --git a/packages/bbui/src/Form/Core/Search.svelte b/packages/bbui/src/Form/Core/Search.svelte index dd70f1eaca..e6384e242a 100644 --- a/packages/bbui/src/Form/Core/Search.svelte +++ b/packages/bbui/src/Form/Core/Search.svelte @@ -7,7 +7,7 @@ export let disabled = false export let id = null export let updateOnChange = true - + export let quiet = false const dispatch = createEventDispatcher() let focus = false @@ -41,6 +41,7 @@